
  :root{
    --ink:var(--c-warm-black); --paper:var(--c-cream); --surface:var(--c-paper);
    --brand:var(--c-tft-green); --brand-deep:var(--c-oxblood); --green:var(--c-leaf-green); --gold:var(--c-goldenrod);
    --muted:var(--fg-muted); --line:var(--border); --line-soft:var(--c-bone);
    --accent:var(--c-autumn-orange);
    /* The seasonal accent is designed to be a SURFACE: button fills, focus
       rings, dots, tints. Every one of the four fails as TEXT on paper —
       leaf green 3.20:1, autumn orange 3.43:1, robin's egg 2.06:1 and
       goldenrod a near-invisible 1.58:1. So each season carries a darker cut
       of its own hue for the two places the accent is set as type. Use
       --accent for fills and --accent-text for words. */
    --accent-text:#AF4F22;
    --serif:var(--font-display);
    --sans:var(--font-body);
  }
  html[data-season="spring"]{--accent:var(--c-leaf-green); --accent-text:#4F7431;}
  html[data-season="summer"]{--accent:var(--c-goldenrod);  --accent-text:#856630;}
  html[data-season="autumn"]{--accent:var(--c-autumn-orange);--accent-text:#AF4F22;}
  html[data-season="winter"]{--accent:var(--c-robins-egg); --accent-text:#3F7273;}
  /* MUST be here and MUST be !important. Any author rule that sets `display`
     beats the browser's own [hidden] rule, so a hidden element stays on screen.
     `.farm-sheet` sets display:flex, which meant the (empty) farm detail covered
     the sidebar on every page load. Second time this exact trap has bitten in
     this project - manage.css carries the same guard for the same reason. */
  [hidden]{display:none !important;}

  *{box-sizing:border-box;}
  html,body{margin:0;}
  body{height:100vh;display:flex;flex-direction:column;overflow:hidden;font-family:var(--sans);color:var(--ink);background:var(--paper);-webkit-font-smoothing:antialiased;}
  .map-shell{position:relative;flex:1;min-height:0;}
  #map{position:absolute;inset:0;}
  .mapboxgl-ctrl-logo,.mapboxgl-ctrl-bottom-right{opacity:.5;}

  .panel{position:absolute;top:18px;left:18px;bottom:18px;width:408px;max-width:calc(100vw - 36px);
    background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
    box-shadow:var(--shadow-paper);
    display:flex;flex-direction:column;overflow:hidden;z-index:5;}
  .p-head{padding:22px 22px 16px;border-bottom:1px solid var(--line-soft);}
  .brand{font-family:var(--serif);font-weight:600;font-size:24px;line-height:1.02;letter-spacing:-.01em;margin:0;color:var(--brand);}
  .brand em{font-style:italic;font-weight:500;color:var(--accent-text);}
  .sub{font-size:12.5px;color:var(--muted);margin:6px 0 0;letter-spacing:.01em;}
  .search{margin-top:16px;position:relative;}
  .search input{width:100%;height:42px;border:1px solid var(--line);border-radius:var(--r-md);
    padding:0 14px 0 40px;font:inherit;font-size:14.5px;color:var(--ink);background:var(--paper);outline:none;transition:border-color .18s,box-shadow .18s;}
  .search input:focus{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 18%,transparent);}
  .search svg{position:absolute;left:13px;top:12px;width:18px;height:18px;stroke:var(--muted);}

  /* 17 filter chips are far taller than the markets map's handful, and left
     unbounded they eat the whole panel and push the farm list off screen.
     Cap them, let them scroll, and keep a floor under the list. */
  /* 17 chips is far more than the markets map's handful. Two things must both
     hold: the block cannot grow so tall it pushes the farm list off screen, and
     it cannot be SHRUNK so far that the first group is invisible - which is what
     happened when the list was allowed to squeeze it down to 93px. Hence a floor
     as well as a ceiling. */
  /* ---------- submit your farm ---------- */
  /* Floated over the map's top-right corner. Solid rather than outlined because
     it sits on satellite imagery, where a transparent control disappears. */
  .submit-farm{
    position:absolute;top:18px;right:18px;z-index:6;
    display:flex;align-items:center;gap:7px;
    font:inherit;font-size:13.5px;font-weight:500;
    padding:10px 16px;border-radius:var(--r-md);
    border:1px solid var(--brand);background:var(--surface);color:var(--brand);
    box-shadow:var(--shadow-md,0 4px 14px rgba(40,33,31,.28));
    cursor:pointer;transition:all .16s;
  }
  .submit-farm svg{width:15px;height:15px;flex:none;}
  .submit-farm:hover{background:var(--brand);color:var(--c-paper);}
  /* Clear of Mapbox's zoom controls at the bottom right, and never overlapping
     the sheet on a phone. */
  @media (max-width:640px){
    .submit-farm{top:12px;right:12px;font-size:12.5px;padding:8px 12px;}
  }

  /* Lifted from events.css so the two submission flows look like one product. */
  .ev-modal{position:fixed;inset:0;z-index:80;background:rgba(40,33,31,.55);
    display:flex;align-items:center;justify-content:center;padding:20px;}
  .ev-modal-card{position:relative;background:var(--c-paper);border-radius:var(--r-lg);
    max-width:440px;width:100%;padding:32px 30px 30px;
    box-shadow:0 24px 60px rgba(40,33,31,.3);text-align:center;}
  .ev-modal-x{position:absolute;top:14px;right:14px;width:32px;height:32px;
    background:transparent;border:none;color:var(--fg-muted);cursor:pointer;}
  .ev-modal-x svg{width:20px;height:20px;}
  .ev-modal-card h2{font-family:var(--font-display);font-weight:600;font-size:26px;
    color:var(--c-tft-green);margin:0 0 12px;}
  .ev-modal-lede{font-family:var(--font-text);font-size:15px;line-height:1.6;
    color:var(--ink-muted);margin:0 0 14px;}
  /* The two things that decide whether a submission ever reaches this map, so
     they are called out rather than buried in the lede. */
  .ev-modal-note{font-family:var(--font-text);font-size:13px;line-height:1.55;
    color:var(--ink);text-align:left;
    background:var(--bg-accent-warm);border-radius:var(--r-sm);
    padding:11px 13px;margin:0 0 20px;}
  .ev-modal-primary{display:block;width:100%;text-align:center;
    font-family:var(--font-body);font-size:14.5px;font-weight:500;
    background:var(--brand);color:var(--c-paper);text-decoration:none;
    padding:12px 18px;border-radius:var(--r-sm);margin-bottom:12px;}
  .ev-modal-primary:hover{background:var(--brand-deep);}
  .ev-modal-secondary{display:inline-block;font-family:var(--font-body);font-size:13px;
    color:var(--c-tft-green);text-decoration:none;
    border-bottom:1px solid var(--c-autumn-orange);padding-bottom:2px;}
  .ev-modal-secondary:hover{color:var(--c-autumn-orange);}

  /* ---------- season buttons ---------- */
  /* Four across, above everything else: the map opens on the current season, so
     this is the first thing to read and the most likely thing to change. Sized
     bigger than the filter chips because it is a different kind of control -
     always exactly one (or none) active, not a multi-select. */
  .seasons{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;padding:14px 20px 4px;}
  .sbtn{font:inherit;font-family:var(--serif);font-size:14px;font-weight:500;letter-spacing:-.01em;
    padding:9px 4px 8px;border-radius:var(--r-md);border:1px solid var(--line);
    background:var(--paper);color:var(--ink);cursor:pointer;transition:all .18s;
    display:flex;flex-direction:column;align-items:center;gap:3px;}
  .sicon{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.5;
    stroke-linecap:round;stroke-linejoin:round;opacity:.75;transition:opacity .18s;}
  .sbtn[aria-pressed="true"] .sicon{opacity:1;}
  .sbtn:hover{border-color:var(--muted);}
  /* White label AND icon. The icon is stroke:currentColor, so colour carries
     both. This used to be tft-green, which worked while the fill was the light
     seasonal accent; once the fill darkened to --accent-text to fix the white
     text on the other season control, green-on-rust fell to 2.10:1 in every
     season. White clears 5.3:1 or better on all four. */
  .sbtn[aria-pressed="true"]{background:var(--accent-text);border-color:var(--accent-text);
    color:#fff;font-weight:600;}
  .sbtn:focus-visible{outline:2px solid var(--brand);outline-offset:2px;}

  /* Always fully visible - never scrolled, never shrunk. These are the reason
     the page exists, and a filter you have to go looking for does not get used.
     The room comes from denser chips and a name-and-town-only list. */
  .filters{padding:12px 20px 10px;display:flex;flex-direction:column;gap:10px;flex:0 0 auto;}
  .chips{display:flex;gap:5px;flex-wrap:wrap;}
  .chip{font:inherit;font-size:13px;font-weight:500;padding:7px 13px;border-radius:var(--r-sm);
    border:1px solid var(--line);background:var(--paper);color:var(--ink);cursor:pointer;transition:all .16s;}
  .chip:hover{border-color:var(--muted);}
  .chip[aria-pressed="true"]{background:var(--brand);border-color:var(--brand);color:#fff;}
  .seg{display:flex;gap:6px;align-items:center;}
  .seg-label{font-size:11.5px;font-weight:500;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);min-width:52px;}
  .season-btns{display:flex;gap:6px;flex-wrap:wrap;}
  .segcol{display:flex;flex-direction:column;gap:8px;}
  .datein{font:inherit;font-size:13px;height:34px;border:1px solid var(--line);border-radius:var(--r-md);padding:0 8px;background:var(--paper);color:var(--ink);cursor:pointer;}
  .reset{font:inherit;font-size:12px;color:var(--brand);background:none;border:none;cursor:pointer;text-decoration:underline;padding:2px;}
  .season-btns button{font:inherit;font-size:12.5px;padding:6px 11px;border-radius:var(--r-md);border:1px solid var(--line);
    background:var(--paper);color:var(--ink);cursor:pointer;transition:all .16s;}
  /* Same as map.html: white on the raw accent is 1.70:1 in summer. */
  .season-btns button[aria-pressed="true"]{background:var(--accent-text);border-color:var(--accent-text);color:#fff;}
  select.day{font:inherit;font-size:13.5px;height:34px;border:1px solid var(--line);border-radius:var(--r-md);padding:0 8px;background:var(--paper);color:var(--ink);cursor:pointer;}

  .count{flex:none;padding:12px 22px 8px;border-top:1px solid var(--line-soft);font-size:12.5px;color:var(--muted);font-weight:500;letter-spacing:.02em;}
  .count b{color:var(--ink);}
  .list{flex:1 1 auto;min-height:90px;overflow-y:auto;padding:0 12px 14px;scrollbar-width:thin;scrollbar-color:var(--line) transparent;}
  .list::-webkit-scrollbar{width:10px;}
  .list::-webkit-scrollbar-track{background:transparent;}
  .list::-webkit-scrollbar-thumb{background:var(--line);border-radius:10px;border:3px solid var(--surface);}
  .list::-webkit-scrollbar-thumb:hover{background:var(--muted);}
  .card{display:flex;gap:10px;align-items:center;padding:9px 12px;border-radius:var(--r-md);cursor:pointer;transition:background .15s;}
  .card:hover{background:var(--paper);}
  .card.on{background:color-mix(in srgb,var(--accent) 10%,var(--surface));}
  .dot{flex:none;width:12px;height:12px;border-radius:50%;margin-top:5px;background:var(--line);border:2px solid var(--line);}
  .card.open .dot{background:var(--accent);border-color:color-mix(in srgb,var(--accent) 55%,#fff);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 22%,transparent);}
  .c-name{font-family:var(--serif);font-weight:500;font-size:15px;line-height:1.12;letter-spacing:-.01em;margin:0;}
  .c-meta{font-size:12.5px;color:var(--muted);margin:3px 0 0;line-height:1.4;}
  .c-open{color:var(--green);font-weight:500;}
  .badge{display:inline-block;font-size:10.5px;font-weight:600;letter-spacing:.03em;text-transform:uppercase;
    color:var(--brand-deep);background:color-mix(in srgb,var(--brand) 12%,#fff);padding:1px 7px;border-radius:6px;margin-left:6px;vertical-align:1px;}

  .coin{width:30px;height:30px;border-radius:50%;background:var(--brand);border:2.5px solid var(--c-paper);
    box-shadow:0 3px 8px -1px rgba(80,30,20,.45);display:flex;align-items:center;justify-content:center;cursor:pointer;
    transition:transform .18s cubic-bezier(.34,1.56,.64,1);}
  .coin svg{width:16px;height:16px;}
  .coin:hover{transform:scale(1.18);}
  .coin.open{box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 45%,transparent),0 3px 8px -1px rgba(80,30,20,.45);}
  .coin.sel{transform:scale(1.25);z-index:3;}
  .marker-drop:hover{z-index:6;}
  .m-under{position:absolute;top:31px;left:50%;transform:translateX(-50%);white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .14s;}
  .m-today{font-family:var(--sans);font-size:9.5px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:var(--c-tft-green);background:var(--c-goldenrod);padding:2px 8px 1px;border-radius:var(--r-sm);box-shadow:var(--shadow-sm);}
  .m-name{font-family:var(--serif);font-weight:500;font-size:12px;color:var(--c-tft-green);background:var(--c-paper);border:1px solid var(--line);padding:3px 9px;border-radius:var(--r-sm);box-shadow:var(--shadow-md);}
  .marker-drop{opacity:0;transform:translateY(-10px);animation:drop .5s cubic-bezier(.22,1,.36,1) forwards;}
  @keyframes drop{to{opacity:1;transform:none;}}


    max-height:min(62vh,460px);display:flex;flex-direction:column;}

  .loader{position:absolute;inset:0;background:var(--paper);z-index:20;display:flex;align-items:center;justify-content:center;
    flex-direction:column;gap:14px;transition:opacity .7s ease;}
  .loader.gone{opacity:0;pointer-events:none;}
  .loader .mark{font-family:var(--serif);font-size:26px;font-weight:600;letter-spacing:-.01em;color:var(--brand);text-align:center;max-width:16ch;line-height:1.12;}
  .loader .mark em{font-style:italic;font-weight:500;color:var(--accent-text);}
  .v2-nav-link[aria-current="page"]{color:var(--c-tft-green);}
  .v2-nav-link[aria-current="page"]::after{transform:scaleX(1);}
  .sheaf{width:46px;height:46px;}
  .sheaf path{stroke:var(--brand);stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round;
    stroke-dasharray:60;stroke-dashoffset:60;animation:grow 1.1s ease forwards;}
  @keyframes grow{to{stroke-dashoffset:0;}}
  @media (prefers-reduced-motion:reduce){.marker-drop,.sheaf path{animation:none;opacity:1;transform:none;stroke-dashoffset:0;}}

  .sheet-toggle{display:none;}   /* mobile only */

  /* ---------- farm detail ---------- */
  /* One component, two placements. On desktop it fills the panel over the list;
     on a phone it rises from the bottom. Never anchored to a pin, so unlike the
     map popup it replaced, it cannot be clipped by the map edge, cannot land in
     a different place for every farm, and cannot slide during the flight. */
  .farm-sheet{
    position:absolute;inset:0;z-index:9;
    display:flex;flex-direction:column;
    background:var(--surface);border-radius:var(--r-lg);
    animation:fsIn .2s cubic-bezier(.22,1,.36,1);
  }
  @keyframes fsIn{from{opacity:0;transform:translateX(-6px);}to{opacity:1;transform:none;}}
  @media (prefers-reduced-motion:reduce){.farm-sheet{animation:none;}}

  .fs-close{
    flex:none;display:flex;align-items:center;gap:8px;
    font:inherit;font-size:13px;font-weight:500;color:var(--brand);
    background:none;border:none;border-bottom:1px solid var(--line-soft);
    padding:14px 20px;cursor:pointer;text-align:left;width:100%;
  }
  .fs-close:hover{color:var(--brand-deep);}
  .fs-back{font-size:15px;}
  .fs-x{display:none;}
  .fs-body{flex:1 1 auto;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--line) transparent;}
  .fs-body::-webkit-scrollbar{width:10px;}
  .fs-body::-webkit-scrollbar-thumb{background:var(--line);border-radius:10px;border:3px solid var(--surface);}
  .fs-photo{height:172px;background-size:cover;background-position:center;flex:none;
    display:block;width:100%;padding:0;border:none;cursor:zoom-in;position:relative;}
  .fs-zoom{
    position:absolute;right:10px;bottom:10px;
    font:inherit;font-size:11.5px;font-weight:500;letter-spacing:.02em;
    background:rgba(11,67,36,.82);color:var(--c-paper);
    padding:4px 10px;border-radius:999px;
  }
  .fs-photo:hover .fs-zoom{background:var(--brand-deep);}

  /* ---------- image viewer ---------- */
  .lightbox{
    position:fixed;inset:0;z-index:50;
    display:flex;align-items:center;justify-content:center;
    background:rgba(20,14,10,.92);padding:5vmin;
  }
  /* Not a radius token: --r-sm/md/lg (2/4/8px) are sized for small UI chrome and
     read as square on a photo this big. 14px is the same visual softness at
     photo scale. */
  .lb-img{max-width:100%;max-height:100%;object-fit:contain;
    border-radius:14px;box-shadow:0 12px 40px rgba(0,0,0,.45);}
  /* Only for a genuinely cold image - a preloaded neighbour swaps instantly and
     never reaches this state. The current photo stays on screen and dims, so the
     click is visibly acknowledged instead of appearing to do nothing. */
  .lightbox.is-loading .lb-img{opacity:.55;transition:opacity .12s ease;}
  .lightbox.is-loading::after{
    content:"";position:absolute;width:34px;height:34px;
    border:3px solid rgba(250,246,234,.28);border-top-color:var(--c-paper);
    border-radius:50%;animation:lb-spin .7s linear infinite;pointer-events:none;
  }
  @keyframes lb-spin{to{transform:rotate(360deg);}}
  @media (prefers-reduced-motion:reduce){
    .lightbox.is-loading::after{animation:none;border-top-color:rgba(250,246,234,.28);}
    .lightbox.is-loading .lb-img{transition:none;}
  }
  .lb-close{
    position:absolute;top:12px;right:16px;width:48px;height:48px;
    font-size:34px;line-height:1;color:var(--c-paper);
    background:none;border:none;cursor:pointer;opacity:.85;
  }
  .lb-close:hover{opacity:1;}
  .lb-nav{
    position:absolute;top:50%;transform:translateY(-50%);
    width:52px;height:52px;border-radius:50%;
    font-size:30px;line-height:1;color:var(--c-paper);
    background:rgba(0,0,0,.35);border:none;cursor:pointer;
  }
  .lb-nav:hover{background:rgba(0,0,0,.55);}
  .lb-prev{left:14px;}
  .lb-next{right:14px;}
  .lb-count{
    position:absolute;bottom:16px;left:50%;transform:translateX(-50%);
    font-size:12.5px;color:var(--c-paper);opacity:.8;letter-spacing:.03em;
  }
  @media (max-width:640px){
    .lightbox{padding:3vmin;}
    .lb-nav{width:44px;height:44px;font-size:26px;}
    .lb-close{top:6px;right:8px;}
  }
  .fs-illust{height:172px;background-size:cover;background-position:center;flex:none;
    display:flex;align-items:center;justify-content:center;}
  .fs-illust img{width:44%;max-height:76%;object-fit:contain;
    filter:drop-shadow(0 5px 10px rgba(0,0,0,.28));}
  .fs-text{padding:18px 20px 24px;}
  .fs-text h3{font-family:var(--serif);font-weight:600;font-size:22px;line-height:1.12;
    margin:0 0 3px;letter-spacing:-.01em;color:var(--brand);}
  .fs-town{font-size:13.5px;color:var(--muted);margin:0 0 12px;}
  .fs-pills{display:flex;flex-wrap:wrap;gap:5px;margin:0 0 14px;}
  .fs-desc{font-size:14.5px;line-height:1.6;color:var(--muted);margin:0 0 16px;}
  .fs-actions{display:flex;flex-wrap:wrap;gap:8px;}
  .fs-link{display:inline-block;font-size:14px;font-weight:500;color:var(--c-paper);
    background:var(--brand);text-decoration:none;padding:10px 17px;border-radius:var(--r-sm);}
  .fs-link:hover{background:var(--brand-deep);}
  /* Directions is secondary to the farm's own site - useful, but not the thing
     we most want someone to click. */
  .fs-link-alt{background:transparent;color:var(--brand);border:1px solid var(--line-strong,var(--border-strong));}
  .fs-link-alt:hover{background:var(--paper);color:var(--brand-deep);}

  @media (max-width:640px){
    /* dvh, not vh: on iOS Safari 100vh is the viewport WITHOUT the browser
       chrome, so a vh-sized sheet sits underneath the address bar and its
       bottom controls - which is exactly what was cutting the UI off. */
    body{height:100dvh;}

    .panel{
      top:auto;left:0;right:0;bottom:0;width:100%;max-width:100%;
      border-radius:var(--r-lg) var(--r-lg) 0 0;
      height:auto;max-height:86dvh;
      padding-bottom:env(safe-area-inset-bottom);
      transition:max-height .28s ease;
    }

    /* COLLAPSED (default): the grip and the search field, nothing else. The map
       is the product on a phone; the sheet earns its space only when asked. */
    .sheet-toggle{
      display:flex;align-items:center;gap:10px;width:100%;
      font:inherit;font-size:13.5px;font-weight:500;color:var(--brand);
      background:none;border:none;cursor:pointer;
      padding:9px 20px 5px;position:relative;
    }
    .sheet-grip{
      position:absolute;top:7px;left:50%;transform:translateX(-50%);
      width:38px;height:4px;border-radius:99px;background:var(--line-soft);
    }
    .sheet-label{margin-top:6px;}
    .sheet-label::after{content:" ▲";font-size:9px;vertical-align:2px;}
    body.sheet-open .sheet-label::after{content:" ▼";}
    .sheet-badge{
      margin-top:6px;margin-left:auto;font-size:11px;font-weight:600;
      background:var(--brand);color:var(--c-paper);border-radius:99px;padding:1px 8px;
    }

    .p-head{padding:6px 20px 14px;border-bottom:none;}
    .brand,.sub{display:none;}
    .seasons,.filters,.count,.list{display:none;}

    /* EXPANDED */
    body.sheet-open .panel{height:78dvh;}
    body.sheet-open .p-head{border-bottom:1px solid var(--line-soft);}
    body.sheet-open .brand{display:block;font-size:20px;}
    body.sheet-open .sub{display:block;}
    /* Restore each element's OWN display value - a blanket display:flex turned
       the farm list into a three-column row. */
    body.sheet-open .seasons{display:grid;}
    body.sheet-open .filters{display:flex;}
    body.sheet-open .count{display:block;}
    body.sheet-open .list{display:block;}
    /* Filters get priority over the list here: the sheet was opened to filter.
       Same floor-and-ceiling principle as desktop, so they can neither be
       squeezed to a row and a half nor swallow the whole sheet. */
    body.sheet-open .filters{flex:0 1 auto;overflow-y:auto;min-height:220px;max-height:46dvh;}
    body.sheet-open .list{flex:1 1 auto;min-height:96px;}

    /* Popups: a 290px card on a 390px phone, anchored to a pin near an edge, was
       clipped and near-impossible to dismiss. Near-full-width, taller close
       target, and never taller than the map area above the sheet. */

    /* ---------- farm detail sheet ---------- */
    /* Replaces the map popup on phones. A popup is anchored to its pin, so it
       lands somewhere different for every farm and has to be chased. This is
       always the same size in the same place: moving between farms swaps the
       contents and nothing else moves. */
    /* The detail lives inside .panel, so on a phone the PANEL becomes the sheet
       rather than the detail being positioned against the viewport. Hiding
       .panel here would hide the detail with it - which collapsed it to 0px. */
    body.farm-open .panel{height:46dvh;max-height:46dvh;}
    body.farm-open .sheet-toggle,
    body.farm-open .p-head{display:none;}

    .farm-sheet{border-radius:var(--r-lg) var(--r-lg) 0 0;animation:fsUp .24s cubic-bezier(.22,1,.36,1);}
    @keyframes fsUp{from{transform:translateY(14px);opacity:0;}to{transform:none;opacity:1;}}
    @media (prefers-reduced-motion:reduce){.farm-sheet{animation:none;}}

    /* On a phone the panel is a filter bar, not a list to go "back" to, so the
       control reads as a close rather than a back link. */
    .fs-close{
      position:absolute;top:4px;right:6px;z-index:2;width:44px;height:44px;
      padding:0;border-bottom:none;justify-content:center;
      font-size:28px;line-height:1;color:var(--muted);
    }
    .fs-back,.fs-close-label{display:none;}
    .fs-x{display:inline;}
    .fs-photo,.fs-illust{height:120px;}
    .fs-text{padding:14px 18px 20px;}
    .fs-text h3{font-size:19px;}
    .fs-pills .plabel{font-size:11.5px;padding:3px 10px;}

    .seasons{grid-template-columns:repeat(4,1fr);padding:12px 20px 4px;}
    .sbtn{font-size:13px;padding:8px 2px 7px;}
    .sicon{width:18px;height:18px;}

    /* 27 pins on a 375px screen crowd badly; smaller markers buy real space
       without losing the photograph. */
    .coin{width:26px;height:26px;border-width:2px;}
    .coin-photo{width:32px;height:32px;}
    .coin svg{width:14px;height:14px;}
    .m-under.u-name{top:28px;}
    .coin-photo + .m-under.u-name,.coin-photo ~ .m-under.u-name{top:34px;}
    .m-name{font-size:10.5px;max-width:112px;}
  }


  /* ---------- label filters ---------- */
  .fgroup{display:flex;flex-direction:column;gap:6px;}
  .fgroup-label{font-size:10.5px;font-weight:500;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);}
  .fchip{font:inherit;font-size:12px;line-height:1.1;padding:5px 9px;border-radius:var(--r-md);border:1px solid var(--line);
    background:var(--paper);color:var(--ink);cursor:pointer;transition:all .16s;}
  .fchip:hover{border-color:var(--muted);}
  .fchip[aria-pressed="true"]{background:var(--brand);border-color:var(--brand);color:#fff;}
  .fclear{font:inherit;font-size:12px;color:var(--brand);background:none;border:none;cursor:pointer;
    text-decoration:underline;padding:2px;align-self:flex-start;}

  /* ---------- list cards ---------- */
  .empty{padding:18px 22px;font-size:13.5px;color:var(--muted);line-height:1.5;}

  /* ---------- markers ---------- */
  /* The farm's own photograph is the marker where there is one; the sheaf glyph
     stands in where there is not. */
  .coin-photo{width:38px;height:38px;padding:0;overflow:hidden;background:var(--c-bone,#e6e0d2);}
  .coin-photo img{width:100%;height:100%;object-fit:cover;display:block;}

  /* Names ride with the markers instead of waiting for a hover that never comes
     on touch. Pointer-events off so the label never eats a click meant for the
     marker or the map. */
  /* Off by default; cullLabels() in js/farms.js adds .on to every label that
     actually fits without colliding. Hover still reveals any single one. */
  .m-under.u-name{opacity:0;top:32px;pointer-events:none;transition:opacity .45s ease;}
  .m-under.u-name.on{opacity:1;}
  .marker-drop:hover .u-name{opacity:1;z-index:8;}
  .coin-photo + .m-under.u-name,.coin-photo ~ .m-under.u-name{top:40px;}
  .marker-drop:hover .u-name{opacity:1;}
  .marker-drop:hover{z-index:7;}
  .m-name{font-size:11px;padding:2px 7px;max-width:132px;overflow:hidden;
    text-overflow:ellipsis;white-space:nowrap;display:inline-block;}
  .coin.sel + .m-under .m-name,.marker-drop:hover .m-name{max-width:none;}

  /* Real farm photography where the record has it; the illustration set is the
     fallback, so a farm without a photo still gets a branded popup. */
  .plabel{font-size:10.5px;padding:2px 8px;border-radius:999px;background:color-mix(in srgb,var(--brand) 12%,#fff);color:var(--brand-deep);}
