/* ===========================================================================
   Ekatra — design system

   Black and white. Every colour on a page comes from a photograph; the
   interface itself has none. The logo is black and white, so the site is too.

   Structure follows the reference directory: grouped fields, each with a
   drawn symbol, a field name and a value. The symbols are drawn rather than
   stock, which is the difference between this and every other plant database.
   =========================================================================== */

:root {
  /* Clean white. The beige was the wrong reading entirely; photographs carry
     every colour on the page and the interface carries none. */
  --paper:       #ffffff;
  --paper-sunk:  #f4f4f3;
  --white:       #ffffff;

  --ink:         #101010;
  --ink-muted:   #5a5a58;
  --ink-faint:   #8c8c89;
  --rule:        #e3e3e1;
  --rule-strong: #c6c6c3;

  /* One warm band, used only by Engage. */
  --beige:       #e9e3d6;
  --beige-deep:  #ded6c4;

  /* A single accent, carried only by the thing you are currently touching or
     have currently chosen — a selected pin, an active filter, a link on hover.
     Never a background, never decoration. The page stays black and white; this
     is what keeps it from reading as flat, and photographs do the rest. One
     value to change if it is ever wrong. */
  --map-h:       clamp(26rem, 64vh, 42rem);

  --accent:      #4f6144;
  --accent-soft: #eef0ea;

  /* The drawn things in the margins — a bee, a watering can, an envelope.
     Pale enough to be found rather than read. */
  --sticker:     #cfd9c3;

  /* Type is set per variant; the system below depends only on these two. */
  --display: 'Archivo', system-ui, sans-serif;
  --text:    'Archivo', system-ui, sans-serif;
  --display-weight: 300;
  --display-track:  0.005em;
  --indic:   'Noto Serif Gujarati', 'Noto Serif Devanagari';

  --step--2: clamp(0.70rem, 0.68rem + 0.09vw, 0.75rem);
  --step--1: clamp(0.83rem, 0.80rem + 0.14vw, 0.91rem);
  --step-0:  clamp(1.00rem, 0.97rem + 0.17vw, 1.11rem);
  --step-1:  clamp(1.22rem, 1.14rem + 0.36vw, 1.46rem);
  --step-2:  clamp(1.50rem, 1.34rem + 0.74vw, 2.02rem);
  --step-3:  clamp(1.85rem, 1.55rem + 1.42vw, 2.90rem);
  --step-4:  clamp(2.40rem, 1.80rem + 2.90vw, 4.60rem);

  --measure: 62ch;
  --gutter:  clamp(1.1rem, 0.6rem + 2.2vw, 3rem);
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--text), var(--indic), system-ui, sans-serif;
  font-size: var(--step-0);
  line-height: 1.6;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  font-family: var(--display), var(--indic), Georgia, serif;
  font-weight: var(--display-weight);
  line-height: 1.12;
  letter-spacing: var(--display-track);
  margin: 0 0 0.4em;
  text-wrap: balance;
}
h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-2); }
h3 { font-size: var(--step-1); }

p { margin: 0 0 1.1em; max-width: var(--measure); }
a { color: inherit; text-underline-offset: 0.18em; text-decoration-thickness: 1px; }
img { max-width: 100%; height: auto; display: block; }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

.shell { width: min(100% - (var(--gutter) * 2), 80rem); margin-inline: auto; }

/* Page content should not start hard against the header. Only where there is
   a header: the home page's opening photograph is full-bleed and meets it. */
.masthead + main { padding-top: clamp(2.5rem, 6vw, 4.5rem); }
.shell--narrow {
  width: min(100% - (var(--gutter) * 2), 44rem);
  margin-inline: max(var(--gutter), (100% - 80rem) / 2);
}

.skip { position: absolute; left: -9999px; }
.skip:focus {
  left: var(--gutter); top: .5rem; z-index: 10;
  background: var(--ink); color: var(--white); padding: .6rem 1rem;
}

/* --- masthead ------------------------------------------------------------ */

.masthead { border-bottom: 1px solid var(--rule-strong); }
/* Same geometry as .top on the home page — full width, gutter padding, the
   same vertical rhythm — so Menu and search sit at identical coordinates on
   every page and the header does not shift underneath you. Inside .shell they
   were inset by a further 40px wherever the 80rem cap bit. The outer columns
   are equal, so the mark is centred by the viewport rather than by whatever
   the controls happen to measure. */
.masthead__inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: clamp(1.1rem, 3vw, 1.9rem) var(--gutter);
}
.masthead__inner .menu-btn { justify-self: start; }
.masthead__inner .search-btn { justify-self: end; }
/* line-height: 1 keeps the mark, which is the largest thing in the row, from
   making the row taller than the home page's and pushing Menu and search down
   a few pixels relative to it. */
/* The name, set in the site's face. Letterspaced a little and no heavier than
   the body face: a masthead, not a logotype competing with the drawn mark that
   the home page carries. */
.masthead__mark {
  justify-self: center;
  line-height: 1;
  display: block;
  text-decoration: none;
  color: var(--ink);
  /* Bold capitals, as a grotesque wordmark. Akzidenz-Grotesk is a Berthold
     typeface and cannot be served without a licence, so this is Archivo at
     700 — the same lineage, and the one face the site uses. Capitals need a
     little more tracking than lowercase to stop them setting solid, and a
     step down in size because they measure taller. */
  font-size: var(--step-1);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .12em;
  /* The tracking adds a gap after the last letter; pull it back so the word
     reads centred between Menu and the search. */
  text-indent: .12em;
}
.masthead__mark:hover { text-decoration: underline; text-underline-offset: .28em; }
/* --- section openers ----------------------------------------------------- */

.section-head {
  display: flex; align-items: center; gap: 1rem;
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--ink-faint);
  margin: 0 0 1.4rem;
}
.section-head::after { content: ''; flex: 1; height: 1px; background: var(--rule); }

.band { background: var(--paper-sunk); padding-block: clamp(2.2rem, 5vw, 4rem); margin-block: clamp(2.2rem, 5vw, 4rem); }

.catalogue {
  font-size: var(--step--2);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: .9rem;
}

/* --- the field grid ------------------------------------------------------
   The reference directory's overview grid, with drawn symbols in place of a
   stock icon set. Four across on a desk, two on a tablet, two on a phone —
   the values are short, so one column would waste the width.            */

.fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: clamp(1.5rem, 3vw, 2.6rem) clamp(1.5rem, 3vw, 3rem);
  margin: 0 0 1rem;
}
.field { display: grid; grid-template-columns: 1.85rem 1fr; gap: 0 .85rem; }
.field__sym { width: 1.85rem; height: 1.85rem; color: var(--ink); grid-row: span 2; }
.field__sym svg { width: 100%; height: 100%; }
.field__k {
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
  line-height: 1.3;
}
.field__v { font-size: var(--step--1); line-height: 1.4; }

/* --- growing it ----------------------------------------------------------
   The same grid, used for what the growing notes turned out to say. Two
   differences: the values are shorter, so the columns are narrower and more
   of them fit; and the whole block sits on a sunk panel, because it is the
   part of an entry a person comes back to and it should be findable by shape
   rather than by reading.                                               */

.fields--growing {
  grid-template-columns: repeat(auto-fit, minmax(11.5rem, 1fr));
  gap: 1.5rem clamp(1.2rem, 3vw, 2.2rem);
  background: var(--paper-sunk);
  padding: 1.5rem clamp(1.1rem, 3vw, 1.8rem) 1.6rem;
  margin-bottom: 1.1rem;
}
.fields--growing .field__sym { color: var(--accent); }

/* The notes the grid was read out of, kept whole and one click away. Folded
   rather than cut: the grid answers the four questions people ask, and the
   notes still carry the rainfall, the altitude and the temperature range.
   <details> does it with no script, and the summary stays in the tab order. */
.notes { margin: 0 0 2.2rem; }
.notes__open {
  display: inline-flex;
  align-items: baseline;
  gap: .55rem;
  cursor: pointer;
  list-style: none;
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--ink-muted);
  padding: .1rem 0;
}
.notes__open::-webkit-details-marker { display: none; }
.notes__open::before {
  content: '+';
  font-size: var(--step--1);
  line-height: 1;
  color: var(--accent);
}
.notes[open] .notes__open::before { content: '\2013'; }
.notes__open:hover { color: var(--accent); }
.notes__open:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.notes__body { margin-top: 1rem; }

/* --- status --------------------------------------------------------------
   With no colour available the four categories are told apart by the shape
   of the mark, which also survives greyscale printing and colour blindness.
   The word is always present regardless.                                  */

.status { display: inline-flex; align-items: center; gap: .5em; font-size: var(--step--2); text-transform: uppercase; letter-spacing: .12em; }
.status::before { content: ''; width: .55em; height: .55em; border: 1.5px solid var(--ink); }
.status--native      ::before, .status--native::before      { background: var(--ink); border-radius: 50%; }
.status--naturalised ::before, .status--naturalised::before { border-radius: 50%; background: linear-gradient(90deg, var(--ink) 50%, transparent 50%); }
.status--exotic      ::before, .status--exotic::before      { border-radius: 50%; background: transparent; }
.status--invasive    ::before, .status--invasive::before    { background: var(--ink); transform: rotate(45deg); }

/* --- species header ------------------------------------------------------ */

.hero { display: grid; grid-template-columns: 1.15fr 1fr; gap: clamp(1.5rem, 4vw, 3.5rem); align-items: start; }
@media (max-width: 54rem) { .hero { grid-template-columns: 1fr; } }

.hero__fig { margin: 0; }
.hero__fig img { width: 100%; aspect-ratio: 4/3; object-fit: cover; background: var(--paper-sunk); }
.hero__fig figcaption { font-size: var(--step--2); color: var(--ink-faint); margin-top: .55rem; }

.species-sci { font-family: var(--display), serif; font-style: italic; font-size: var(--step-1); color: var(--ink-muted); margin: 0 0 .8rem; }
.vernaculars { font-size: var(--step-0); margin: 0 0 1.2rem; }
.vernaculars span + span::before { content: ' · '; color: var(--ink-faint); }

.tally { display: flex; gap: clamp(1.4rem, 4vw, 3rem); margin: 1.4rem 0; }
.tally__n { font-family: var(--display), serif; font-size: var(--step-3); line-height: 1; font-variant-numeric: tabular-nums; }
.tally__l { font-size: var(--step--2); text-transform: uppercase; letter-spacing: .1em; color: var(--ink-faint); display: block; margin-top: .3rem; }

.sourcing {
  display: inline-flex; align-items: center; gap: .6rem;
  font-size: var(--step--1); text-decoration: none;
  background: var(--ink); color: var(--paper);
  padding: .78rem 1.2rem;
}
.sourcing svg { width: 1.15rem; height: 1.15rem; }
.sourcing:hover { background: var(--ink-muted); }
.sourcing--none { background: transparent; color: var(--ink-muted); border: 1px dashed var(--rule-strong); }

/* --- wildlife ------------------------------------------------------------ */

.groups { display: flex; flex-wrap: wrap; list-style: none; margin: 0 0 1.6rem; padding: 0; border-bottom: 1px solid var(--rule); }
.groups a { display: flex; align-items: baseline; gap: .4rem; text-decoration: none; font-size: var(--step--1); padding: .5rem 0; margin-right: 1.6rem; margin-bottom: -1px; border-bottom: 2px solid transparent; color: var(--ink-faint); }
.groups a[aria-current] { color: var(--ink); border-bottom-color: var(--ink); }
.groups__n { font-size: var(--step--2); font-variant-numeric: tabular-nums; }

.fauna-list { list-style: none; margin: 0; padding: 0; }
.fauna-list li {
  display: flex; gap: .8rem; align-items: baseline; flex-wrap: wrap;
  padding: .6rem 0; border-bottom: 1px solid var(--rule);
  max-width: 54rem;
}
.fauna-list__name { font-weight: 500; }
.fauna-list__rel { font-size: var(--step--2); text-transform: uppercase; letter-spacing: .09em; color: var(--ink-muted); }
.evidence { font-size: var(--step--2); text-transform: uppercase; letter-spacing: .06em; color: var(--ink-faint); margin-left: auto; }
.evidence--observed_local { color: var(--ink); }

/* --- prose --------------------------------------------------------------- */

/* A title short enough to hold one line, kept on one. It still wraps where a
   narrow screen leaves it no room, rather than overflowing. */
.h1--oneline { max-width: none; text-wrap: balance; }
@media (min-width: 34rem) { .h1--oneline { white-space: nowrap; } }

.prose { max-width: var(--measure); font-size: var(--step-0); }
.onward {
  max-width: var(--measure);
  font-size: var(--step-0);
  color: var(--ink-muted);
  border-top: 1px solid var(--rule);
  padding-top: 1.6rem;
}
.onward a { color: var(--ink); text-decoration: underline; text-underline-offset: .22em; }
/* The link sits on its own line under the sentence, which is where it reads as
   the destination rather than as another clause. It stays at body size: larger
   type here made a card of what should be a quiet hand-off at the foot of the
   page. The rule is carried by the title alone, because text-decoration
   propagates to descendants and cannot be lifted from one — on the anchor it
   struck through the publisher too. */
.onward .onward__link {
  display: block;
  margin-top: .5rem;
  font-size: var(--step--1);
  text-decoration: underline;
  text-underline-offset: .22em;
}
.prose h2 { margin-top: 2rem; }
.disclaimer { font-size: var(--step--1); color: var(--ink-muted); border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); padding: .9rem 0; margin: 2rem 0; max-width: var(--measure); }

/* --- directory ----------------------------------------------------------- */

.toolbar { display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap; font-size: var(--step--1); border-bottom: 1px solid var(--rule-strong); padding-bottom: .8rem; margin-bottom: 1.6rem; }
.toolbar__spacer { flex: 1; }
.viewswitch { display: flex; }
.viewswitch a { text-decoration: none; border: 1px solid var(--rule-strong); padding: .32rem .8rem; margin-left: -1px; color: var(--ink-muted); }
.viewswitch a[aria-current] { background: var(--ink); color: var(--paper); border-color: var(--ink); }

.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: clamp(1.2rem, 3vw, 2.2rem); list-style: none; padding: 0; margin: 0; }
.grid__item a { text-decoration: none; display: block; }
.grid__fig { aspect-ratio: 4/5; background: var(--paper-sunk); overflow: hidden; margin-bottom: .7rem; }
.grid__fig img { width: 100%; height: 100%; object-fit: cover; }
.grid__name { font-family: var(--display), serif; font-size: var(--step-1); line-height: 1.1; }
.grid__sci { font-family: var(--display), serif; font-style: italic; font-size: var(--step--1); color: var(--ink-muted); }
.grid__meta { font-size: var(--step--2); color: var(--ink-faint); margin-top: .35rem; }

.list { width: 100%; border-collapse: collapse; font-size: var(--step--1); }
.list th { text-align: left; font-size: var(--step--2); text-transform: uppercase; letter-spacing: .1em; font-weight: 400; color: var(--ink-faint); border-bottom: 1px solid var(--rule-strong); padding: .5rem .75rem .5rem 0; }
.list td { border-bottom: 1px solid var(--rule); padding: .6rem .75rem .6rem 0; vertical-align: baseline; }
.list tr:hover td { background: var(--paper-sunk); }
.list__name { font-family: var(--display), serif; font-size: var(--step-0); }
.list__sci { font-family: var(--display), serif; font-style: italic; color: var(--ink-muted); }
.list__num { font-variant-numeric: tabular-nums; color: var(--ink-faint); }
@media (max-width: 46rem) {
  .list thead { display: none; }
  .list tr { display: block; border-bottom: 1px solid var(--rule); padding: .6rem 0; }
  .list td { display: block; border: 0; padding: .1rem 0; }
}

/* --- footer -------------------------------------------------------------- */

.footer {
  border-top: 1px solid var(--rule-strong);
  margin-top: clamp(3rem, 8vw, 6rem);
  padding-block: clamp(2.75rem, 6vw, 4.5rem);
  font-size: var(--step--1);
}

/* Two zones. What this is on the left, where to go on the right — the link
   groups sized to their own content and pushed to the right edge, rather than
   four equal columns that gave a sentence the width of a three-word list and
   left the whole footer bunched against the left margin. */
.footer__cols { display: grid; gap: clamp(2.5rem, 6vw, 5rem); align-items: start; }
.footer__links { display: grid; gap: clamp(1.5rem, 4vw, 3.25rem); }
@media (min-width: 62rem) {
  .footer__cols { grid-template-columns: minmax(0, 1fr) auto; }
  .footer__links { grid-template-columns: repeat(3, minmax(0, max-content)); }
}
@media (max-width: 61.999rem) {
  .footer__links { grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); }
}
/* Its own measure. The global 62ch would run this line most of the way across
   a wide footer and leave the link groups stranded beside it. */
.footer__brand p { max-width: 30rem; margin: 0; }

.footer h4 {
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--ink-faint);
  margin: 0 0 1rem;
  font-weight: 400;
}
.footer ul { list-style: none; margin: 0; padding: 0; }
.footer li { margin-bottom: .55rem; }
.footer a { color: var(--ink-muted); text-decoration: none; }
.footer a:hover { color: var(--ink); text-decoration: underline; }

.footer__fine {
  color: var(--ink-faint);
  font-size: var(--step--2);
  max-width: none;
  margin: clamp(2.75rem, 6vw, 4rem) 0 0;
  padding-top: 1.4rem;
  border-top: 1px solid var(--rule);
}

/* ===========================================================================
   Patterns taken from the reference journal
   =========================================================================== */

/* Its section openers are just a word, a colon and a rule running the full
   width. No uppercase, no tracking, no decoration — which is why they read as
   a table of contents rather than as headings competing with the titles. */
.rule-head {
  font-size: var(--step-1);
  font-weight: 400;
  letter-spacing: 0;
  margin: 0 0 1.1rem;
  padding-bottom: .55rem;
  border-bottom: 1px solid var(--ink);
}

/* Image left, text right, uneven. The images are deliberately not a uniform
   height: that is what stops a long index reading as a generated grid. */
.row {
  display: grid;
  /* The proportions came off a phone recording, where the image column is
     roughly half the screen. Carried straight to a desktop container that
     makes a 3:4 image nine hundred pixels tall with a stranded caption beside
     it, so the image column is capped rather than left proportional. */
  grid-template-columns: minmax(0, 23rem) minmax(0, 1fr);
  gap: clamp(1.2rem, 4vw, 3rem);
  align-items: start;
  margin-bottom: clamp(2rem, 5vw, 3.5rem);
  max-width: 62rem;
}
.row__fig { position: relative; margin: 0; background: var(--paper-sunk); }
.row__fig img { width: 100%; object-fit: cover; aspect-ratio: var(--ar, 4/5); }
.row:nth-child(3n + 1) .row__fig { --ar: 4/3; }
.row:nth-child(3n + 2) .row__fig { --ar: 3/4; }
.row:nth-child(3n + 3) .row__fig { --ar: 1/1; }
@media (max-width: 44rem) { .row { grid-template-columns: 1fr; } }

/* The category tag sits on the image corner, not above the title. */
.tag {
  position: absolute; top: 0; left: 0;
  background: rgba(16, 16, 16, 0.72);
  color: #fff;
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  padding: .45rem .8rem;
}

/* Title and byline run on as one line, the byline dropping to grey. It reads
   as a sentence rather than as a headline stacked over a credit. */
.row__title {
  font-size: var(--step-1);
  font-weight: 300;
  line-height: 1.3;
  letter-spacing: 0.005em;
  margin: 0;
}
.row__title a { text-decoration: none; }
.row__by { color: var(--ink-faint); }

/* ===========================================================================
   Home
   =========================================================================== */

/* Full-bleed opening: the photograph IS the first screen. No nav bar, no
   scroll cue, no overlaid copy competing with it — only the wordmark and the
   two controls, the way the reference magazine opens. */
.opening {
  position: relative;
  /* svh rather than vh so mobile browser chrome does not crop it. */
  height: 100svh;
  min-height: 34rem;
  overflow: hidden;
  background: #111;
}
/* Until there is a photograph. The opening is built for one — white type over
   a picture — and with nothing behind it that is a full screen of black,
   which reads as a fault rather than as a choice. A soft ground and dark type
   instead; the moment a picture is set, none of this applies. */
.opening--plain {
  background: linear-gradient(168deg, #f2f4ee 0%, #e6ebdd 60%, #dde4d1 100%);
  height: auto;
  min-height: 0;
  padding-bottom: clamp(3rem, 8vw, 6rem);
}
.opening--plain .wordmark { position: static; transform: none; color: var(--ink); }
.opening--plain .wordmark {
  padding-top: clamp(3.5rem, 9vw, 6rem);
  width: auto;
  text-shadow: none;
}
.opening--plain .opening__media { display: none; }
/* Menu and search are white for a photograph. On the pale ground they have to
   come back to ink or they vanish into it. */
.opening--plain .top,
.opening--plain .top a { color: var(--ink); }
.opening--plain .top svg { color: var(--ink); }

.opening__media { position: absolute; inset: 0; }
/* Full width, because the photograph here is landscape: across a 16:10 screen
   it loses about a sixth of its height and nothing else. The column this was
   briefly held to existed for a 3:4 portrait, which full width had to enlarge
   by a third and crop two thirds of the height from — put a portrait back in
   here and that column should come back with it. */
.opening__media img, .opening__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 45%;
}
/* Wash, where type sits and nowhere else.

   A bright, busy photograph — pale grass, bare branches, high sun — leaves
   white line art and white text with almost nothing to sit against. Two
   gradients rather than one flat scrim: down from the top for the mark and
   the two controls, and in from the left for the opening sentences. Both run
   to fully transparent well before the middle, so the part of the picture
   that is the picture — the people, the cattle, the light through the trees —
   keeps its own colour and is never dimmed. */
.opening__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(0,0,0,.52) 0, rgba(0,0,0,.2) 20%, rgba(0,0,0,0) 40%),
    linear-gradient(97deg, rgba(0,0,0,.5) 0, rgba(0,0,0,.2) 34%, rgba(0,0,0,0) 60%);
}

/* Sits high rather than centred, as the reference does — it reads as a
   masthead on the photograph, not as a caption floating in it. */
.wordmark {
  position: absolute;
  top: clamp(2.2rem, 5vh, 3.6rem);
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
  color: #fff;
  width: 100%;
  padding-inline: var(--gutter);
}
.wordmark p { max-width: none; }

/* The drawn mark, not type. Width rather than height, because the mark is
   wider than it is tall and it is the width of the drawing that has to sit
   inside the gutters. */
.wordmark__logo {
  display: block;
  width: min(34vw, 7.5rem);
  height: auto;
  margin-inline: auto;
}
/* Over a photograph the charcoal would disappear. The mark is solid black with
   an alpha channel and invert leaves alpha alone, so inverting it gives the
   white cut of the same drawing exactly — no second file. Menu and search are
   already white in that case; this is the same decision for the mark. */
.opening:not(.opening--plain) .wordmark__logo {
  filter: invert(1) drop-shadow(0 1px 3px rgba(0, 0, 0, .55))
                    drop-shadow(0 2px 18px rgba(0, 0, 0, .45));
}

/* The words on the ink travel as the panel passes, rather than sitting still
   in the middle of a black screen. Driven by the panel's own position in the
   viewport, so it is the reading that moves and not the page under it.

   Scroll-driven and declarative, so it costs no JavaScript; where the timeline
   is not supported the text is simply where it would have ended up, and anyone
   who has asked for reduced motion gets that too. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .declare__inner {
      animation: drift linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    /* 12svh, not 7. At 7 the travel was about 66px across a whole screen of
       scrolling — real, measurable, and too small to notice, which is the same
       as not being there. */
    @keyframes drift {
      from { transform: translateY(12svh); }
      to   { transform: translateY(-12svh); }
    }
  }
}

/* A deliberate pause. The writing and the winter row are two different things
   being offered, and at the ordinary spacing they ran together as one long
   column of tiles. This is the one gap on the page that is meant to be felt. */
/* Two classes, because `.reveal` sets a margin of its own further down the
   sheet and would otherwise win on source order alone. */
.shell.shell--apart { margin-top: clamp(6rem, 25vw, 23rem); }

/* The editorial paragraph under the opening. Set larger than body copy and
   held to a short measure so it reads as a statement, not as an article. */
.lede {
  font-size: var(--step-1);
  line-height: 1.5;
  font-weight: 300;
  max-width: 40rem;
  margin: 0 0 1.2rem;
}
.lede--sub { font-size: var(--step-0); color: var(--ink-muted); max-width: 44rem; }
/* A lede of a line or two can run wider than body text — it is read once, not
   dwelt in, and at the usual measure a short introduction stacks into a narrow
   column that reads as a paragraph rather than as an opening. */
.lede--wide { max-width: 54rem; }

.more {
  display: inline-block;
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: .14em;
  text-decoration: none;
  border-bottom: 1px solid var(--ink);
  padding-bottom: .25rem;
  margin-top: .6rem;
}

/* --- overlaid masthead + menu --------------------------------------------
   On the home page the masthead has no bar of its own: two controls sit
   directly on the photograph. Everywhere else it stays a ruled row.       */

.top {
  position: absolute;
  top: 0; left: 0; right: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: clamp(1.1rem, 3vw, 1.9rem) var(--gutter);
  color: #fff;
}
.top a, .top label { color: inherit; }

.menu-btn {
  font-size: var(--step-0);
  cursor: pointer;
  user-select: none;
  background: none;
  border: 0;
  padding: 0;
  font-family: inherit;
  color: inherit;
}
.menu-btn:hover { text-decoration: underline; text-underline-offset: .3em; }

.search-btn { display: inline-flex; }
.search-btn svg { width: 1.3rem; height: 1.3rem; }

/* CSS-only overlay: no script, so it still works if JavaScript fails or is
   blocked, and there is nothing to hydrate. */
/* Visually hidden but still focusable and operable. opacity:0 with
   pointer-events:none would have taken the checkbox out of the keyboard path
   entirely, leaving the menu openable only with a mouse. */
.menu-toggle {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* The checkbox receives focus, so its ring has to show on the label the
   reader actually sees. */
body:has(.menu-toggle:focus-visible) .menu-btn {
  outline: 2px solid currentColor;
  outline-offset: 4px;
}

/* Base state: off-screen and inert. This has to be the panel's own rule — if
   it only applies under some other condition, the whole menu renders inline
   at the top of every page and pushes the masthead down. */
.menu-panel {
  position: fixed;
  inset: 0;
  z-index: 20;
  background: var(--ink);
  color: var(--paper);
  overflow-y: auto;
  opacity: 0;
  visibility: hidden;
  transition: opacity 180ms ease, visibility 180ms;
}
.menu-toggle:checked ~ .menu-panel { opacity: 1; visibility: visible; }

.menu-inner {
  width: min(100% - (var(--gutter) * 2), 74rem);
  margin-inline: auto;
  padding-block: clamp(3.5rem, 9vh, 6rem);
}

/* Two columns with a rule between them: the list on the left, and whatever
   the hovered item opens on the right. */
.menu-grid {
  position: relative;
  border-top: 1px solid rgba(255,255,255,.35);
  border-bottom: 1px solid rgba(255,255,255,.35);
  padding-block: clamp(1.5rem, 4vh, 3rem);
}
.menu-grid::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0; left: 58%;
  width: 1px;
  background: rgba(255,255,255,.35);
}
@media (max-width: 52rem) { .menu-grid::after { display: none; } }

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

.menu-item {
  display: flex;
  align-items: baseline;
  gap: 1.1rem;
  padding-block: clamp(.35rem, 1vh, .7rem);
}
.menu-item__n {
  font-size: var(--step--2);
  font-variant-numeric: tabular-nums;
  color: rgba(255,255,255,.5);
  min-width: 1.6rem;
}
.menu-item__a {
  font-family: var(--display), var(--indic), serif;
  font-weight: var(--display-weight);
  font-size: clamp(1.5rem, 4vw, 2.5rem);
  line-height: 1.2;
  text-decoration: none;
  color: rgba(255,255,255,.62);
  transition: color 120ms ease;
}
.menu-item:hover .menu-item__a,
.menu-item:focus-within .menu-item__a { color: #fff; }

/* Children live in the right column. Positioned against .menu-grid, so they
   line up whichever parent is hovered. */
.menu-item__sub {
  display: none;
  position: absolute;
  left: 58%;
  top: clamp(1.5rem, 4vh, 3rem);
  padding-left: clamp(1.5rem, 4vw, 3rem);
  list-style: none;
  margin: 0;
}
.menu-item:hover .menu-item__sub,
.menu-item:focus-within .menu-item__sub { display: block; }
.menu-item__sub li { margin-bottom: .55rem; }
.menu-item__sub a {
  font-size: var(--step-0);
  text-decoration: none;
  color: rgba(255,255,255,.72);
}
.menu-item__sub a:hover { color: #fff; text-decoration: underline; text-underline-offset: .25em; }

/* On a phone there is no second column, so children sit under their parent
   and stay open rather than depending on hover, which does not exist there. */
@media (max-width: 52rem) {
  .menu-item { flex-wrap: wrap; }
  .menu-item__sub {
    display: block;
    position: static;
    padding: .4rem 0 .5rem 2.7rem;
    width: 100%;
  }
}

.menu-foot {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 1.4rem;
  font-size: var(--step--1);
  color: rgba(255,255,255,.55);
}
.menu-foot a { color: inherit; text-decoration: none; margin-right: 1.2rem; }
.menu-foot a:hover { color: #fff; }

.menu-close {
  position: fixed;
  top: clamp(1.1rem, 3vw, 1.9rem);
  right: var(--gutter);
  z-index: 2;
  font-size: var(--step-1);
  cursor: pointer;
}
@media (prefers-reduced-motion: reduce) { .menu-panel { transition: none; } }

/* The ruled masthead shows its full link row on a desk and collapses to the
   same Menu control on a phone, so there is one menu, not two. */

/* ===========================================================================
   Shelf — a labelled row of square tiles

   Taken from the reference journal's index: an uppercase tracked heading over
   a full-width rule, then a row label sitting far left with the tiles running
   horizontally to the right of it. The wide gap between label and tiles is
   the point, not an accident — it is what stops the row reading as a card
   grid.
   =========================================================================== */

/* No rule under the head. The tiles below it already make an edge, and the
   line was drawing a second one a few pixels above it. The head is a link to
   the filtered view, and a link's own underline is a line too — so it comes
   back on hover and focus rather than sitting there by default. */
.shelf-head a { text-decoration: none; }
.shelf-head a:hover,
.shelf-head a:focus-visible { text-decoration: underline; text-underline-offset: .3em; }
.shelf-head {
  font-size: var(--step--1);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  margin: 0 0 1rem;
}

/* One horizontal row, full width — no label column, no empty margin. */
.tiles {
  display: grid;
  gap: clamp(.9rem, 2vw, 1.6rem);
}
.tiles--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tiles--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

/* A row that runs on sideways rather than wrapping down the page. Thirteen
   winter vegetables laid out as a grid would be four rows of tiles before you
   reached the next thing to read; as a row you see five, know there are more,
   and the page keeps moving. It is the same sideways-scrolling behaviour the
   small screens already had, brought up to every width.

   No script: overflow and scroll-snap do all of it, so it works with a
   trackpad, a wheel, a touchscreen and the keyboard alike. The negative
   margin lets the row start at the gutter and run past it, so the tile at the
   right edge is visibly cut — which is what says there is more without
   putting an arrow on the page. */
.tiles--slide {
  grid-auto-flow: column;
  grid-template-columns: none;
  grid-auto-columns: clamp(9rem, 17vw, 14rem);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  overscroll-behavior-x: contain;
  margin-inline: calc(var(--gutter) * -1);
  padding: 0 var(--gutter) .7rem;
  scroll-padding-left: var(--gutter);
  scrollbar-width: thin;
  scrollbar-color: var(--rule-strong) transparent;
}
.tiles--slide > * { scroll-snap-align: start; }

/* Below the breakpoint the row scrolls sideways with snap points rather than
   stacking into a column of tall squares. */
@media (max-width: 52rem) {
  .tiles { grid-auto-flow: column; grid-template-columns: none; grid-auto-columns: 64vw;
           overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: .5rem; }
  .tiles > * { scroll-snap-align: start; }
}

.tile { display: block; text-decoration: none; }
.tile__kicker {
  display: block;
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--ink);
  margin-bottom: .5rem;
}
.tile__fig { display: block; aspect-ratio: 1 / 1; background: var(--paper-sunk); overflow: hidden; }
.tile__fig img { width: 100%; height: 100%; object-fit: cover; }
.tile__title { display: block; font-size: var(--step--1); line-height: 1.35; margin-top: .6rem; }
.tile:hover .tile__title { text-decoration: underline; text-underline-offset: .2em; }
.tile__by { color: var(--ink-faint); }
/* On the writing row that second line is the kind of piece — essay, field
   note — and it belongs under the headline, not running on from it the way a
   species' Gujarati name does. */
.tiles--writing .tile__by { display: block; margin-top: .15rem; }

/* The way between the community map and the page that adds to the repository.
   They are two halves of the same thing — one pins what you grew, the other
   names what we are missing — and before this there was no way from either to
   the other without going back through the menu. */
.crosslink {
  margin: 1.4rem 0 0;
  font-size: var(--step--1);
}
.crosslink a {
  color: var(--ink-muted);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .3em;
}
.crosslink a:hover { color: var(--ink); }


/* Sections rise slightly into place as they enter, which is what gives the
   page its unhurried feel on the way down. Scroll-driven and declarative, so
   it costs no JavaScript; without support the content is simply already
   there, and anyone who has asked for reduced motion gets that too. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 58%;
    }
    @keyframes rise {
      from { opacity: 0; transform: translateY(1.6rem); }
      to   { opacity: 1; transform: none; }
    }
  }
}


/* ===========================================================================
   Engage — the community map
   =========================================================================== */

.engage {
  background: var(--beige);
  padding-block: clamp(2.8rem, 6vw, 4.5rem);
}

/* Words on the left, the picture on the right. Only a pair when there is a
   picture — with none, the words take the whole band rather than sitting in a
   column with a hole beside them. */
.engage__split { display: grid; gap: clamp(1.8rem, 5vw, 4rem); align-items: center; }
.engage__split--pair { grid-template-columns: minmax(0, 1fr) minmax(0, 26rem); }

/* Cropped to fill its side rather than floated at its own shape: a survey plan
   is a tall sheet and its own ratio would leave the band ragged. object-fit
   does the cropping, so the file is never cut and a different picture can take
   its place without being re-made. */
.engage__map { margin: 0; }
.engage__map a {
  display: block;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: #ded7c7;
}
/* Biased up rather than centred: a centre crop kept a strip of the factory
   list along the foot and cut the title block off the top. This keeps the
   plan's own heading and loses the list, which is the part that does not read
   at this size anyway. */
.engage__map img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 34%;
  display: block;
}
.engage__map figcaption {
  margin-top: .6rem;
  font-size: var(--step--2);
  line-height: 1.4;
  color: var(--ink-muted);
}

.engage__text { max-width: 44rem; }

/* Below the split the map goes above the words, where it is a full-width
   sheet rather than a column too narrow to read. */
@media (max-width: 52rem) {
  .engage__split { grid-template-columns: minmax(0, 1fr); }
}


/* What Ekatra is, on ink. The only dark ground on the site, and it earns that
   by coming straight off the photograph: the picture ends, the page states the
   thing plainly, and then the writing begins. No .reveal above it — it butts
   directly against the foot of the opening rather than floating below it, and
   the join is what makes the two read as one movement. */
/* The panel is now the page's opening words as well as its statement of what
   Ekatra is — the photograph carries nothing but the mark. Given the height of
   the picture above it, so the two read as a pair: a screen you look at, then
   a screen you read. */
.declare {
  background: var(--ink);
  color: var(--paper);
  min-height: 100svh;
  display: grid;
  align-content: center;
  padding-block: clamp(3rem, 8vw, 6rem);
}
/* At the gutter, not in the 80rem content column: the line that used to sit on
   the photograph started at the edge of the page, and moving it onto the black
   should not move it inward. */
.declare__inner {
  padding-inline: var(--gutter);
  max-width: 62rem;
}
.declare__text {
  max-width: none;
  margin: 0 0 clamp(1.6rem, 4vw, 2.6rem);
  font-size: var(--step-1);
  font-weight: 300;
  line-height: 1.5;
}
.declare__text:last-of-type { margin-bottom: 0; }
/* .more draws its rule in ink, which on ink is nothing at all. */
.declare__go {
  color: var(--paper);
  border-bottom-color: rgba(255, 255, 255, .55);
  margin-top: 1.6rem;
}
.declare__go:hover { border-bottom-color: var(--paper); }

/* The line that introduces a section, set the way the standalone passage
   between sections is set rather than as a small grey caption above a heading:
   same size, same measure, same ink, and real space under it so the heading
   that follows is plainly a new thing rather than its second line. */
.statement-line {
  max-width: 70rem;
  margin: 0 0 clamp(4rem, 9vw, 7rem);
  font-size: var(--step-1);
  font-weight: 300;
  line-height: 1.42;
  letter-spacing: -0.005em;
}

/* One rule owns the gap between blocks on the home page, so the rhythm stays
   even whatever each block contains. */
.reveal { margin-top: clamp(3.5rem, 8vw, 6rem); }
/* Follows straight on from the line that introduces it. */
.ecomap--tight { margin-top: clamp(1.25rem, 3vw, 2rem); }
.shelf-head { margin-bottom: clamp(1.8rem, 3.6vw, 2.8rem); }


/* The standalone paragraph between the repository and the features. It is the
   one place on the page that is only writing, so it gets real air on both
   sides and a larger size — it should read as a pause, not as a caption
   wedged between two rows of tiles. */
.statement {
  margin-block: clamp(5.5rem, 15vw, 11rem);
  /* A step down from the old size, which is what buys the three lines: at
     step-2 this passage ran to five however wide the measure went, and past
     70rem a line gets too long to track back from comfortably. */
  font-size: var(--step-1);
  font-weight: 300;
  line-height: 1.42;
  letter-spacing: -0.005em;
}
/* The measure belongs to the paragraph, not to the block. Capping the block
   instead left it centred by .shell's auto margins, off the left edge every
   section head on the page lines up on.

   Wide, not narrow: at 34rem this sat as a column against the left margin with
   half the page empty beside it. At 58rem it starts on the same edge and runs
   out past the middle, which is what turns it from a caption into a passage —
   and it closes in three lines rather than seven. */
.statement p { max-width: 70rem; margin: 0; }

/* ===========================================================================
   Ecoregion map

   Hovering is the reason this map is ours rather than an embed: a
   cross-origin iframe cannot be hit-tested or styled from the page around it.
   The whole interaction is :has() and :focus-visible — no script at all.

   Ecoregions overlap: twenty-one of the thirty-four districts fall in more
   than one. So the map draws each district once and tags it with every
   ecoregion that reaches it, and the interaction runs both ways — hover the
   map to ask "what is this place in", hover the key to ask "how far does this
   ecoregion reach". A district in two ecoregions simply lights two keys.
   =========================================================================== */

.ecomap {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: start;
}
@media (max-width: 54rem) { .ecomap { grid-template-columns: 1fr; } }

.ecomap__svg { width: 100%; height: auto; display: block; }

.ecomap__d { cursor: pointer; }
.ecomap__d path {
  fill: #e6e6e3;
  stroke: var(--paper);
  stroke-width: 1.1;
  transition: fill 140ms ease;
}
/* The district name sits at the centroid of its largest polygon and only
   appears under the pointer, so the map stays a shape until it is asked a
   question. White, because the district it names has just filled black, with
   a dark halo for the letters that overrun onto a paler neighbour. Which name
   shows is paired to which district is hovered in the generated ecomap.css,
   because the names are drawn in a later group — SVG paints in document order,
   so a name inside its own district group ends up beneath the districts drawn
   after it. */
.ecomap__labels { pointer-events: none; }
.ecomap__labels text {
  font-family: var(--text), sans-serif;
  font-size: 16px;
  text-anchor: middle;
  letter-spacing: .03em;
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 3.5;
  paint-order: stroke fill;
  opacity: 0;
  pointer-events: none;
  transition: opacity 140ms ease;
}
.ecomap__d:hover path { fill: var(--ink); }

.ecomap__here { pointer-events: none; }
.ecomap__here text { transition: opacity 140ms ease; }
.ecomap:has(.ecomap__d:hover) .ecomap__here text { opacity: 0; }
.ecomap__here circle { fill: var(--ink); stroke: var(--paper); stroke-width: 2; }
.ecomap__here text {
  font-family: var(--text), sans-serif;
  font-size: 19px;
  fill: var(--ink);
  letter-spacing: .04em;
  stroke: var(--paper);
  stroke-width: 4;
  paint-order: stroke fill;
}

/* --- the key -------------------------------------------------------------- */

.ekey { position: sticky; top: 2rem; list-style: none; margin: 0; padding: 0; }
.ekey__item {
  border-top: 1px solid var(--rule);
  padding: .95rem 0;
  cursor: pointer;
  outline: none;
  transition: color 140ms ease;
}
.ekey__lead {
  font-size: var(--step--1);
  color: var(--ink-faint);
  max-width: 38ch;
  margin-bottom: 1.6rem;
}
.ekey__item:first-child,
.ekey__lead + .ekey__item { border-top: 0; padding-top: 0; }
.ekey__item:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }
.ekey__h {
  font-size: var(--step-0);
  font-weight: 400;
  line-height: 1.25;
  margin: 0;
  color: inherit;
}
.ekey__n {
  display: block;
  font-size: var(--step--2);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: .45rem;
}
/* The note is the answer, so it waits to be asked for. Without this the key
   is five paragraphs of prose beside the map rather than a legend. */
/* Collapsed with max-height rather than display:none, so the note stays in
   the accessibility tree and a screen reader gets all five whether or not
   anything has been hovered. Sighted readers get a legend, not a wall. */
.ekey__note {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  margin: 0;
  font-size: var(--step--1);
  color: var(--ink-muted);
  max-width: 44ch;
  transition: max-height 220ms ease, opacity 160ms ease, margin-top 220ms ease;
}

/* Anything hovered mutes the rest of the key, so the one being answered is
   the one you are reading. Weighs (0,4,0); every rule that restores a key
   below weighs more, and order alone would not be enough. */
.ecomap:has(.ecomap__d:hover) .ekey__item,
.ecomap:has(.ekey__item:hover) .ekey__item { color: var(--ink-faint); }

/* One pair of rules per ecoregion. First: hovering or tabbing to a key fills
   every district that ecoregion reaches. */
.ecomap:has(.ekey__item[data-region="khathiar-gir"]:hover) .r-khathiar-gir path,
.ecomap:has(.ekey__item[data-region="khathiar-gir"]:focus-visible) .r-khathiar-gir path,
.ecomap:has(.ekey__item[data-region="aravalli-west-thorn"]:hover) .r-aravalli-west-thorn path,
.ecomap:has(.ekey__item[data-region="aravalli-west-thorn"]:focus-visible) .r-aravalli-west-thorn path,
.ecomap:has(.ekey__item[data-region="north-western-ghats"]:hover) .r-north-western-ghats path,
.ecomap:has(.ekey__item[data-region="north-western-ghats"]:focus-visible) .r-north-western-ghats path,
.ecomap:has(.ekey__item[data-region="rann-of-kutch"]:hover) .r-rann-of-kutch path,
.ecomap:has(.ekey__item[data-region="rann-of-kutch"]:focus-visible) .r-rann-of-kutch path,
.ecomap:has(.ekey__item[data-region="indus-arabian-mangroves"]:hover) .r-indus-arabian-mangroves path,
.ecomap:has(.ekey__item[data-region="indus-arabian-mangroves"]:focus-visible) .r-indus-arabian-mangroves path {
  fill: var(--ink);
}

/* Second: hovering a district lights every key it belongs to — two of them,
   for the twenty-one districts that sit in more than one ecoregion. The first
   two selectors are the key's own hover and focus; they repeat :hover inside
   and outside :has() only to outweigh the dimming rule above. */
.ecomap:has(.ekey__item:hover) .ekey__item:hover,
.ecomap:has(.ekey__item:focus-visible) .ekey__item:focus-visible,
.ecomap:has(.r-khathiar-gir:hover) .ekey__item[data-region="khathiar-gir"],
.ecomap:has(.r-aravalli-west-thorn:hover) .ekey__item[data-region="aravalli-west-thorn"],
.ecomap:has(.r-north-western-ghats:hover) .ekey__item[data-region="north-western-ghats"],
.ecomap:has(.r-rann-of-kutch:hover) .ekey__item[data-region="rann-of-kutch"],
.ecomap:has(.r-indus-arabian-mangroves:hover) .ekey__item[data-region="indus-arabian-mangroves"] {
  color: var(--ink);
}

.ekey__item:hover .ekey__note,
.ekey__item:focus-visible .ekey__note,
.ecomap:has(.r-khathiar-gir:hover) .ekey__item[data-region="khathiar-gir"] .ekey__note,
.ecomap:has(.r-aravalli-west-thorn:hover) .ekey__item[data-region="aravalli-west-thorn"] .ekey__note,
.ecomap:has(.r-north-western-ghats:hover) .ekey__item[data-region="north-western-ghats"] .ekey__note,
.ecomap:has(.r-rann-of-kutch:hover) .ekey__item[data-region="rann-of-kutch"] .ekey__note,
.ecomap:has(.r-indus-arabian-mangroves:hover) .ekey__item[data-region="indus-arabian-mangroves"] .ekey__note {
  max-height: 14rem;
  opacity: 1;
  margin-top: .6rem;
}

/* A touch screen has no hover, so the notes would never open. There, the key
   simply reads as a list with all five notes showing. */
@media (hover: none) {
  .ekey__note { max-height: none; opacity: 1; margin-top: .6rem; }
  .ekey__item { color: var(--ink); }
}

@media (prefers-reduced-motion: reduce) {
  .ecomap__d path, .ecomap__labels text, .ekey__item, .ekey__note { transition: none; }
}

/* --- reasons: alternating text and photograph ----------------------------- */

.reason {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 5vw, 4rem);
  align-items: center;
  margin-block: clamp(3rem, 8vw, 6rem);
}
/* Every second reason puts the photograph on the other side, so the page
   alternates instead of forming two fixed columns. */
.reason:not(.reason--solo):nth-child(even) .reason__fig { order: -1; }
/* On its own in a section, the block takes the page's spacing instead of the
   looser rhythm that separates one reason from the next, and it opts out of
   the alternation, which has nothing to alternate against and only repeats
   whatever two-column split came before it. */
.reason--solo { margin-block: 0; }
@media (max-width: 50rem) { .reason { grid-template-columns: 1fr; } }

.reason__n {
  font-size: var(--step--2);
  letter-spacing: .16em;
  color: var(--ink-faint);
  display: block;
  margin-bottom: .8rem;
}
.reason__h { font-size: var(--step-2); margin-bottom: .7rem; }
.reason:not(:has(.reason__h)) .reason__body { font-size: var(--step-1); line-height: 1.45; }
.reason__fig { margin: 0; }
.reason__fig img { width: 100%; aspect-ratio: 4/3; object-fit: cover; background: var(--paper-sunk); }
/* A picture the 4:3 crop would spoil — a herd spread right across a plain, a
   tall tree standing over a camp — keeps its own proportions instead, and
   nothing is cut out of it. A portrait is held back from running away down the
   page beside five lines of text. */
.reason__fig--natural img {
  aspect-ratio: auto;
  width: auto;
  max-width: 100%;
  max-height: 34rem;
  /* Sized by its own proportions inside those bounds, so a tall picture is not
     letterboxed into a box with empty gutters either side of it. */
  object-fit: fill;
}
.reason__credit {
  margin-top: .5rem;
  font-size: var(--step--2);
  color: var(--ink-faint);
}


.source {
  font-size: var(--step--2);
  color: var(--ink-faint);
  margin-top: .6rem;
}

/* A fact set large enough to stop on. */
.figure-note {
  border-top: 1px solid var(--ink);
  border-bottom: 1px solid var(--ink);
  padding-block: clamp(1.6rem, 4vw, 2.4rem);
  margin-block: clamp(2.5rem, 6vw, 4rem);
}
.figure-note__n { font-size: var(--step-4); line-height: 1; font-variant-numeric: tabular-nums; display: block; }
.figure-note__t { font-size: var(--step-0); color: var(--ink-muted); margin: .8rem 0 0; max-width: 40rem; }

.numbered { list-style: none; margin: 0; padding: 0; max-width: 42rem; }
.numbered li { border-top: 1px solid var(--rule); padding: 1.2rem 0; }
.numbered li:last-child { border-bottom: 1px solid var(--rule); }
.numbered b { display: block; font-weight: 500; margin-bottom: .35rem; }

/* ===========================================================================
   Directory — nurseries and seed banks

   The list and the map answer different halves of one question, so they sit
   side by side and light each other: hover a listing to find it on the map,
   hover a pin to find its listing. The pairing rules are generated per page,
   since they depend on which listings exist; everything here is the frame.
   =========================================================================== */

/* --- search and the one filter -------------------------------------------- */

.finder {
  display: grid;
  gap: clamp(1.4rem, 3vw, 2.2rem);
  padding-bottom: clamp(1.5rem, 3vw, 2rem);
  border-bottom: 1px solid var(--rule-strong);
}
@media (min-width: 56rem) { .finder { grid-template-columns: minmax(0, 22rem) minmax(0, 1fr); } }

.finder__label {
  display: block;
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--ink-faint);
  margin-bottom: .7rem;
}
.finder__row { display: flex; }
.finder__input {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: var(--step--1);
  color: var(--ink);
  background: none;
  border: 1px solid var(--rule-strong);
  border-right: 0;
  padding: .55rem .75rem;
  border-radius: 0;
  -webkit-appearance: none;
  appearance: none;
}
.finder__input:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
.finder__go {
  font: inherit;
  font-size: var(--step--1);
  cursor: pointer;
  background: var(--ink);
  color: var(--paper);
  border: 1px solid var(--ink);
  padding: .55rem 1.1rem;
  border-radius: 0;
}

.chips { display: flex; flex-wrap: wrap; gap: .5rem; list-style: none; margin: 0; padding: 0; }
.chip {
  display: inline-flex;
  align-items: baseline;
  gap: .45rem;
  font-size: var(--step--1);
  text-decoration: none;
  color: var(--ink-muted);
  border: 1px solid var(--rule-strong);
  /* Matches .finder__input's vertical padding. The two controls sit on one
     line, so unequal padding left the chips finishing 7px short of the search
     box and the whole row reading as misaligned. */
  padding: .55rem .8rem;
}
.chip:hover { color: var(--ink); border-color: var(--ink); }
.chip[aria-current] { background: var(--accent); color: var(--paper); border-color: var(--accent); }
.chip__n { font-size: var(--step--2); color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.chip[aria-current] .chip__n { color: var(--paper); opacity: .65; }

/* --- the two halves ------------------------------------------------------- */

/* One column until there is a map to put beside it. The script adds
   .places--mapped once it has something to draw, so a directory with nothing
   geocoded yet uses the full width instead of leaving half the page empty. */
.places { display: grid; gap: clamp(1.75rem, 4vw, 3.25rem); align-items: start; }
@media (min-width: 62rem) {
  .places--mapped { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
  /* The listings scroll inside the height of the map, so the two read as one
     panel. Without this a long directory ran far below the map and left the
     right-hand side empty for most of the page. Only where there is a map:
     on a phone the list is the page and scrolls with it. */
  .places--mapped .places__list {
    max-height: var(--map-h);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-right: .9rem;
    scrollbar-width: thin;
  }
  .places--mapped .places__list:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
  /* The count stays put while the listings move under it. */
  .places--mapped .places__count {
    position: sticky;
    top: 0;
    background: var(--paper);
    padding: .2rem 0 .7rem;
    margin: 0;
    border-bottom: 1px solid var(--rule-strong);
    z-index: 1;
  }
  /* The map stays put while the list scrolls past it, which is the whole
     reason for putting them side by side. */
  .places__map { position: sticky; top: 2rem; }
}

.places__count {
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--ink-faint);
  margin: 0 0 .4rem;
}
.places__none { font-size: var(--step--1); color: var(--ink-muted); padding: 2rem 0; }

.place {
  display: grid;
  grid-template-columns: 2.1rem 6.5rem minmax(0, 1fr);
  gap: 0 1rem;
  padding: 1.1rem .8rem 1.1rem 0;
  border-bottom: 1px solid var(--rule);
  transition: background 140ms ease;
}
@media (max-width: 34rem) { .place { grid-template-columns: 2.1rem minmax(0, 1fr); } }

/* The photograph is where colour comes into this page. Everything else here
   is black and white, so a column of them down the left is what stops a long
   directory reading as a spreadsheet. */
.place__fig {
  margin: 0;
  aspect-ratio: 1;
  background: var(--paper-sunk);
  overflow: hidden;
}
.place__fig img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 34rem) { .place__fig { display: none; } }

/* No photograph yet: a drawn mark rather than an empty frame or a broken one. */
.place__fig--none {
  display: grid;
  place-items: center;
  background: var(--accent-soft);
  color: var(--accent);
}
.place__fig--none svg { width: 44%; height: 44%; }
.place__n {
  font-size: var(--step--2);
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  padding-top: .35rem;
}
.place__name { font-size: var(--step-1); font-weight: 400; margin: 0 0 .2rem; line-height: 1.2; }
.place__name a { color: var(--ink); text-decoration: none; }
.place__name a:hover { text-decoration: underline; text-underline-offset: .2em; }
.place__contact a:hover, .place__stock a:hover { color: var(--accent); }
.place__kind { font-size: var(--step--1); color: var(--ink-muted); margin: 0; }
.place__addr { font-size: var(--step--1); color: var(--ink-faint); margin: .35rem 0 0; max-width: 34rem; }

.place__contact { display: flex; flex-wrap: wrap; gap: 0 1.1rem; list-style: none; margin: .55rem 0 0; padding: 0; }
.place__contact a { font-size: var(--step--1); color: var(--ink-muted); text-decoration: underline; text-underline-offset: .2em; }
.place__contact a:hover { color: var(--ink); }

.place__stock { font-size: var(--step--1); margin: .55rem 0 0; }
.place__stock a { color: var(--ink); text-decoration: underline; text-underline-offset: .2em; }
/* Every listing carries the date it was last confirmed, because a nursery
   that closed two years ago is worse than no listing at all. */
.place__seen { display: block; font-size: var(--step--2); color: var(--ink-faint); margin-top: .3rem; }
.place__seen--stale::before { content: '⚠ '; }

.place__note { font-size: var(--step--1); color: var(--ink-muted); margin: .4rem 0 0; max-width: 38rem; }
.place__eg { display: block; font-size: var(--step--2); color: var(--ink-faint); margin-top: .25rem; max-width: 38rem; }

/* --- the map ---------------------------------------------------------------

   Leaflet drawing OpenStreetMap tiles. Ours to style, which a Google frame
   never is: the tile layer is greyscaled, so the one place a third party would
   otherwise put colour on this page stays black and white. Remove that filter
   to let the map be its own colours.                                        */

.lmap {
  height: var(--map-h);
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
}
@media (min-width: 62rem) { .places--mapped .places__map { position: sticky; top: 2rem; } }
.places__map[hidden] { display: none; }

/* A light touch now, not a heavy one: the tiles are already a pale basemap,
   and the old 0.82 greyscale was there to tame a full-colour OSM map. */
.leaflet-tile-pane { filter: grayscale(0.35) contrast(1.02); }
.leaflet-container { font-family: var(--text), sans-serif; background: var(--paper-sunk); }
.leaflet-control-zoom a { color: var(--ink); border-radius: 0; }
.leaflet-control-attribution { font-size: 10px; background: rgba(255, 255, 255, .8); }

/* A numbered disc, so a pin and its row carry the same number. */
.pin span {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink);
  border: 1.5px solid var(--ink);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
}
.pin.is-on span { background: var(--accent); color: var(--paper); border-color: var(--accent); }

.leaflet-popup-content-wrapper { border-radius: 0; background: var(--paper); }
.leaflet-popup-tip { background: var(--paper); }
.leaflet-popup-content { margin: .85rem 1rem; font-size: var(--step--1); }
.pop__kind {
  display: block;
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--ink-faint);
}
.pop__name { display: block; font-size: var(--step-0); margin: .25rem 0; }
.pop__addr { display: block; color: var(--ink-muted); }
.pop__go { display: inline-block; margin-top: .5rem; color: var(--accent); font-weight: 500; }

.places__hint { font-size: var(--step--2); color: var(--ink-faint); margin: .6rem 0 0; }
.places__hint[hidden] { display: none; }

/* The listing the map is showing. */
/* The listing the map is showing. Warm rather than grey, so it reads as
   chosen rather than as disabled. */
.place.is-on { background: var(--beige); }
.place.is-on .place__n { color: var(--accent); }

@media (prefers-reduced-motion: reduce) { .place { transition: none; } }

/* --- adding a place --------------------------------------------------------
   The one form on the public site. Plain controls, the page's own type, and
   no framework: it posts and reloads, so it works before any script runs.  */

.notice {
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
  padding: .9rem 1.1rem;
  font-size: var(--step--1);
  max-width: 44rem;
  margin: 1.2rem 0 1.9rem;
}
.notice--bad { border-left-color: #8a3a2f; background: #f7ecea; }

.addform { margin-top: clamp(1.5rem, 3vw, 2.2rem); }
.addform__grid {
  display: grid;
  gap: 1.15rem clamp(1.2rem, 3vw, 2.2rem);
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  max-width: 54rem;
}
.addform__field--wide { grid-column: 1 / -1; }

.addform__field { margin: 0; max-width: none; }
.addform__field label {
  display: block;
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--ink-faint);
  margin-bottom: .5rem;
}
.addform__req, .addform__opt { text-transform: none; letter-spacing: .02em; }
.addform__opt { color: var(--rule-strong); }
.addform__help { display: block; font-size: var(--step--2); color: var(--ink-faint); margin-top: .4rem; }

/* A yes/no inside a form, where the question is the label rather than a
   heading over a box: a permission, not a field. */
.addform__check {
  display: flex;
  align-items: flex-start;
  gap: .6rem;
  margin: 0;
  max-width: none;
  font-size: var(--step--1);
  line-height: 1.45;
  color: var(--ink-muted);
}
.addform__check input { margin: .2rem 0 0; accent-color: var(--accent); flex: none; }
.addform__check label { cursor: pointer; }

/* --- the photographer's name ----------------------------------------------
   A signature in the corner of a photograph, the size a signature is: small
   enough to be a mark rather than a caption, and legible over a bright sky or
   a dark leaf without a box drawn round it. The scrim is a gradient that
   fades to nothing, so on most photographs you notice the name and not the
   thing behind it.

   Set in the page rather than burnt into the file, so it stays sharp on a
   retina screen at every size, can be read aloud, and can be restyled without
   re-exporting a single photograph.                                       */

.shot { position: relative; display: block; }
.shot > img { display: block; width: 100%; height: auto; }

.shot__by {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  /* Prose on this site is held to a measure. A signature is not prose, and
     without this it is pulled in from the corner it is meant to sit in. */
  max-width: none;
  margin: 0;
  padding: 2.4em 1em .7em;
  text-align: right;
  font-size: clamp(.54rem, .48rem + .18vw, .62rem);
  letter-spacing: .18em;
  text-transform: uppercase;
  line-height: 1.2;
  color: rgba(255,255,255,.93);
  /* Fades out upwards, so it does not cut a band across the picture. */
  background: linear-gradient(to top, rgba(0,0,0,.38), rgba(0,0,0,.12) 45%, rgba(0,0,0,0));
  pointer-events: none;
}
/* The shadow is what keeps it readable where the gradient is weakest —
   over a pale sky, the fade alone is not enough. */
.shot__by span { text-shadow: 0 1px 3px rgba(0,0,0,.55); }

/* The opening photograph carries the same signature, but it sits above the
   wash rather than under it — ::after covers the whole picture here, and a
   credit painted beneath it would be dimmed along with the sky. */
.opening__by { z-index: 1; }
/* A signature that points somewhere still has to be clickable: the credit
   block is set pointer-events: none so it never sits between a reader and the
   photograph, which would swallow the link with it. */
.opening__by a { color: inherit; pointer-events: auto; text-shadow: 0 1px 3px rgba(0,0,0,.55); }

/* On a thumbnail the name would be most of the picture, so it goes. The
   credit is on the entry the thumbnail leads to. */
.shot--bare .shot__by { display: none; }

.addform__field input, .addform__field select, .addform__field textarea {
  width: 100%;
  font: inherit;
  font-size: var(--step--1);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  border-radius: 0;
  padding: .6rem .75rem;
  -webkit-appearance: none;
  appearance: none;
}
.addform__field select {
  /* appearance:none removes the arrow too, so one is drawn back on. */
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-muted) 50%),
                    linear-gradient(135deg, var(--ink-muted) 50%, transparent 50%);
  background-position: calc(100% - 1.05rem) 55%, calc(100% - .75rem) 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 2.2rem;
}
.addform__field textarea { resize: vertical; line-height: 1.5; }
.addform__field input:focus-visible, .addform__field select:focus-visible, .addform__field textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-color: var(--accent);
}

/* Off-screen rather than display:none, because a bot reading the markup
   ignores CSS and fills it in anyway — which is the whole point. */
.addform__trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* --- a form in a card ----------------------------------------------------
   The community map and the open-source page both put a form in a bordered
   panel with one green button at the foot of it. Defined once, because two
   copies of a button drift and then the site has two greens.          */

.formpanel {
  border: 1px solid var(--rule-strong);
  border-radius: 10px;
  padding: clamp(1.3rem, 3vw, 2rem);
  background: var(--paper);
  position: relative;
}
.formpanel__head { font-size: var(--step-1); font-weight: 400; margin: 0 0 1.3rem; }
.formpanel .addform__field { margin-bottom: 1.1rem; }

.formpanel__go {
  display: block;
  width: 100%;
  border: 0;
  border-radius: 6px;
  background: var(--accent);
  color: var(--paper);
  font: inherit;
  font-size: var(--step-0);
  padding: .8rem 1rem;
  cursor: pointer;
  transition: background .2s ease;
}
.formpanel__go:hover { background: #3e4d36; }
.formpanel__go:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* --- the drawn things ----------------------------------------------------
   Bigger cousins of the field symbols, in the same uneven line, put in the
   margin a text column leaves rather than anywhere near a word. Decoration,
   so they are hidden from a screen reader and take no clicks. Each page says
   where its own go; this is only what they all share.                   */

.sticker {
  position: absolute;
  color: var(--sticker);
  pointer-events: none;
  z-index: 0;
}

.addform__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .8rem 1.2rem;
  margin-top: 1.6rem;
}
.addform__note { font-size: var(--step--2); color: var(--ink-faint); max-width: 32rem; }

.btn {
  font: inherit;
  font-size: var(--step--1);
  cursor: pointer;
  background: var(--accent);
  color: var(--paper);
  border: 1px solid var(--accent);
  border-radius: 0;
  padding: .7rem 1.5rem;
}
.btn:hover { background: var(--ink); border-color: var(--ink); }

/* ===========================================================================
   Species repository — filters beside the results

   The sidebar is links, not controls: each option carries the URL that turning
   it on or off produces. So filtering applies on click with no script, every
   state has an address, and the back button walks the filters.
   =========================================================================== */

.finder--one { grid-template-columns: minmax(0, 28rem); }

.browse { display: grid; gap: clamp(1.75rem, 4vw, 3rem); align-items: start; }

/* Wide: two columns, the sidebar pinned, and the fold control gone entirely. */
@media (min-width: 60rem) {
  .browse { grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); }
  .filters { position: sticky; top: 2rem; max-height: calc(100vh - 4rem); overflow-y: auto; scrollbar-width: thin; }
  .filters__toggle, .filters__btn { display: none; }
}

/* Narrow: the filters sit above the results, shut, so the species are the
   first thing on screen rather than eight hundred pixels below it. */
@media (max-width: 59.999rem) {
  /* Hidden from sight but not from the keyboard: opacity or pointer-events
     here would take the control out of the tab order entirely. */
  .filters__toggle {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
  }
  .filters__btn {
    display: flex;
    align-items: center;
    gap: .5rem;
    cursor: pointer;
    user-select: none;
    font-size: var(--step--1);
    border: 1px solid var(--rule-strong);
    padding: .55rem 1rem;
    justify-self: start;
  }
  .filters__btn::after {
    content: '';
    width: .4rem; height: .4rem;
    border-right: 1px solid var(--ink-muted);
    border-bottom: 1px solid var(--ink-muted);
    transform: rotate(45deg) translate(-.1em, -.1em);
  }
  .filters__toggle:checked ~ .filters__btn::after { transform: rotate(-135deg) translate(-.15em, -.15em); }
  .filters__toggle:focus-visible ~ .filters__btn { outline: 2px solid var(--ink); outline-offset: 2px; }
  .filters { display: none; }
  /* Opens as a panel hanging off the button rather than as a block that
     appears in the flow, so it reads as a dropdown and the button plainly
     belongs to it. */
  .filters__toggle:checked ~ .filters {
    display: block;
    border: 1px solid var(--rule-strong);
    margin-top: -1px;
    padding: 0 1rem 1rem;
    background: var(--paper);
  }
  .filters__toggle:checked ~ .filters__btn { border-bottom-color: var(--paper); }
  .filters__toggle:checked ~ .filters .filters__top { border-bottom-color: var(--rule); }
}
/* How many are on, so a shut panel still says it is filtering something. */
.filters__on {
  background: var(--accent);
  color: var(--paper);
  font-size: var(--step--2);
  padding: .1rem .42rem;
  font-variant-numeric: tabular-nums;
}

.filters__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: .7rem;
  border-bottom: 1px solid var(--ink);
}
.filters__head {
  font-size: var(--step--1);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: .14em;
  margin: 0;
}
.filters__reset { font-size: var(--step--2); color: var(--accent); }

/* <details> gives a collapsing group with no script at all. */
.facet { border-bottom: 1px solid var(--rule); }
.facet__name {
  list-style: none;
  cursor: pointer;
  padding: .85rem 0;
  font-size: var(--step--1);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.facet__name::-webkit-details-marker { display: none; }
.facet__name::after {
  content: '';
  width: .42rem; height: .42rem;
  border-right: 1px solid var(--ink-muted);
  border-bottom: 1px solid var(--ink-muted);
  transform: rotate(45deg) translate(-.1em, -.1em);
  transition: transform 140ms ease;
}
.facet[open] > .facet__name::after { transform: rotate(-135deg) translate(-.15em, -.15em); }
.facet__list { list-style: none; margin: 0 0 .9rem; padding: 0; }

.facet__opt {
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .3rem 0;
  font-size: var(--step--1);
  color: var(--ink-muted);
  text-decoration: none;
  line-height: 1.3;
}
.facet__opt:hover { color: var(--ink); }
.facet__box {
  flex: none;
  width: .95rem; height: .95rem;
  border: 1px solid var(--rule-strong);
  background: var(--paper);
}
.facet__opt:hover .facet__box { border-color: var(--ink); }
.facet__opt.is-on { color: var(--ink); }
.facet__opt.is-on .facet__box {
  background: var(--accent);
  border-color: var(--accent);
  /* The tick is drawn rather than a character, so it lines up at any size. */
  background-image:
    linear-gradient(45deg, transparent 44%, var(--paper) 44%, var(--paper) 58%, transparent 58%),
    linear-gradient(-45deg, transparent 56%, var(--paper) 56%, var(--paper) 70%, transparent 70%);
  background-size: 100% 100%;
}
.facet__label { flex: 1; }
.facet__n { font-size: var(--step--2); color: var(--ink-faint); font-variant-numeric: tabular-nums; }

/* --- results --------------------------------------------------------------- */

.results__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem 1.5rem;
  padding-bottom: .8rem;
  border-bottom: 1px solid var(--ink);
}
.results__n {
  margin: 0;
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: .12em;
}
.results__sort { font-size: var(--step--2); color: var(--ink-faint); }
.results__sort strong { font-weight: 400; color: var(--ink-muted); }
.results__none { grid-column: 1 / -1; font-size: var(--step--1); color: var(--ink-muted); padding: 3rem 0; }

.chips--active { margin: 1.1rem 0 0; }
.chip--drop { gap: .5rem; border-style: dashed; }
.chip__x { color: var(--ink-faint); }
.chip--drop:hover .chip__x { color: var(--accent); }

.grid--species { margin-top: 1.6rem; grid-template-columns: repeat(auto-fill, minmax(13.5rem, 1fr)); }

.card a { display: block; text-decoration: none; color: inherit; }
.card__fig {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  background: var(--paper-sunk);
  overflow: hidden;
  margin-bottom: .65rem;
}
.card__fig img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 320ms ease; }
.card a:hover .card__fig img { transform: scale(1.03); }

/* Anything not native says so on the card. The repository carries exotics and
   invasives deliberately, and a reader should not have to open one to find
   out which they are looking at. */
.card__flag {
  position: absolute;
  left: 0; top: 0;
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: .1em;
  padding: .25rem .5rem;
  background: var(--paper);
  color: var(--ink);
}
.card__flag--invasive { background: var(--ink); color: var(--paper); }

/* Gujarati carries the card, so it gets its own face and a little more room:
   the script sits taller than Latin at the same size, and cramping it is the
   usual way a page like this ends up looking careless about it. */
.card__lead[lang="gu"] {
  font-family: var(--indic), serif;
  font-size: var(--step-1);
  line-height: 1.4;
}
.card__lead { display: block; font-size: var(--step-0); line-height: 1.2; }
.card__sci { display: block; font-style: italic; font-size: var(--step--1); color: var(--ink-muted); }
.card__alt { display: block; font-size: var(--step--1); color: var(--ink-muted); }

.vh { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); }

@media (prefers-reduced-motion: reduce) {
  .card__fig img, .facet__name::after { transition: none; }
}

/* ===========================================================================
   A species entry
   =========================================================================== */

.catalogue a { color: inherit; text-decoration: none; }
.catalogue a:hover { color: var(--accent); }

.entry__name { margin-bottom: .2rem; }
.entry__sci {
  font-style: italic;
  font-size: var(--step-1);
  color: var(--ink-muted);
  margin: 0 0 .3rem;
  max-width: none;
}
/* The Gujarati and Hindi names, in their own faces. Set at the same size as
   the botanical name and given room, rather than shrunk into a footnote. */
.entry__native {
  font-family: var(--indic), serif;
  font-size: var(--step-1);
  line-height: 1.6;
  margin: 0;
  max-width: none;
}
.entry__also {
  font-size: var(--step--1);
  color: var(--ink-faint);
  max-width: 48rem;
  margin: .9rem 0 0;
}

.entry__fig { margin-top: clamp(2rem, 5vw, 3.5rem); }
.entry__fig figure { margin: 0; }
/* Capped by height as well as ratio: sixteen by nine across the full measure
   is seven hundred pixels of photograph before the first word, which is a
   poster rather than an entry. */
.entry__fig img {
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: min(56vh, 30rem);
  object-fit: cover;
  background: var(--paper-sunk);
  display: block;
}

/* --- the facts ------------------------------------------------------------
   Every value is a link back into the repository filtered by it, so a fact is
   a way on rather than a dead end.                                          */

.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1.4rem clamp(1.2rem, 3vw, 2.5rem);
  margin: clamp(2rem, 5vw, 3rem) 0 0;
  padding-top: 1.3rem;
  border-top: 1px solid var(--ink);
}
.facts__item { min-width: 0; }
.facts dt {
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--ink-faint);
  margin-bottom: .45rem;
}
.facts dd { margin: 0; font-size: var(--step--1); line-height: 1.4; }
.facts dd a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--rule-strong); }
.facts dd a:hover { color: var(--accent); border-color: var(--accent); }

/* --- the writing ---------------------------------------------------------- */

.prose--long { font-size: var(--step-0); line-height: 1.62; }
.prose--long p { margin: 0 0 1.05em; }
.prose--long p:last-child { margin-bottom: 0; }

.sources { list-style: none; margin: 0; padding: 0; }
.sources li { margin-bottom: .45rem; font-size: var(--step--1); }
.sources a { color: var(--ink-muted); }
.sources a:hover { color: var(--accent); }

/* --- the photographs -------------------------------------------------------
   Scroll-snap, so a swipe on a phone, a trackpad on a laptop and the numbers
   underneath all drive the same track, and none of it is scripted.         */

.gallery { position: relative; }
.gallery__track {
  display: flex;
  gap: .6rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  list-style: none;
  margin: 0;
  padding: 0;
  /* The dots are the affordance; a scrollbar under a photograph is noise. */
  scrollbar-width: none;
}
.gallery__track::-webkit-scrollbar { display: none; }
.gallery__track:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

.gallery__slide { flex: 0 0 100%; scroll-snap-align: center; min-width: 0; }
.gallery__slide img {
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: min(56vh, 30rem);
  object-fit: cover;
  background: var(--paper-sunk);
  display: block;
}

.gallery__dots {
  display: flex;
  gap: .4rem;
  list-style: none;
  margin: .7rem 0 0;
  padding: 0;
  font-variant-numeric: tabular-nums;
}
.gallery__dots a {
  display: grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  font-size: var(--step--2);
  color: var(--ink-muted);
  text-decoration: none;
  border: 1px solid var(--rule-strong);
}
.gallery__dots a:hover { color: var(--paper); background: var(--accent); border-color: var(--accent); }
/* Whichever was last asked for. Swiping does not move this — there is no
   script to watch the scroll — so it marks the last one chosen, not the one
   on screen, and it only appears once something has been chosen. */
.gallery__slide:target ~ .gallery__slide { }
.gallery:has(.gallery__slide:target) .gallery__dots a:focus { color: var(--accent); }

.entry__fig--none {
  display: grid;
  place-items: center;
  margin: 0;
  aspect-ratio: 16 / 9;
  max-height: min(42vh, 22rem);
  background: var(--accent-soft);
  color: var(--accent);
}
.entry__fig--none svg { width: 4rem; height: 4rem; }

@media (prefers-reduced-motion: reduce) { .gallery__track { scroll-behavior: auto; } }

/* The gallery standing in for the hero figure, and the state where there is
   no photograph at all — which is where a hundred and seven species are. */
.hero__fig.gallery { margin: 0 0 1.4rem; }
/* The slides fill the hero column, not the page. */
.hero__fig.gallery .gallery__slide img { aspect-ratio: 4 / 3; max-height: none; }
.hero__fig--none {
  display: grid;
  place-items: center;
  margin: 0;
  aspect-ratio: 1;
  background: var(--accent-soft);
  color: var(--accent);
}
.hero__fig--none svg { width: 4.5rem; height: 4.5rem; }

/* Prose set as points. Marked with a rule rather than a bullet: the entries
   are a field journal, and a disc in front of every line is a memo. */
.points { list-style: none; margin: 0; padding: 0; max-width: var(--measure); }
.points li {
  position: relative;
  padding-left: 1.2rem;
  margin-bottom: .7rem;
  font-size: var(--step--1);
  line-height: 1.55;
}
.points li::before {
  content: '';
  position: absolute;
  left: 0;
  top: .72em;
  width: .55rem;
  height: 1px;
  background: var(--accent);
}
.entry__gu { font-family: var(--indic), var(--display), serif; line-height: 1.3; }

/* --- where a photograph is not yet ----------------------------------------
   The sheet carries no pictures. Rather than an empty src, which a browser
   draws as a broken image and a reader reads as a fault in the page, the
   figure keeps its shape and holds the drawn mark instead.              */

.tile__fig--none, .card__fig--none {
  display: grid;
  place-items: center;
  background: var(--paper-sunk);
}
.tile__fig--none svg, .card__fig--none svg {
  width: 32%;
  max-width: 4rem;
  height: auto;
  color: var(--rule-strong);
}

/* --- cross-stitch marks ----------------------------------------------------
   Built by scripts/build-stitch.mjs; see views/partials/stitch.njk. Two
   threads, a dark one and a sage one, named here so one rule changes every
   mark on the site and a single placement can be retinted without touching
   the chart it was worked from.                                          */

/* Below about 60px the stitches run together and it stops reading as thread,
   so nothing here is drawn smaller than that. */
.stitch { display: block; width: 4.6rem; height: 4.6rem; }
.stitch__a { stroke: var(--stitch-a, var(--ink)); }
.stitch__b { stroke: var(--stitch-b, #7d9169); }

/* A mark set loose in a margin rather than set on the centre line: the job the
   drawn stickers used to do. Quieter thread, because a margin is beside
   something that is being read and must not compete with it. It never sits
   under text, it takes no clicks, and it is not announced. */
.stitch--float {
  position: absolute;
  pointer-events: none;
  z-index: 0;
  --stitch-a: #96a689;
  --stitch-b: #c4d0b7;
}

/* A row of them, as a border runs along a hem. */
.stitchband {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: .2rem;
  margin: 0;
  max-width: none;
}
.stitchband .stitch { width: 3.8rem; height: 3.8rem; }

/* A bee at the end of the line about Ahmedabad, rather than a mark of its own
   centred above the columns: the centred one read as a sunburst and sat there
   announcing itself.

   It is bigger than the line it sits in, and a taller inline box would push
   that line away from the one above it — so the negative block margins take
   the extra height back out of the line box. What the line measures is then no
   more than the text does, and the paragraph's leading is untouched: the bee
   overhangs the line instead of opening it. */
.footer__bee {
  display: inline-block;
  width: 3rem;
  height: 3rem;
  vertical-align: -1.1rem;
  margin: -1.05rem 0 -1.05rem .1rem;
  transform: rotate(-9deg);
  /* Deeper than the marks that float in the margins: those sit beside running
     text and must not compete with it, this one has to read at the size of a
     word. */
  --stitch-a: #4f6243;
  --stitch-b: #8ba07c;
}
