/* ============================================================
   DISCIPLANT - visual identity
   ------------------------------------------------------------
   Three nested surfaces, darkest on the outside:

       bark   (wooden frame, carries the grain)
        └── clay   (tan panel)
             └── linen  (light card, where words live)

   Everything reads from the tokens below. If a colour is not
   in this block it is garden scenery (sky, soil, plant art),
   not interface, and is deliberately left alone.
   ============================================================ */

@import url('https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700;800&family=Nunito:wght@400;600;700;800&display=swap');

:root {
  /* --- Bark: frame layer --- */
  --bark:        #7A5C42;
  --bark-deep:   #5E4632;
  --bark-light:  #9A7855;

  /* --- Clay: panel layer --- */
  --clay:        #E7CDA6;
  --clay-edge:   #D4B58A;
  --clay-deep:   #C39F73;

  /* --- Linen: card layer --- */
  --linen:       #F8EEDC;
  --linen-edge:  #EADFC6;

  /* --- Text --- */
  --ink:         #4A3826;
  --ink-soft:    #8A6E52;
  --ink-faint:   #A98D6B;
  --ink-on-dark: #E7CDA6;

  /* --- Accents. One job each, never decoration. --- */
  --leaf:        #7FA968;   /* done, growth, focus */
  --leaf-deep:   #5C8149;
  --leaf-dark:   #47673A;
  --sun:         #E8B75C;   /* streaks, current page */
  --sun-deep:    #C4913A;
  --bloom:       #D89A8E;   /* delete, warnings */
  --bloom-deep:  #A5544A;
  --bloom-tint:  #F7E4DF;
  --dusk:        #9C8FB8;   /* locked */

  /* --- Type scale. Five sizes. Nothing outside this list. --- */
  --fs-xs:   0.75rem;
  --fs-sm:   0.875rem;
  --fs-base: 1rem;
  --fs-lg:   1.25rem;
  --fs-xl:   1.75rem;
  --fs-2xl:  2.5rem;

  --font-display: 'Baloo 2', 'Nunito', sans-serif;
  --font-body:    'Nunito', sans-serif;

  /* --- Corners --- */
  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 20px;

  /* --- Grain. Laid over bark surfaces only. --- */
  --wood-grain: repeating-linear-gradient(
      93deg,
      rgba(60, 42, 28, 0)       0px,
      rgba(60, 42, 28, 0)       9px,
      rgba(60, 42, 28, 0.15)    9px,
      rgba(60, 42, 28, 0.15)   11px,
      rgba(60, 42, 28, 0)      11px,
      rgba(60, 42, 28, 0)      26px,
      rgba(255, 231, 194, 0.06) 26px,
      rgba(255, 231, 194, 0.06) 29px
    );

  /* --- The hard bottom edge that makes a control look pressable. --- */
  --lip:      0 4px 0;
  --lip-deep: 0 6px 0;
}

* {
  box-sizing: border-box;
}

html {
  background: #0a1628;
}

body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  background: transparent;
  font-family: var(--font-body);
  color: var(--ink);
}




/* ========================================
   Sky background + garden ground scene
   ======================================== */

.sky-bg {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  z-index: -1;
  background: linear-gradient(180deg, #0a1628 0%, #1a2a44 100%);
  overflow: hidden;

  /* Diameter of the sun/moon. Was 40px of glyph. */
  --sky-body-size: 64px;
}

/* --- The sun and the moon ------------------------------------------
   updateSky() in 05-stats-app.js owns #skyBody: it writes a character
   into it and walks it across the sky by setting left/top. The art
   below is therefore a BACKGROUND IMAGE on ::before, not a child
   element - a child would be wiped the next time updateSky() repaints
   the glyph, whereas a background image survives any textContent write.
   font-size: 0 on the box means whatever character 05 still writes
   stays invisible, so none of this needs an edit in 05.

   Both bodies are flat and id-free (ids would collide the moment this
   art were ever inlined twice), with no gradients and no filters in
   the art itself - shaded the way every bench prop is, by stacking
   rounded shapes each nudged up-left of the one beneath.

   Size is ONE knob: --sky-body-size on .sky-bg. Changing it is safe.
   The old glyph was 2.5rem/40px on line-height 1, and an absolutely
   positioned line box puts its TOP on 05's `top`, so the disc's centre
   sat 20px below that point. The translate below keeps the new disc's
   CENTRE on that same 20px line at any size, so the arc the sun and
   the moon travel across the sky is exactly the one they travelled
   before.
   ------------------------------------------------------------------ */
.sky-body {
  position: absolute;
  width: var(--sky-body-size);
  height: var(--sky-body-size);
  font-size: 0;
  line-height: 0;
  transform: translate(-50%, calc(20px - var(--sky-body-size) / 2));
  transition: left 62s linear, top 32s ease-in-out;
  pointer-events: none;
  user-select: none;
  filter: drop-shadow(0 0 16px rgba(255, 206, 92, 0.55));
}

/* Daytime: a four-layer disc under twelve rays of alternating length,
   each ray nudged a fraction off its true spoke angle and given a
   rounded tip, so the wheel doesn't read as a machine part. */
.sky-body::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath fill='%23E39429' d='M74.43 54.88L94.41 52.23Q98.03 51.2 94.46 49.17L74.57 45.89ZM58.11 73.55L70.39 89.31Q73.23 91.84 73.06 87.81L65.95 69.14ZM34.12 69.19L27.48 87.16Q27.41 91.24 30.15 88.65L41.98 73.58ZM25.41 46.01L6.8 49.37Q3.24 51.58 6.86 52.43L25.6 55.01ZM41.44 26.61L29.19 11.66Q25.83 9.46 26.55 13.21L33.69 31.17ZM66.57 31.4L74.52 12.62Q75.64 9.08 71.9 11.03L58.88 26.73Z'/%3E%3Cpath fill='%23EFAB35' d='M69.64 65.15L79.81 67.77Q83.46 67.6 81.08 65.44L73.38 58.3ZM45.46 74.39L47.73 86.08Q48.37 89.22 50.38 86.15L53.26 74.59ZM26.77 58.71L18.41 66.42Q16.27 68.98 19.72 68.73L30.62 65.49ZM31.04 34.01L19.92 30.31Q16.79 29.45 18.55 32.58L27.01 40.68ZM54.24 25.56L51.83 14.24Q50.86 11.11 49.17 14.2L46.44 25.45ZM73.01 40.72L80.58 33.17Q82.37 30.09 79.22 30.9L68.99 34.04Z'/%3E%3Cpath fill='%23D9812A' d='M77.82 51.1C77.79 56.73 74.66 63.52 70.95 67.92C67.24 72.33 61.15 76.42 55.56 77.51C49.97 78.59 42.31 77.36 37.41 74.46C32.51 71.56 28.1 65.53 26.15 60.11C24.21 54.69 23.82 47.24 25.75 41.95C27.69 36.66 32.8 31.25 37.77 28.36C42.74 25.47 50.01 23.66 55.57 24.62C61.13 25.58 67.43 29.71 71.13 34.12C74.84 38.54 77.85 45.47 77.82 51.1Z'/%3E%3Cpath fill='%23EFA030' d='M72.31 54.96C71.21 59.9 68.04 65.71 64.1 68.73C60.15 71.76 53.71 73.35 48.64 73.11C43.58 72.88 37.43 70.67 33.69 67.31C29.94 63.95 26.73 57.94 26.15 52.96C25.56 47.97 27.41 41.66 30.17 37.4C32.92 33.15 37.93 28.91 42.66 27.44C47.4 25.96 53.92 26.62 58.59 28.56C63.25 30.49 68.38 34.64 70.66 39.04C72.95 43.44 73.4 50.01 72.31 54.96Z'/%3E%3Cpath fill='%23F8BC42' d='M65.66 56.73C63.97 60.31 60 63.75 56.33 65.43C52.66 67.12 47.46 67.91 43.66 66.86C39.86 65.82 35.81 62.49 33.52 59.17C31.24 55.86 29.64 50.97 29.95 47C30.25 43.03 32.51 38.14 35.37 35.37C38.22 32.6 43.06 30.7 47.06 30.37C51.07 30.04 56.17 31.11 59.41 33.38C62.65 35.65 65.43 40.09 66.48 43.98C67.52 47.87 67.36 53.16 65.66 56.73Z'/%3E%3Cpath fill='%23FDD873' d='M56.82 54.79C55.24 56.87 52.45 58.85 49.9 59.44C47.35 60.04 43.83 59.59 41.53 58.34C39.23 57.1 37.09 54.42 36.09 51.97C35.1 49.52 34.76 46.09 35.56 43.63C36.36 41.17 38.65 38.61 40.89 37.21C43.12 35.81 46.39 34.88 48.97 35.2C51.56 35.53 54.66 37.2 56.39 39.16C58.13 41.11 59.3 44.33 59.37 46.94C59.44 49.54 58.4 52.71 56.82 54.79Z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
}

/* Night: a real crescent - one arc of the moon's own edge and one arc
   of the terminator, joined at the horns.

   Worth not re-trying: a circle with a circular bite taken out of it
   via fill-rule="evenodd" does NOT give a crescent. The biting circle
   reaches past the moon's edge, so even-odd fills the part hanging
   outside it too and the result is a ring. The second trap is the
   shade pass - a plain offset copy underneath pokes its own two horns
   out past the lit crescent as a pair of dark spikes, so both the
   shade and the highlight are drawn with their horns pulled back (a
   WIDER horn angle, which shortens the tips) and small enough that
   the offset can't carry them past the silhouette. */
.sky-night .sky-body::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath fill='%23B7A97C' d='M48.14 24.51A27.6 27.6 0 1 0 64.88 76.04Q12.06 64.71 48.14 24.51Z'/%3E%3Cpath fill='%23E7DDB7' d='M50.52 20A30 30 0 1 0 68.05 73.96Q6.09 64.27 50.52 20Z'/%3E%3Cpath fill='%23F8F2DC' d='M44.07 21.22A27.8 27.8 0 1 0 61.09 73.59Q0.9 64.2 44.07 21.22Z'/%3E%3C/svg%3E");
}

/* The halo is the one thing that has to change with the body: a warm
   glow around a pale moon reads as a smudge. */
.sky-night .sky-body {
  filter: drop-shadow(0 0 18px rgba(176, 200, 255, 0.45));
}

.sky-ground {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 30vh;
  background: linear-gradient(
    to bottom,
    #4a7a30 0%,
    #567840 6%,
    #7a5a3a 28%,
    #5c4030 58%,
    #38261a 100%
  );
}

.sky-ground::before {
  content: '';
  position: absolute;
  top: -32px;
  left: -3%;
  width: 106%;
  height: 60px;
  background: #4a7a30;
  border-radius: 52% 48% 58% 42% / 55% 62% 40% 50%;
}

.sky-ground::after {
  content: '';
  position: absolute;
  top: -10px;
  left: 0;
  right: 0;
  height: 28px;
  background: rgba(80, 130, 50, 0.30);
  filter: blur(12px);
  pointer-events: none;
}

/* On the two garden pages the ground is the garden's own, and it
   scrolls. Everything the fixed sky keeps down at its own horizon -
   the ground strip, the hills, the haze, the fireflies - is pinned to
   the window instead, so leaving it on would slide a second, perfectly
   motionless horizon out from behind the real one the moment you
   scrolled up. Toggled by navigateTo() in 01-app-core.js.

   None of this changes the unscrolled view: all four sit below the
   50vh line, where the lawn has always covered them anyway. */
.sky-bg.sky-on-garden .sky-ground,
.sky-bg.sky-on-garden .sky-hills,
.sky-bg.sky-on-garden .sky-horizon-haze,
.sky-bg.sky-on-garden .sky-fireflies {
  display: none;
}

.sky-ground-inner {
  position: absolute;
  top: -18px;
  left: 25%;
  width: 80%;
  height: 36px;
  background: #517d35;
  border-radius: 50% 50% 0 0 / 80% 80% 0 0;
  opacity: 0.55;
}


/* ========================================
   Utility
   ======================================== */

.hidden {
  display: none !important;
}

.loading-state {
  text-align: center;
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--clay);
  font-size: var(--fs-base);
  margin: 60px 0;
}


/* ========================================
   Garden scene - SVG plants in the world
   ======================================== */

.garden-scene {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 100vh;
  z-index: 5;

  /* How much empty sky stands above the plot, and therefore how far
     up the scene scrolls: two half-screen layers of sky on top of the
     half-screen of ordinary sky that was always visible. Read by the
     track's top margin and by the empty-garden message below, so the
     whole scene resizes from this one number. */
  --garden-sky-lift: 150vh;

  /* Both axes now. The vertical scroll is the sky - see the "sky above
     the garden" section near the end of this file for the layer map.
     display was flex/align-items:flex-end, which pinned the 50vh track
     to the bottom of the 100vh scene; a flex item taller than its line
     overflows the START edge, and overflow past the start edge cannot
     be scrolled to, so the sky above the plot would have been
     unreachable. Plain block flow plus the track's top margin puts the
     plot in exactly the same place and keeps every pixel above it
     scrollable. */
  overflow: auto;
  /* `none`, not `contain`. Both stop a scroll here from chaining out
     to the page behind, but `contain` still lets THIS element
     rubber-band past its own end - and since the scene paints no
     background of its own, that bounce shows .sky-bg straight
     through, as a strip of sky sliding in under the plot. */
  overscroll-behavior: none;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: rgba(242, 184, 75, 0.55) transparent;
}

.garden-scene::-webkit-scrollbar {
  height: 8px;
  width: 8px;
}
.garden-scene::-webkit-scrollbar-track {
  background: transparent;
}

/* The little square where the two scrollbars meet. Left unstyled it
   does NOT inherit the transparent track above - it falls back to the
   browser's own opaque default, a pale block in the bottom-right
   corner of the sky. */
.garden-scene::-webkit-scrollbar-corner {
  background: transparent;
}
.garden-scene::-webkit-scrollbar-thumb {
  background: rgba(242, 184, 75, 0.5);
  border-radius: 8px;
}
.garden-scene::-webkit-scrollbar-thumb:hover {
  background: rgba(242, 184, 75, 0.75);
}

/* The actual wide canvas plants are placed in - 3x the visible
   width, so there's real room to spread more plants out. Percentage
   left/bottom values on .garden-plant are relative to THIS box, not
   the viewport, so a plant's x-position is stable no matter how the
   surrounding viewport is scrolled. */
/* The clip box: the plot's true edges, and nothing is allowed to move
   them.

   The bug this exists for: a scroll container's range is the union of
   its descendants' boxes AFTER transforms, and .plant-visual's scale
   is uncapped - about 12x on a year-old habit, so a 120px plant is
   1539px wide. clampCenterPct in 04 only keeps a plant's CENTRE 68px
   inside the edge (it protects the 130px name tag, not the plant), so
   half a wide canopy hung over the end of the plot and the scene grew
   its scroll range to reach it. Scrolling out there found no scenery,
   just .sky-bg showing through the transparent scene - the blue
   rectangle past the bottom and right edges. The same thing happened
   downward through .garden-plant's negative bottom offset.

   Clipping HERE and not on the track itself is the whole trick. The
   sky strip is a child of the track sitting entirely above it
   (bottom: 100%), so clipping the track would need an
   overflow-clip-margin to spare it - and that margin moves the clip
   edge on every side, taking the scroll range with it and handing the
   plants their overhang straight back. This box has no margin at all
   because it does not need one: it is a plain block wrapper whose own
   height is the track's margin box, so the sky strip is already
   inside it. overflow: clip establishes the formatting context that
   keeps the track's top margin in, and is not a scroll container, so
   the scene stays the only thing that scrolls.

   Nothing visible is lost. At full scroll the plot's edges sit
   exactly on the edges of the screen, so everything this cuts away
   was already off-screen - it just cannot be chased any more. */
.garden-scene-clip {
  width: 300%;

  /* flow-root is load-bearing, not decoration. This box's whole job is
     to be exactly as tall as the plot, and it gets that height by
     containing the track's 150vh top margin. `overflow: clip` will NOT
     do that on its own - clip is the one overflow value that does not
     establish a block formatting context, so without this the margin
     would collapse straight through, leaving this box 50vh tall,
     shunted 150vh down, with the sky strip clipped clean off the top
     of the garden. flow-root establishes the context explicitly. */
  display: flow-root;
  overflow: clip;
}

.garden-scene-track {
  position: relative;
  /* Full width of the clip box above, which is the 300% that used to
     be declared here - the plot is the same three screens wide, the
     measurement just moved out one level. */
  width: 100%;
  height: 50vh;
  /* Deliberately still 50vh. Every .garden-plant's bottom% is a
     percentage of THIS box, so growing the track to make room for the
     sky would have moved every plant in every garden. The sky is
     bought with margin instead, which adds scrollable height without
     touching the coordinate space the plants live in. .garden-scene
     establishes its own formatting context (overflow: auto), so this
     margin cannot collapse out through the top of it. */
  margin-top: var(--garden-sky-lift, 150vh);
}

/* The lawn. This used to be a fixed element in #gardenBackdrop, which
   is exactly why it stayed put while the fence and the plants standing
   on it scrolled away - the ground was welded to the window. As a
   child of the track it fills the plot precisely as before (the track
   IS the bottom 50vh) and now moves with everything rooted in it.
   Painted first and z-indexed under the grass clumps (1), the fence
   (2) and the plants (3+). Built by renderLawn() in 04. */
/* ========================================
   Garden landscape skin tokens

   Everything the ground is painted with, pulled out of the rules
   below into one named set so a landscape skin can repaint the plot
   without any rule here knowing which skin is on. The values below
   ARE the Meadow skin - the garden as it has always looked - so with
   no skin applied at all (first paint, JS not run yet, a skin id that
   no longer exists) the scene renders exactly as before.

   GARDEN_SKINS in 03-plant-art.js overrides these by setting the same
   custom properties inline on .garden-scene; see applyGardenSkin() in
   04-garden-scene.js. Inline properties beat these, so a skin never
   has to fight specificity.

   Two of these deliberately point at the shared UI palette rather
   than at hexes: the fence and the signpost were painted straight
   from --clay and --bark, which are the same tokens the Tasks page
   and the nav planks use. Skinning them without this indirection
   would have repainted half the app along with the garden.

   Grass is the one part NOT here. Blades are built in JS with their
   gradients written as inline styles (see buildGrassClump), so their
   palette rides on the skin object itself as skin.grass.
   ======================================== */
.garden-scene {
  /* Ground. back = the far edge up by the fence, front = underfoot. */
  --lawn-back:  #6fae46;
  --lawn-mid:   #58973a;
  --lawn-front: #3f7a2a;
  --lawn-crest: #6fae46;

  /* Fence - four picket shades cycling every 104px */
  --fence-a:    var(--clay);
  --fence-b:    var(--clay-edge);
  --fence-c:    var(--clay-deep);
  --fence-d:    var(--clay-deep);
  --fence-rail: var(--clay-deep);
  /* Bare channels, so the three shadow stops can share one colour at
     three alphas. A green-black contact shadow is right on grass and
     wrong on anything else. */
  --fence-shadow-rgb: 20, 40, 12;

  /* Man-made props standing in the plot (the signpost, for now) */
  --scenery-wood:      var(--bark);
  --scenery-wood-lit:  var(--bark-light);
  --scenery-wood-deep: var(--bark-deep);
  --scenery-ink:       var(--linen);

  /* Night dimming. A skin whose ground is already dark or already
     pale gets crushed by one fixed filter, so each skin carries its
     own numbers rather than inheriting these. */
  --scene-night-bright:       0.45;
  --scene-night-sat:          0.85;
  --scene-night-fence-bright: 0.55;
}

.garden-lawn {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(to bottom,
    var(--lawn-back) 0%, var(--lawn-mid) 45%, var(--lawn-front) 100%);
  transition: filter 1.5s ease;
}

/* The soft crest where the lawn meets the sky, sitting just proud of
   the top edge so the horizon isn't a ruled line. Full width rather
   than the old 106%: that 3% overhang was invisible on a backdrop the
   width of the window, but on a track three screens wide it is 9vw of
   green sticking out past the right-hand end of the plot, and the
   scene would scroll to it. */
.garden-lawn::before {
  content: '';
  position: absolute;
  top: -18px;
  left: 0;
  width: 100%;
  height: 40px;
  background: var(--lawn-crest);
  border-radius: 52% 48% 58% 42% / 55% 62% 40% 50%;
}

/* Same real-time night dimming the lawn always had, moved onto the
   scene's own night class (see updateSky in 05-stats-app.js). */
.garden-scene.scene-night .garden-lawn {
  filter: brightness(var(--scene-night-bright)) saturate(var(--scene-night-sat));
}


/* Ground texture layer - grass blade clumps scattered across the
   scrollable garden track (see renderGrassField in script.js). Lives
   inside .garden-scene-track so it scrolls WITH the content, giving
   swipe/scroll a visible sense of motion that a flat green plane
   couldn't. Sits behind every .garden-plant, whose z-index (10+, see
   PLANT_Z_INDEX in 04-garden-scene.js) is always higher than this
   layer's. */
.grass-tuft-field {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

.grass-clump {
  position: absolute;
  transform: translateX(-50%);
  pointer-events: none;
}

.grass-blade {
  position: absolute;
  bottom: 0;
  border-radius: 40% 60% 0 0;
  transform-origin: bottom center;
}


/* Landscape props - the small things lying about in the plot, built
   by renderSkinProps() in 04-garden-scene.js from the active skin.

   This layer DOES carry a z-index, and that is load-bearing: it makes
   the field a stacking context, so every prop inside is sealed into
   band 3 no matter how high its own depth-derived z-index climbs. That
   is what guarantees the rule the plot is ordered by:

     scenery (0-3)  <  a plant's label  <  that plant's art

   A plant's wrapper starts at PLANT_Z_INDEX (10, see 04-garden-scene.js)
   and is itself a stacking context, so its label - even at z-index 0
   inside it - still paints above every prop, while the art above it at
   z-index 1 still paints above the label. Interleaving the two sets of
   z-indices, which is what this layer used to do, meant a lava smear or
   a sandcastle placed nearer the viewer than a plant would land on top
   of that plant's text.

   The tradeoff, accepted: a foreground prop can no longer paint in
   front of a background plant. In practice the overlap is small, since
   plant art grows upward from its base while nearer props sit lower on
   screen.

   The layer still must NOT take a filter - that would be a second way
   to make a stacking context, but it would also flatten the per-prop
   night dimming below into one blanket pass. */
.garden-prop-field {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
}

.garden-prop {
  position: absolute;
  transform: translateX(-50%);
  pointer-events: none;
  /* Contact shadow, matching the upper-left light every prop is drawn
     for. Without it they float. */
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.28));
}

/* The drop-shadow has to be repeated rather than inherited: filter is
   a single property, so naming brightness here would drop the shadow
   at dusk. Both dimming numbers come off the skin, which is how the
   Emberfield lava keeps glowing at midnight while the meadow goes
   properly dark. */
.garden-scene.scene-night .garden-prop {
  filter:
    drop-shadow(0 2px 3px rgba(0, 0, 0, 0.28))
    brightness(var(--scene-night-bright))
    saturate(var(--scene-night-sat));
  transition: filter 1.5s ease;
}


/* ========================================
   Garden fence - lives INSIDE #gardenSceneTrack
   (the scrollable element), not the fixed
   backdrop, so it moves together with the plants
   and grass field as the garden is panned. It sits
   near the top of the track, with a soft shadow
   bridging its base down into the grass field
   below it, so the posts read as rooted in the
   ground rather than floating above it. Rebuilt
   fresh on every renderGarden() call, same as the
   grass field (see renderFence() in script.js).
   ======================================== */
.garden-fence-strip {
  position: absolute;
  top: -7%;
  left: 0;
  width: 100%;
  height: 42px;
  z-index: 2;
  pointer-events: none;
  border-top: 4px solid var(--fence-rail);
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.20);
  opacity: 0.95;
  /* Four picket shades of the same tan/brown family, cycling every
     104px. A single flat colour would look static while scrolling -
     alternating shades make the panning motion obvious at a glance. */
  background-image: repeating-linear-gradient(
    90deg,
    var(--fence-a) 0px,  var(--fence-a) 9px,
    transparent 9px, transparent 26px,
    var(--fence-b) 26px, var(--fence-b) 35px,
    transparent 35px, transparent 52px,
    var(--fence-c) 52px, var(--fence-c) 61px,
    transparent 61px, transparent 78px,
    var(--fence-d) 78px, var(--fence-d) 87px,
    transparent 87px, transparent 104px
  );
}

/* Soft shadow bridging the fence's base into the grass field just
   below it, so the posts look planted in the lawn instead of hanging
   in empty space above it. */
.garden-fence-shadow {
  position: absolute;
  /* Matches the fence strip's own top (-7%) + height (42px) exactly,
     so the shadow always starts right at the fence's actual bottom
     edge instead of a fixed pixel offset that drifted away from it
     on taller/shorter viewports - that drift was what produced a
     stray, disconnected dark-green sliver above the real fence base. */
  top: calc(-7% + 42px);
  left: 0;
  width: 100%;
  height: 26px;
  z-index: 1;
  pointer-events: none;
  /* Softer, wider falloff (three stops instead of a flat two-stop
     drop) plus a bigger blur so it reads as a gentle contact shadow
     rather than a hard-edged stripe. */
  background: linear-gradient(
    to bottom,
    rgba(var(--fence-shadow-rgb), 0.26) 0%,
    rgba(var(--fence-shadow-rgb), 0.12) 45%,
    rgba(var(--fence-shadow-rgb), 0) 100%
  );
  filter: blur(5px);
}

/* Night dimming - toggled via .scene-night on #gardenScene (see
   updateSky() in script.js), kept in sync with the same real-time
   day/night state as the rest of the garden. */
.garden-scene.scene-night .garden-fence-strip {
  filter: brightness(var(--scene-night-fence-bright));
  transition: filter 1.5s ease;
}
.garden-scene.scene-night .garden-fence-shadow {
  opacity: 0.7;
  transition: opacity 1.5s ease;
}

@media (max-width: 600px) {
  .garden-fence-strip {
    height: 32px;
  }
  .garden-fence-shadow {
    top: calc(-7% + 32px);
  }
}


/* Perspective planting bed - a literal patch of soil narrowing
   toward the top (far/horizon) and widening toward the bottom
   (near/foreground), matching the far/mid/near depth rows plants
   are assigned to in script.js. Sits behind every .garden-plant
   (which carry z-index 10+) since this pseudo-element has no
   z-index of its own and is first in paint order. Purely decorative
   - pointer-events are already off on the whole .garden-scene. */
/* Ground-anchored wrapper. bottom is pulled below the scene's own
   bottom edge, deliberately sinking the plant a bit INTO the grass
   rather than sitting exactly on the seam - the base doesn't need to
   touch the literal top pixel of the ground to read as "grounded";
   sitting a little lower in the green still reads as closer/rooted.
   The sink grows along with the plant's own --plant-scale so bigger
   plants (whose few-pixel art/origin mismatch gets multiplied by
   scale) get pulled down further too, instead of drifting upward. */
.garden-plant {
  position: absolute;
  bottom: calc(var(--plant-depth-bottom, 0%) - 14px - (var(--plant-scale, 1) - 1) * 3px);
  transform: translateX(-50%);
  pointer-events: auto;
  cursor: grab;
}

/* While a plant is being press-and-held/dragged: no growth-transition
   lag on scale/position, sits above everything, and gets a subtle
   lift so it reads as "picked up". */
.garden-plant.dragging {
  cursor: grabbing;
  z-index: 9990 !important;
  transition: none;
}

.garden-plant.dragging .plant-visual {
  transition: none;
  filter: brightness(1.10) drop-shadow(0 14px 18px rgba(0, 0, 0, 0.45));
}

/* Depth rows are drawn with a fixed stacking order (far behind mid
   behind near), which normally means a "far" plant can end up
   partly covered by a closer one. On hover, force the hovered plant
   - and everything inside it, including its label - to the very
   front of the stack, overriding the inline z-index set in JS, so
   it (and its description) is always fully visible while inspected. */
.garden-plant:hover {
  z-index: 999 !important;
}

/* The part that actually scales as the plant grows. transform-origin
   is pinned near the base of the drawn art (not the raw SVG canvas
   edge, which has empty padding below the shadow), so the plant's
   visual base stays rooted to the ground instead of drifting upward
   as it gets bigger. */
.plant-visual {
  position: relative;
  /* Inside .garden-plant's own stacking context. Pairs with
     .plant-label-tag's z-index: 0 below: the art always paints over
     the plant's own text, so a low-hanging leaf or a skin accessory
     covers the label rather than the other way round. */
  z-index: 1;
  transform: scale(var(--plant-scale, 1));
  transform-origin: center 97%;
  transition: transform 1.1s cubic-bezier(0.34, 1.2, 0.4, 1), filter 0.25s ease;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.30));
}

.garden-plant:hover .plant-visual {
  filter: brightness(1.12) drop-shadow(0 4px 10px rgba(0, 0, 0, 0.30)) drop-shadow(0 0 12px rgba(242, 184, 75, 0.45));
}

/* Discrete growth - stacked SVG stages that crossfade into each
   other when a task crosses a milestone day (2 / 15 / 60), rather
   than the same shape just stretching bigger. */
.plant-stage-crossfade {
  position: relative;
  width: 120px;
  height: 195px;
}

.plant-stage-layer {
  position: absolute;
  inset: 0;
  transition: opacity 0.8s ease;
}

/* Height tooltip - small dark pill that appears above a plant only
   while it's hovered, showing its current height in meters. Sits
   outside .plant-visual so it stays a fixed, readable size no matter
   how big/small the plant itself has scaled. */
/* Positioned at ~40% of the plant's actual rendered height rather than
   a fixed offset above the layout box. transform: scale() on
   .plant-visual doesn't affect layout size, so bottom:100% never
   tracked growth/depth - this reads the same --plant-scale custom
   property the plant's own scale transform uses (see .plant-visual),
   so the tag rises and falls together with however big the plant is
   actually drawn on screen right now, live during drag too. 195px is
   the default rendered pixel height of the plant art (getPlantSVG's
   default width=120 → height=195); 0.4 = 40% up that height. */
.plant-height-tag {
  position: absolute;
  left: 50%;
  bottom: calc(var(--plant-scale, 1) * 78px);
  transform: translateX(-50%) translateY(4px);
  padding: 3px 9px;
  border-radius: 20px;
  background: rgba(20, 16, 10, 0.86);
  border: 1px solid rgba(242, 184, 75, 0.45);
  color: var(--sun);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.16s ease, transform 0.16s ease;
  z-index: 5;
}

.garden-plant:hover .plant-height-tag {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

@media (max-width: 600px) {
  .plant-height-tag {
    font-size: var(--fs-xs);
    padding: 2px 7px;
    /* Mobile scales .plant-visual by an extra 0.60x on top of
       --plant-scale (see .plant-visual below) - match it here so the
       tag still lands at ~40% of the plant's actual on-screen height
       instead of floating too high above a smaller mobile plant. */
    bottom: calc(var(--plant-scale, 1) * 46.8px);
  }
}

/* Sits in the middle of the plant's three bands: above the ground
   scenery outside it (which is sealed at 3, and this whole wrapper
   starts at 10), below .plant-visual's art at 1, and above the
   .daily-ready halo at -1. */
.plant-label-tag {
  position: absolute;
  top: 100%;
  left: 50%;
  z-index: 0;
  transform: translateX(-50%);
  text-align: center;
  margin-top: 7px;
  pointer-events: none;
  white-space: normal;
  width: 130px;
}

/* Absolutely positioned inside the scroller, which means it is placed
   against the scroll ORIGIN (the top of the sky), not against the
   plot - so this is measured down from the top instead of up from the
   bottom, landing it in the middle of the plot where the garden it is
   describing actually is. */
.garden-empty-msg {
  position: absolute;
  left: 50%;
  top: calc(var(--garden-sky-lift, 150vh) + 25vh);
  transform: translate(-50%, -50%);
  width: 82%;
  max-width: 340px;
  text-align: center;
  font-family: var(--font-body);
  font-size: var(--fs-base);
  color: var(--ink-on-dark);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.6);
  pointer-events: none;
}

.plant-label-name {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--linen);
  text-shadow:
    0 1px 6px rgba(0, 0, 0, 0.95),
    0 0 16px rgba(0, 0, 0, 0.70);
  line-height: 1.3;
  letter-spacing: 0.02em;
}

.plant-label-streak {
  display: block;
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--sun);
  text-shadow:
    0 1px 5px rgba(0, 0, 0, 0.95),
    0 0 12px rgba(0, 0, 0, 0.80);
  line-height: 1.2;
  letter-spacing: 0.01em;
}

@media (max-width: 600px) {
  .garden-plant {
    bottom: calc(var(--plant-depth-bottom, 0%) - 8px - (var(--plant-scale, 1) - 1) * 1.8px);
  }
  .plant-visual {
    transform: scale(calc(var(--plant-scale, 1) * 0.60));
  }
  .plant-label-name {
    font-size: var(--fs-xs);
  }
  .plant-label-streak {
    font-size: var(--fs-xs);
  }
}


/* ========================================
   Sky ambient detail - stars, clouds, hills,
   birds, fireflies, ground tufts
   ======================================== */

/* --- Night stars --- */
.sky-stars {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 3s ease;
}

/* Small star field - 1 px dots via box-shadow */
.sky-stars::before {
  content: '';
  position: absolute;
  top: 0; left: 0;
  width: 1px;
  height: 1px;
  background: rgba(255, 255, 255, 0.90);
  box-shadow:
    40px 28px rgba(255,255,255,0.80),
    115px 52px rgba(255,255,255,0.60),
    228px 18px rgba(255,255,255,0.75),
    345px 44px rgba(255,255,255,0.90),
    412px 32px rgba(255,255,255,0.55),
    528px 62px rgba(255,255,255,0.70),
    645px 22px rgba(255,255,255,0.85),
    732px 48px rgba(255,255,255,0.65),
    845px 35px rgba(255,255,255,0.50),
    958px 58px rgba(255,255,255,0.80),
    1048px 28px rgba(255,255,255,0.70),
    1162px 45px rgba(255,255,255,0.60),
    1275px 32px rgba(255,255,255,0.85),
    1388px 55px rgba(255,255,255,0.55),
    1478px 40px rgba(255,255,255,0.75),
    1545px 25px rgba(255,255,255,0.65),
    72px 98px rgba(255,255,255,0.70),
    185px 122px rgba(255,255,255,0.85),
    298px 88px rgba(255,255,255,0.60),
    425px 115px rgba(255,255,255,0.75),
    538px 95px rgba(255,255,255,0.90),
    652px 138px rgba(255,255,255,0.55),
    768px 108px rgba(255,255,255,0.80),
    882px 82px rgba(255,255,255,0.65),
    995px 122px rgba(255,255,255,0.70),
    1108px 95px rgba(255,255,255,0.85),
    1222px 118px rgba(255,255,255,0.60),
    1335px 102px rgba(255,255,255,0.50),
    1448px 132px rgba(255,255,255,0.75),
    1522px 88px rgba(255,255,255,0.65),
    28px 172px rgba(255,255,255,0.80),
    148px 195px rgba(255,255,255,0.55),
    265px 168px rgba(255,255,255,0.75),
    385px 188px rgba(255,255,255,0.65),
    502px 162px rgba(255,255,255,0.85),
    618px 192px rgba(255,255,255,0.50),
    735px 175px rgba(255,255,255,0.70),
    852px 185px rgba(255,255,255,0.80),
    968px 165px rgba(255,255,255,0.60),
    1085px 192px rgba(255,255,255,0.75),
    1202px 175px rgba(255,255,255,0.85),
    1318px 162px rgba(255,255,255,0.55),
    1435px 188px rgba(255,255,255,0.70),
    1548px 172px rgba(255,255,255,0.65),
    55px 252px rgba(255,255,255,0.60),
    175px 275px rgba(255,255,255,0.80),
    308px 248px rgba(255,255,255,0.70),
    428px 268px rgba(255,255,255,0.55),
    558px 255px rgba(255,255,255,0.85),
    672px 278px rgba(255,255,255,0.65),
    792px 262px rgba(255,255,255,0.75),
    912px 288px rgba(255,255,255,0.50),
    1035px 255px rgba(255,255,255,0.80),
    1155px 272px rgba(255,255,255,0.60),
    1275px 248px rgba(255,255,255,0.70),
    1395px 265px rgba(255,255,255,0.85);
}

/* Larger bright stars layer */
.sky-stars::after {
  content: '';
  position: absolute;
  top: 0; left: 0;
  width: 2px;
  height: 2px;
  background: rgba(255, 255, 255, 1.00);
  border-radius: 50%;
  box-shadow:
    188px 42px rgba(255,255,255,0.95),
    525px 55px rgba(255,255,255,0.90),
    845px 30px rgba(255,255,255,1.00),
    1162px 48px rgba(255,255,255,0.90),
    1435px 38px rgba(255,255,255,0.95),
    315px 148px rgba(255,255,255,0.90),
    728px 165px rgba(255,255,255,0.85),
    1075px 152px rgba(255,255,255,0.95),
    1388px 168px rgba(255,255,255,0.85),
    468px 242px rgba(255,255,255,0.90),
    902px 258px rgba(255,255,255,0.85),
    1252px 238px rgba(255,255,255,0.95);
}

/* Show stars at night */
.sky-night .sky-stars {
  opacity: 1;
}

/* Subtle star twinkle on the big-star layer */
@keyframes starTwinkle {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.60; }
}
.sky-night .sky-stars::after {
  animation: starTwinkle 3.5s ease-in-out infinite;
}


/* --- Daytime clouds --- */
.sky-clouds {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 62%;
  pointer-events: none;
  overflow: hidden;
}

.cloud {
  position: absolute;
  background: rgba(255, 255, 255, 0.82);
  border-radius: 100px;
  filter: blur(1px);
  opacity: 0;
  transition: opacity 4s ease;
}
.cloud::before,
.cloud::after {
  content: '';
  position: absolute;
  background: inherit;
  border-radius: 50%;
}

/* Show clouds by day */
.sky-day .cloud {
  opacity: var(--c-opacity, 0.68);
}

.cloud-1 {
  width: 180px; height: 44px;
  top: 14%; left: -220px;
  --c-opacity: 0.72;
  animation: cloudDrift 68s linear infinite;
}
.cloud-1::before {
  width: 80px; height: 66px;
  top: -38px; left: 30px;
}
.cloud-1::after {
  width: 58px; height: 52px;
  top: -28px; right: 34px;
}

.cloud-2 {
  width: 135px; height: 36px;
  top: 30%; left: -180px;
  --c-opacity: 0.52;
  filter: blur(2px);
  animation: cloudDrift 96s linear infinite 22s;
}
.cloud-2::before {
  width: 60px; height: 50px;
  top: -26px; left: 20px;
}
.cloud-2::after {
  width: 46px; height: 40px;
  top: -20px; right: 26px;
}

.cloud-3 {
  width: 215px; height: 52px;
  top: 6%; left: -270px;
  --c-opacity: 0.50;
  filter: blur(1.5px);
  animation: cloudDrift 120s linear infinite 48s;
}
.cloud-3::before {
  width: 92px; height: 76px;
  top: -44px; left: 44px;
}
.cloud-3::after {
  width: 70px; height: 58px;
  top: -34px; right: 48px;
}

@keyframes cloudDrift {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(100vw + 360px)); }
}


/* --- Daytime birds --- */
.sky-birds {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}

.bird {
  position: absolute;
  width: 28px;
  height: 8px;
  opacity: 0;
  transition: opacity 3s ease;
}
.bird::before,
.bird::after {
  content: '';
  position: absolute;
  top: 0;
  width: 13px;
  height: 6px;
  border-top: 2.5px solid rgba(18, 18, 28, 0.48);
  border-radius: 50% 50% 0 0;
}
.bird::before { left: 0; }
.bird::after  { left: 15px; }

.sky-day .bird {
  opacity: 1;
}

.bird-1 {
  top: 20%;
  left: -40px;
  animation: birdGlide 28s linear infinite 4s;
}
.bird-2 {
  top: 15%;
  left: -40px;
  animation: birdGlide 38s linear infinite 16s;
}

@keyframes birdGlide {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(100vw + 120px)); }
}


/* --- Background hill silhouettes --- */
.sky-hills {
  position: absolute;
  bottom: 24vh;
  left: 0;
  right: 0;
  height: 22vh;
  pointer-events: none;
}

/* Far left hill - muted cool-green, lower opacity */
.sky-hills::before {
  content: '';
  position: absolute;
  bottom: 0;
  left: -8%;
  width: 62%;
  height: 100%;
  background: linear-gradient(to bottom, #1d4230 0%, #2a5840 50%, transparent 100%);
  border-radius: 58% 42% 0 0 / 100% 100% 0 0;
  opacity: 0.42;
}

/* Far right hill - slightly darker, offset */
.sky-hills::after {
  content: '';
  position: absolute;
  bottom: 0;
  right: -6%;
  width: 56%;
  height: 86%;
  background: linear-gradient(to bottom, #162e22 0%, #224838 50%, transparent 100%);
  border-radius: 44% 56% 0 0 / 100% 100% 0 0;
  opacity: 0.50;
}


/* --- Atmospheric horizon haze --- */
.sky-horizon-haze {
  position: absolute;
  bottom: 20vh;
  left: 0;
  right: 0;
  height: 14vh;
  background: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(70, 110, 80, 0.10) 60%,
    rgba(60, 90, 65, 0.18) 100%
  );
  pointer-events: none;
}


/* --- Night fireflies --- */
.sky-fireflies {
  position: absolute;
  bottom: 24vh;
  left: 0;
  right: 0;
  height: 22vh;
  pointer-events: none;
}

.firefly {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: transparent;
}

/* Fireflies animate only at night */
.sky-night .firefly {
  animation: fireflyGlow 3.5s ease-in-out infinite,
             fireflyDrift 5.2s ease-in-out infinite;
}

@keyframes fireflyGlow {
  0%, 100% {
    background: rgba(200, 255, 100, 0.05);
    box-shadow: 0 0 2px 1px rgba(180, 255, 60, 0.05);
  }
  50% {
    background: rgba(220, 255, 100, 0.95);
    box-shadow: 0 0 8px 3px rgba(200, 255, 60, 0.70);
  }
}

@keyframes fireflyDrift {
  0%, 100% { transform: translate(0, 0); }
  25%  { transform: translate(7px,  -11px); }
  50%  { transform: translate(13px, -5px); }
  75%  { transform: translate(3px,  -14px); }
}

.firefly-1 { left: 10%; top: 28%; animation-delay: 0s,    1.2s; }
.firefly-2 { left: 30%; top: 58%; animation-delay: -1.2s, -2.8s; }
.firefly-3 { left: 63%; top: 38%; animation-delay: -2.5s, -1.0s; }
.firefly-4 { left: 82%; top: 50%; animation-delay: -0.8s, -3.5s; }


/* --- Ground grass tufts --- */
.ground-tuft {
  position: absolute;
  top: -20px;
  width: 6px;
  height: 20px;
  background: linear-gradient(to top, #3a6020, #5a9035);
  border-radius: 40% 60% 0 0;
  transform-origin: bottom center;
}

.gt-1  { left:  3%;  transform: rotate(-8deg);  height: 16px; }
.gt-2  { left:  5%;  transform: rotate(6deg); }
.gt-3  { left: 12%;  transform: rotate(-4deg);  height: 18px; background: linear-gradient(to top,#3a6020,#52883a); }
.gt-4  { left: 24%;  transform: rotate(5deg);   height: 22px; }
.gt-5  { left: 38%;  transform: rotate(-6deg); }
.gt-6  { left: 52%;  transform: rotate(7deg);   height: 18px; background: linear-gradient(to top,#3a6020,#52883a); }
.gt-7  { left: 62%;  transform: rotate(-3deg);  height: 22px; }
.gt-8  { left: 74%;  transform: rotate(8deg);   height: 16px; }
.gt-9  { left: 83%;  transform: rotate(-5deg); }
.gt-10 { left: 93%;  transform: rotate(4deg);   height: 18px; background: linear-gradient(to top,#3a6020,#52883a); }


/* --- Ground pebbles --- */
.ground-pebble {
  position: absolute;
  background: rgba(58, 38, 22, 0.52);
  border-radius: 50%;
}

.gp-1 { left: 18%; top: 40%; width: 7px;  height: 5px; }
.gp-2 { left: 45%; top: 54%; width: 9px;  height: 6px; }
.gp-3 { left: 70%; top: 44%; width: 6px;  height: 4px; }
.gp-4 { left: 87%; top: 50%; width: 5px;  height: 4px; }


/* ========================================
   Page containers - client-side routing
   ======================================== */

.page {
  position: fixed;
  inset: 0;
  z-index: 10;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}


/* ========================================
   Home page - the potting bench

   The whole frame is the top of a workbench,
   seen close up. No sky, no horizon, no
   landscape: #sky is switched off entirely
   while this page is showing (see the
   sky-off rule at the end of this section
   and the toggle in navigateTo).

   Five layers, back to front:

     0  .bench            the wood itself -
                          boards, grain, seams,
                          knots, and the pool
                          of light
     1  .bench-props-back objects further away:
                          smaller, higher up
                          the frame, slightly
                          knocked back in
                          contrast
     2  .home-inner       the tag, the label
                          and the buttons
     3  .bench-props-front objects nearer the
                          viewer: larger,
                          lower, sharper, and
                          allowed to overlap
                          the buttons
     5  .bench-vignette   one shading pass over
                          everything at once

   That last layer is the important one. The
   props are flat illustrations and the buttons
   are DOM elements; lighting each of them
   separately would never agree. Darkening the
   whole frame from one place is what makes
   them look like they are sitting on the same
   table under the same light.

   Both prop layers are pointer-events:none, so
   a leaf lying across a tag is a picture and
   the tag underneath still takes the tap.
   ======================================== */

.page-home {
  /* The wordmark plank sets the scale for the page. Everything else
     is measured against it: the buttons sit inside it, and the props
     stand outside it. Change this one number to rebalance the page. */
  --tag-width: min(560px, 90vw);

  /* Where the light is. Kept as variables because the pool and the
     vignette have to agree with each other, and every prop's own
     shading was drawn assuming light from up here and slightly left
     (see the shadow direction note in 01-app-core.js). */
  --light-x: 47%;
  --light-y: 24%;

  background: transparent;
  display: block;
}

.home-inner {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 660px;
  margin: 0 auto;
  padding: clamp(20px, 5vh, 62px) 16px 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
}


/* --- The table ---------------------------------------------------
   position:fixed so the bench fills the window rather than the
   content, and so a short window scrolls the menu over a still
   table instead of dragging the wood up with it. .page is fixed and
   sets no transform, so fixed here still resolves to the viewport. */

.bench {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}

/* Boards run front-to-back, so the seams are vertical.

   Five background layers, and the order they are listed in is the
   order they paint, topmost first:

     1  fine grain, tight and low-contrast
     2  broader figure, the wider dark streaks
     3  seams: a dark groove with a lit edge on its right, at five
        deliberately UNEVEN positions - evenly spaced seams are what
        made the first attempt read as tiling rather than timber
     4  board-to-board tone, so no two boards are quite the same
        colour, keyed to the same five positions
     5  the base colour underneath all of it

   The grain angles are 91deg and 89.2deg rather than 90deg. That
   half-degree of disagreement stops the two layers from lining up
   into a moire and costs nothing. */
.bench-boards {
  position: absolute;
  inset: 0;
  background-color: #A97C50;
  background-image:
    repeating-linear-gradient(
      91deg,
      rgba(88, 58, 30, 0)      0px,  rgba(88, 58, 30, 0)      3px,
      rgba(88, 58, 30, 0.12)   3px,  rgba(88, 58, 30, 0.12)   4px,
      rgba(88, 58, 30, 0)      4px,  rgba(88, 58, 30, 0)     12px,
      rgba(255, 226, 180, 0.05) 12px, rgba(255, 226, 180, 0.05) 14px,
      rgba(88, 58, 30, 0)     14px,  rgba(88, 58, 30, 0)     23px
    ),
    repeating-linear-gradient(
      89.2deg,
      rgba(70, 44, 22, 0)      0px,  rgba(70, 44, 22, 0)     34px,
      rgba(70, 44, 22, 0.09)  34px,  rgba(70, 44, 22, 0.09)  41px,
      rgba(70, 44, 22, 0)     41px,  rgba(70, 44, 22, 0)     97px
    ),
    linear-gradient(
      90deg,
      rgba(0, 0, 0, 0)           0%,     rgba(0, 0, 0, 0)          15.2%,
      rgba(52, 32, 14, 0.55)    15.2%,   rgba(52, 32, 14, 0.55)    15.8%,
      rgba(244, 206, 154, 0.16) 15.8%,   rgba(244, 206, 154, 0.16) 16.4%,
      rgba(0, 0, 0, 0)          16.4%,   rgba(0, 0, 0, 0)          32.6%,
      rgba(52, 32, 14, 0.55)    32.6%,   rgba(52, 32, 14, 0.55)    33.2%,
      rgba(244, 206, 154, 0.16) 33.2%,   rgba(244, 206, 154, 0.16) 33.8%,
      rgba(0, 0, 0, 0)          33.8%,   rgba(0, 0, 0, 0)          49.4%,
      rgba(52, 32, 14, 0.55)    49.4%,   rgba(52, 32, 14, 0.55)    50.0%,
      rgba(244, 206, 154, 0.16) 50.0%,   rgba(244, 206, 154, 0.16) 50.6%,
      rgba(0, 0, 0, 0)          50.6%,   rgba(0, 0, 0, 0)          67.8%,
      rgba(52, 32, 14, 0.55)    67.8%,   rgba(52, 32, 14, 0.55)    68.4%,
      rgba(244, 206, 154, 0.16) 68.4%,   rgba(244, 206, 154, 0.16) 69.0%,
      rgba(0, 0, 0, 0)          69.0%,   rgba(0, 0, 0, 0)          84.2%,
      rgba(52, 32, 14, 0.55)    84.2%,   rgba(52, 32, 14, 0.55)    84.8%,
      rgba(244, 206, 154, 0.16) 84.8%,   rgba(244, 206, 154, 0.16) 85.4%,
      rgba(0, 0, 0, 0)          85.4%,   rgba(0, 0, 0, 0)         100%
    ),
    linear-gradient(
      90deg,
      rgba(255, 226, 182, 0.05)  0%,    rgba(255, 226, 182, 0.05) 15.2%,
      rgba(60, 38, 18, 0.06)    15.2%,  rgba(60, 38, 18, 0.06)    32.6%,
      rgba(255, 226, 182, 0.03) 32.6%,  rgba(255, 226, 182, 0.03) 49.4%,
      rgba(60, 38, 18, 0.05)    49.4%,  rgba(60, 38, 18, 0.05)    67.8%,
      rgba(255, 226, 182, 0.06) 67.8%,  rgba(255, 226, 182, 0.06) 84.2%,
      rgba(60, 38, 18, 0.07)    84.2%,  rgba(60, 38, 18, 0.07)   100%
    ),
    linear-gradient(178deg, #B4855A 0%, #A67A4C 46%, #96693E 100%);
}

/* Knots and one split. They belong to the wood, so they go under
   every prop and carry no shadow of their own - a knot with a drop
   shadow reads as a sticker of a knot. */
.bench-marks {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.bench-mark {
  position: absolute;
  display: block;
}

.bench-mark svg {
  display: block;
  width: 100%;
  height: auto;
}

.bench-knot-a { top: 14%;  left: 8%;   width: clamp(46px, 6vw, 82px);  transform: rotate(-8deg);  }
.bench-knot-b { top: 63%;  left: 24%;  width: clamp(34px, 4.4vw, 60px); transform: rotate(14deg); }
.bench-knot-c { top: 34%;  right: 11%; width: clamp(28px, 3.6vw, 48px); transform: rotate(-21deg); }

.bench-split {
  top: 8%;
  right: 31%;
  width: 26px;
  height: 30%;
}

.bench-split svg {
  height: 100%;
  width: 100%;
}

/* The pool of light on the wood, and the fall-off towards the edges.
   Under the props, so the props keep their own drawn shading; the
   layer that unifies everything is .bench-vignette, further down. */
.bench-light {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(
      118% 82% at var(--light-x) var(--light-y),
      rgba(255, 231, 178, 0.34) 0%,
      rgba(255, 216, 152, 0.16) 30%,
      rgba(255, 205, 140, 0.04) 52%,
      rgba(0, 0, 0, 0) 70%
    ),
    radial-gradient(
      132% 116% at var(--light-x) 30%,
      rgba(38, 20, 6, 0) 38%,
      rgba(38, 20, 6, 0.16) 62%,
      rgba(32, 17, 5, 0.36) 82%,
      rgba(26, 13, 4, 0.56) 100%
    );
}

/* The one shading pass over the whole frame - wood, props and
   buttons together. Gentle, because .bench-light has already done
   most of the work on the wood; this is only here to stop the props
   and the buttons from looking lit differently to the table. */
.bench-vignette {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background:
    radial-gradient(
      104% 88% at var(--light-x) var(--light-y),
      rgba(255, 232, 186, 0.10) 0%,
      rgba(255, 220, 164, 0.03) 34%,
      rgba(0, 0, 0, 0) 58%
    ),
    radial-gradient(
      126% 112% at var(--light-x) 34%,
      rgba(34, 18, 5, 0) 44%,
      rgba(34, 18, 5, 0.10) 70%,
      rgba(28, 14, 4, 0.26) 100%
    );
}


/* --- Where the objects sit ---------------------------------------
   Every prop is placed from the CENTRE outwards, with a percentage
   floor: `max(2%, calc(50% - 620px))`. On a wide window the props
   keep their distance from the menu; on a narrow one the floor takes
   over and they hug the edges instead of sliding underneath the
   buttons. Same trick the old horizon layout used, and the reason
   this page needs no JS to lay itself out.

   Sizes are clamp()ed rather than fixed so the whole scene scales
   with the window instead of turning into a few big objects on a
   large empty table. */

.bench-props {
  position: fixed;
  inset: 0;
  pointer-events: none;
}

.bench-props-back  { z-index: 1; }
.bench-props-front { z-index: 3; }

.bench-prop {
  position: absolute;
  display: block;
}

.bench-prop svg {
  display: block;
  width: 100%;
  height: auto;
}

/* Depth, part one: the far objects lose a little contrast and warmth,
   the way anything does at the edge of a pool of light. Part two is
   the placement below - back props sit higher in the frame and are
   drawn smaller. */
.bench-props-back .bench-prop {
  filter: saturate(0.9) brightness(0.93);
}

/* Back row */
.prop-can {
  top: 5vh;
  left: max(2%, calc(50% - 620px));
  width: clamp(122px, 15vw, 200px);
  transform: rotate(-3deg);
}

.prop-pot-back {
  top: 7vh;
  right: max(3%, calc(50% - 590px));
  width: clamp(70px, 8.5vw, 108px);
  transform: rotate(2deg);
}

.prop-leaf-b {
  top: 40vh;
  left: max(4%, calc(50% - 500px));
  width: clamp(52px, 6.5vw, 84px);
}

.prop-twine {
  top: 44vh;
  right: max(2%, calc(50% - 560px));
  width: clamp(62px, 7.5vw, 100px);
  transform: rotate(-6deg);
}

/* Front row */
.prop-pot-main {
  bottom: 2vh;
  left: max(1%, calc(50% - 690px));
  width: clamp(116px, 15vw, 188px);
  transform: rotate(-2deg);
}

.prop-pot-small {
  bottom: 15vh;
  left: max(3%, calc(50% - 585px));
  width: clamp(80px, 10vw, 128px);
  transform: rotate(3deg);
}

.prop-soil {
  bottom: 0;
  left: 50%;
  width: clamp(230px, 34vw, 420px);
  transform: translateX(-52%);
}

.prop-trowel {
  bottom: 18vh;
  right: max(1%, calc(50% - 655px));
  width: clamp(148px, 19vw, 248px);
  transform: rotate(9deg);
}

.prop-packets {
  bottom: 1vh;
  right: max(2%, calc(50% - 610px));
  width: clamp(136px, 18vw, 226px);
  transform: rotate(-4deg);
}

.prop-leaf-a {
  bottom: 31vh;
  right: max(6%, calc(50% - 470px));
  width: clamp(54px, 6.5vw, 88px);
}


/* --- The wordmark plank ------------------------------------------
   A board lying flat on the table with the vine grown across it, not
   a panel floating above one: hence the small rotation, the four
   nails, and a lip that is short compared to the drop shadow. A tall
   lip would stand it up on its edge. */

.home-tag {
  position: relative;
  width: var(--tag-width);
  padding: 20px clamp(18px, 4vw, 34px) 24px;
  border-radius: 18px 16px 17px 15px;
  transform: rotate(-1.2deg);
  background-color: #C08D5A;
  background-image:
    repeating-linear-gradient(
      88.6deg,
      rgba(96, 62, 30, 0)      0px,  rgba(96, 62, 30, 0)      5px,
      rgba(96, 62, 30, 0.13)   5px,  rgba(96, 62, 30, 0.13)   6px,
      rgba(96, 62, 30, 0)      6px,  rgba(96, 62, 30, 0)     17px,
      rgba(255, 232, 190, 0.07) 17px, rgba(255, 232, 190, 0.07) 19px,
      rgba(96, 62, 30, 0)     19px,  rgba(96, 62, 30, 0)     31px
    ),
    linear-gradient(176deg, #CE9B66 0%, #BC8853 52%, #A87343 100%);
  box-shadow:
    inset 0 3px 0 rgba(255, 231, 190, 0.34),
    inset 0 -5px 0 rgba(80, 50, 24, 0.34),
    0 7px 0 #7A5333,
    0 20px 28px rgba(38, 20, 6, 0.44);
}

.home-tag-face {
  position: relative;
}

.home-tag-face svg {
  display: block;
  width: 100%;
  height: auto;
}

/* Four nails, one per corner, each with its own head shadow so the
   light on them agrees with the light on everything else. */
.home-nail {
  position: absolute;
  width: 11px;
  height: 11px;
  border-radius: 50% 50% 46% 54% / 52% 48% 50% 50%;
  background: linear-gradient(160deg, #E4D4BC 0%, #B3A791 38%, #8C806D 72%, #665C4D 100%);
  box-shadow:
    inset 0 -2px 0 rgba(48, 40, 30, 0.4),
    0 2px 3px rgba(38, 20, 6, 0.45);
}

.home-nail-tl { top: 11px;    left: 13px;  }
.home-nail-tr { top: 11px;    right: 13px; }
.home-nail-bl { bottom: 11px; left: 13px;  }
.home-nail-br { bottom: 11px; right: 13px; }


/* --- The label ---------------------------------------------------
   Same pale board as before - the surface, the tilt and the lip are
   all untouched. What changed is the lettering: it is now cut into
   the board by the same three-pass machinery as the four signs (see
   hbCutText in 01-app-core.js) instead of being set in Nunito, so
   the whole menu is inscribed rather than one printed line sitting
   among four carved ones.

   Two things follow from that:

   1. There is no text here any more, so none of the font properties
      that used to be on this rule survive. The wording lives in
      HB_SIGNS and is read out by the aria-label on the host.

   2. It has to be sized by WIDTH, not by cap height like the
      buttons. The three passes are offset by a fixed fraction of the
      cap, so a line this long left to set its own height would come
      out with a cap about twice the buttons'. Pinning the width and
      letting the height follow lands it at ~16px against their 16px.

   Tilted the opposite way to the plank above, as before, so the two
   do not read as one glued object. */

.home-label {
  position: relative;
  z-index: 1;
  width: calc(var(--tag-width) * 0.74);
  margin: 18px 0 0;
  padding: 7px 16px 8px;
  border-radius: 9px 7px 9px 8px;
  transform: rotate(1deg);
  background: linear-gradient(178deg, #F8ECD5 0%, #EEDFC0 58%, #DCC79E 100%);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.62),
    0 4px 0 #BFA67C,
    0 11px 15px rgba(38, 20, 6, 0.32);
}

.home-label svg {
  display: block;
  width: 100%;
  height: auto;
}


/* --- Buttons: painted plant markers ------------------------------
   Each sign is TWO pieces, not one, and that is where the depth
   comes from:

     .marker       the block itself. What you see of it is the
                   chamfer running all the way round - the cut edge
                   of a thick piece of wood, darker than the face
                   because it turns away from the light.
     .marker-face  the flat top, inset inside that chamfer, lighter,
                   with its own grain and its own bevel bands.

   A single element with a border cannot do this: a border is one
   flat colour on all four sides, and the whole point is that the top
   edge catches the lamp while the bottom edge does not. Two nested
   surfaces, each with its own gradient and its own inset bands, is
   what reads as a solid object rather than a rounded rectangle.

   Under both: a hard offset shadow for the block's thickness, and a
   soft one for where it meets the bench. The hard one alone floats.

   Colour comes entirely from five custom properties set by the four
   paint classes below, so the structure is written once. Every
   transform composes rotate(var(--tilt)) FIRST, so hover and press
   can move a sign without straightening it.

   There is no stake under these. They are signs lying on a bench,
   not markers pushed into soil. */

.marker {
  --tilt: 0deg;
  --cut-height: 16px;
  position: relative;
  display: inline-block;
  padding: 7px;
  border: 0;
  border-radius: 17px 15px 16px 14px;
  background: linear-gradient(176deg, var(--rim-lit) 0%, var(--rim) 46%, var(--rim-deep) 100%);
  cursor: pointer;
  transform: rotate(var(--tilt));
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.20),
    inset 0 -2px 0 rgba(0, 0, 0, 0.22),
    0 7px 0 var(--rim-deep),
    0 17px 22px rgba(34, 18, 5, 0.44);
  transition: transform 0.1s, box-shadow 0.1s, filter 0.16s;
}

.marker-face {
  display: block;
  padding: 11px 17px 12px;
  border-radius: 12px 10px 11px 10px;
  background-color: var(--face);
  background-image:
    repeating-linear-gradient(
      89deg,
      rgba(20, 12, 4, 0)       0px,  rgba(20, 12, 4, 0)       4px,
      rgba(20, 12, 4, 0.10)    4px,  rgba(20, 12, 4, 0.10)    5px,
      rgba(20, 12, 4, 0)       5px,  rgba(20, 12, 4, 0)      13px,
      rgba(255, 255, 255, 0.07) 13px, rgba(255, 255, 255, 0.07) 15px,
      rgba(20, 12, 4, 0)      15px,  rgba(20, 12, 4, 0)      24px
    ),
    linear-gradient(177deg, var(--face-lit) 0%, var(--face) 52%, var(--face-deep) 100%);
  /* The four inset bands are the chamfer between face and block:
     bright along the top, dark along the bottom, and a weaker pair
     on the sides where the light is glancing rather than direct. */
  box-shadow:
    inset 0 3px 0 rgba(255, 255, 255, 0.30),
    inset 0 -4px 0 rgba(0, 0, 0, 0.26),
    inset 2px 0 0 rgba(255, 255, 255, 0.10),
    inset -2px 0 0 rgba(0, 0, 0, 0.16);
}

.marker-face svg {
  display: block;
  width: auto;
  height: var(--cut-height);
}

.marker:hover {
  filter: brightness(1.05);
  transform: rotate(var(--tilt)) translateY(-2px);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.20),
    inset 0 -2px 0 rgba(0, 0, 0, 0.22),
    0 9px 0 var(--rim-deep),
    0 20px 26px rgba(34, 18, 5, 0.46);
}

.marker:active {
  transform: rotate(var(--tilt)) translateY(5px);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.16),
    inset 0 -2px 0 rgba(0, 0, 0, 0.22),
    0 2px 0 var(--rim-deep),
    0 7px 11px rgba(34, 18, 5, 0.42);
}

/* --- The four paints --------------------------------------------
   Muted rather than primary. A pure red next to a pure yellow on a
   warm wood table fights the table; these are the same four hues
   pulled towards the palette the rest of the app already uses, so
   they read as painted wood rather than as plastic. */

.marker-green {
  --face-lit:  #8FBE6D;
  --face:      #6E9E52;
  --face-deep: #557F3E;
  --rim-lit:   #55813C;
  --rim:       #446F31;
  --rim-deep:  #2C4C1F;
}

.marker-blue {
  --face-lit:  #82B3CD;
  --face:      #5B8CA8;
  --face-deep: #466F88;
  --rim-lit:   #47738C;
  --rim:       #396078;
  --rim-deep:  #234254;
}

.marker-red {
  --face-lit:  #DE9583;
  --face:      #C0705E;
  --face-deep: #9E5545;
  --rim-lit:   #9A5241;
  --rim:       #834032;
  --rim-deep:  #5B2920;
}

.marker-yellow {
  --face-lit:  #F1CB7C;
  --face:      #DFA94F;
  --face-deep: #C08833;
  --rim-lit:   #B27E29;
  --rim:       #98681F;
  --rim-deep:  #6E4A14;
}

/* Primary. Bigger block, deeper chamfer, taller lettering - it wins
   on mass rather than on colour, so the other three stay readable. */
.marker-primary {
  --tilt: -1.6deg;
  --cut-height: clamp(21px, 4.6vw, 30px);
  margin: clamp(26px, 4.5vh, 52px) 0 0;
  padding: 9px;
  border-radius: 20px 17px 19px 16px;
}

.marker-primary .marker-face {
  padding: 14px clamp(24px, 5vw, 40px) 15px;
  border-radius: 14px 12px 13px 12px;
}

/* The three secondary signs. Staggered heights AND angles: three
   identical tags at the same y is a toolbar, not a handful of signs
   someone put down. */
.home-markers {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(8px, 1.8vw, 16px);
  margin: clamp(30px, 5vh, 56px) 0 0;
}

.marker-sm {
  --cut-height: clamp(13px, 2.9vw, 17px);
}

.marker-sm .marker-face {
  padding: 10px clamp(10px, 1.9vw, 16px) 11px;
}

.marker-tasks      { --tilt: -3.4deg; margin-top: 6px;  }
.marker-greenhouse { --tilt:  2.2deg; margin-top: 22px; }
.marker-friends    { --tilt: -1.2deg; margin-top: 2px;  }

/* Keyboard focus has to survive all of the above. */
.marker:focus-visible {
  outline: 3px solid var(--linen);
  outline-offset: 4px;
}


/* --- #sky is off here -------------------------------------------
   Every other page in the app sits on the time-of-day sky. This one
   does not, and the bench is opaque, so leaving it running would be
   paying for star, cloud and firefly animation nobody can see.
   navigateTo() in 01-app-core.js adds and removes this class; the
   class is also in the markup, because home is the page the app
   opens on and navigateTo has not run yet at that point. */
.sky-bg.sky-off {
  display: none;
}


/* --- Narrower windows -------------------------------------------
   The props come off in order of how little they are missed. A
   tabletop can be busy and still legible at 1200px; at 480px the
   same object count buries the buttons. */

@media (max-width: 1024px) {
  .page-home {
    --tag-width: min(520px, 92vw);
  }

  .prop-trowel  { bottom: 21vh; }
  .prop-leaf-a  { bottom: 34vh; }
}

@media (max-width: 720px) {
  /* Five objects instead of ten. The ones kept are the ones that
     hold a corner each, so the frame still reads as full. */
  .prop-twine,
  .prop-leaf-b,
  .prop-pot-small,
  .prop-leaf-a {
    display: none;
  }

  .prop-can       { top: 3vh; width: clamp(96px, 24vw, 140px); }
  .prop-pot-back  { top: 4vh; width: clamp(58px, 14vw, 82px);  }
  .prop-pot-main  { bottom: 1vh; width: clamp(94px, 26vw, 140px); }
  .prop-trowel    { bottom: 15vh; width: clamp(112px, 32vw, 170px); }
  .prop-packets   { bottom: 0; width: clamp(108px, 31vw, 165px); }
  .prop-soil      { width: clamp(200px, 62vw, 300px); }

  .bench-knot-c,
  .bench-split { display: none; }

  .home-inner { padding-top: 26px; }

  .marker-primary {
    --cut-height: clamp(18px, 5.2vw, 24px);
    padding: 8px;
  }
}

@media (max-width: 480px) {
  /* "GREENHOUSE" is the longest label and sets the floor for the
     whole row at this width, exactly as it did on the old plaques. */
  .marker-sm {
    --cut-height: 12px;
  }

  .marker-sm .marker-face {
    padding: 9px 8px 10px;
  }

  .marker-greenhouse { margin-top: 15px; }

  /* Nothing to shrink here now - the cut sizes itself off the board.
     The board gets WIDER instead, because the line is fixed length
     and a narrow screen is the one place it can go unreadable. */
  .home-label {
    width: calc(var(--tag-width) * 0.96);
    padding-left: 10px;
    padding-right: 10px;
  }

  .prop-trowel { display: none; }
}

/* A short window has no vertical room to spend on the props at the
   top and bottom, and the menu has to win. */
@media (max-height: 620px) {
  .prop-can,
  .prop-pot-back,
  .prop-twine,
  .prop-leaf-a,
  .prop-leaf-b {
    display: none;
  }

  .home-inner { padding-top: 16px; }
  .marker-primary { margin-top: 18px; }
  .home-markers   { margin-top: 20px; }
}


/* ========================================
   Community counters on the home page
   Sits between the tagline and the first
   CTA. Deliberately quiet - it's context,
   not the point of the page.
   ======================================== */

.home-stats {
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: 4px;
  margin: 22px 0 0;
  padding: 12px 10px;
  width: 100%;
  max-width: 400px;
  border-radius: 11px 9px 11px 10px;
  transform: rotate(-0.6deg);
  background: linear-gradient(178deg, #F6E9D1 0%, #EADCBB 60%, #D6C199 100%);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.6),
    0 4px 0 #BBA277,
    0 11px 15px rgba(38, 20, 6, 0.3);
}

/* The strip is absent, not blank, until its data arrives - see
   renderHomeStats() in 08-home-stats.js. `display: none` rather than
   visibility so it takes up no vertical space and the CTA doesn't
   jump down when the numbers land. */
.home-stats.hidden {
  display: none;
}

.home-stat {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  min-width: 0;
}

.home-stat-value {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 600;
  line-height: 1;
  color: var(--bloom-deep);
  font-variant-numeric: tabular-nums;
}

.home-stat-label {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-align: center;
  line-height: 1.25;
}

.home-stat-divider {
  width: 1px;
  align-self: center;
  height: 26px;
  background: rgba(122, 92, 66, 0.28);
  flex: 0 0 auto;
}

@media (max-width: 420px) {
  .home-stats     { gap: 2px; padding: 11px 5px; }
  .home-stat-value { font-size: var(--fs-lg); }
  .home-stat-label { font-size: var(--fs-xs); letter-spacing: 0.05em; }
}


/* ========================================
   Site nav - small persistent bar
   ======================================== */







/* ========================================
   Garden page
   ======================================== */

.page-garden {
  background: transparent;
  pointer-events: none;       /* let clicks reach garden scene below */
}


.page-garden #loadingState {
  pointer-events: none;
}

/* ========================================
   Tasks page
   ======================================== */

.page-tasks {
  background: rgba(58, 40, 26, 0.55);
}

.tasks-nav {
  /* slight backdrop for readability on tasks page */
  background: rgba(8, 5, 2, 0.18);
  border-bottom: 1px solid rgba(251, 247, 238, 0.07);
}

.tasks-content {
  max-width: 640px;
  margin: 0 auto;
  padding: 12px 18px 72px;
}

/* Add-task form section - sits flush under the nav */
.task-add-section {
  margin: 0 14px 14px;
  padding: 10px;
  background-color: var(--bark);
  background-image: var(--wood-grain);
  border: 2px solid var(--bark-deep);
  border-radius: var(--r-lg);
  box-shadow: var(--lip) var(--bark-deep);
}


/* ========================================
   Task form
   ======================================== */

.task-form {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  padding: 10px;
  background: var(--clay);
  border: 2px solid var(--clay-deep);
  border-radius: var(--r-md);
}

.task-form input[type="text"] {
  flex: 1 1 140px;
  padding: 11px 13px;
  border: 2px solid var(--clay-deep);
  border-radius: var(--r-sm);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  background: var(--linen);
  color: var(--ink);
  transition: border-color 0.18s, box-shadow 0.18s;
}

.task-form input[type="text"]::placeholder {
  color: var(--ink-faint);
  font-weight: 400;
}

.task-form input[type="text"]:focus {
  outline: none;
  border-color: var(--leaf);
  box-shadow: 0 0 0 3px rgba(127, 169, 104, 0.35);
}

.cat-select {
  padding: 11px 10px;
  border: 2px solid var(--clay-deep);
  border-radius: var(--r-sm);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  background: var(--linen);
  color: var(--ink);
  cursor: pointer;
  transition: border-color 0.18s, box-shadow 0.18s;
}

.cat-select:focus {
  outline: none;
  border-color: var(--leaf);
  box-shadow: 0 0 0 3px rgba(127, 169, 104, 0.35);
}

.task-form button {
  padding: 11px 20px;
  border: 2px solid var(--leaf-dark);
  border-radius: var(--r-sm);
  background: var(--leaf-deep);
  color: var(--linen);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
  letter-spacing: 0.03em;
  box-shadow: var(--lip) var(--leaf-dark);
  transition: transform 0.1s, box-shadow 0.1s, filter 0.16s;
}

.task-form button:hover {
  filter: brightness(1.07);
}

.task-form button:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--leaf-dark);
}


/* ========================================
   Category sub-nav
   ======================================== */

.cat-subnav {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 0 14px 8px;
  margin-bottom: 14px;
  -ms-overflow-style: none;
  scrollbar-width: none;
}

.cat-subnav::-webkit-scrollbar {
  display: none;
}

.cat-tab {
  flex-shrink: 0;
  padding: 8px 16px 9px;
  border: 2px solid var(--bark-deep);
  border-radius: var(--r-sm);
  background: var(--bark-light);
  color: var(--linen);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: var(--lip) var(--bark-deep);
  transition: transform 0.1s, box-shadow 0.1s, filter 0.16s;
}

.cat-tab:hover {
  filter: brightness(1.07);
}

.cat-tab.active {
  background: var(--linen);
  border-color: var(--bark-deep);
  color: var(--ink);
  box-shadow: inset 0 3px 0 rgba(94, 70, 50, 0.14);
  transform: translateY(3px);
}


/* ========================================
   Task tab panels
   ======================================== */

.task-tab-panel {
  background-color: var(--bark);
  background-image: var(--wood-grain);
  border: 2px solid var(--bark-deep);
  border-radius: var(--r-lg);
  padding: 10px;
  box-shadow: var(--lip-deep) var(--bark-deep);
}

#taskTabsContainer .task-tab-panel {
  margin-top: 0;
}


/* ========================================
   Category lore section
   ======================================== */











/* ========================================
   Task list items
   ======================================== */

.task-list {
  list-style: none;
  margin: 0;
  padding: 12px;
  background: var(--clay);
  border: 2px solid var(--clay-deep);
  border-radius: var(--r-md);
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 60px;
}

.task-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  background: var(--linen);
  border: 2px solid var(--clay-edge);
  flex-wrap: wrap;
  transition: background 0.18s, border-color 0.18s, transform 0.1s;
}

.task-item:hover {
  background: #FDF7EA;
  border-color: var(--leaf);
}

.task-item input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--leaf-deep);
  cursor: pointer;
  flex-shrink: 0;
}

.task-item .task-text {
  flex: 1;
  font-size: var(--fs-sm);
  min-width: 50px;
  transition: color 0.22s;
}

.task-item.completed .task-text {
  text-decoration: line-through;
  color: var(--ink-faint);
}

/* Per-category badge colours */
.cat-badge {
  font-size: var(--fs-xs);
  font-weight: 700;
  border-radius: 20px;
  padding: 2px 7px;
  white-space: nowrap;
  flex-shrink: 0;
  background: var(--ink-on-dark);
  color: var(--bark);
}

.cat-badge[data-category="education"]   { background: #eaf3ea; color: #3d7a50; }
.cat-badge[data-category="exercise"]    { background: #fef8de; color: #8c6406; }
.cat-badge[data-category="mindfulness"] { background: #eeeaf8; color: #6858a8; }
.cat-badge[data-category="sleep"]       { background: #e6eef8; color: #3464a0; }
.cat-badge[data-category="chores"]      { background: var(--linen); color: var(--bark); }
.cat-badge[data-category="finance"]     { background: #e3efe9; color: #2f7d63; }
.cat-badge[data-category="misc"]        { background: var(--bloom-tint); color: var(--bloom-deep); }

.task-item button.remove {
  border: none;
  background: none;
  color: var(--bloom);
  cursor: pointer;
  font-size: var(--fs-sm);
  line-height: 1;
  flex-shrink: 0;
  padding: 2px 4px;
  border-radius: 6px;
  opacity: 0.5;
  transition: opacity 0.18s, color 0.18s, background 0.18s;
}

.task-item:hover button.remove {
  opacity: 1;
}

.task-item button.remove:hover {
  color: var(--bloom-deep);
  background: rgba(160, 56, 56, 0.10);
}

.empty-state {
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-align: center;
  margin: 14px 0 4px;
  line-height: 1.5;
}

.empty-state.hidden {
  display: none !important;
}


/* ========================================
   Stats page
   ======================================== */

.page-stats {
  background: rgba(8, 5, 2, 0.42);
}

.stats-nav {
  background: rgba(8, 5, 2, 0.18);
  border-bottom: 1px solid rgba(251, 247, 238, 0.07);
}

.stats-content {
  max-width: 1100px;
  margin: 0 auto;
  padding: 12px 18px 72px;
}

.stats-view-select-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}

.stats-view-select {
  flex: 1 1 200px;
  padding: 11px 13px;
  border: 2px solid var(--bark-deep);
  border-radius: var(--r-sm);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 700;
  background: var(--linen);
  color: var(--ink);
  cursor: pointer;
  box-shadow: var(--lip) var(--bark-deep);
  transition: border-color 0.18s, box-shadow 0.18s;
}

.stats-view-select:focus {
  outline: none;
  border-color: var(--leaf);
  box-shadow: 0 0 0 3px rgba(143, 191, 127, 0.22);
}

.stats-view.hidden {
  display: none !important;
}

.stats-heatmap-card {
  background: var(--clay);
  border: 3px solid var(--bark);
  border-radius: var(--r-lg);
  padding: 14px;
  margin-bottom: 14px;
  box-shadow: var(--lip) var(--bark-deep);
}

.stats-card-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--ink);
  margin: 0 0 12px;
}

/* GitHub-style contribution grid - scrolls horizontally on narrow
   screens rather than squeezing cells illegibly small. */
.heatmap-wrap {
  overflow: visible;
  padding-bottom: 4px;
}

.heatmap-grid {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(7, 1fr);
  grid-auto-columns: minmax(0, 1fr);
  gap: 3px;
  width: 100%;
  height: 130px;
}

.heatmap-week {
  display: contents;
}

.heatmap-cell {
  width: 100%;
  height: 100%;
  min-width: 0;
  border-radius: 3px;
  background: rgba(107, 79, 59, 0.10);
  cursor: pointer;
  transition: transform 0.12s ease, outline-color 0.12s ease;
}

.heatmap-cell:hover {
  transform: scale(1.25);
  outline: 1.5px solid rgba(58, 107, 74, 0.55);
  outline-offset: 0.5px;
}

.heatmap-cell-empty {
  background: transparent;
  cursor: default;
}
.heatmap-cell-empty:hover {
  transform: none;
  outline: none;
}

.heat-stage-0 { background: rgba(107, 79, 59, 0.10); }
.heat-stage-1 { background: #C3E4B0; }
.heat-stage-2 { background: #7CB37F; }
.heat-stage-3 { background: var(--leaf-deep); }

.heatmap-legend {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 10px;
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--ink-soft);
}

.heatmap-legend-swatch {
  width: 11px;
  height: 11px;
  border-radius: 3px;
  display: inline-block;
}

/* Shared tooltip, positioned via JS to follow the pointer over
   whichever heatmap cell is currently hovered. */
.heatmap-tooltip {
  position: fixed;
  z-index: 10000;
  pointer-events: none;
  background: rgba(20, 16, 10, 0.92);
  color: var(--sun);
  border: 1px solid rgba(242, 184, 75, 0.40);
  padding: 5px 10px;
  border-radius: 8px;
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  white-space: nowrap;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);

  /* The JS flips this label to the other side of the cursor when it
     would run off an edge (positionHeatmapTooltip in 05-stats-app.js).
     Flipping cannot help with a label WIDER than the viewport, which
     is reachable on a narrow phone, so cap the width as well. */
  max-width: calc(100vw - 16px);
}

/* nowrap is what keeps the date and the figures on one line on a
   desktop, where there is room. Below this width there is not, and a
   single line that cannot fit would overflow the max-width above
   rather than being clipped by it - so let it wrap instead. */
@media (max-width: 560px) {
  .heatmap-tooltip { white-space: normal; }
}

.stats-cards-grid {
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
}

.stat-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  flex: 1 1 0;
  min-width: 0;
  background: var(--linen);
  border: 2px solid var(--clay-deep);
  border-radius: var(--r-md);
  padding: 11px 12px;
  box-shadow: var(--lip) var(--clay-deep);
}

.stat-card-emoji {
  font-size: var(--fs-lg);
  line-height: 1;
  flex-shrink: 0;
}

.stat-card-body {
  min-width: 0;
  width: 100%;
}

.stat-card-label {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.stat-card-value {
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-weight: 700;
  color: var(--ink);
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.stat-card-sub {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  color: var(--ink-soft);
  margin-top: 1px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 600px) {
  .stats-content {
    padding: 0 12px 52px;
  }
  .heatmap-grid {
    height: 98px;
    gap: 2px;
  }
  .stats-cards-grid {
    gap: 5px;
  }
  .stat-card {
    padding: 8px 8px;
  }
  .stat-card-emoji {
    font-size: var(--fs-base);
  }
  .stat-card-label {
    font-size: var(--fs-xs);
  }
  .stat-card-value {
    font-size: var(--fs-xs);
  }
  .stat-card-sub {
    font-size: var(--fs-xs);
  }
}


/* ========================================
   Responsive
   ======================================== */

@media (max-width: 600px) {
  .tasks-content {
    padding: 0 12px 52px;
  }

  .task-add-section {
    border-radius: 0 0 14px 14px;
    padding: 12px 12px 14px;
  }
}


/* ========================================
   Auth widget - persistent account pill,
   top-right corner on every page
   ======================================== */

.auth-widget-btn {
  position: fixed;
  top: 14px;
  right: 16px;
  z-index: 9997;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px 6px 6px;
  border-radius: 24px;
  border: 1.5px solid rgba(251, 247, 238, 0.25);
  background: rgba(20, 16, 10, 0.72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: var(--ink-on-dark);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
  transition: border-color 0.16s, background 0.16s, transform 0.16s;
  max-width: 190px;
}

.auth-widget-btn:hover {
  border-color: rgba(242, 184, 75, 0.55);
  background: rgba(20, 16, 10, 0.88);
  transform: translateY(-1px);
}

.auth-avatar-img,
.auth-avatar-fallback {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  flex-shrink: 0;
  object-fit: cover;
}

.auth-avatar-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(145deg, var(--leaf-deep), var(--leaf-dark));
  font-size: var(--fs-sm);
  line-height: 1;
}

.auth-widget-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 600px) {
  .auth-widget-btn {
    top: 10px;
    right: 10px;
    max-width: 40px;
    padding: 5px;
    justify-content: center;
  }
  .auth-widget-label { display: none; }
}


/* ========================================
   Auth modal
   ======================================== */

.auth-modal {
  position: fixed;
  inset: 0;
  z-index: 10001;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.auth-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(8, 5, 2, 0.62);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

.auth-modal-card {
  position: relative;
  width: 100%;
  max-width: 360px;
  background: rgba(251, 247, 238, 0.98);
  border-radius: 20px;
  padding: 28px 24px 24px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
  text-align: center;
}

.auth-modal-close {
  position: absolute;
  top: 12px;
  right: 14px;
  background: none;
  border: none;
  font-size: var(--fs-base);
  color: var(--ink-soft);
  cursor: pointer;
  padding: 4px;
  opacity: 0.7;
}
.auth-modal-close:hover { opacity: 1; }

.auth-modal-avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  margin: 0 auto 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-xl);
  background: linear-gradient(145deg, var(--leaf), var(--leaf-deep));
  color: var(--linen);
  object-fit: cover;
}

.auth-modal-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  color: var(--ink);
  margin: 0 0 4px;
}

.auth-modal-subtitle {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  margin: 0 0 20px;
  line-height: 1.5;
}

.auth-google-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 11px 16px;
  border-radius: 12px;
  border: 1.5px solid var(--clay-edge);
  background: var(--linen);
  color: var(--ink);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: background 0.16s, box-shadow 0.16s;
}
.auth-google-btn:hover {
  background: var(--linen);
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
}
.auth-google-btn svg { width: 18px; height: 18px; flex-shrink: 0; }

.auth-signout-btn {
  width: 100%;
  padding: 11px 16px;
  border-radius: 12px;
  border: none;
  background: var(--bloom);
  color: var(--linen);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: background 0.16s;
}
.auth-signout-btn:hover { background: var(--bloom-deep); }

.auth-modal-note {
  margin-top: 14px;
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  line-height: 1.5;
}

.auth-modal-error {
  margin-top: 12px;
  font-size: var(--fs-xs);
  color: var(--bloom-deep);
  line-height: 1.5;
}


/* ========================================
   Profile identity header (Stats page)
   ======================================== */

.profile-header {
  background: rgba(251, 247, 238, 0.97);
  border-radius: 18px;
  padding: 18px 18px 16px;
  margin-bottom: 14px;
  box-shadow:
    0 8px 40px rgba(0, 0, 0, 0.28),
    0 2px 8px rgba(0, 0, 0, 0.12),
    0 1px 0 rgba(255, 255, 255, 0.55) inset;
}

.profile-header-top {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.profile-avatar-img,
.profile-avatar-fallback {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  flex-shrink: 0;
  object-fit: cover;
}

.profile-avatar-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-xl);
  background: linear-gradient(145deg, var(--leaf), var(--leaf-deep));
  color: var(--linen);
}

.profile-id-block {
  flex: 1 1 160px;
  min-width: 0;
}

.profile-name {
  font-family: var(--font-display);
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.profile-email {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.profile-badge {
  display: inline-block;
  margin-top: 5px;
  padding: 3px 10px;
  border-radius: 20px;
  background: rgba(143, 191, 127, 0.20);
  color: #3A5C30;
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.03em;
}

.profile-action-btn {
  flex-shrink: 0;
  padding: 8px 14px;
  border-radius: 10px;
  border: 1.5px solid var(--clay-edge);
  background: var(--linen);
  color: var(--ink);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.16s, box-shadow 0.16s;
}
.profile-action-btn:hover {
  background: var(--linen);
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.10);
}
.profile-action-btn.is-signout {
  border-color: rgba(201, 112, 112, 0.4);
  color: var(--bloom-deep);
}

.profile-quick-stats {
  display: flex;
  gap: 8px;
  margin-top: 14px;
  flex-wrap: wrap;
}

.profile-quick-stat {
  flex: 1 1 0;
  min-width: 90px;
  background: rgba(107, 79, 59, 0.06);
  border-radius: 12px;
  padding: 9px 10px;
  text-align: center;
}

.profile-quick-stat-value {
  font-family: var(--font-display);
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--leaf-deep);
  line-height: 1.2;
}

.profile-quick-stat-label {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-top: 2px;
}

@media (max-width: 600px) {
  .profile-header { padding: 14px 14px 12px; }
  .profile-avatar-img, .profile-avatar-fallback { width: 46px; height: 46px; font-size: var(--fs-lg); }
  .profile-name { font-size: var(--fs-sm); }
  .profile-quick-stat-value { font-size: var(--fs-sm); }
}


/* ========================================
   Developer mode - editable growth inspector
   ======================================== */

.dev-toggle-btn {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 9998;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 1.5px solid rgba(251, 247, 238, 0.25);
  background: rgba(20, 16, 10, 0.80);
  color: var(--ink-on-dark);
  font-size: var(--fs-lg);
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
  transition: transform 0.16s, border-color 0.16s, background 0.16s;
}

.dev-toggle-btn:hover {
  transform: translateY(-2px);
  border-color: rgba(242, 184, 75, 0.55);
}

.dev-toggle-btn.active {
  background: var(--sun);
  color: var(--ink);
  border-color: var(--sun);
}

.dev-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 380px;
  max-width: 92vw;
  z-index: 9999;
  background: rgba(14, 12, 8, 0.95);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-left: 1px solid rgba(251, 247, 238, 0.14);
  color: var(--ink-on-dark);
  font-family: var(--font-body);
  display: flex;
  flex-direction: column;
  box-shadow: -8px 0 34px rgba(0, 0, 0, 0.5);
}

.dev-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid rgba(251, 247, 238, 0.14);
  flex-shrink: 0;
}

.dev-panel-title {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--sun);
  letter-spacing: 0.03em;
}

.dev-panel-close {
  background: none;
  border: none;
  color: var(--ink-on-dark);
  font-size: var(--fs-base);
  cursor: pointer;
  opacity: 0.65;
  padding: 2px 6px;
}

.dev-panel-close:hover {
  opacity: 1;
}

.dev-panel-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 10px 16px;
  border-bottom: 1px solid rgba(251, 247, 238, 0.10);
  flex-shrink: 0;
}

.dev-btn {
  padding: 6px 10px;
  border-radius: 7px;
  border: 1px solid rgba(251, 247, 238, 0.22);
  background: rgba(251, 247, 238, 0.06);
  color: var(--ink-on-dark);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}

.dev-btn:hover {
  background: rgba(242, 184, 75, 0.16);
  border-color: rgba(242, 184, 75, 0.45);
}

.dev-panel-body {
  padding: 14px 16px 28px;
  overflow-y: auto;
  flex: 1;
}

.dev-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  margin-bottom: 8px;
}

.dev-note {
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--ink-soft);
  font-style: italic;
  margin: 0 0 16px;
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(251, 247, 238, 0.10);
}

.dev-task-row {
  background: rgba(251, 247, 238, 0.05);
  border: 1px solid rgba(251, 247, 238, 0.12);
  border-radius: 10px;
  padding: 10px 11px 11px;
  margin-bottom: 10px;
}

.dev-task-row-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 9px;
}

.dev-task-title {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--linen);
}

.dev-task-done {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  cursor: pointer;
  white-space: nowrap;
}

.dev-task-done input {
  accent-color: var(--sun);
  cursor: pointer;
}

.dev-field-group {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
  margin-bottom: 6px;
}

.dev-field-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--ink-soft);
  min-width: 76px;
}

.dev-number-input {
  width: 52px;
  padding: 4px 5px;
  border-radius: 6px;
  border: 1px solid rgba(251, 247, 238, 0.25);
  background: rgba(0, 0, 0, 0.3);
  color: var(--linen);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  text-align: center;
}

.dev-number-input:focus {
  outline: none;
  border-color: var(--sun);
}

.dev-quick-btn {
  padding: 3px 8px;
  border-radius: 6px;
  border: 1px solid rgba(251, 247, 238, 0.18);
  background: rgba(251, 247, 238, 0.06);
  color: var(--ink-on-dark);
  font-size: var(--fs-xs);
  font-weight: 700;
  cursor: pointer;
  transition: background 0.14s, border-color 0.14s;
}

.dev-quick-btn:hover {
  background: rgba(242, 184, 75, 0.20);
  border-color: rgba(242, 184, 75, 0.5);
}

.dev-milestones .dev-quick-btn {
  background: rgba(58, 107, 74, 0.18);
  border-color: rgba(143, 191, 127, 0.30);
}

.dev-milestones .dev-quick-btn:hover {
  background: rgba(143, 191, 127, 0.28);
  border-color: rgba(143, 191, 127, 0.55);
}

.dev-task-readout {
  font-size: var(--fs-xs);
  color: var(--sun);
  margin-top: 6px;
  padding-top: 7px;
  border-top: 1px dashed rgba(251, 247, 238, 0.12);
}

.dev-empty {
  text-align: center;
  font-style: italic;
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  margin-top: 20px;
}

@media (max-width: 600px) {
  .dev-panel {
    width: 100%;
    max-width: 100%;
    top: auto;
    height: 75vh;
    border-left: none;
    border-top: 1px solid rgba(251, 247, 238, 0.14);
  }
  .dev-toggle-btn {
    right: 14px;
    bottom: 14px;
    width: 42px;
    height: 42px;
    font-size: var(--fs-base);
  }
}

/* ========================================
   Plant skins - task-row pip + Greenhouse page
   ======================================== */

/* Task-row button. The pip carries the skin's three key colours, so
   the row itself tells you what that plant is wearing - tapping it
   jumps to the Greenhouse with that plant's skins already open. */
.task-item button.skin-btn {
  border: none;
  background: none;
  cursor: pointer;
  flex-shrink: 0;
  padding: 3px;
  border-radius: 8px;
  line-height: 0;
  opacity: 0.55;
  transition: opacity 0.18s, background 0.18s, transform 0.18s;
}

.task-item:hover button.skin-btn { opacity: 1; }

.task-item button.skin-btn:hover {
  background: rgba(58, 107, 74, 0.10);
  transform: scale(1.08);
}

.task-item button.skin-btn:focus-visible {
  outline: 2px solid var(--leaf-deep);
  outline-offset: 2px;
  opacity: 1;
}

.skin-pip {
  display: inline-block;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid rgba(46, 42, 34, 0.22);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);
  vertical-align: middle;
}

/* ========================================
   Greenhouse page
   ======================================== */

.page-greenhouse {
  background: rgba(8, 5, 2, 0.42);
}

.greenhouse-nav {
  background: rgba(8, 5, 2, 0.18);
  border-bottom: 1px solid rgba(251, 247, 238, 0.07);
}

.greenhouse-content {
  max-width: 900px;
  margin: 0 auto;
  padding: 12px 18px 72px;
}

.greenhouse-header {
  margin: 4px 0 18px;
}

.greenhouse-title {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: 800;
  color: var(--sun);
  margin: 0 0 5px;
  letter-spacing: 0.02em;
}

.greenhouse-subtitle {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.6;
  color: var(--clay);
  margin: 0;
  max-width: 54ch;
}

.greenhouse-empty {
  font-family: var(--font-display);
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--clay);
  text-align: center;
  padding: 34px 12px;
}

.greenhouse-empty.hidden {
  display: none !important;
}


/* ---- One card per plant ---- */

.plant-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(152px, 1fr));
  gap: 12px;
  align-items: start;
  padding: 14px;
  background-color: var(--bark);
  background-image: var(--wood-grain);
  border: 2px solid var(--bark-deep);
  border-radius: var(--r-lg);
  box-shadow: var(--lip-deep) var(--bark-deep);
}

.plant-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 14px 10px 12px;
  border-radius: var(--r-md);
  border: 2px solid var(--clay-deep);
  border-top-width: 5px;
  background: var(--clay);
  font: inherit;
  cursor: pointer;
  text-align: center;
  -webkit-appearance: none;
  appearance: none;
  box-shadow: var(--lip) var(--clay-deep);
  transition: transform 0.1s, box-shadow 0.1s, background 0.16s;
}

.plant-card:hover {
  background: #F0DBBB;
}

.plant-card:focus-visible {
  outline: 2px solid var(--leaf);
  outline-offset: 3px;
}

.plant-card.open {
  background: var(--linen);
  border-color: var(--bark-deep);
  transform: translateY(3px);
  box-shadow: inset 0 3px 0 rgba(94, 70, 50, 0.13);
}

/* Species tint on the top edge, matching the category badges. */
.plant-card[data-category="education"]   { border-top-color: #3D7A50; }
.plant-card[data-category="exercise"]    { border-top-color: #C4913A; }
.plant-card[data-category="mindfulness"] { border-top-color: #7A69B8; }
.plant-card[data-category="sleep"]       { border-top-color: #4A7BB0; }
.plant-card[data-category="chores"]      { border-top-color: var(--bark); }
.plant-card[data-category="finance"]     { border-top-color: #2F8A6C; }
.plant-card[data-category="misc"]        { border-top-color: #B4675C; }

/* Fixed-height stage so every card lines up whatever the plant. */
.plant-card-art {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  height: 134px;
  width: 100%;
  overflow: hidden;
  margin-bottom: 4px;
  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.25));
}

.plant-card-art svg {
  margin-bottom: -6px;
}

.plant-card-name {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.32;
  color: var(--ink);
  overflow-wrap: anywhere;
  width: 100%;
  /* Cap at 2 lines and reserve that height even for a short name, so
     a long task name can never make one card taller than the rest. */
  height: calc(1.32em * 2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.plant-card-species {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  color: #7d6d58;
}

.plant-card-skin {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 5px;
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(46, 42, 34, 0.09);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: #4a4034;
}

.plant-card.open .plant-card-skin {
  background: rgba(58, 107, 74, 0.16);
  color: #2c5a3c;
}


/* ---- The skin section that unfolds under a card ----
   Spans the whole grid, so it always drops onto its own row
   directly beneath the card that opened it. ---- */

.skin-drawer {
  grid-column: 1 / -1;
  background: rgba(251, 247, 238, 0.97);
  border-radius: 18px;
  padding: 16px 16px 18px;
  box-shadow:
    0 10px 44px rgba(0, 0, 0, 0.32),
    0 1px 0 rgba(255, 255, 255, 0.55) inset;
  animation: skinDrawerIn 0.22s ease-out;
}

@keyframes skinDrawerIn {
  from { opacity: 0; transform: translateY(-10px); }
  to   { opacity: 1; transform: translateY(0); }
}

.skin-drawer-head {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 14px;
}

.skin-drawer-heading {
  flex: 1 1 auto;
  min-width: 0;
  text-align: left;
}

.skin-drawer-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 400;
  color: var(--ink);
  margin: 0 0 3px;
  overflow-wrap: anywhere;
}

.skin-drawer-subtitle {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  color: var(--ink-soft);
  margin: 0;
}

.skin-drawer-close {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: 50%;
  background: rgba(46, 42, 34, 0.08);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  line-height: 1;
  cursor: pointer;
  transition: background 0.16s, color 0.16s;
}

.skin-drawer-close:hover {
  background: rgba(46, 42, 34, 0.16);
  color: var(--ink);
}

.skin-drawer-close:focus-visible {
  outline: 2px solid var(--leaf-deep);
  outline-offset: 2px;
}

.skin-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 12px;
}

.skin-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 12px 10px 11px;
  border-radius: 14px;
  border: 2px solid rgba(217, 207, 184, 0.7);
  background: linear-gradient(180deg, #EFE7D3 0%, #E3D8BE 62%, #CFC0A0 100%);
  cursor: pointer;
  text-align: center;
  transition: border-color 0.18s, box-shadow 0.18s, transform 0.18s;
}

.skin-tile:hover {
  border-color: rgba(58, 107, 74, 0.55);
  transform: translateY(-2px);
}

.skin-tile:focus-visible {
  outline: 2px solid var(--leaf-deep);
  outline-offset: 2px;
}

.skin-tile.selected {
  border-color: var(--leaf-deep);
  box-shadow: 0 0 0 3px rgba(58, 107, 74, 0.18);
}

/* Fixed-height stage so tiles line up whatever the plant's height. */
.skin-tile-art {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  height: 118px;
  width: 100%;
  overflow: hidden;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.22));
}

.skin-tile-art svg {
  margin-bottom: -6px;
}

.skin-tile-name {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink);
}

.skin-tile-note {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  line-height: 1.35;
  color: var(--ink-soft);
}

.skin-tile.selected .skin-tile-name::after {
  content: '';
  display: inline-block;
  width: 5px;
  height: 9px;
  margin-left: 5px;
  border: solid var(--leaf-deep);
  border-width: 0 2.5px 2.5px 0;
  transform: translateY(-1px) rotate(45deg);
}

/* ---- Locked skins -------------------------------------------------
   A locked tile is still a full tile: same size, same slot, and the
   plant at FULL colour. Nothing is dimmed or desaturated - a skin
   nobody can see is a skin nobody wants, and wanting it is the whole
   mechanic. The tile carries its unavailability elsewhere instead: a
   padlock pinned to the top-right corner, a muted name, and the
   requirement in place of the flavour note. */
.skin-tile {
  position: relative;   /* anchors the corner padlock */
}

.skin-tile.locked {
  cursor: not-allowed;
  border-color: rgba(180, 170, 150, 0.55);
  background: linear-gradient(180deg, #E7E1D3 0%, #DAD2BF 62%, #C6BDA8 100%);
}

/* No lift and no green edge on hover - a locked tile must not feel
   clickable even for the moment before nothing happens. */
.skin-tile.locked:hover {
  transform: none;
  border-color: rgba(180, 170, 150, 0.55);
}

.skin-tile.locked .skin-tile-name {
  color: #7d7364;
}

/* Pinned to the tile's corner rather than laid over the plant, so it
   never covers the thing it's advertising. The opaque disc keeps it
   legible against whatever colour happens to be behind it - these
   plants run from near-white to neon. */
.skin-lock {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgba(247, 243, 233, 0.94);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.22);
  font-size: var(--fs-xs);
  line-height: 1;
  pointer-events: none;
}

/* Stands in for .skin-tile-note while a skin is locked. */
.skin-tile-req {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  line-height: 1.3;
  color: var(--ink-soft);
}

.skin-tile-progress {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--leaf-deep);
  background: rgba(58, 107, 74, 0.12);
  border-radius: 999px;
  padding: 1px 8px;
}

/* Only rendered when that plant still has something left to earn, so
   a fully unlocked card never carries it. */
.plant-card-locked {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--ink-soft);
}

@media (max-width: 600px) {
  .greenhouse-title { font-size: var(--fs-lg); }
  .greenhouse-content { padding: 12px 14px 72px; }
  .plant-card-grid { grid-template-columns: repeat(auto-fill, minmax(134px, 1fr)); gap: 10px; }
  .plant-card-art { height: 116px; }
  .skin-drawer { padding: 14px 12px 16px; }
  .skin-grid { grid-template-columns: repeat(auto-fill, minmax(116px, 1fr)); gap: 10px; }
}

@media (max-width: 420px) {
  .skin-tile-art { height: 100px; }
  .skin-tile-note { font-size: var(--fs-xs); }
  .skin-tile-req { font-size: var(--fs-xs); }
  .skin-lock { width: 20px; height: 20px; font-size: var(--fs-xs); top: 5px; right: 5px; }
  .plant-card-art { height: 104px; }
}

@media (prefers-reduced-motion: reduce) {
  .task-item button.skin-btn,
  .plant-card,
  .skin-tile { transition: none; }
  .skin-tile:hover,
  .plant-card:hover { transform: none; }
  .skin-drawer { animation: none; }
}

/* ========================================
   Friends page
   ======================================== */

.page-friends {
  background: rgba(8, 5, 2, 0.42);
}

.friends-nav {
  /* slight backdrop for readability, same as the tasks page */
  background: rgba(8, 5, 2, 0.18);
  border-bottom: 1px solid rgba(251, 247, 238, 0.07);
}

.friends-content {
  max-width: 640px;
  margin: 0 auto;
  padding: 12px 18px 72px;
}

/* Card shell - same paper treatment as .task-tab-panel */
.friends-section {
  background: var(--clay);
  border: 3px solid var(--bark);
  border-radius: var(--r-lg);
  padding: 14px;
  margin-bottom: 12px;
  box-shadow: var(--lip) var(--bark-deep);
}

.friends-section-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--ink);
  margin: 0 0 12px;
}

.friends-sub-label {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 14px 0 6px;
}

.friends-sub-label:first-of-type {
  margin-top: 0;
}


/* ---- Search form ---- */

.friends-search-form {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}

.friends-search-form input[type="text"] {
  flex: 1 1 160px;
  padding: 11px 13px;
  border: 2px solid var(--clay-deep);
  border-radius: var(--r-sm);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  background: var(--linen);
  color: var(--ink);
  transition: border-color 0.18s, box-shadow 0.18s;
}

.friends-search-form input[type="text"]::placeholder {
  color: var(--ink-faint);
}

.friends-search-form input[type="text"]:focus {
  outline: none;
  border-color: var(--leaf);
  box-shadow: 0 0 0 3px rgba(143, 191, 127, 0.22);
}

.friends-search-form button {
  padding: 11px 18px;
  border: 2px solid var(--leaf-dark);
  border-radius: var(--r-sm);
  background: var(--leaf-deep);
  color: var(--linen);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: var(--lip) var(--leaf-dark);
  transition: transform 0.1s, box-shadow 0.1s, filter 0.16s;
}

.friends-search-form button:hover {
  filter: brightness(1.07);
}

.friends-search-result:not(:empty) {
  margin-top: 10px;
}

.friends-status {
  margin: 10px 0 0;
  font-size: var(--fs-sm);
  line-height: 1.45;
  color: #4A6B4A;
}

.friends-status.is-error {
  color: #A64B32;
}

.friends-status.hidden {
  display: none !important;
}


/* ---- Rows: your own name, search hits, requests, friends ---- */

.friends-me,
.friend-item {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 10px 12px;
  border: 2px solid var(--clay-deep);
  border-radius: var(--r-sm);
  background: var(--linen);
}

.friends-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.friends-list:empty {
  display: none;
}

.friend-name {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink);
  word-break: break-word;
}

.friend-sub {
  flex: 1 1 auto;
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  line-height: 1.4;
}

.friend-actions {
  margin-left: auto;
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

.friend-btn {
  padding: 6px 13px;
  border: 1.5px solid rgba(217, 207, 184, 0.75);
  border-radius: 20px;
  background: rgba(251, 247, 238, 0.9);
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.16s, border-color 0.16s, color 0.16s;
}

.friend-btn:hover {
  background: var(--linen);
  border-color: rgba(143, 191, 127, 0.7);
}

.friend-btn.is-primary {
  background: var(--leaf-deep);
  border-color: var(--leaf-deep);
  color: var(--linen);
}

.friend-btn.is-primary:hover {
  background: #2f5a3d;
  border-color: #2f5a3d;
}

.friend-btn:disabled {
  opacity: 0.5;
  cursor: default;
}


/* ========================================
   Username modal - sits inside the shared
   .auth-modal shell
   ======================================== */

.username-form {
  display: flex;
  flex-direction: column;
  gap: 9px;
  width: 100%;
}

.username-input {
  width: 100%;
  padding: 10px 12px;
  border: 1.5px solid var(--clay-edge);
  border-radius: 12px;
  font-family: var(--font-body);
  font-size: var(--fs-base);
  background: rgba(255, 253, 248, 0.9);
  color: var(--ink);
  transition: border-color 0.18s, box-shadow 0.18s;
}

.username-input::placeholder {
  color: var(--ink-faint);
}

.username-input:focus {
  outline: none;
  border-color: var(--leaf);
  box-shadow: 0 0 0 3px rgba(143, 191, 127, 0.22);
}

.username-input:disabled {
  opacity: 0.65;
}

.username-submit-btn:disabled {
  opacity: 0.6;
  cursor: default;
}


/* ========================================
   Friend garden page - read-only view of
   one friend's plot. The scene, plants,
   grass and fence all reuse the Garden
   page's own classes, so only this page's
   chrome is new.
   ======================================== */

/* Transparent overlay above the fixed scene, exactly like .page-garden
   - clicks must fall through to the garden underneath, so only the
   real controls opt back in below. */
.page-friend-garden {
  background: transparent;
  pointer-events: none;
}


.friend-garden-bar {
  /* .page-friend-garden above is pointer-events:none so clicks fall
     through to the scrollable garden scene behind it. That applies to
     this bar and the "Friends" button in it too, which is why the
     button did nothing: the click never reached it. The owner's Garden
     page has the same page-level rule and re-enables events on
     .garden-toolbar the same way - anything interactive that lives on
     top of a scene has to opt back in here. */
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  max-width: 640px;
  margin: 0 auto;
  padding: 10px 18px 0;
}

.friend-garden-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 400;
  color: var(--linen);
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
  margin: 0;
}

/* Says plainly that nothing here can be changed - this garden belongs
   to someone else, and none of it is editable or draggable. */
.friend-garden-readonly {
  margin-left: auto;
  padding: 3px 10px;
  border: 1px solid rgba(242, 184, 75, 0.45);
  border-radius: 20px;
  background: rgba(20, 16, 10, 0.55);
  color: var(--sun);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

/* Staleness note - shown when the summary's date isn't today, i.e.
   its owner hasn't opened the app yet today. */
.friend-garden-note {
  max-width: 640px;
  margin: 6px auto 0;
  padding: 0 18px;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  color: rgba(251, 247, 238, 0.82);
  text-shadow: 0 1px 5px rgba(0, 0, 0, 0.55);
}

.friend-garden-note.hidden {
  display: none;
}

/* Text only - must not intercept clicks meant for the scene below. */
.friend-garden-note,
.page-friend-garden .garden-empty-msg {
  pointer-events: none;
}

/* Look, don't touch: setupPlantDrag() is never called on these, so
   the grab cursor inherited from .garden-plant would be a lie. */
.friend-plant {
  cursor: default;
}


@media (max-width: 600px) {
  .friends-content { padding: 12px 14px 72px; }
  .friends-section { padding: 14px 13px 16px; }
  .friend-actions { margin-left: 0; }
  .friend-garden-bar { padding: 10px 14px 0; }
  .friend-garden-note { padding: 0 14px; }
  .friend-garden-title { font-size: var(--fs-base); }
}

/* ========================================
   Garden edit bar

   Sits under the Daily/Long-Term tabs on the Garden page. Needs
   pointer-events back on, because .page-garden turns them off so
   clicks fall through to the garden scene underneath.
   ======================================== */

.garden-edit-bar {
  pointer-events: auto;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 18px 8px;
}

.garden-edit-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.garden-edit-hint {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--linen);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  padding-right: 2px;
}

.garden-edit-save {
  border-color: rgba(122, 168, 88, 0.75);
  background: rgba(122, 168, 88, 0.92);
  color: var(--linen);
}

.garden-edit-discard {
  border-color: rgba(217, 207, 184, 0.45);
  background: rgba(251, 247, 238, 0.72);
  color: #7A5C4A;
}

/* While editing, plants get a soft halo so it's obvious which things
   are currently grabbable - and that the mode is on at all. */
.garden-scene.garden-editing .garden-plant {
  cursor: grab;
}

.garden-scene.garden-editing .garden-plant .plant-visual {
  filter: drop-shadow(0 0 10px rgba(242, 184, 75, 0.55));
}

@media (max-width: 600px) {
  .garden-edit-hint { display: none; }
}


/* ============================================================
   Quality floor
   ============================================================ */

/* One visible focus ring for the whole app. Sun is reserved for
   "you are here", which is exactly what focus means. */
button:focus-visible,
input:focus-visible,
select:focus-visible,
a:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--sun);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* The lip animation is the only motion that matters here; drop it
   along with everything else when the person has asked for less. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* Planks need room to breathe before they can be chunky. Below this
   the nav wraps to two rows rather than shrinking the tap targets. */
@media (max-width: 560px) {
  .task-add-section {
    margin: 0 10px 12px;
  }
  .cat-subnav {
    padding: 0 10px 8px;
  }
}


/* ============================================================
   The plot - one card per plant, replacing the pill row
   ============================================================ */

.plot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(116px, 1fr));
  gap: 10px;
  margin: 0 14px 16px;
  padding: 12px;
  background-color: var(--bark);
  background-image: var(--wood-grain);
  border: 2px solid var(--bark-deep);
  border-radius: var(--r-lg);
  box-shadow: var(--lip) var(--bark-deep);
}

.plot-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 10px 8px 12px;
  background: var(--clay);
  border: 2px solid var(--clay-deep);
  border-radius: var(--r-md);
  cursor: pointer;
  box-shadow: var(--lip) var(--clay-deep);
  transition: transform 0.1s, box-shadow 0.1s, background 0.16s;
}

.plot-card:hover { background: #F0DBBB; }

.plot-card.active {
  background: var(--linen);
  border-color: var(--bark-deep);
  transform: translateY(3px);
  box-shadow: inset 0 3px 0 rgba(94, 70, 50, 0.13);
}

/* A fixed art box so cards stay the same height whether the plant is
   a seed or a full canopy. The plant sits on the floor of it. */
.plot-card-art {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  height: 74px;
  overflow: hidden;
  pointer-events: none;
}

.plot-card-art svg { max-height: 100%; width: auto; }

.plot-card-name {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink);
  line-height: 1.2;
  /* "Miscellaneous" is one unbreakable word wider than a 116px card,
     and a single long word has no break opportunity in it - without
     this it spills out over the card's edge instead of wrapping. */
  overflow-wrap: anywhere;
  text-align: center;
}

.plot-card-count {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--ink-soft);
}

.plot-card-streak {
  margin-top: 4px;
  padding: 1px 8px 2px;
  border-radius: 20px;
  background: var(--sun);
  border: 1.5px solid var(--sun-deep);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: #4A3009;
}

/* "All tasks" has no species, so it gets the empty plot's height. */
.plot-card-all {
  justify-content: center;
  gap: 4px;
  min-height: 118px;
}

/* ---- The way back to the garden ---------------------------------
   Not a button in a corner with an arrow. A strip of the actual
   ground, sitting under the plot the way soil sits under plants. */
.plot-gate {
  display: block;
  width: calc(100% - 28px);
  margin: -6px 14px 18px;
  padding: 30px 0 14px;
  border: 2px solid #3E5B28;
  border-top: none;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  background:
    linear-gradient(180deg, #6FA044 0 14px, #5B3F28 14px);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #F2E4C8;
  cursor: pointer;
  transition: filter 0.16s;
}

.plot-gate:hover { filter: brightness(1.12); }


/* ============================================================
   Tab panel header - name and species, nothing more
   ============================================================ */

.panel-head {
  display: flex;
  align-items: baseline;
  gap: 9px;
  padding: 2px 4px 10px;
}

.panel-head-name {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--linen);
}

.panel-head-species {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--clay);
  letter-spacing: 0.09em;
  text-transform: uppercase;
}


/* ============================================================
   Navigation drawer
   ============================================================ */

.nav-post {
  position: fixed;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  z-index: 9994;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 16px 10px 18px;
  border: 2px solid var(--bark-deep);
  border-right: none;
  border-radius: var(--r-md) 0 0 var(--r-md);
  background-color: var(--bark);
  background-image: var(--wood-grain);
  cursor: pointer;
  box-shadow: -4px 0 0 var(--bark-deep);
  transition: filter 0.16s;
}

.nav-post:hover { filter: brightness(1.1); }

.nav-post-slat {
  width: 18px;
  height: 3px;
  border-radius: 2px;
  background: var(--clay);
  box-shadow: 0 6px 0 var(--clay), 0 12px 0 var(--clay);
  margin-bottom: 12px;
}

.nav-post-label {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--linen);
  writing-mode: vertical-rl;
  text-transform: uppercase;
}

.nav-scrim {
  position: fixed;
  inset: 0;
  z-index: 9995;
  background: rgba(30, 20, 12, 0.5);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
}

.nav-scrim.is-open {
  opacity: 1;
  pointer-events: auto;
}

.nav-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 9996;
  width: 320px;
  max-width: 78vw;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 22px 16px;
  overflow-y: auto;
  background-color: var(--bark);
  background-image: var(--wood-grain);
  border-left: 3px solid var(--bark-deep);
  transform: translateX(100%);
  transition: transform 0.24s ease;
}

.nav-drawer.is-open { transform: translateX(0); }

.nav-drawer-title {
  margin: 0 0 10px;
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 800;
  letter-spacing: 0.03em;
  color: var(--sun);
}

.nav-plank {
  display: block;
  width: 100%;
  text-align: left;
  padding: 13px 16px 14px;
  background: var(--bark-light);
  border: 2px solid var(--bark-deep);
  border-radius: var(--r-sm);
  font-family: var(--font-display);
  font-size: var(--fs-base);
  font-weight: 700;
  color: var(--linen);
  cursor: pointer;
  box-shadow: var(--lip) var(--bark-deep);
  transition: transform 0.1s, box-shadow 0.1s, filter 0.16s;
}

.nav-plank:hover { filter: brightness(1.09); }

.nav-plank:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--bark-deep);
}

/* Where you already are. Pressed into the frame rather than raised. */
.nav-plank.current {
  background: var(--linen);
  color: var(--ink);
  transform: translateY(3px);
  box-shadow: inset 0 3px 0 rgba(94, 70, 50, 0.14);
  cursor: default;
}

body.nav-open { overflow: hidden; }

/* The drawer covers the screen on a phone; 1/4 of 390px cannot hold
   the word "Greenhouse" at a size worth tapping. */
@media (max-width: 560px) {
  .nav-drawer { width: 100%; max-width: 82vw; }
  .plot-grid { margin: 0 10px 14px; grid-template-columns: repeat(auto-fit, minmax(104px, 1fr)); }
  .plot-gate { width: calc(100% - 20px); margin: -6px 10px 16px; }
}


/* Buttons on these three pages share the tasks-page press. */
.plant-card:active,
.stat-card:active,
.stats-view-select:active,
.friends-search-form button:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--clay-deep);
}

/* ============================================================
   Garden signpost - the way back to the tasks list.
   Lives inside the scrollable track, so it pans with the plot.
   ============================================================ */

.garden-signpost {
  position: absolute;
  left: 26px;
  bottom: 12%;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  transition: transform 0.14s ease;
}

.garden-signpost:hover { transform: translateY(-3px) rotate(-1.5deg); }

.garden-signpost-board {
  padding: 8px 16px 9px;
  background-color: var(--scenery-wood-lit);
  background-image: var(--wood-grain);
  border: 2px solid var(--scenery-wood-deep);
  border-radius: 4px 4px 10px 10px;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: 0.08em;
  color: var(--scenery-ink);
  transform: rotate(-2deg);
  box-shadow: var(--lip) var(--scenery-wood-deep);
}

.garden-signpost-stake {
  width: 8px;
  height: 46px;
  margin-top: -2px;
  background: var(--scenery-wood);
  border: 2px solid var(--scenery-wood-deep);
  border-top: none;
  border-radius: 0 0 3px 3px;
}

@media (max-width: 560px) {
  .garden-signpost { left: 14px; }
  .garden-signpost-stake { height: 34px; }
}


/* ============================================================
   Drawn icons
   Nothing here is a character in a font. The padlock is built from
   the element's own corners so it inherits the wood palette, and
   .skin-lock zeroes its font-size so any glyph a render path still
   pushes into it stays invisible.
   ============================================================ */

.skin-lock {
  font-size: 0;
  color: transparent;
  background: var(--linen);
  border: 2px solid var(--bark-deep);
  box-shadow: none;
  width: 24px;
  height: 24px;
}

.skin-lock::before {
  content: '';
  position: absolute;
  top: 3px;
  left: 50%;
  transform: translateX(-50%);
  width: 9px;
  height: 7px;
  border: 2px solid var(--bark-deep);
  border-bottom: none;
  border-radius: 5px 5px 0 0;
}

.skin-lock::after {
  content: '';
  position: absolute;
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  width: 13px;
  height: 9px;
  background: var(--bark-deep);
  border-radius: 2px;
}

/* The stat cards used to lead with an emoji. The label carries the
   meaning on its own. */
.stat-card-emoji { display: none; }

/* Close and remove buttons now hold an SVG cross rather than a glyph. */
.auth-modal-close svg,
.dev-panel-close svg,
.task-item .remove svg {
  display: block;
}

.auth-avatar-fallback svg {
  display: block;
}


/* ============================================================
   The account picture
   ------------------------------------------------------------
   AVATAR_SPROUT_SVG (02-auth-tasks.js) replaced the Google account
   photo everywhere one was shown. It draws its own disc, so it needs
   no border-radius, no overflow clip and no currentColor - it only
   has to fill whichever round host it lands in: the 24px widget pill,
   the 64px auth and username modals, the 56px profile header. Those
   hosts are all flex centred, hence flex: 0 0 auto - without it the
   SVG is a flex item that can be squeezed below its 100%.
   ============================================================ */

.avatar-sprout {
  display: block;
  width: 100%;
  height: 100%;
  flex: 0 0 auto;
}


/* ============================================================
   Garden toolbar - the growth toggle and the edit button on one row
   ------------------------------------------------------------
   This used to also hold a Daily / Long-Term pair of view tabs, in a
   .garden-subnav. Both the tabs and that class are gone: the two
   views had become one render with an optional day of difference, so
   the split was two names for one garden.
   ============================================================ */

.garden-toolbar {
  pointer-events: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px;
  /* The top value is doing real work. #loadingState above this is
     display:none once auth resolves, and .page is fixed inset:0, so
     without it the row sits flush against the very top of the
     viewport with nothing above it at all. */
  padding: 16px 18px 8px;
}

.garden-toolbar .garden-grow-bar,
.garden-toolbar .garden-edit-bar {
  margin: 0;
  padding: 0;
  overflow: visible;
}

.garden-toolbar .garden-edit-bar.hidden {
  display: none;
}

/* The empty-plot stand-in for the two bars above. Matches
   .garden-edit-hint rather than body text: it sits on the sky, so it
   needs the same shadow the rest of this row uses to stay readable
   over both a bright noon and a dark night. pointer-events: none
   because .garden-toolbar re-enables clicks for its buttons, and a
   line of text there would otherwise swallow drags aimed at the top
   of the plot behind it. */
.garden-toolbar-note {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--linen);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  text-align: center;
  pointer-events: none;
}

.garden-toolbar .garden-toolbar-note.hidden {
  display: none;
}

@media (max-width: 560px) {
  .garden-toolbar {
    gap: 6px;
    /* A little less than on desktop - the same gap eats a bigger
       share of a phone's screen, and the row still clears the top. */
    padding-top: 12px;
  }
}


/* ============================================================
   The sky above the garden
   ------------------------------------------------------------
   The scene scrolls up two half-screens past the top of the frame,
   and this is what fills them: not a second scene, just the sky
   already on screen carried further up and deepening as it goes.

   Reading DOWN the scrollable scene, in content coordinates:

     0     - ~110vh   this strip, darkest at the very top
     ~110vh - 150vh   nothing at all. The strip has faded out by here,
                      so this band is the fixed #sky backdrop showing
                      through untouched - which is why the unscrolled
                      view is exactly what it always was.
     150vh - 200vh    the plot: lawn, fence, grass, plants

   The two colours come from the clock, not from this file.
   updateSky() in 05-stats-app.js publishes --sky-top (whatever the
   top of the frame is showing right now) and --sky-high (that same
   colour taken further toward space) every sixty seconds, so this
   gradient follows the time of day on its own. The fallbacks below
   are .sky-bg's own base colours, for the moment before the first
   tick lands.

   The strip is built by renderSky() in 04-garden-scene.js and lives
   INSIDE the track, so it pans left and right with the plot rather
   than sitting still behind it - the same choice the fence and the
   grass field made.
   ============================================================ */

.garden-sky-extension {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;          /* stands directly on top of the plot */
  height: var(--garden-sky-lift, 150vh);
  z-index: 0;            /* under the grass field (1), the props (3) and the plants (10+) */
  overflow: hidden;
  pointer-events: none;
  /* Fully transparent by 73% (about 110vh down), which is above the
     band you can see without scrolling - so there is no seam to line
     up against the fixed sky, and nothing tints the default view. */
  background: linear-gradient(
    to bottom,
    var(--sky-high, #05091a) 0%,
    var(--sky-top, #0a1628) 46%,
    transparent 73%
  );
}

/* Stars for the scrolled-to part of the strip, one element per field
   (see buildStarfield in 04). They fade in and out with real nightfall
   on the same 3s transition .sky-stars uses, so the two agree. */
.garden-sky-extension .sky-dust {
  position: absolute;
  top: 0;
  left: 0;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: transparent;   /* only the box-shadow copies are visible */
  opacity: 0;
  transition: opacity 3s ease;
}

.garden-scene.scene-night .garden-sky-extension .sky-dust {
  opacity: 1;
}


/* ============================================================
   What hangs in the sky above the garden
   ============================================================
   Balloons and kites while the sun is up, constellations and small
   planets once it is down. Built by getSkyDecorMarkup() in
   04-garden-scene.js and appended into .garden-sky-extension, so it
   lives INSIDE the scrollable track and pans left and right with the
   plot - the same choice the fence, the grass and the sky strip
   itself made.

   Both layers are always in the DOM. .scene-night (put on the scene
   by updateSky() in 05-stats-app.js) decides which one is visible,
   and the 3s fade matches the one .sky-dust and .sky-stars already
   use so the whole sky turns over together rather than in pieces.

   Nothing here is touched by the sky gradient: --sky-top/--sky-high
   still paint the strip exactly as before, and these sit on top of
   it.
   ============================================================ */

.garden-sky-decor {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 3s ease;
}

.garden-scene:not(.scene-night) .garden-sky-decor.sky-decor-day {
  opacity: 1;
}

.garden-scene.scene-night .garden-sky-decor.sky-decor-night {
  opacity: 1;
}

/* The positioned box. Its transform is the centring one and nothing
   else ever writes to it - the animations all live on the child,
   because an animated transform replaces the whole property and
   would otherwise throw this translate away on the first keyframe. */
.sky-decor-item {
  position: absolute;
  transform: translate(-50%, -50%);
}

.sky-decor-item > div {
  line-height: 0;   /* no descender gap under the inline svg */
}

.sky-decor-item svg {
  display: block;
  width: 100%;
  height: auto;
}

/* --- Balloons --- */
.sky-balloon {
  animation: skyBalloonDrift 28s ease-in-out infinite;
  filter: drop-shadow(0 5px 7px rgba(20, 32, 58, 0.13));
}

@keyframes skyBalloonDrift {
  0%, 100% { transform: translate(-9px,  7px) rotate(-2deg); }
  50%      { transform: translate( 9px, -13px) rotate( 2deg); }
}

/* --- Kites --- */
/* Pivoting about the bottom of the box, where the string runs off
   toward whoever is holding it, so the kite swings on its line
   instead of spinning about its own middle. */
.sky-kite {
  transform-origin: 50% 98%;
  animation: skyKiteSway 10s ease-in-out infinite;
}

@keyframes skyKiteSway {
  0%, 100% { transform: rotate(-8deg) translateY( 3px); }
  50%      { transform: rotate( 7deg) translateY(-7px); }
}

/* --- Planets --- */
.sky-planet {
  animation: skyPlanetDrift 34s ease-in-out infinite;
}

@keyframes skyPlanetDrift {
  0%, 100% { transform: translateY( 5px); }
  50%      { transform: translateY(-7px); }
}

/* --- Constellations --- */
/* One slow breath across the whole figure rather than a twinkle per
   star: dozens of independently animating nodes up here would cost
   far more than it is worth, and a constellation that shimmers as a
   single thing reads as distance. */
.sky-constellation {
  animation: skyConstellationTwinkle 8s ease-in-out infinite;
}

@keyframes skyConstellationTwinkle {
  0%, 100% { opacity: 0.95; }
  50%      { opacity: 0.58; }
}


/* ========================================
   Landscape picker - Greenhouse page

   The plant tiles below choose what ONE plant wears. This chooses
   what the whole plot is made of, so it sits above them in its own
   panel rather than inside a per-plant drawer, and there is exactly
   one of it. Tiles reuse .skin-tile / .skin-grid wholesale: same
   selected ring, same focus ring, same hover lift, so the two
   pickers can't drift apart visually.

   The preview inside each tile is drawn from the skin's own custom
   properties, which are set inline on the tile button - so the
   swatch is not a picture OF the skin, it is the skin, painted by
   the same tokens the real garden reads. A skin can never preview
   as something it won't render as.
   ======================================== */
.landscape-picker {
  margin: 0 0 16px;
  padding: 14px;
  /* Same bench as .plant-card-grid, down to the border and the lip.
     The two pickers sit one above the other on this page, and a cream
     panel over a wood one read as two unrelated widgets. Both are the
     same thing now: a wooden bench with tiles laid out on it. */
  background-color: var(--bark);
  background-image: var(--wood-grain);
  border: 2px solid var(--bark-deep);
  border-radius: var(--r-lg);
  box-shadow: var(--lip-deep) var(--bark-deep);
}

.landscape-picker-head {
  margin-bottom: 12px;
  text-align: left;
}

/* Sized and coloured like .greenhouse-title rather than a drawer
   heading, since it is now sitting on wood and has to read against
   bark instead of paper. */
.landscape-picker-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 800;
  color: var(--sun);
  margin: 0;
  letter-spacing: 0.02em;
}

/* Shorter than the 118px plant stage: a landscape preview is a wide
   letterbox, not a tall plant, and the extra height would just be
   empty tile. */
.landscape-tile-art {
  display: block;
  width: 100%;
  height: 78px;
  border-radius: 9px;
  overflow: hidden;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.22) inset;
}

.landscape-tile-art svg {
  display: block;
  width: 100%;
  height: 100%;
}


/* ============================================================
   Today's growth - the preview toggle and its payoff
   ------------------------------------------------------------
   The Daily Garden IS the regular garden - same plants, same sizes,
   same labels, one render path. The button below is the only
   difference: it rolls today's day of growth back so it can be
   watched happening, and switching it on puts the plot back to
   exactly the Long-Term garden. Nothing is spent and no number
   changes - it is a view. See the "Today's growth" section in
   04-garden-scene.js.

   Two rules worth keeping if any of this is edited:

     the growing itself is NOT animated here. It is the existing
     transform transition on .plant-visual doing the work, driven
     by JS moving --plant-scale. Everything in this block is the
     celebration laid over that - a pop, some sparks and a glow -
     and all of it is safe to drop, which is what the global
     prefers-reduced-motion block further up already does.

     nothing here touches .plant-visual's own filter. The hover
     highlight and the edit-mode halo both live on that property,
     and an animation on it beats both of them for as long as it
     runs. The waiting-plant glow is a separate pseudo-element
     underneath the plant instead.
   ============================================================ */

.garden-grow-bar {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 8px;
}

.garden-toolbar .garden-grow-bar.hidden {
  display: none;
}

/* Leaf rather than bark: this is the one control on the page that
   makes something happen in the garden, so it is the one control
   painted in the growth colour. */
.garden-grow-btn {
  border-color: var(--leaf-dark);
  background: var(--leaf);
  color: var(--linen);
  box-shadow: var(--lip) var(--leaf-dark);
}

.garden-grow-btn:active:not(:disabled) {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--leaf-dark);
}

/* Nothing ticked yet. Still says "Grow today" - the hint beside it
   explains why it can't be pressed, which is more use than a button
   that greys out and gives no reason. */
.garden-grow-btn:disabled {
  cursor: default;
  background: var(--bark-light);
  border-color: var(--bark-deep);
  box-shadow: var(--lip) var(--bark-deep);
  opacity: 0.7;
}

.garden-grow-btn:disabled:hover {
  filter: none;
}

/* There is a day sitting there waiting to be spent. The only thing
   breathing on the whole toolbar, so it reads as the one thing asking
   to be pressed. Expanding ring rather than a pulse of the button
   itself, so the chunky lip underneath never distorts. */
.garden-grow-btn.is-ready {
  animation: growReadyRing 2.1s ease-in-out infinite;
}

@keyframes growReadyRing {
  0%, 100% {
    box-shadow: var(--lip) var(--leaf-dark), 0 0 0 0 rgba(232, 183, 92, 0.6);
  }
  55% {
    box-shadow: var(--lip) var(--leaf-dark), 0 0 0 8px rgba(232, 183, 92, 0);
  }
}

/* Currently showing today's growth. Held down and darker, the way the
   active tab beside it is - a toggle has to look switched on, or the
   only thing telling you which of the two plots you are looking at is
   the wording on the button itself. */
.garden-grow-btn.is-showing {
  background: var(--leaf-deep);
  border-color: var(--leaf-dark);
  box-shadow: inset 0 3px 0 rgba(45, 62, 34, 0.28);
  transform: translateY(3px);
}

.garden-grow-btn.is-showing:active:not(:disabled) {
  box-shadow: inset 0 4px 0 rgba(45, 62, 34, 0.34);
}

.garden-grow-hint {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--linen);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  white-space: nowrap;
}


/* A plant whose box is ticked but whose day has not been released
   yet. Sits BEHIND the plant (negative z-index inside .garden-plant,
   which is already a stacking context thanks to its inline z-index),
   so a tall plant is haloed rather than washed out. Sized and placed
   off --plant-scale so the glow tracks how big the plant actually is
   on screen, the same way .plant-height-tag does. */
.garden-plant.daily-ready::before {
  content: '';
  position: absolute;
  left: 50%;
  bottom: calc(var(--plant-scale, 1) * 22px);
  width: calc(var(--plant-scale, 1) * 96px);
  height: calc(var(--plant-scale, 1) * 96px);
  transform: translateX(-50%);
  background: radial-gradient(
    closest-side,
    rgba(232, 183, 92, 0.36) 0%,
    rgba(232, 183, 92, 0.16) 46%,
    rgba(232, 183, 92, 0) 78%
  );
  pointer-events: none;
  z-index: -1;
  animation: dailyReadyGlow 2.4s ease-in-out infinite;
}

@keyframes dailyReadyGlow {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 1; }
}

/* Edit mode is about moving plants, not growing them, and the grow
   button hides there anyway - so the waiting glow would just be
   competing with the edit halo for attention. */
.garden-scene.garden-editing .garden-plant.daily-ready::before {
  display: none;
}

/* The light that comes up over a plant while it grows.

   Same property, same warm colour and the same idea as the halo edit
   mode puts on a grabbable plant - just swelling and fading instead of
   sitting still. Deliberately a LIGHT and not a movement: this replaced
   a scale bounce that moved the plant on top of the one movement the
   button exists to show, and a 1% size change cannot be read through a
   12% bounce happening at the same time.

   Only ever on plants that grew - JS adds the class from
   playGrowthFlourish, which only runs for a habit that was ticked.

   Runs 1.4s against the 1.1s growth transition on .plant-visual, so it
   is still up as the plant settles and lets go just after. No
   fill-mode on purpose: the moment it ends, .plant-visual's own filter
   (and its hover and edit-mode overrides) take back over cleanly. The
   base drop-shadow is repeated in every frame because filter is one
   property, so a keyframe that named only the glow would drop the
   plant's contact shadow for the duration. */
.garden-plant.plant-growing .plant-visual {
  animation: plantGrowLight 1.4s ease-in-out;
}

@keyframes plantGrowLight {
  0% {
    filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.30));
  }
  40% {
    filter:
      drop-shadow(0 4px 10px rgba(0, 0, 0, 0.30))
      drop-shadow(0 0 13px rgba(242, 184, 75, 0.7))
      brightness(1.09);
  }
  70% {
    filter:
      drop-shadow(0 4px 10px rgba(0, 0, 0, 0.30))
      drop-shadow(0 0 13px rgba(242, 184, 75, 0.55))
      brightness(1.06);
  }
  100% {
    filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.30));
  }
}

/* Sparks. A zero-size anchor point placed up the plant, with each
   spark flying out from it - so the spans can be positioned in plain
   pixels regardless of how big the plant under them is. */
.plant-grow-burst {
  position: absolute;
  left: 50%;
  bottom: calc(var(--plant-scale, 1) * 62px);
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 6;
}

.plant-grow-spark {
  position: absolute;
  left: 0;
  top: 0;
  color: var(--sun);
  opacity: 0;
  animation: growSparkFly 1.15s ease-out forwards;
  animation-delay: var(--spark-delay, 0s);
}

.plant-grow-spark svg {
  display: block;
  width: 100%;
  height: 100%;
}

@keyframes growSparkFly {
  0% {
    opacity: 0;
    transform: translate(0, 0) scale(0.25) rotate(0deg);
  }
  22% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform:
      translate(var(--spark-x, 0), var(--spark-y, -40px))
      scale(1.05)
      rotate(var(--spark-spin, 90deg));
  }
}

/* Mobile scales .plant-visual by a further 0.60x on top of
   --plant-scale (see .plant-visual), so both anchors that are placed
   off the plant's on-screen height have to be scaled to match, or the
   glow and the sparks float above a smaller plant. */
@media (max-width: 600px) {
  .garden-plant.daily-ready::before {
    bottom: calc(var(--plant-scale, 1) * 13px);
    width: calc(var(--plant-scale, 1) * 58px);
    height: calc(var(--plant-scale, 1) * 58px);
  }
  .plant-grow-burst {
    bottom: calc(var(--plant-scale, 1) * 37px);
  }
}

@media (max-width: 560px) {
  /* The hint is the first thing to go when the toolbar starts
     wrapping - the button's own count already says how many. */
  .garden-grow-hint { display: none; }
}


/* ========================================
   Account data modal - export + deletion
   (10-account-data.js)

   Deliberately NOT the .auth-modal shell. That card is 360px,
   centred text, and built to hold a single button; this one is a
   read-then-decide screen with a heading hierarchy, a list and a
   text field, and it has to survive a phone in landscape with text
   scaled up. Sharing the shell would have meant overriding almost
   every property in it.

   The one thing it borrows exactly is the surface language: clay
   panel, linen card, a hard lip under every button.
   ======================================== */

.account-modal {
  position: fixed;
  inset: 0;
  z-index: 10002;              /* above .auth-modal, which opens it */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.account-modal.hidden { display: none; }

.account-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(8, 5, 2, 0.66);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

/* max-height + scroll rather than a fixed height: WCAG reflow means
   this has to stay usable at 200% text zoom on a short window, and
   the delete button must never be the thing that falls off the
   bottom. overscroll-behavior keeps a flick inside the card from
   scrolling the garden underneath it. */
.account-modal-card {
  position: relative;
  width: 100%;
  max-width: 440px;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--clay);
  border: 3px solid var(--bark-deep);
  border-radius: var(--r-lg);
  padding: 26px 22px 22px;
  box-shadow: var(--lip-deep) var(--bark-deep), 0 22px 60px rgba(0, 0, 0, 0.45);
  text-align: left;
}

.account-modal-close {
  position: absolute;
  top: 10px;
  right: 12px;
  background: none;
  border: none;
  color: var(--ink);
  cursor: pointer;
  padding: 6px;
  line-height: 0;
  opacity: 0.75;
  border-radius: var(--r-sm);
}
.account-modal-close:hover:not(:disabled) { opacity: 1; }
.account-modal-close:disabled { opacity: 0.3; cursor: default; }

.account-modal-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 800;
  color: var(--ink);
  margin: 0 0 14px;
  padding-right: 26px;         /* clear of the close button */
}
/* The heading takes focus when the dialog opens so the announcement
   starts with what this is. It is a programmatic focus target only,
   never a tab stop, so it gets no ring of its own. */
.account-modal-title:focus { outline: none; }

.account-modal-lede {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  margin: 0;
  line-height: 1.55;
}

.account-block {
  background: var(--linen);
  border: 2px solid var(--linen-edge);
  border-radius: var(--r-md);
  padding: 14px 15px 15px;
  margin-bottom: 14px;
}
.account-block:last-child { margin-bottom: 0; }

/* The danger card is marked three ways over - a warm border, a tinted
   ground and the words in the heading - because a colour alone tells
   a colour-blind user nothing at all. */
.account-block-danger {
  background: var(--bloom-tint);
  border-color: var(--bloom);
}

.account-block-title {
  font-family: var(--font-display);
  font-size: var(--fs-base);
  font-weight: 800;
  color: var(--ink);
  margin: 0 0 6px;
}
.account-block-danger .account-block-title { color: var(--bloom-deep); }

.account-block-text {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  color: var(--ink);
  line-height: 1.55;
  margin: 0 0 10px;
}

.account-block-note {
  font-size: var(--fs-xs);
  color: var(--ink-soft);
}

.account-list {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  color: var(--ink);
  line-height: 1.5;
  margin: 0 0 10px;
  padding-left: 20px;
}
.account-list li { margin-bottom: 3px; }

/* A real label, sized and spaced like one. The rest of this app has
   none at all, which is the single biggest gap in its accessibility
   - this is the pattern the rest of the forms should follow. */
.account-label {
  display: block;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 800;
  color: var(--ink);
  margin: 14px 0 6px;
}

.account-input {
  width: 100%;
  padding: 9px 12px;
  border: 2px solid var(--clay-deep);
  border-radius: var(--r-sm);
  background: var(--linen);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-base);   /* 16px: anything smaller makes iOS zoom the page */
  letter-spacing: 0.06em;
}
.account-input:focus { border-color: var(--bloom-deep); }

.account-help {
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  color: var(--ink-soft);
  margin: 6px 0 12px;
  line-height: 1.45;
}

.account-btn {
  width: 100%;
  padding: 11px 16px;
  border: none;
  border-radius: var(--r-sm);
  background: var(--bark);
  color: var(--linen);
  font-family: var(--font-body);
  font-weight: 800;
  font-size: var(--fs-sm);
  cursor: pointer;
  box-shadow: var(--lip) var(--bark-deep);
  transition: transform 0.12s, box-shadow 0.12s, background 0.16s;
}
.account-btn:hover:not(:disabled) { background: var(--bark-light); }
.account-btn:active:not(:disabled) {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--bark-deep);
}

/* --bloom-deep, not --bloom: the pale one carries the tint behind
   this button and cannot also carry white text on top of it. This
   pairing clears 4.5:1 against --linen. */
.account-btn-danger {
  background: var(--bloom-deep);
  box-shadow: var(--lip) #7C3A32;
}
.account-btn-danger:hover:not(:disabled) { background: #8E463D; }
.account-btn-danger:active:not(:disabled) { box-shadow: 0 1px 0 #7C3A32; }

/* Off until the word is typed. Flattened as well as faded, so the
   state reads without relying on the colour difference. */
.account-btn:disabled {
  background: var(--clay-deep);
  color: var(--ink-soft);
  box-shadow: none;
  cursor: not-allowed;
}

/* The live region. Sits outside the body that gets rewritten, so an
   announcement is never cut off by the next render. */
.account-modal-status {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--ink);
  background: var(--linen);
  border-left: 4px solid var(--leaf-deep);
  border-radius: var(--r-sm);
  padding: 10px 12px;
  margin: 14px 0 0;
}
.account-modal-status.is-error {
  color: var(--bloom-deep);
  border-left-color: var(--bloom-deep);
}
.account-modal-status.hidden { display: none; }

/* The quiet way in, at the foot of the auth modal. Underlined rather
   than tinted, so it is still obviously a control in greyscale. */
.auth-data-link {
  display: block;
  width: 100%;
  margin-top: 16px;
  padding: 6px;
  background: none;
  border: none;
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  border-radius: var(--r-sm);
}
.auth-data-link:hover { color: var(--ink); }

@media (max-width: 480px) {
  .account-modal { padding: 12px; }
  .account-modal-card {
    padding: 22px 16px 18px;
    max-height: calc(100vh - 24px);
  }
}


/* ========================================
   About panel on the home page

   A fifth sign in the row that does not go
   anywhere. It opens the board below it,
   which is the only prose on this page.

   The board is the same pale stock the
   tagline is cut into, dropped into a bark
   frame so it reads as an object lying on
   the bench rather than a dialog floating
   over one. Everything in the scene is
   position:fixed, so a long panel simply
   scrolls across the wood and the props
   stay where they were put.
   ======================================== */

/* Text that is present for screen readers and crawlers but not for
   eyes. Used by the page <h1> and by the About heading, both of which
   are drawn as SVG where you can see them. NOT display:none, which
   would take them away from assistive tech as well. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Four signs, not three. On a narrow screen the row is allowed to
   fall onto a second line rather than squeezing every label. */
.home-markers {
  flex-wrap: wrap;
}

/* The pale board paint. Deliberately not a fifth colour: this sign is
   cut into the same stock as the tagline and the panel it opens, which
   is what tells you it belongs to them and not to the three
   destinations beside it. */
.marker-cream {
  --face-lit:  #FBF2E1;
  --face:      #F0E1C4;
  --face-deep: #DDC9A4;
  --rim-lit:   #C9AE83;
  --rim:       #B79A6E;
  --rim-deep:  #8E7450;
}

.marker-about { --tilt: 2.9deg; margin-top: 14px; }

/* The front prop layer (z-index 3) sits ABOVE .home-inner (z-index 2)
   on purpose - a leaf or a pot lying across a button is what makes the
   scene read as objects on a table rather than a menu on a picture.
   That falls apart for the About panel, which is a long board of text
   the soil tray then covers.

   So the whole menu layer steps forward while the panel is open, and
   only while it is open. .home-inner has position and a z-index, which
   makes it a stacking context - raising .home-about's own z-index does
   nothing, because a child cannot climb out past its parent. The class
   is toggled by setHomeAboutOpen() in 01-app-core.js.

   4 and not 6: it clears the props at 3 but stays under the vignette at
   5, so the panel is still lit by the same lamp as everything else. Put
   it above the vignette and it turns into a bright rectangle floating
   over a shaded room. */
.home-inner.is-about-open {
  z-index: 4;
}


.home-about {
  position: relative;
  z-index: 1;
  width: min(var(--tag-width), 100%);
  margin: clamp(20px, 4vh, 38px) 0 8px;
  padding: 10px;
  border-radius: 19px 16px 18px 15px;
  transform: rotate(-0.7deg);
  background-color: var(--bark);
  background-image: var(--wood-grain);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.16),
    inset 0 -2px 0 rgba(0, 0, 0, 0.24),
    0 7px 0 var(--bark-deep),
    0 18px 26px rgba(34, 18, 5, 0.46);
}

/* Same three stops as .home-label. If that board is ever repainted,
   this one has to move with it or the sign will stop matching the
   panel it opens. */
.home-about-face {
  position: relative;
  padding: clamp(18px, 3.4vw, 28px) clamp(15px, 3.2vw, 28px) clamp(20px, 3.6vw, 30px);
  border-radius: 13px 11px 12px 11px;
  background: linear-gradient(178deg, #F8ECD5 0%, #EEDFC0 58%, #DCC79E 100%);
  box-shadow:
    inset 0 3px 0 rgba(255, 255, 255, 0.62),
    inset 0 -3px 0 rgba(0, 0, 0, 0.10);
  text-align: left;
}

.home-about-title {
  margin: 0 0 clamp(14px, 2.6vw, 20px);
  text-align: center;
}

/* Sized by WIDTH, like the tagline and for the same reason: the three
   cut passes are offset by a fixed fraction of the cap height, so a
   long line left to set its own size comes out with a cap roughly
   twice the buttons above it. */
.home-about-cut {
  display: block;
  width: min(260px, 74%);
  margin: 0 auto;
}

.home-about-cut svg {
  display: block;
  width: 100%;
  height: auto;
}

.home-about-close {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 2px solid var(--clay-deep);
  border-radius: 50%;
  background: var(--linen);
  color: var(--bark-deep);
  cursor: pointer;
  transition: background 0.14s, transform 0.1s;
}

.home-about-close:hover { background: var(--clay); }
.home-about-close:active { transform: translateY(1px); }

.home-about-q {
  margin: clamp(16px, 2.8vw, 22px) 0 5px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-base);
  line-height: 1.25;
  color: var(--bark-deep);
}

/* The first question sits directly under the carved heading, which
   already carries its own space below it. */
.home-about-q:first-of-type { margin-top: 0; }

.home-about-a {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  line-height: 1.64;
  color: var(--ink);
}

/* The close button and the heading share the top line, so on a narrow
   board the heading gives up the width rather than running under it. */
@media (max-width: 480px) {
  .home-about-cut { width: min(210px, 66%); }
  .marker-about   { margin-top: 10px; }
}

/* ========================================
   Consent banner - analytics opt-in
   (11-consent.js)

   A board resting on the bottom edge of the
   screen rather than a dialog floating over
   the middle of it. The garden stays fully
   usable underneath: this asks a question,
   it does not hold the app hostage, and
   there is no backdrop for that reason.

   Same surface language as the account
   modal - clay panel, bark frame, a hard
   lip under every button.
   ======================================== */

.consent-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10003;              /* above .account-modal, which is 10002 */
  display: flex;
  justify-content: center;
  padding: 14px;
  pointer-events: none;        /* the gutter either side stays clickable */
}

.consent-banner.hidden { display: none; }

.consent-card {
  pointer-events: auto;
  width: 100%;
  max-width: 560px;
  max-height: calc(100vh - 28px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--clay);
  border: 3px solid var(--bark-deep);
  border-radius: var(--r-lg);
  padding: 18px 20px 16px;
  box-shadow: var(--lip-deep) var(--bark-deep), 0 18px 48px rgba(0, 0, 0, 0.38);
  text-align: left;
}

.consent-title {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 800;
  color: var(--ink);
  outline: none;               /* it is focused on open, not clicked */
}

.consent-text {
  margin: 0 0 8px;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--ink);
}

.consent-text-quiet {
  color: var(--ink-soft);
  font-size: var(--fs-xs);
  margin-bottom: 14px;
}

.consent-link {
  color: var(--ink-soft);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.consent-link:hover { color: var(--ink); }

/* Two equal columns, and they stay equal. The refusal must not be
   made quieter, narrower or lighter than the acceptance - an
   asymmetric pair here is the most commonly enforced defect in this
   whole area of law. If you restyle one of these, restyle both. */
.consent-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.consent-btn {
  padding: 11px 16px;
  border: none;
  border-radius: var(--r-sm);
  font-family: var(--font-body);
  font-weight: 800;
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: transform 0.12s, box-shadow 0.12s, background 0.16s;
}

.consent-btn:active {
  transform: translateY(3px);
  box-shadow: none;
}

/* Both buttons carry a lip of the same depth. The only difference
   between them is hue, which is decoration, not hierarchy. */
.consent-btn-yes {
  background: var(--leaf-deep);
  color: var(--linen);
  box-shadow: var(--lip) var(--leaf-dark);
}

.consent-btn-no {
  background: var(--bark);
  color: var(--linen);
  box-shadow: var(--lip) var(--bark-deep);
}

/* Stacked below the width where two labels stop fitting on one line
   each. Refusal goes on TOP in this arrangement, so the thumb does
   not have to travel past the accept button to reach it. */
@media (max-width: 380px) {
  .consent-actions { grid-template-columns: 1fr; }
  .consent-btn-no  { order: -1; }
}

/* A <button> that reads as a link. It has to be a
   button and not an <a href="#"> - there is nowhere
   to navigate to, and a hash link would scroll the
   About panel back to the top on the way. Reset the
   UA button chrome so it sits in the paragraph flow
   like ordinary text. */
.home-about-link {
  display: inline;
  padding: 0;
  border: none;
  background: none;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 800;
  line-height: 1.64;
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.home-about-link:hover { color: var(--ink); }

/* ========================================
   Legal pages - privacy.html, terms.html

   Long-form reading, which the rest of this
   stylesheet never has to do. The app is a
   garden seen from a distance; these are a
   sheet of paper held close, so the surface
   is linen rather than clay and the measure
   is capped at 68ch - past that the eye
   loses the line on the way back.

   Same tokens, same fonts, no new palette.
   These pages must feel like the same place,
   not a lawyer's annexe.
   ======================================== */

.legal-page {
  min-height: 100vh;
  background: var(--clay);
  padding: 20px 16px 64px;
}

.legal-sheet {
  max-width: 720px;
  margin: 0 auto;
  background: var(--linen);
  border: 3px solid var(--bark-deep);
  border-radius: var(--r-lg);
  box-shadow: var(--lip-deep) var(--bark-deep);
  padding: 30px 26px 34px;
}

.legal-back {
  display: inline-block;
  margin-bottom: 20px;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 800;
  color: var(--ink-soft);
  text-decoration: none;
}
.legal-back:hover { color: var(--ink); }
.legal-back::before { content: "\2190\00a0"; }

.legal-body {
  max-width: 68ch;
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: 1.68;
  color: var(--ink);
}

.legal-body h1 {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: 800;
  line-height: 1.1;
  color: var(--ink);
}

.legal-body h2 {
  margin: 34px 0 10px;
  padding-top: 16px;
  border-top: 2px solid var(--linen-edge);
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: 800;
  line-height: 1.2;
  color: var(--ink);
}

.legal-body h3 {
  margin: 22px 0 6px;
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--bark-deep);
}

.legal-body p  { margin: 0 0 14px; }
.legal-body ul { margin: 0 0 14px; padding-left: 22px; }
.legal-body li { margin-bottom: 6px; }

.legal-body a {
  color: var(--leaf-dark);
  font-weight: 700;
  text-underline-offset: 3px;
}

.legal-body hr {
  margin: 26px 0;
  border: none;
  border-top: 2px solid var(--linen-edge);
}

.legal-body strong { font-weight: 800; }

/* Callouts. Every blockquote in these documents
   is a note to the operator, not to the reader -
   they are all flagged DRAFT NOTE and they all
   have to come out before publication. Loud on
   purpose. */
.legal-body blockquote {
  margin: 0 0 16px;
  padding: 12px 14px;
  background: var(--bloom-tint);
  border-left: 4px solid var(--bloom-deep);
  border-radius: 0;
  font-size: var(--fs-sm);
  line-height: 1.6;
}
.legal-body blockquote p:last-child { margin-bottom: 0; }

/* Unfilled placeholders. Rendered as something
   impossible to miss so a draft cannot be
   published with [LEGAL NAME] still in it. */
.legal-body code {
  padding: 1px 6px;
  background: var(--sun);
  border-radius: 4px;
  font-family: var(--font-body);
  font-size: 0.92em;
  font-weight: 800;
  color: var(--ink);
}

.legal-table-wrap { overflow-x: auto; margin: 0 0 18px; }

.legal-body table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}

.legal-body th,
.legal-body td {
  padding: 8px 10px;
  border: 1px solid var(--linen-edge);
  text-align: left;
  vertical-align: top;
}

.legal-body th {
  background: var(--clay);
  font-weight: 800;
}

.legal-updated {
  margin-top: 30px;
  padding-top: 14px;
  border-top: 2px solid var(--linen-edge);
  font-size: var(--fs-xs);
  color: var(--ink-faint);
}

.legal-updated a { color: var(--ink-soft); font-weight: 700; }

@media (max-width: 480px) {
  .legal-page  { padding: 12px 10px 40px; }
  .legal-sheet { padding: 22px 18px 26px; border-radius: var(--r-md); }
  .legal-body h1 { font-size: var(--fs-xl); }
  .legal-body h2 { font-size: var(--fs-lg); }
}

/* The sign-in-wrap notice under the Google button
   (02-auth-tasks.js). Quiet, but present in the
   same eyeline as the button - that adjacency is
   the whole point of it. */
.auth-modal-terms {
  margin: 10px 0 0;
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--ink-soft);
  text-align: center;
}

.auth-modal-terms a {
  color: var(--ink-soft);
  font-weight: 800;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.auth-modal-terms a:hover { color: var(--ink); }