/* site.shared.css - DERIVED, do not edit.
 *
 * The part of site.css that the GENERATED pages actually use, selected by
 * assets/scripts/stylesheet.split.mjs by asking a browser which selectors match on real pages.
 * site.css itself is unchanged and is still what the two carried legacy pages link.
 *
 * Regenerate with:  node assets/scripts/stylesheet.split.mjs --write
 */
/* =============================================================================
   DRN Industriel — shared site stylesheet
   -----------------------------------------------------------------------------
   Conventions this file establishes, and that every page using it must follow:

   1. ONE type scale. --fs-1 .. --fs-9 on a 1.22 ratio. No page invents a size.
   2. ONE spacing scale. --sp-1 .. --sp-9, 4px quantum, 8pt-grid aligned.
   3. ONE breakpoint ladder. --bp-sm 640 / --bp-md 900 / --bp-lg 1240, declared in :root
      below so the claim is at least inspectable. MEASURED, honestly: the file currently uses
      eleven distinct widths (520, 600, 640, 700, 899, 900, 960, 1000, 1099, 1100, 1240). The
      899/900 seam is exact and the 1240 step is defended with real device measurements further
      down; the rest is drift. 1200 was never used once, which is why the rung is written 1240.
   4. ONE page inset. --page-inset is the single horizontal gutter for EVERY
      top-level container: header, section, footer. A container that adds its own
      left padding on top of this breaks site-level alignment.
   5. Anything inside a bounded component sizes off THE COMPONENT (cqi), never off
      the viewport. Viewport-keyed sizing inside a fixed-width box is what produces
      "correct at 1024 and 1440, broken at 1280".
   6. Content is never truncated to make a layout fit. No text-overflow:ellipsis.
   ========================================================================== */

:root{
  /* ---- colour ---- */
  --c-bg:#0b0b0d;
  --c-surface:#121216;
  --c-ink:#f4f4f4;
  --c-ink-dim:rgba(244,244,244,.68);
  --c-ink-faint:rgba(244,244,244,.4);
  --c-line:rgba(244,244,244,.1);
  --c-line-soft:rgba(244,244,244,.06);
  --c-primary:#539435;
  --c-primary-strong:#7cc653;
  --c-primary-ink:#0b0b0d;
  --c-primary-edge:rgba(83,148,53,.28);
  --c-plate:#f3f4f5;
  /* the tile field. --c-tile sits above --c-bg so a card reads as an object without a frame;
     --c-tile-hi is the hover step, far enough to be seen at a glance. */
  --c-tile:#151519;
  --c-tile-hi:#1d1d23;
  --c-foot-a:#0e1d0a;
  --c-foot-b:#1e4114;
  /* CTA panel: deep green ground with light ink. Title #e6f3dd on #17330f measures 12.4:1,
     and the primary button keeps its own dark-on-green pairing so it is unaffected. */
  --c-cta-panel:#17330f;
  --c-cta-panel-edge:#2c5c1e;
  --c-cta-panel-ink:#e6f3dd;

  /* ---- spacing: 4px quantum ---- */
  --bp-sm:640px;--bp-md:900px;--bp-lg:1240px;
  --sp-1:4px;  --sp-2:8px;  --sp-3:12px; --sp-4:16px; --sp-5:24px;
  --sp-6:32px; --sp-7:48px; --sp-8:64px; --sp-9:96px;

  /* ---- type: one modular scale, ratio 1.22 ---- */
  --fs-1:.6875rem; --fs-2:.75rem;  --fs-3:.875rem; --fs-4:1rem;
  --fs-5:1.22rem;  --fs-6:1.49rem; --fs-7:1.82rem; --fs-8:2.22rem; --fs-9:2.71rem;
  --fs-display:clamp(1.75rem, .35rem + 2.45vw, 3.05rem);

  /* ---- layout ---- */
  --page-max:1720px;
  --gutter:clamp(12px, 1.6vw, 24px);   /* tightened again: the sidebar reclaims the width */
  --page-inset:max(var(--gutter), calc((100% - var(--page-max)) / 2));
  --measure:68ch;

  /* ---- form ---- */
  --radius-sm:8px; --radius-md:14px; --radius-lg:26px; --radius-full:999px;
  --border-w:1px;
  --dur-fast:.18s; --dur-med:.32s;
  --ease-out:cubic-bezier(.16,1,.3,1);
  /* The hover gesture of a CARD-sized surface. Duration is Material 3 motion
     duration-medium4 (400ms) — the top of the Medium band, which is the band a
     transition covering 10-30% of the viewport belongs to. The curve is the
     symmetric easeInOutQuart spline: with y1 = 0 and y2 = 1 the velocity is zero
     at BOTH ends (Disney "Slow In and Slow Out"), and pushing x1 out to .76 and
     x2 back to .24 lengthens the flat run at each end, which is what reads as
     "softer" against the Material-2 standard (.4,0,.2,1) this replaces.
     One token, used by every property that moves on hover, so nothing can drift
     back into arriving on its own schedule. */
  --dur-card:400ms;
  --ease-card:cubic-bezier(.76,0,.24,1);
  --shadow-card:0 30px 70px -30px rgba(0,0,0,.6);

  --font-sans:'Inter',system-ui,-apple-system,'Segoe UI',Helvetica,Arial,sans-serif;
}


/* ---------- reset ---------- */
*,*::before,*::after{box-sizing:border-box;}

html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;}

body{
  margin:0;background:var(--c-bg);color:var(--c-ink);
  font-family:var(--font-sans);font-size:var(--fs-4);line-height:1.6;
  -webkit-font-smoothing:antialiased;
}

img{display:block;max-width:100%;height:auto;}

a{color:inherit;text-decoration:none;}

:where(a,button,input,textarea,select):focus-visible{
  outline:2px solid var(--c-primary-strong);outline-offset:3px;border-radius:var(--radius-sm);
}

::selection{background:var(--c-primary);color:var(--c-primary-ink);}
@media (prefers-reduced-motion:reduce) {

  html{scroll-behavior:auto;}
}


/* ---------- the one skip link ---------- */
.skip{
  position:absolute;left:-9999px;top:0;z-index:200;
  background:var(--c-primary);color:var(--c-primary-ink);
  padding:var(--sp-3) var(--sp-5);border-radius:0 0 var(--radius-sm) 0;font-weight:600;
}

.skip:focus{left:0;}


/* ---------- page shell: the single horizontal inset ---------- */
.shell{padding-inline:var(--page-inset);}

.shell-in{max-width:var(--page-max);margin-inline:auto;}


/* ---------- header ---------- */
/* Sticky. This reverses the earlier "do not make the top bar sticky", on a later instruction.
   It also forces the .has-hero treatment to change: that rule used position:absolute so the
   hero video ran under the bar, and absolute and sticky cannot both apply to one element.
   The bar is sticky and TRANSPARENT instead, and the hero simply starts under it. */
.site-head{
  position:sticky;top:0;z-index:120;
  display:flex;align-items:center;gap:var(--sp-4);
  padding:var(--sp-4) var(--page-inset);
  background:rgba(11,11,13,.82);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  border-bottom:0;
}

/* the wordmark retires once the reader is past the end of the results — the h1 and the
   controls stay, so the bar is still a bar and still navigable. */
.site-head__brand{display:inline-flex;align-items:center;flex:none;
  transition:opacity var(--dur-med) var(--ease-out),transform var(--dur-med) var(--ease-out);}

.site-head__brand img{height:clamp(30px,2.7vw,40px);width:auto;}
  /* -20% */
/* the one h1 of every page. Visually it is a quiet label beside the mark, not a headline —
   the page's own title carries the visual weight and is an h2. */
.site-head__title{
  margin:0;font-size:var(--fs-6);font-weight:650;letter-spacing:.02em;
  color:var(--c-ink);line-height:1;padding-left:var(--sp-4);
  border-left:1px solid var(--c-line);white-space:nowrap;
}

.site-head__end{display:flex;align-items:center;gap:var(--sp-3);margin-left:auto;}


/* ---------- the hamburger ---------- */
.navburger{
  display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:44px;height:44px;                      /* WCAG 2.2 SC 2.5.8 with room to spare */
  border:0;border-radius:var(--radius-sm);background:none;cursor:pointer;
  transition:background var(--dur-fast);
}

.navburger:hover{background:var(--c-tile-hi);}

.navburger:focus-visible{outline:2px solid var(--c-primary-strong);outline-offset:2px;}

.navburger__bars{display:grid;gap:5px;width:22px;}

.navburger__bars i{
  display:block;height:2px;border-radius:2px;background:var(--c-ink);
  transition:transform var(--dur-fast) var(--ease-out),opacity var(--dur-fast);
}

/* the bars become an X, so the control states what it will do next */
.navburger[aria-expanded="true"] i:nth-child(1){transform:translateY(7px) rotate(45deg);}


/* ---------- the menu panel ---------- */
/* Without JS the panel is a plain block under the bar and every link is reachable; nav.js
   removes `hidden` and switches to data-open, so the no-JS path is the working default. */
.site-nav{display:flex;flex-direction:column;gap:var(--sp-2);}

/* The no-JS path, made to match the promise above it. `.site-nav` is a direct child of the
   `.site-head` FLEX ROW, so with JS off the panel was not "a plain block under the bar" — it
   was a fifth flex item inside the bar, carrying 8 categories, 5 industries and 36 suppliers.
   Wrapping the header and giving the panel the full line puts it where the comment says it is.
   Inert once nav.js runs: `[data-nav-js="on"] .site-nav` is absolutely positioned, and an
   absolutely positioned child is not a flex item at all. */
.site-head{flex-wrap:wrap;}

/* the page's own title is a <p class="page-title"> now, not an h1: the shared header carries
   the single h1 of every page, and two h1s is exactly what the a11y sweep flags. It keeps the
   full display weight — this is a semantic change, not a visual one. */
.page-head .page-title{
  margin:0 0 var(--sp-5);font-size:var(--fs-display);line-height:1.05;
  letter-spacing:-.025em;font-weight:700;text-wrap:balance;
}


/* ---------- sections ---------- */
/* minimalist: no rule between sections. The block rhythm separates them, and on the linecard
   the tile fields already draw the eye to where one group ends. */
.section{padding-block:var(--sp-8);border-top:0;}


/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-3);
  padding:var(--sp-4) var(--sp-6);border-radius:var(--radius-full);
  /* background:none and font:inherit are load-bearing: .btn is used on BOTH <a> and <button>,
     and a <button> with no background declared falls back to the UA buttonface grey. The print
     control rendered as a light grey pill next to a transparent phone link for exactly that. */
  background:none;font-family:inherit;
  font-size:var(--fs-3);font-weight:650;cursor:pointer;border:1px solid transparent;
  transition:transform var(--dur-fast) var(--ease-out),background var(--dur-fast);
}

.btn:active{transform:translateY(1px);}

.btn--primary{background:var(--c-primary);color:var(--c-primary-ink);}

.btn--primary:hover,.btn--primary:focus-visible{background:var(--c-primary-strong);}


/* =============================================================================
   COMPONENT: supplier card (linecard)
   Sizes its own contents off the card, not the viewport — convention 5.
   ========================================================================== */
/* auto-fill, not fixed column counts: four of the seven categories hold a single supplier, and a
   lone card parked in a 3-column track leaves two dead cells. The track size decides the count. */
/* =============================================================================
   LINE CARD — one place controls the whole surface.
   Every spacing and column decision below reads from a token declared here, so a
   change is made once and cannot drift into per-card exceptions.
   ========================================================================== */
.lc{
  --lc-aside:232px;      /* sticky filter sheet width — +32, funded by the tighter page gutter */
  --lc-gap:var(--sp-4);  /* gap between tiles */
  --lc-pad:24px;         /* interior padding of one tile */
  --lc-cols:1;
  display:grid;grid-template-columns:1fr;gap:var(--lc-gap);
}
   /* three columns, laptop */
/* 1240, not 1400: a 1366 laptop is 1351 CSS px and a 1280 one is 1265, so a 1400-keyed step
   never fired on either — the operator was looking at 3 columns on a desktop and being told
   it was 4. The tile is 240px at 1280, which still holds the 260px mark box at 100%. */
@media (min-width:1240px){ .lc{--lc-cols:4;} }
   /* four columns, desktop */

.linecard{
  display:grid;gap:var(--lc-gap);
  /* The track count is the LESSER of the breakpoint's column count and the number of suppliers
     the category actually holds. Four of the seven categories hold exactly one supplier, so a
     flat 4-track grid left 14 dead cells across the page and read as a half-empty catalogue.
     --n is emitted per section by the build; the max-width stops a lone card stretching to the
     full row instead. */
  grid-template-columns:repeat(min(var(--lc-cols),var(--n,99)),minmax(0,1fr));
  justify-content:start;
  align-items:stretch;           /* tiles match within a row; the capped list keeps them short */
}

/* the count belongs to the results, not floating 88px above them */
/* the sections sit inside [data-lc-view], not directly under #lc-results, so a child combinator
   matched nothing and the 64px band survived. */
#lc-results .section:first-of-type{padding-top:var(--sp-4);}

/* the count reads as a label on the results, seated on the heading it describes */
.lc-search{
  display:flex;align-items:center;gap:var(--sp-3);flex:1 1 16rem;min-width:0;
  padding:0 var(--sp-4);min-height:40px;
  border:0;border-radius:var(--radius-full);background:var(--c-tile);
  color:var(--c-ink-faint);
}

/* align-self:stretch, so the field's hit area is the whole 40px shell instead of the 22px the
   text line occupied — clicking the visible box's top or bottom edge now focuses it. */
.lc-search input{
  flex:1;min-width:0;align-self:stretch;border:0;background:none;color:var(--c-ink);
  font:inherit;font-size:var(--fs-3);padding:0;
}

/* =============================================================================
   COMPONENT: multi-select (.ms) — ONE component, used for suppliers AND categories.
   Collapsed it is a single row, so the option count no longer drives panel height:
   30 suppliers at 2-up cost the sidebar nothing until the user opens the dropdown.
   Only the option ROWS differ between the two instances; every behaviour is shared.
   ========================================================================== */
.ms{position:relative;margin-bottom:var(--sp-3);}

/* the picked marks, ON the collapsed field. A count alone made the visitor reopen the panel to
   recall their own answer; the logos ARE the answer, and they read at a glance. */
.ms__marks{display:flex;align-items:center;gap:3px;flex:none;}


/* two columns for the marks — three made them unreadable. The panel, not the sidebar,
   absorbs the height, and only the option list scrolls. */
.ms__opts{
  display:grid;grid-template-columns:repeat(var(--ms-cols,1),minmax(0,1fr));gap:2px;
  max-height:min(46dvh,320px);overflow:auto;
  scrollbar-width:thin;scrollbar-color:var(--c-primary) transparent;
}


/* Inside the mobile sheet the panel goes back IN FLOW. The sheet is a scroll container, so an
   absolutely-positioned panel would be clipped by it — the same trap that made the desktop
   sidebar grow a scrollbar and swallow its own menu. */
@media (max-width:899px){
  .ms__panel{position:static;width:auto;box-shadow:none;margin-top:var(--sp-2);}
  .ms__opts{max-height:none;}
}

/* mobile: the sheet is hidden until the sticky bar's filter button opens it */
@media (max-width:899px){
  .filters{
    position:fixed;left:0;right:0;bottom:0;z-index:120;
    max-height:76dvh;overflow:auto;
    padding:var(--sp-5) var(--page-inset) calc(var(--sp-8) + env(safe-area-inset-bottom));
    border-top:1px solid var(--c-line);border-radius:var(--radius-lg) var(--radius-lg) 0 0;
    background:var(--c-bg);
    transform:translateY(101%);transition:transform var(--dur-med) var(--ease-out);
  }
  .filters[data-open="true"]{transform:translateY(0);}
  .filters__grab{
    display:block;width:44px;height:4px;margin:0 auto var(--sp-4);
    border-radius:var(--radius-full);background:var(--c-line);
  }
}

/* the bar's three controls are .btn .btn--primary / .btn--ghost — box, radius, hover, focus
   and active all come from there. This block adds ONLY what the bar changes: a tap target,
   a tighter fit, and the expanded state the generic button has no concept of. */
.lc-bar .btn{min-height:44px;padding:0 var(--sp-5);font-size:var(--fs-2);}

/* The bar must not sit on top of the last card or the footer — but only on the page that HAS a
   bar. Unconditional, this put 96px of dead padding under the copyright on the four pages that
   never render one, which is the only bottom padding the footer had left. */
@media (max-width:899px){
  body:has(.lc-bar) .site-foot{padding-bottom:calc(var(--sp-9) + env(safe-area-inset-bottom));}
}

/* declared LAST so it beats the display:flex above — order, not specificity, decides here.
   The bar is removed from the box tree on laptop/desktop, not merely hidden. */
@media (min-width:900px){ .filters__grab,.lc-bar,.lc-scrim{display:none !important;} }


/* ---------- compact hero with the drone video behind ----------
   On .has-hero pages the header is lifted out of flow so the footage runs to the very top of
   the page and the nav sits over it. Scoped to >=900 because the mobile header wraps to a
   second row and would collide with the hero copy. */
/* On a hero page the bar has NO field of its own — the footage runs behind it. The header stays
   sticky and in flow; the hero is pulled up by the header's own height and pads that height back,
   so the video occupies the band the bar sits on without either leaving the flow. --head-h is
   the one number both sides read. */
:root{ --head-h:80px; }

/* with no copy inside it, the hero has no intrinsic height — it collapsed to its own padding.
   min-height makes it a real band; 34vh capped so it never eats the results. */
.phero{position:relative;isolation:isolate;overflow:hidden;padding-block:0;
       min-height:clamp(200px,34vh,320px);}

/* the scrim is what keeps the copy at AA over moving footage. It is now a LEFT wedge rather than
   a flat wash: opaque behind the copy, clearing to almost nothing on the right so the plant is
   actually visible there. */
/* Much lighter: no copy sits over the footage any more, so the scrim only has to hold the
   sticky bar's own links and the fade into the page below. */
/* Held dark across the top band and then cleared fast: the sticky bar's wordmark, h1, CTA and
   hamburger all sit over the footage now, and at .12 by 22% they were washing out against the
   bright plant signage. The hold is the height of the header, not a guess. */
.phero__scrim{
  position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(11,11,13,.72) 0%,rgba(11,11,13,.6) 12%,
             rgba(11,11,13,.14) 34%,rgba(11,11,13,.06) 70%,var(--c-bg) 100%);
}

/* REMOVED: a declaration body with no selector sat here (margin / font-size clamp /
   line-height / letter-spacing / font-weight), left behind when `.phero__title` was deleted.
   This file describes this exact failure twice already — "a declaration body with no selector
   is not inert: the parser reads it as a prelude and keeps consuming until the next {" — and
   this was the third live instance. Its target appears on zero pages, so nothing is lost. */
@media (prefers-reduced-motion:reduce){ .phero__video{display:none;} }


/* ---------- CTA box, BELOW the filters in the left panel ---------- */
/* No outer border and no heading: the buttons are the whole message. The dark green field
   still carries them, so the panel reads as one block without a frame around it. */
.lc-cta{
  display:flex;flex-direction:column;gap:var(--sp-2);
  padding:var(--sp-4);          /* 12 -> 16: the box now holds a form, not two buttons */
  background:var(--c-cta-panel);border:0;
  border-radius:var(--radius-md);
}


/* ---------- the header utilities: call, print, download ----------
   They were three controls inside the green box competing with the ask. They are page-level
   actions, so they sit with the chrome instead. */
/* Shown from 900 up — the same width the mobile bar stops carrying its own actions at, so no
   band is left without print and download. Between 900 and 1100 they are icon-only: the labels
   are what does not fit, not the controls. */
.site-head__tools{display:none;align-items:center;gap:var(--sp-2);}

/* in the header the menu drops DOWN, not up as it did from the panel's foot */
.site-head__tools .dl__menu{top:calc(100% + 8px);bottom:auto;right:0;left:auto;}


/* ---------- the inventory request, embedded on the green field ----------
   The panel used to carry a button that promised a form. The form IS the panel now: step one
   asks what they came to ask, and nothing personal, so the first interaction costs nothing.
   The pickers are the SAME .ms component the filter above uses — the visitor has already
   learned this control by the time they reach it. */
.ivf{display:flex;flex-direction:column;gap:var(--sp-2);}

/* one tight rhythm: the fields read as a single block, not four separate controls */
.ivf__step{border:0;margin:0;padding:0;min-width:0;display:flex;flex-direction:column;gap:4px;}

/* the rail is two ticks, not a percentage: two steps do not need a progress bar */
.ivf__rail{display:flex;gap:4px;margin:0 0 2px;padding:0;list-style:none;}


/* ---------- the ask, emphasised ----------
   A continuous breath plus a sheen that crosses it. Motion is the cheapest way to say "this is
   the control" on a panel where everything else is quiet — and it is the ONLY moving thing here,
   so it does not compete. Both loops stop under prefers-reduced-motion; the states below do not,
   because a state is information and a preference about motion is not a preference about feedback. */
/* 3.4s on a symmetric ease-in-out, not 2.6s on ease-out. Slower and eased at BOTH ends is what
   reads as smooth: ease-out alone snaps into the pass and then coasts, so the loop drew the eye
   as a flicker instead of a breath. The sheen is softer (.28 over a wider band) and rests longer
   between passes, so it registers as light moving over the surface rather than a wipe. */
.ivf__go{
  width:100%;justify-content:center;position:relative;overflow:hidden;
  animation:ivf-breathe 3.4s cubic-bezier(.4,0,.2,1) infinite;
  transition:transform var(--dur-med),box-shadow var(--dur-med),filter var(--dur-med);
}

@keyframes ivf-breathe{
  0%,100%{ box-shadow:0 0 0 0 rgba(124,198,83,.34); }
  58%    { box-shadow:0 0 0 9px rgba(124,198,83,0); }
}

@keyframes ivf-sheen{
  0%,60%{ transform:translateX(-130%); }
  94%,100%{ transform:translateX(130%); }
}

/* column-reverse: the primary sits first and full width, Back beneath it as the quiet escape.
   Side by side, "Request Inventory" wrapped to two lines inside a 250px panel. */
.ivf__acts{display:flex;flex-direction:column-reverse;gap:var(--sp-2);}

/* The panel's whole job after a send is to hand the visitor the next move, so the one control
   here inherits .ivf__go — same breathing loop, same states, one definition. text-decoration
   is the only thing an anchor needs that the shared .btn does not already give it. */
.ivf__doneGo{margin-top:var(--sp-3);text-decoration:none;}

/* The two-segment rail is still frozen at "step 1 of 2" when the done block appears, which sits
   directly above a line saying access is granted and contradicts it. There is no progress left
   to report once the panel is no longer a form, so the rail goes rather than being re-filled. */
.ivf:has([data-ivf-done]:not([hidden])) .ivf__rail{display:none;}


/* the Products | Suppliers toggle is gone: one view, so the control and its styles
   are removed rather than hidden. */

/* the products view is gone with its toggle; .productgrid/.product/.product__* removed. */

/* ---------- mobile bar: three one-tap actions ---------- */
.lc-bar .btn{flex:1 1 0;min-width:0;min-height:46px;padding:0 var(--sp-3);
  font-size:var(--fs-1);gap:var(--sp-2);justify-content:center;}

/* the sticky header must not cover the heading a jump link lands on */
[id]{scroll-margin-top:calc(var(--sp-9) + var(--sp-4));}


/* Copy left, product photo right. flex-wrap + flex-basis does the responsive work intrinsically:
   the shot drops below the copy the moment the card is too narrow to hold both, at whatever width
   that happens to be. No media query, so there is no band where it is wrong. */
/* Source spec, reproduced: 1px solid #28712A, radius 48px 8px 48px 8px, 20px padding,
   transparent background, copy left and product photo right. */
/* No border, no radius. With the frame gone the only thing separating one tile from the next is
   rhythm, so the row gap carries that job and a hairline rule marks the baseline of each tile. */
/* tile: photo on top, body beneath. A dark grey FIELD separates one tile from the next, so
   there is no border and no baseline rule anywhere on the card — the surface does the work a
   hairline used to. */
.supplier{
  container-type:inline-size;
  position:relative;
  display:flex;flex-direction:column;gap:var(--sp-3);
  border:0;border-radius:var(--radius-md);
  padding:var(--lc-pad) var(--lc-pad) calc(var(--lc-pad) + var(--sp-2));
  background:var(--c-tile);
  /* ONE token for every property that moves on hover — see --dur-card / --ease-card.
     transform used to run --dur-fast/ease-out while the rest ran --dur-med, so the lift
     arrived before the shadow and the card read as two events instead of one. opacity and
     filter join the list because the SIBLING dim below rides them. */
  transition:background var(--dur-card) var(--ease-card),
             transform  var(--dur-card) var(--ease-card),
             box-shadow var(--dur-card) var(--ease-card),
             opacity    var(--dur-card) var(--ease-card),
             filter     var(--dur-card) var(--ease-card);
}

/* Hover is a real change of state, not just the action bar arriving: the field lifts, brightens
   and casts, and the product photo scales a touch inside its box. focus-within gets the SAME
   treatment so a keyboard reaches it — /design scores the state set by its weakest member. */
.supplier:hover,.supplier:focus-within{
  background:var(--c-tile-hi);
  transform:translateY(-8px);
  box-shadow:0 26px 44px -20px rgba(0,0,0,.9);
  /* the ring is an OUTER shadow, so the card's own overflow:hidden cannot clip it */
  animation:card-pulse 2.6s var(--ease-card) infinite;
}

/* The ring travels 10px from a .5 alpha — one step up from 6px/.34, which was close to
   invisible against the cast shadow underneath it. It stays a RING that expands and fades,
   never a standing glow: at every keyframe the alpha is either falling or zero, so there is
   no frame where the card is wearing a halo. */
@keyframes card-pulse{
  0%,100%{ box-shadow:0 26px 44px -20px rgba(0,0,0,.9), 0 0 0 0 rgba(124,198,83,.5); }
  70%    { box-shadow:0 26px 44px -20px rgba(0,0,0,.9), 0 0 0 10px rgba(124,198,83,0); }
}


/* ---------- one card lit, the rest stood down ----------
   Hovering a card dims every OTHER card in the result set, so the grid resolves to a single
   subject. This is a HIERARCHY device, which is the justification /taste requires before any
   motion ships — it is not decoration.
   Scoped to #lc-results so a card in the category above also stands down, with .linecard as
   the fallback scope for a standalone grid (the contact page carries one). Both share one
   declaration block rather than repeating it.
   :not(:focus-within) is load-bearing: a keyboard user parked inside one card and then moving
   the mouse over another should not have their own card go dark under them.
   @media (hover:hover) keeps it off touch entirely — there is no hover there, so the whole
   grid would sit dimmed or flash on tap. */
@media (hover:hover){
  #lc-results:has(.supplier:hover) .supplier:not(:hover):not(:focus-within),
  .linecard:has(.supplier:hover) .supplier:not(:hover):not(:focus-within){
    opacity:.38;
    filter:saturate(.45);
  }
}

/* the shot-scale pair used to be declared here AND again at .supplier__shot below — one of the
   two was dead weight. It lives with the shot now, on the card's own curve. */
.supplier__body{
  min-width:0;
  display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-2);
}

/* the source scales each mark individually (24%-100%); --logo-w carries that per supplier.
   The box bounds the absolute size so a 100% mark cannot run away on a wide row, while the
   relative weights the original author chose are preserved inside it. */
/* fixed-height box: logo intrinsic heights vary, which pushed every brand name to a different
   baseline across a row. The box holds the row rhythm; --logo-w still sets relative weight. */
/* No plate behind the mark — the field is the card. */
.supplier__logobox{
  display:flex;align-items:center;
  width:100%;height:58px;
}

/* No blanket grayscale: a mark keeps its own colours. But 24 of the 30 are drawn for light
   backgrounds — mean opaque luminance under 105, measured per file — and on a #151519 field
   they vanish. Those 24 carry data-dark from the build and get invert + hue-rotate(180deg),
   which flips LIGHTNESS while preserving HUE, so a navy wordmark reads as pale blue rather
   than as grey. The 6 light marks are left exactly as their owners drew them. */
.supplier__logo{
  display:block;width:var(--logo-w,72%);max-width:100%;max-height:100%;height:auto;object-fit:contain;object-position:left center;
  opacity:.92;
  transition:opacity var(--dur-card) var(--ease-card);
}

/* demoted so the mark leads the tile, not the wordmark under it.
   h3 qualifier is load-bearing: .section h3 is (0,1,1) and would otherwise win. */
/* one step above the capability list, so the brand reads as the heading of its own list rather
   than the first bullet. Weight stays at 500 — the LOGO still leads the tile. */
h3.supplier__name{
  margin:0;font-size:var(--fs-3);font-weight:500;letter-spacing:.01em;
  line-height:1.25;color:var(--c-ink);
}

/* No cap: every capability is shown. Tiles in one ROW still match via align-items:stretch;
   heights differ BETWEEN rows now, which is correct once the list is the card's content. */
/* The `+N more` toggle this comment used to sit above was deleted by J2 — but only its
   SELECTOR was, and the orphaned body stayed. A declaration body with no selector is not
   inert: the parser reads it as a prelude and keeps consuming until the next `{`, so it ate
   `.lc-search:hover` whole. Measured absent from document.styleSheets before this deletion. */

/* search: hover / focus-within / filled are three distinct states, not one */
.lc-search:hover{background:var(--c-tile-hi);}

/* hover / focus-visible / active on the bar's controls come from .btn and .btn--ghost;
   only the focus ring is restated, because the shared button relies on the browser default. */
.lc-bar .btn:focus-visible{outline:2px solid var(--c-primary-strong);outline-offset:3px;}


/* ---------- tile hover action bar ----------
   Reserved by padding-bottom on the tile so revealing it shifts nothing. focus-within carries
   it to the keyboard, and a coarse pointer gets it permanently since hover never fires there. */
/* No reserved strip. The bar was costing every card 30px of height for a control only visible
   on hover; it is absolute OVER the content now, with a scrim so the text beneath stays legible. */
/* THE WHOLE CARD IS THE TARGET.
   The stretched-::before trick does NOT work here and it is worth recording why: a pseudo-element
   resolves inset:0 against its nearest POSITIONED ancestor, and that is .supplier__act, not the
   card — so it covered the bar only. Measured: a click at the card's centre landed on
   .supplier__logo and opened nothing. The click is forwarded in JS instead (linecard.js), and the
   real button keeps the name, the role and the keyboard path. */
.supplier{cursor:pointer;}


/* the DRN symbol, bottom centre — a branded graphic that arrives with the bar. Decorative, so
   it is a pseudo-element and carries no markup, no alt and nothing for a screen reader. */
/* The signature. A large DRN mark anchored to the card's bottom edge and translated down by
   half its own height, so the card's own overflow clips it: what shows is the top half rising
   out of the bottom. Sized off the CARD (cqi) so it is the same gesture in a 223px tile and a
   356px one. z-index 0 keeps it BEHIND the copy — it is a mark on the card's back, not a badge
   over its front — while still sitting above the field. */
.supplier{overflow:hidden;}


/* The standalone print button moved into the nav's icon bar at T1. Its selector went with it
   and its body did not — the same orphan defect as above, and this one ate
   `.linecard[data-loading="true"] .supplier`, so the loading state dimmed nothing. */

/* loading state: the grid can show skeletons before data paints, and is announced politely */
.linecard[data-loading="true"] .supplier{opacity:.35;pointer-events:none;}


/* no plate: all 30 product shots are keyed transparent, so they sit directly on the field.
   The shot LEADS the tile: it bleeds to the card's edges instead of sitting inside the padding,
   and it sizes off the CARD (cqi) rather than the viewport, so it stays proportionate in the
   261px 4-column tile and the 356px 2-column one alike — convention 5.
   The cap is still what stops a wider tile growing a taller hero; without it adding columns
   buys no density because each card grows to cancel it. It is just a higher cap now. */
/* the mark needs more air beneath it than the card's uniform gap gives */
.supplier__logobox + .supplier__shot{margin-top:var(--sp-4);}

/* The shot is a STACK of frames, not one image. Every frame occupies the same box; exactly one
   carries .is-on and is opaque, and the rest sit at zero. Advancing the carousel is therefore a
   cross-fade between two stacked images rather than a src swap, which would flash.
   A supplier with only one frame gets this identical rule set and simply never changes frame —
   there is no second code path for the 21 cards that have no alternate view. */
/* `scale-down`, NOT `contain`, and the difference is nine of the ten upscaled pictures on this
   site. Both fit the frame without cropping; `contain` will also ENLARGE a picture smaller than
   its box, and these pictures are small. Measured on a 390px phone at 2x: the frame is 350x257
   CSS, and southern-environmental-power-water-treatment-equipment.webp is 200x160, so `contain`
   scaled it to 321 CSS px — 644 device pixels out of 200 the file holds, a 3.22x invention.
   And there is nothing larger to reach for: every variant of all ten, the `-original.png`
   included, is the same 200x160 or 300x240. The shortage is real; the stretching was a choice.

   base/_cards.scss already states this rule for `.card__media img` and already records why —
   "a logo is never cropped, and never enlarged", after #86 measured a 200px ARO air-valve shot
   being painted at 234px. The lesson was learned for the generated cards and never carried to the
   line card's own frame, which is the shape #108 records: a fix that covered half the site.

   What it costs: a 200px picture now sits at 200px inside a 350px frame rather than filling it,
   so the shot is smaller with more plate around it. A catalogue can afford a small sharp
   photograph; it cannot afford a blurry one. */
.supplier__frame{
  position:absolute;inset:var(--sp-2);
  width:calc(100% - var(--sp-2) * 2);height:calc(100% - var(--sp-2) * 2);
  object-fit:scale-down;object-position:center;
  opacity:0;
  transition:opacity var(--dur-card) var(--ease-card),transform var(--dur-card) var(--ease-card);
}

/* .is-on is emitted on frame 0 by the build, so the stack renders correctly with JS off. */
.supplier__frame.is-on{opacity:1;}


/* =============================================================================
   COMPONENT: definition rows (contact, sitemap)
   ========================================================================== */
.rows{display:grid;gap:var(--sp-5);margin:0;}
/* A CONTACT ROW HOLDS A STRING THE RECORD SUPPLIES, NOT ONE THE LAYOUT CHOSE.
   `info@drnindustriel.com` is 22 characters with no space, hyphen or other break opportunity in
   it, so a grid track sized to its content floors at that string's width whatever the container
   is. Measured on /404.html at 320px with the browser's default font at 32px: the block came to
   449px and WAS the document's scrollWidth, so the page scrolled sideways. The same defect was
   fixed in base/_footer.scss for the generated pages; this is the legacy sheet's own copy of it,
   and the two carried pages are the only ones that read it.
   `anywhere` lets the address break when the container is narrower than the address. */
.rows{overflow-wrap:anywhere;max-width:100%;}


.row{display:flex;gap:var(--sp-4);align-items:flex-start;color:var(--c-ink-dim);line-height:1.5;
  transition:color var(--dur-fast);}

a.row:hover,a.row:focus-visible{color:var(--c-primary-strong);}

.row__ico{
  flex:none;width:28px;height:28px;display:grid;place-items:center;border-radius:50%;
  border:1px solid var(--c-line);color:var(--c-primary-strong);font-size:var(--fs-3);
}

/* A LABEL IS NOT A DECORATION. These name the rows a reader contacts the business through -
   "TELEPHONE" above the phone number - and they shipped at --c-ink-faint, rgba(244,244,244,.4),
   which composites to 3.03:1 on the footer's green ground and 3.54:1 on the contact page. Both
   under the 4.5:1 WCAG AA floor, and at 11px uppercase with .14em tracking, which is the hardest
   case for legibility rather than the easiest. --c-ink-dim clears it at 7.1:1 while still reading
   as secondary beside the value it labels. Measured by assets/scripts/contrast.verify.mjs. */
.row__label{display:block;font-size:var(--fs-1);letter-spacing:.14em;text-transform:uppercase;
  color:var(--c-ink-dim);margin-bottom:2px;}

/* inline-block + a padded box, so the target is the 24px WCAG 2.2 SC 2.5.8 minimum rather than
   the 22px the line-height alone produced. These are a navigation list, not links inside a
   sentence, so the SC's inline exception does not cover them. */
.tree a{display:inline-block;min-height:24px;padding-block:1px;
        color:var(--c-ink-dim);border-bottom:1px solid transparent;transition:color var(--dur-fast),border-color var(--dur-fast);}

/* A URL PATH IS INFORMATION, not decoration. These spans spell out the address of each page in
   the 404 listing, and at --c-ink-faint, rgba(244,244,244,.4), they composite to 3.54:1 against
   the page - under the 4.5:1 WCAG AA floor, at 11px. Same token and same mistake as .row__label,
   found on the 404 page only once the probes stopped walking index.html alone. Measured by
   assets/scripts/contrast.verify.mjs. */
.tree .u{font-size:var(--fs-1);color:var(--c-ink-dim);letter-spacing:.04em;}


/* =============================================================================
   SECTION TYPE: convert-proof-team-slide (.tsl)
   The face is the only element that gets scale here; everything else is quiet, because the
   claim being made is "a person answers" and a person is the evidence.
   /design reactivity scores the ten css states on a FLOOR, not an average — so rest, hover,
   focus-visible, active, disabled and the two data-states below are each authored, and the
   component is only as good as the state it forgot.
   ============================================================================= */
/* the section is emitted OUTSIDE the page wrapper, so it carries the page inset itself —
   without this it ran full-bleed to x=0 while every other block sat on the gutter */
.tsl{padding:var(--sp-9) var(--page-inset) var(--sp-8);max-width:var(--page-max);margin-inline:auto;}

/* auto-fill with a max, not auto-fit: a two-person team must not stretch two cards across a
   1720px row, and must not leave the row looking truncated either. The cap holds the card at a
   portrait's natural size and the row simply ends. */
@media (min-width:900px){ .tsl__grid{grid-template-columns:repeat(auto-fill,minmax(260px,300px));} }


/* the two stills are stacked, not swapped in the src — a src swap flashes white on first hover */
.tsl__face{position:relative;aspect-ratio:1;background:#97948f;overflow:hidden;}

@keyframes tsl-vo{0%,100%{height:6px}50%{height:15px}}

/* the autoplay refusal, stated on the surface rather than swallowed */
[data-tsl-locked] .tsl__vo-txt::after{content:" — tap once";font-weight:400;opacity:.8;}

/* the number is the primary act of this block, so it carries the primary treatment — filled, not
   outlined — and the SAME breathing + sheen loop the Request Inventory button uses. The keyframes
   are reused, not restated: two copies of one motion drift apart on the first edit. */
.tsl__cta--call{
  border-color:transparent;color:var(--c-primary-ink);font-weight:700;
  background:linear-gradient(180deg,var(--c-primary-strong),var(--c-primary));
  position:relative;overflow:hidden;
  animation:ivf-breathe 3.4s cubic-bezier(.4,0,.2,1) infinite;
  transition:transform var(--dur-med),filter var(--dur-med),box-shadow var(--dur-med);
}


/* the profile — in the served HTML, so the credibility marks are crawlable and survive JS off */
.tsl__profile{position:relative;margin-top:var(--sp-6);padding:var(--sp-6);
  background:var(--c-tile);border-radius:var(--radius-md);}


/* ---------- the slot variant: the sidebar container that changes the human ----------
   The track holds every member side by side and slides; nothing is fetched or src-swapped on
   advance, so the transition cannot flash and JS-off still shows the first person. */
.tsl--slot{padding:0;margin:0;max-width:none;overflow:hidden;
  background:var(--c-tile);border-radius:var(--radius-md);}


/* ---------- the icon bar: search · print · download ----------
   Three boxes, three icons, one row. The label lives in .u-sr and in title=, so the control is
   named for assistive tech and on hover without spending width on it. */
/* ---------- the action bar: ONE container, ONE radius ----------
   The radius belongs to the bar and to nothing inside it. overflow:hidden makes the container
   the only thing that can round a corner, so a hovered end cell is clipped by the bar rather
   than carrying a matching radius of its own — no item can drift out of agreement with the
   container, because no item has a corner to get wrong. */
.lctools{position:relative;display:grid;gap:var(--sp-2);}

/* declared AFTER the border reset, or the reset wins on equal specificity and the second cell
   silently loses its divider. Inset shadow, not border: a border takes a pixel out of the cell's
   content box and the third icon measured 73 against 74. */
.lctools__bar > * + * > .lctools__btn,
.lctools__bar > .lctools__btn + .lctools__btn{box-shadow:inset 1px 0 0 var(--c-line);}

/* anchored to .lctools, so it escapes the bar's clip */
.lctools > .dl__menu{position:absolute;top:44px;right:0;left:auto;bottom:auto;z-index:70;}


/* ---------- the slot, laid out to the reference ----------
   Map on top, place, invitation, faces, who, the number. Values are taken from the screenshot
   rather than from the token ladder where the two disagree, because the reference IS the spec. */
.tsl__map{display:block;overflow:hidden;}


/* full-width pills, stacked. The reference shows one; a second action takes the same shape below
   it rather than halving the first, so the number never shrinks when an action is added. */
.tsl--slot .tsl__actions{display:grid;gap:8px;padding:0;margin:16px 0 0;}


/* ONE action row for the whole carousel, re-pointed at the active slide. Its position never
   moves between people, so the control does not have to be re-found on every change. */
.tsl__actions{display:flex;flex-wrap:wrap;gap:var(--sp-2);padding:var(--sp-3);}


/* ---------- the quote popup ----------
   It began as a 680x760 box inside the homepage's own stylesheet, then became a 960x880 panel.
   Both were still a WIDGET sitting on a page. It is the page now: the overlay is the viewport,
   the iframe fills it edge to edge, and there is no frame, no radius, no shadow and no scrim
   blur left to render — the form's own field is the background.
   The single piece of chrome is the close control, which floats over the surface at the same
   position and size the marketplace funnel puts its own. */
.qmodal{
  position:fixed;inset:0;z-index:200;
  background:var(--c-bg);
  animation:qmodal-in .22s var(--ease-out) both;
}

@keyframes qmodal-in{from{opacity:0}to{opacity:1}}

/* dvh over vh: on iOS vh counts the space behind the URL bar, which would push the
   form's primary control off the bottom of the screen it is measured against. */
.qmodal__box{position:absolute;inset:0;height:100dvh;overflow:hidden;}

/* The iframe carries its OWN close button (form.html), which is the one a visitor
   inside the form reaches. This one is the host's escape hatch and sits above it,
   so the two must not collide — the frame's own sits at 20px, this at 20px too, and
   only ONE of them is rendered: the host's is hidden once the frame reports ready.
   Until then it is the only way out if the frame fails to load at all. */
.qmodal__close{
  position:absolute;top:20px;right:20px;z-index:2;
  width:44px;height:44px;border-radius:50%;display:grid;place-items:center;
  border:1px solid var(--c-line);background:rgba(244,244,244,.04);
  color:var(--c-ink);font-size:1.25rem;line-height:1;cursor:pointer;
  transition:background var(--dur-fast),transform var(--dur-fast);
}


/* ---------- footer ---------- */
.site-foot{
  margin-top:var(--sp-9);
  padding:var(--sp-9) var(--page-inset) 0;
  border-top:0;
  /* eleven stops rather than five: the old ramp banded visibly across the first third, where
     the steps between #0b0b0d, #0e1d0a and #14290d are large enough to read as edges. */
  /* Two layers, and the split is the whole point: the gradient is SIZED to stop exactly one
     viewport short of the footer's bottom, and the solid --c-foot-b sits under it. So the
     fully-saturated green the reader lands on at the page bottom is exactly 100vh — not a
     gradient that happens to look finished. */
  background:
    linear-gradient(180deg,
      var(--c-bg) 0%, #0c0f0b 6%, #0d140a 12%, #0f1c0a 19%,
      var(--c-foot-a) 26%, #12240c 34%, #14290d 42%,
      #17300f 52%, #1a3711 66%, #1c3c12 82%, var(--c-foot-b) 100%)
      top left / 100% calc(100% - 30svh) no-repeat,
    var(--c-foot-b);
  position:relative;
}

/* the coordinate rows sit closer together */
/* the drone film as GROUND. z-index 0 puts it under every child of .site-foot, and the gradient
   background already painted on .site-foot sits on top of it — which is what keeps the coordinates
   readable over moving footage instead of hoping the frames stay dark. */
.site-foot > *{position:relative;z-index:1;}

/* the poster still carries the image when motion is refused; the film simply stops */
@media (prefers-reduced-motion:reduce){ .site-foot__film{display:none;} }


.site-foot .rows{gap:var(--sp-3);}


/* ---------- footer CTA, directly under the last coordinate row ---------- */
.foot-cta{display:flex;align-items:center;gap:var(--sp-2);margin-top:var(--sp-5);}


/* the nudge, reused from pcania's NudgeArrow — same path, same bob, DRN's green.
   It loops TOWARD the button (translateX negative would point away here, since the arrow
   sits to the button's LEFT and its head points right). */
.nudge{display:inline-flex;align-items:center;flex:none;color:var(--c-primary-strong);
       animation:nudge-bob 1.6s var(--ease-out) infinite;}

/* the arrow now sits to the RIGHT of the button, so the glyph keeps pcania's own orientation
   (head pointing left) and the bob runs LEFT — toward the control, never away from it. */
.nudge svg{width:4.2rem;height:3.2rem;display:block;overflow:visible;}

@keyframes nudge-bob{
  0%,100%{transform:translateX(0);opacity:.65;}
  50%    {transform:translateX(-.4rem);opacity:1;}
}


/* ---------- and it has to fit the narrowest phone ----------
   MEASURED at 320 CSS pixels, the width WCAG 1.4.10 names: this row is a non-wrapping flex pair,
   a `white-space:nowrap` button of about 230px beside a 67px decorative arrow, against the 272px
   the page inset leaves. 305 into 272 does not go, so .foot-cta reached x=338, the .nudge 334 and
   its own path 331 — the svg carries `overflow:visible`, so the drawing escapes even its box.
   Four escapes and a sideways-scrolling document, on the one page a lost visitor lands on.

   It was invisible because layout.verify measured 390, 768 and 1440; adding 320 to that list is
   what reported it.

   `flex-wrap` costs nothing where there is room — this row only wraps when it must. The arrow is
   decoration and already `aria-hidden`, and an arrow stacked BELOW the button points at nothing,
   so below that width it goes rather than wraps. */
.foot-cta{flex-wrap:wrap;}


/* ---------- the signature at the footer's foot ---------- */
/* Uses the page's OWN .rise reveal, so there is one observer on the page and no second
   mechanism to keep in step. Without JS .rise never gets opacity:0, so it is simply visible. */
/* ---------- the immersive band ---------- */
/* Exactly one viewport tall, which is what makes the solid-green region above measure 100vh. */
/* Exactly one viewport tall — that is what makes the solid-green region measure 100vh — and
   it now COMPOSES rather than sitting empty: the watermark rises through it and the legal line
   is seated at its foot, centred. */
/* Exactly one viewport tall — that is what makes the solid-green region measure 100vh.
   A column: the mark's own box takes the space above, the legal line sits beneath it. The
   two were previously both anchored to the band's bottom edge and the copyright printed
   straight over the symbol. */
/* 50svh, halved. This SUPERSEDES the earlier "exactly 100 view height" instruction — the band
   is where the footer's height actually lives, and the content block above it is real content
   that cannot be halved. Revert this one value to restore the full-viewport band. */
.foot-immersive{
  position:relative;height:30svh;
  display:flex;flex-direction:column;
  padding:0 var(--page-inset);
}

/* the mark is clipped by THIS box, so its half-cut edge is a line above the legal, not the
   page's bottom edge */
.foot-markwrap{position:relative;flex:1 1 auto;min-height:0;overflow:hidden;}


/* The symbol is absolutely positioned and pushed down by half its own height, so the band
   clips it and only the TOP HALF is ever visible. Double the previous 200px. A thin
   translucent white rather than a solid one — it is a watermark in the field, not a logo
   sitting on it. */
/* Vector, and painted rather than pictured. drn-symbol-mono.svg is the traced silhouette of the
   mark (VTracer via the /logo pipeline's own tracer); as a mask it takes `color`, so the same one
   file serves a white watermark here and any other tint elsewhere. The 433px raster it replaces
   was resampled 2.65x on a retina screen at this size — the softness was in the source. */
.foot-mark{
  position:absolute;left:50%;bottom:0;
  width:min(575px,92vw);aspect-ratio:2048/1991;   /* +25% */
  color:var(--c-ink);background:currentColor;
  -webkit-mask:url(/communities/drnindustriel/brand/symbol/en/drn-symbol-mono.svg) no-repeat center / contain;
          mask:url(/communities/drnindustriel/brand/symbol/en/drn-symbol-mono.svg) no-repeat center / contain;
  transform:translate(-50%,62%);
  opacity:.18;
  will-change:transform;
}

/* Scroll-driven where the browser supports it: the mark rises as the band enters the viewport,
   tied to the scroll position itself rather than to a timer, which is what makes it read as
   parallax instead of as an animation that happens to play. */
@supports (animation-timeline: view()) {
  .foot-mark{
    animation:foot-mark-rise linear both;
    animation-timeline:view();
    animation-range:entry 0% cover 40%;
  }
  @keyframes foot-mark-rise{
    from{ transform:translate(-50%,104%); opacity:.05; }
    to  { transform:translate(-50%,62%);  opacity:.22; }
  }
}


/* The reveal for the SHARED pages. .rise only existed in the homepage's own inline CSS and
   observer, so on /en/* the mark would simply have been static — the ease-in was asked for on
   every page. Gated on data-rise-js: the class is inert until the script announces itself, so
   with JS off the content is visible rather than stuck at opacity 0. */
[data-rise-js="on"] .rise{opacity:0;transform:translateY(14px);}

/* start, not center: centring measured the short column against the tall one and pushed the ask
   93px below the wordmark, so the two blocks shared no edge. They now begin on the same line. */
.site-foot__in{max-width:var(--page-max);margin-inline:auto;display:grid;gap:var(--sp-8);
  align-items:start;padding-top:32px;}

/* one column: the second held the Request-for-information block, which is gone. A 1fr .9fr
   grid with one child leaves a dead column the reader reads as a missing thing. */
.site-foot__logo{height:clamp(78px,7.2vw,108px);width:auto;object-fit:contain;margin-bottom:var(--sp-5);}

/* WHEN A SINGLE WORD NO LONGER FITS ITS COLUMN — the legacy sheet's copy of the rule
   base/_typography.scss carries for the generated pages, and the same reasoning.
   14em is 224px at the browser's default 16px, narrower than any screen, so this is inert for a
   reader who has changed nothing; at a 32px default it is 448px and covers both phone widths.
   `em` in a media query answers to the browser default and ignores every font-size the page
   declares, which is exactly the proportion being asked about.
   `hyphens: auto` first, because these documents declare their language and the browser will then
   break on the language's own hyphenation points. `overflow-wrap: anywhere` second — and ANYWHERE
   rather than break-word, because only `anywhere` changes an element's min-content contribution.
   `break-word` is excluded from intrinsic sizing by CSS Text 3, so it lets a word break visually
   while the grid track above it still floors at the whole word, which is the measurement that
   sent me to the spec: 103 findings before that distinction and 103 after. */
@media (max-width:14em){
  h1,h2,h3,h4,h5,h6,p,li,dt,dd,figcaption,blockquote,a,button,label,summary,span,td,th{
    hyphens:auto;overflow-wrap:anywhere;
  }
}

/* AND A LABEL IN A CONTROL MAY WRAP RATHER THAN BE CUT. Measured on the line card at 320px with a
   32px default: the toolbar buttons are 88px and their labels were held on one line by
   `white-space: nowrap` inside `overflow: hidden`, so "Quote" rendered 38px of a 64px word and
   "Filter" 38px of 53px. This sheet's own stated rule, at the top of this file, is that content is
   never truncated to make a layout fit; at this threshold a taller button is the price of keeping
   that true. */
@media (max-width:14em){
  /* `.lc-bar .btn span` is where the line card states the truncation, and a bare `.btn span`
     loses to it on specificity — measured: the rule shipped and the labels stayed cut. Matching
     the selector that set it is the fix; raising specificity with !important would win the same
     argument while hiding which rule is actually in charge. */
  .lc-bar .btn span,.btn span,button span{white-space:normal;overflow:visible;}
}
