/* ============================================================================
   PLANT-POTS. Warm cream, living leaf green, a soft serif.

   REWRITTEN 2026-09-08 against design/DIFFERENTIATION.md.

   WHAT CHANGED AND WHY.

   1. THE GROUND IS NOW CREAM, NOT GREY. It was #F5F3ED, which is a warm-ish
      neutral in a set where five of eight grounds sat between #F0F0EC and
      #FCFCFB. Read next to ceramics (#F0F0EC) and lights (#F1EFE8) it was a
      third indistinguishable pale grey, and the founder was right that the
      pages looked like clones. It is now #F2EFE6: same lightness band, but the
      blue channel drops 7 points against the red, so it reads as unbleached
      paper stock rather than as plaster. This is the domestic brand. Cream is
      the cheapest and most honest way to say so.

   2. DISPLAY: Fraunces, replacing Instrument Sans. Instrument Sans is a
      competent neutral grotesque and that is exactly the problem: it said
      nothing that Inter next to it was not already saying. Fraunces is a soft
      serif with an optical-size axis, a SOFT axis that rounds the terminals
      and a WONK axis that swaps in the cocked, slightly irregular alternates.
      Set at SOFT 40 and WONK 1 it has genuine warmth and a little humour
      without tipping into a children's face, which is the line this brand has
      to walk. It also makes this the only soft serif in the house: lights runs
      Petrona (an editorial serif on warm paper), aquatics runs Newsreader (a
      sharp specimen-plate serif), the hub runs Noto Serif Armenian (an
      institutional reference serif). Four serifs, four different jobs, and
      only this one is round.

   3. BODY: Karla, replacing Inter. Karla is a grotesque with things wrong with
      it in the useful sense: a squared-off bowl, a low-slung ear on the g, an
      unusually wide e and a slightly compressed rhythm. At paragraph length it
      reads friendly where Inter reads administrative. It is also metrically
      unlike Inter, so a 200px thumbnail of a paragraph is visibly a different
      page rather than the same page in another colour.

   4. LEADING STAYS GENEROUS, and it is a typeface decision rather than a
      spacing-scale one, so it is legitimately a brand override. --lh-body is
      held at 1.7rem against the house 1.5625rem. The category research (§3.5)
      records Kinto running 2.16 and nothing mass-market above 1.6, and calls
      the leading the thing that reads premium before a word is read. Karla has
      a large x-height and short descenders, so it can take the air without the
      lines coming apart. The SPACING SCALE, THE GRID and THE MOTION TIMING are
      untouched, as required.

   5. SECTION SEPARATION IS STILL WHITESPACE ONLY. No tonal bands, no rules
      between sections. Ceramics is the sibling that bands; this one breathes.
      That is this brand's signature move and it is unchanged.

   CONTRAST, computed 2026-09-08. Floors: body 7.0, secondary and tertiary 4.5.
     on --surface #F2EFE6              on --surface-2 #FAF8F1
       ink      13.29                    ink      14.38
       ink-2     7.62                    ink-2     8.24
       ink-3     4.74                    ink-3     5.13
       accent    5.39                    accent    5.83
       accent-h  7.60                    rule      1.22 (hairline, exempt)
     surface-2 on accent 5.83, used for the primary button label.
     surface on surface-inv 13.00, used by the toast.
     ink-4 is 2.44 and is a non-text mark only. Never #000, never #fff.

   RADIUS: System B (soft), unchanged. 2 / 8 / 16 / 28 / pill.
   Inner radius = outer minus padding.

   Overridden here: palette, typefaces, radius tier, and the body leading that
   belongs to the typeface. Nothing else.
   ========================================================================= */
/* ---------------------------------------------------------------------------
   THE PALETTE. FOUR VALUES, AND IT IS NOT SETTLED.

   The founder is still choosing between four palettes, so all four live here
   and nowhere else on this site. A swap is an edit to these four lines; if it
   ever needs a second edit somewhere, that somewhere is the bug.

     --logo     the mark, and only the mark
     --primary  primary button fill, active states
     --deep     secondary button, hover, and any reversed ground
     --safe     links, and every piece of text under 24px

   Worth knowing about this particular hue: green is 0.2 per cent of the
   saturated pixels in the ten reference feeds the palette was measured
   against. This brand's colour is a decision, not an observation, and it is
   the deliberate one, because the subject is plants.

   --safe is the vivid green pulled back until it clears 4.5:1 on white, which
   it does at 4.55:1. --primary is 2.63:1 and cannot carry text at all.

   CONTRAST, RE-MEASURED 2026-09-14. Both surfaces are #FFFFFF now, so the
   table further up this file, which names #F2EFE6, is superseded.
   Floors: body 7.0, secondary 4.5, tertiary 4.5.

                          on #FFFFFF
     ink      #22271B       15.28
     ink-2    #474D3D        8.76
     ink-3    #666C59        5.45
     ink-4    #9A9C8A        2.59   non-text marks only
     rule     #E0D9C8        1.41   hairlines, exempt, and now load bearing
     --safe   #12883E        4.55
     --primary#16A34A        3.30   FAILS as text. Fills and the mark only.
     --deep   #0D662E        7.10
     --on-fill is --ink, 4.64 on --primary. --on-fill-h 8.05 on --deep.
   ------------------------------------------------------------------------ */
@layer tokens {
  :root {
    --logo:    #16A34A;
    --primary: #16A34A;
    --deep:    #0D662E;
    --safe:    #12883E;   /* 4.55:1 on white                                 */

    /* Derived from the four, in the same block so a swap stays one edit. A
       button label is text under 24px on a COLOURED ground, which is the one
       measurement the four cannot answer.

       The house rule, applied the same way on all eight sites: take the vivid
       --primary as the fill if any legal label sits on it, and drop to --deep
       only if none does. Here the near-white fails at 3.30:1 but this
       brand's own green-black ink clears it at 4.64:1, so the button keeps
       the vivid green and takes a dark label. The hover ground is --deep,
       which is the other way round, so the label flips with the ground and
       both cuts are named. */
    --fill:      var(--primary);
    --fill-h:    var(--deep);
    --on-fill:   var(--ink);   /* 4.64:1 on --primary                        */
    --on-fill-h: #FAF8F1;      /* the old page cream, now the reversed ink.
                                  8.05:1 on --deep. Never #fff.             */

    /* The mark as typographic furniture, for brandmark.css. Copied from
       design/logos/mark-masks.css, which is generated. */
    --mark-mask: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%2211 6 14 16%22 role=%22img%22 aria-label=%22a leaf%22%3E %3Cg fill=%22%23000%22%3E %3Cpath d=%22M12 21C12 13 16 8 23 8c0 8-4 13-11 13z%22/%3E %3C/g%3E %3C/svg%3E");
  }
}

@layer tokens {
  :root {
    /* --- surfaces -------------------------------------------------------- */
    --surface:     #FFFFFF;   /* white. design/COLOUR.md: the colour on this
                                 site should arrive in photographs of real
                                 printed pots, and a cream ground costs a
                                 vivid palette 0.7 of a contrast point.     */
    --surface-2:   #FFFFFF;   /* raised card, enquiry block, footer plate.
                                 White as well, and that is forced: the
                                 footer link hover is --safe, which has 0.05
                                 of a point of headroom on white and none at
                                 all on a tint. The card and the enquiry
                                 block used to be visible because the page
                                 was cream, so they now draw a 1px --rule
                                 border instead. The cream itself is not
                                 lost: it became --on-fill-h, the ink on a
                                 reversed ground.                           */
    --surface-inv: #23291C;   /* green-black. The toast, and nothing else. */
    --rule:        #E0D9C8;   /* warm hairline, 1.22:1. Earned, not drawn. */

    /* --- ink: warm green-black, one hue, low chroma ----------------------- */
    --ink:         #22271B;   /* body. Green-black, never #000. 13.29:1 */
    --ink-2:       #474D3D;   /* secondary and running prose. 7.62:1 */
    --ink-3:       #666C59;   /* tertiary, labels, spec keys. 4.74:1 */
    --ink-4:       #9A9C8A;   /* non-text marks only. 2.44:1 */

    /* --- accent: ONE, max 3 instances per viewport ------------------------ */
    --accent:      var(--safe);  /* the text cut. 4.55:1 on white.          */
    --accent-h:    var(--deep);  /* hover, and the reversed ground          */
    --focus:       var(--safe);

    /* Illustration warm neutrals. Graphics only, never UI, never text. */
    --leaf-light:  #6B9B5A;
    --terracotta:  #C9B49C;

    /* --- typefaces -------------------------------------------------------- */
    --font-display: Fraunces, "Source Serif 4", Georgia, serif;
    --font-body:    Karla, "Segoe UI", system-ui, sans-serif;
    --font-mono:    "Geist Mono", ui-monospace, monospace;

    /* Three weights, permitted because one of them is the display cut.
       Fraunces at 400 with a raised optical size is already substantial;
       taking it to 500 would make the page shout, which this brand does not. */
    --w-display:   400;
    /* SOFT rounds the terminals, WONK swaps the cocked alternates in. Both are
       what make this face warm rather than merely serif. */
    --vf-display:  "SOFT" 40, "WONK" 1;

    /* Leading belongs to the face, not to the spacing scale. See note 4. */
    --lh-body:     1.7rem;

    /* --- radius: System B (soft) ------------------------------------------ */
    --r-1: 2px; --r-2: 8px; --r-3: 16px; --r-4: 28px; --r-pill: 999px;
  }
}


/* ============================================================================
   Site chrome: masthead, footer, button. Added 2026-09-10.

   These rules used to live in an inline <style> block on the homepage only,
   which is why /substrate/ and /roots/ shipped with no masthead and /light/
   and /humidity/ with neither masthead nor footer: those two pages carry no
   inline style block at all. Shared stylesheet, so every page gets them.
   ========================================================================= */

@layer components {
.nav{position:sticky;top:0;z-index:10;background:color-mix(in oklab,var(--surface) 84%,transparent);
       backdrop-filter:saturate(160%) blur(18px)}
/* THE MASTHEAD ROW, corrected 2026-09-20. The button used to drop onto a second
   line: six labels at 0.12em tracking plus a 48px gap needed 1074px, the row was
   revealed at 860, and flex-wrap on .nav-in was the only thing keeping it inside
   the document. Three changes, in the order they pay: the first label is now
   "Products" rather than "Pots and trellises", the tracking comes down to 0.06em,
   and the gap comes down from 48 to 24. The row now measures 700px, so it fits
   the moment it is revealed. .nav-in no longer wraps and the button is flex:none,
   so if a label ever grows again the LABELS wrap and the button stays on the line. */
.nav-in{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-5);
          flex-wrap:nowrap;
          max-width:var(--max-content);margin-inline:auto;padding:var(--sp-4) var(--margin)}
.nav-in>.btn{flex:none}
.nav-links{display:none;flex-wrap:nowrap;gap:var(--sp-2) var(--sp-5);min-width:0;
             font-size:var(--t-caption);letter-spacing:0.06em;
             color:var(--ink-2)}
.nav-links a{text-decoration:none;white-space:nowrap;
             transition:color var(--dur-1) var(--ease-standard)}
/* Under about 460px the wordmark and the button together are wider than the
   screen, and the labels are hidden anyway, so the button takes a line of its
   own rather than running off the side. Above that the row never wraps. */
@media(max-width:459px){
  .nav-in{flex-wrap:wrap}
  .nav-in>.btn{width:100%;justify-content:center}
}
.wordmark{font-weight:var(--w-display);
                        font-variation-settings:var(--vf-display)}
.wordmark span{color:var(--ink-3);font-weight:var(--w-regular)}
.btn{display:inline-flex;align-items:center;border:1px solid var(--rule);background:transparent;
       color:var(--ink);font:inherit;font-size:var(--t-caption);letter-spacing:0.12em;
       padding:calc(var(--sp-3) + 1px) var(--sp-6) var(--sp-3);border-radius:var(--r-pill);
       text-decoration:none;cursor:pointer;
       transition:background var(--dur-1) var(--ease-standard),border-color var(--dur-1) var(--ease-standard)}
.btn:hover{border-color:var(--ink-4);background:var(--surface-2)}
.foot{margin-top:var(--section-y-major);border-top:1px solid var(--rule)}
.foot-in{max-width:var(--max-content);margin-inline:auto;padding:var(--sp-8) var(--margin);
           display:flex;flex-wrap:wrap;gap:var(--sp-7);justify-content:space-between;
           font-size:var(--t-caption);line-height:1.5rem;color:var(--ink-3)}
.foot a{color:var(--ink-2);text-decoration:none}
@media(min-width:880px){.nav-links{display:flex}}
@media(max-width:1039px){.nav-in>.lang{display:none}}
}

@layer components {
  /* The masthead lockup: the leaf, then the name, on one line. Inline SVG
     rather than an <img> so it takes --logo and --deep straight off this file
     and there is no second asset for a reversed ground. Sized in em so it
     tracks whatever the masthead sets the wordmark at. */
  .wordmark { display: inline-flex; align-items: center; gap: 0.42em }
  .wordmark .wm-mark { width: 1.3em; height: 1.3em; flex: none }
  .wordmark .wm-name { font: inherit }
}

/* ============================================================================
   PRODUCT MEDIA. Added 2026-09-20, shared by the homepage and /products/.

   Nothing has been photographed yet, because nothing has been manufactured yet.
   So every picture slot renders a PLACEHOLDER that is obviously deliberate: the
   brand's own ground, at the aspect ratio the real thing will occupy, carrying
   the sentence describing the shot that belongs there. No stock photography of
   somebody else's pots, and no reflow when the real file arrives.

   Both slots take their source from content/inventory/ — banners.json for the
   showcase banners, the per-product images[] array in products.json for the
   sliders — so swapping a placeholder for a render is a data edit.

   THE SLIDER DEGRADES TO A GRID. The markup is a plain list of figures laid out
   as a grid, which is what a visitor with no JavaScript gets: every example
   visible at once, nothing hidden. lib is added by the script on the container
   only after it has built the dots, and that class is what turns the grid into
   a one-frame track.
   ========================================================================= */
@layer components {
  .showcase{grid-column:1/-1;position:relative;min-width:0}
  .showcase>picture,.showcase>img,.showcase>video,.showcase>.ph{
    display:block;position:relative;overflow:hidden;border-radius:var(--r-4);aspect-ratio:4/3}
  @media(min-width:700px){
    .showcase>picture,.showcase>img,.showcase>video,.showcase>.ph{aspect-ratio:2/1}
  }
  @media(min-width:1100px){
    .showcase>picture,.showcase>img,.showcase>video,.showcase>.ph{aspect-ratio:5/2}
  }
  .showcase img,.showcase video{width:100%;height:100%;object-fit:cover;display:block}
  .showcase figcaption{margin-top:var(--sp-3);font-size:var(--t-caption);color:var(--ink-3)}
  .media-credit{margin-left:var(--sp-2);white-space:nowrap}
  .media-credit a{color:inherit;text-underline-offset:3px}

  /* The placeholder ground: the same warm bisque the product drawings use. */
  .ph{display:flex;flex-direction:column;justify-content:flex-end;
      gap:var(--sp-2);padding:var(--sp-5);
      background:linear-gradient(178deg,#F6F2E4 0%,#EEE8D6 62%,#E5DCC7 100%)}
  .ph::before{content:"";position:absolute;left:8%;bottom:12%;width:14%;aspect-ratio:5/6;
      border-radius:var(--r-3) var(--r-3) 34% 34%;
      background:linear-gradient(100deg,#ECE5D1 0%,#DDD3B9 46%,#C8BC9F 100%);
      box-shadow:14px 8px 26px -10px rgba(46,40,30,0.28)}
  /* ink-2, not ink-3: the placeholder ground darkens towards the bottom where this
     label sits, and ink-3 measured 4.45 against it. */
  .ph b{position:relative;font-family:var(--font-mono);font-size:10px;line-height:16px;
        letter-spacing:0.12em;text-transform:uppercase;color:var(--ink-2);font-weight:400}
  .ph span{position:relative;font-size:var(--t-sm);line-height:1.5rem;color:var(--ink-2);
           max-width:46ch}
  /* AT 320 THE FRAME IS SHORTER THAN THE CAPTION. .ph bottom-aligns its children inside a
     4/3 box with overflow hidden, so a description longer than the box pushes the label out
     through the TOP and the banner reads as a sentence starting in the middle of itself.
     The label is the part that has to survive, so the description is what gets clamped. */
  @media(max-width:560px){
    .ph span{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;
             overflow:hidden}
  }

  /* --- the slider ------------------------------------------------------- */
  .mslider{position:relative;min-width:0}
  .slides{display:grid;gap:var(--sp-4);margin:0;padding:0;list-style:none;
          grid-template-columns:repeat(auto-fit,minmax(min(180px,100%),1fr))}
  .slide{margin:0;position:relative}
  .slide .frame{position:relative;aspect-ratio:4/3;border-radius:var(--r-3);overflow:hidden}
  .slide img{width:100%;height:100%;object-fit:cover;display:block}
  .slide figcaption{margin-top:var(--sp-3);font-size:var(--t-caption);line-height:1.4rem;
                    color:var(--ink-3)}

  .mslider.lib .mview{overflow:hidden;min-width:0;border-radius:var(--r-3)}
  .mslider.lib .slides{display:flex;flex-wrap:nowrap;gap:0;
                       transition:transform var(--dur-3) var(--ease-out)}
  .mslider.lib .slide{flex:0 0 100%;min-width:0}
  .mslider.lib .slide .frame{border-radius:var(--r-3) var(--r-3) 0 0}
  .mslider.lib .slide figcaption{padding:var(--sp-3) var(--sp-4) 0}
  @media(prefers-reduced-motion:reduce){.mslider.lib .slides{transition:none}}

  .dots{display:flex;flex-wrap:wrap;gap:var(--sp-2);margin-top:var(--sp-4)}
  .dot{width:28px;height:28px;padding:0;border:0;background:transparent;cursor:pointer;
       display:grid;place-items:center;border-radius:var(--r-pill)}
  .dot::before{content:"";width:8px;height:8px;border-radius:var(--r-pill);
               background:var(--ink-4);transition:background var(--dur-1) var(--ease-standard)}
  .dot[aria-current="true"]::before{background:var(--accent)}
  .dot:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
}


/* ============================================================================
   DISPLAY LINE-HEIGHT, MEASURED RATHER THAN CHOSEN.

   The shared tokens set --lh-h1 to 4.75rem against a 4.5rem --t-h1, a ratio of
   1.056, and --lh-display to exactly the font size, a ratio of 1.00. That is
   tighter than these faces' own ascent plus descent, so the wrapped lines of a
   headline physically collided -- the founder photographed it.

   Fraunces stops colliding at 1.22, measured by binary search on the ink
   rects of a real two-line heading with the font loaded, not guessed and not
   taken from the font's bounding box. It is set to 1.26 here for a little
   air. Across the eight sites the needed ratio runs from 1.00 to 1.58, which is
   why this belongs per site and cannot be one global number.

   h3 is left alone: it already sits at 1.23 and was never the problem.
   ========================================================================= */
:root {
  --lh-h1:      calc(var(--t-h1) * 1.26);
  --lh-h2:      calc(var(--t-h2) * 1.26);
  --lh-display: calc(var(--t-display) * 1.26);
}
