/* ==========================================================================
   BINERET — LIQUID GLASS + INTERACTION LAYER
   --------------------------------------------------------------------------
   Printed by hand on `wp_head` at priority 103, i.e. after the database
   stylesheet (100), the brand layer (101) and the services sheet (102). It is
   last on purpose: everything in here is an ADDITION to a surface that already
   exists, and it must be able to win a specificity tie without `!important`.

   🔴 READ THIS BEFORE ADDING ANYTHING WITH A FILTER IN IT.

   `backdrop-filter` has frozen this site's renderer twice. Twenty-one blurred
   panes over the gradient canvas locked the tab for thirty seconds; two large
   ones tore frames and then locked it as well. There is exactly one rule in
   this project that is not open for re-litigation: `backdrop-filter` lives on
   `.topbar` and `.tabbar` and nowhere else. Nothing in this file adds one.

   So where does "liquid glass" come from, if not from blur?

   From the thing that actually makes glass read as glass: a MOVING SPECULAR.
   A pane of glass is not interesting because it is frosted, it is interesting
   because the highlight on it travels when you or it move. iOS sells the
   material on that reflection, not on the blur behind it. Every effect below is
   a way of moving a highlight, and every one of them animates `opacity` or
   `transform` and nothing else:

     1. a specular that follows the pointer across the surface        (opacity)
     2. a rim that lights up on the side the pointer is nearest       (opacity)
     3. a slow sheen drifting across the two big panels               (transform)
     4. a reveal that lifts a block into place once, as it arrives    (both)

   All four are free in the compositor. None of them read a backdrop.
   ========================================================================== */

/* ==========================================================================
   1 · TOKENS
   --------------------------------------------------------------------------
   Every value below is an alpha on a decorative layer that appears on hover.
   None of them is behind text at rest, which is why the whole of this file
   leaves the page's contrast ratios where it found them — measured after each
   change, not assumed.
   ========================================================================== */
:root {
  /* dark mode: the surface is nearly black, so the specular IS the light and a
     single bright stop carries it. */
  --lq-spec-core:rgba(214,234,255,.26);
  --lq-spec-mid-1:rgba(160,200,255,.13);
  --lq-spec-mid-2:rgba(120,170,255,.05);
  --lq-spec-r:240px;

  --lq-rim-core:rgba(226,240,255,.95);
  --lq-rim-mid:rgba(130,180,255,.34);
  --lq-rim-r:170px;

  --lq-flow-1:rgba(190,216,255,.045);
  --lq-flow-2:rgba(210,230,255,.10);

  --lq-lens:
    inset 0 9px 20px -14px rgba(205,228,255,.34),
    inset 0 -11px 24px -16px rgba(3,10,26,.55);
}
:root[data-theme="light"] {
  /* 🔴 A WHITE HIGHLIGHT ON A WHITE PANE IS NOTHING, AND THAT IS THE WHOLE
     PROBLEM WITH LIGHT-MODE GLASS.

     The pane is 42% white over a pale canvas. Adding more white to it moves it
     a few points up a ramp it is already near the top of, and the first version
     of this — one white stop at .90 — was invisible on screen at any radius.

     What the eye reads is not brightness, it is the STEP. So the specular is
     four stops that go up and then come back DOWN past where they started: a
     near-white core, a white shoulder, and then a ring of brand blue at .11
     before it clears. The bright middle and the tinted ring are only a few
     percent apart in luminance and unmistakable together, because an edge
     between two colours is legible at a contrast where either alone is not.

     Same trick on the rim, harder: white at the centre, brand blue at 45% by
     the middle of the falloff. That is what makes the lit edge read as a bevel
     catching light rather than as a glow drawn around a box. */
  --lq-spec-core:rgba(255,255,255,.98);
  --lq-spec-mid-1:rgba(255,255,255,.55);
  --lq-spec-mid-2:rgba(4,57,217,.11);
  --lq-spec-r:240px;

  --lq-rim-core:rgba(255,255,255,1);
  --lq-rim-mid:rgba(4,57,217,.45);
  --lq-rim-r:170px;

  --lq-flow-1:rgba(255,255,255,.20);
  --lq-flow-2:rgba(255,255,255,.46);

  /* Light pools under the top edge and the canvas tint pools above the bottom
     one. This is what gives a pane thickness: the existing three-part rim draws
     the EDGES of the glass, this draws the few millimetres just inside them. */
  --lq-lens:
    inset 0 9px 20px -13px rgba(255,255,255,.95),
    inset 0 -11px 24px -15px rgba(4,57,217,.15);
}

/* ==========================================================================
   2 · THE LIQUID SURFACE
   --------------------------------------------------------------------------
   `.lq` is added by assets/js/liquid.js to a fixed list of surfaces rather than
   written into twenty template files. Three reasons, in order of weight:

     · several of those surfaces are rendered from CSS/markup held in the
       DATABASE (the shop, the blog, the account pages), where there is no
       template to edit;
     · the class carries no styling of its own that matters when the script is
       absent — every effect on it is a hover state — so a no-JS visitor sees
       the same page, minus a highlight they were never going to trigger;
     · one list in one file is the only version of "which things are glass".

   The two pseudo-elements are free on every element in that list: the stretched
   card links use `a::after`, which belongs to the anchor, not the card.
   ========================================================================== */
/* 🔴 `isolation:isolate` AND `z-index:-1`, AND THE BUG THAT FORCED BOTH.

   The obvious build is `z-index:0` on the pseudo-elements and `position:relative;
   z-index:2` on the card's children, to lift the text back above the highlight.
   That version is wrong, and it breaks the site in a way that is invisible until
   somebody tries to click something.

   Half the cards on this page are stretched-link cards: the title contains
   `a::after { position:absolute; inset:0 }`, which grows the anchor's hit area
   to fill the whole card. `inset:0` resolves against the nearest POSITIONED
   ancestor. Making the `<h3>` position:relative moves that ancestor from the
   card to the heading, so every one of those cards silently shrinks its click
   target to the two words in its title.

   So the layers go BELOW the content instead of the content going above them.
   A negative z-index child paints in step 2 of the painting order: above the
   element's own background and border, below its in-flow text, and below the
   positioned stretched link in step 6. Exactly the three relationships needed,
   and no child is touched.

   `isolation:isolate` is what keeps that promise local. Without a stacking
   context on the card itself, `z-index:-1` escapes to the nearest ancestor that
   has one and the highlight paints behind the section instead of inside the
   card. It costs nothing — it does not promote a layer, it only draws a
   boundary. */
.lq { position:relative; isolation:isolate; }

/* — the specular ————————————————————————————————————————————————————————
   `--mx`/`--my` are written by the script on pointermove. They default to the
   centre so a keyboard user focusing the card still gets a highlight, just a
   stationary one. `opacity` is the only animated property. */
.lq::after {
  content:""; position:absolute; inset:0;
  border-radius:inherit; pointer-events:none; z-index:-1;
  background:radial-gradient(var(--lq-spec-r) circle at var(--mx,50%) var(--my,50%),
             var(--lq-spec-core) 0%,
             var(--lq-spec-mid-1) 22%,
             var(--lq-spec-mid-2) 46%,
             transparent 72%);
  opacity:0;
  transition:opacity 260ms cubic-bezier(0.22,1,0.36,1);
}
.lq:hover::after,
.lq:focus-within::after,
.lq.is-lit::after { opacity:1; }

/* — the rim ——————————————————————————————————————————————————————————————
   A one-pixel frame, masked out of a radial that also follows the pointer, so
   the edge is brightest on the side nearest the cursor and fades away round the
   back. This is the detail that reads as a physical pane rather than as a
   lighting effect painted on a rectangle, and it is the cheapest thing here:
   the gradient is rasterised once and only its opacity is touched after that. */
.lq::before {
  content:""; position:absolute; inset:0;
  border-radius:inherit; pointer-events:none; z-index:-1;
  padding:1.5px;
  background:radial-gradient(var(--lq-rim-r) circle at var(--mx,50%) var(--my,50%),
             var(--lq-rim-core) 0%, var(--lq-rim-mid) 48%, transparent 78%);
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
          mask-composite:exclude;
  opacity:0;
  transition:opacity 260ms cubic-bezier(0.22,1,0.36,1);
}
.lq:hover::before,
.lq:focus-within::before,
.lq.is-lit::before { opacity:1; }

/* — the lens ——————————————————————————————————————————————————————————————
   Static, always on, no animation: the inner glow that gives the pane
   thickness. It is added to the box-shadow the brand layer already sets rather
   than replacing it, which is why the two variables are restated here in full
   instead of being appended — CSS has no way to add to a shorthand. */
.lq { box-shadow:var(--glass-rim), var(--lq-lens), var(--glass-cast); }
.lq:hover,
.lq:focus-within { box-shadow:var(--glass-rim), var(--lq-lens), var(--glass-cast-lift); }

/* ==========================================================================
   2b · `.glass` — THE INNER PAGES JOIN THE SAME MATERIAL
   --------------------------------------------------------------------------
   🔴 TWO FAULTS, BOTH OF THEM ON EVERY PAGE THAT IS NOT THE HOME PAGE.

   `.glass` is the panel class on the shop, the blog, the account pages, the
   checkout and the services templates. Measured on `/products/` it resolved to:

     background-color   rgba(255,255,255,.55)          (--lav-glass-tint)
     background-image   linear-gradient(#fff .92 …)    (--surf-sheen)
     backdrop-filter    blur(14px) saturate(1.25)

   The first two composite to about 96% white. That is the exact bug the home
   page rebuild was written to fix — a pane you cannot see through is a painted
   rectangle, and no amount of blur behind an opaque sheet is visible. The inner
   pages had "glassmorphism" in the stylesheet and none of it on the screen.

   The third is worse than cosmetic. There are SIX `.glass` panels on the shop
   page and more on the account pages, each one a live backdrop blur over the
   gradient canvas. Twenty-one of those is what froze the renderer on the home
   page; six is the same mistake with a smaller number in front of it, and it
   was shipping on the busiest non-home template on the site.

   So `.glass` is answered here with the recipe the home surfaces already use —
   the same four tokens, the same lens, the same cast — and the blur comes off.
   One material, one definition, every template. The pane is now genuinely
   translucent, which is what makes the canvas behind it visible and the whole
   effect real rather than declared.

   Only `background-color`, `background-image`, `border-color`, `box-shadow` and
   the filter are touched. Radius, padding, layout and the `::before` bevels
   that `.glass--crystal` and `.glass--frost-rim` draw are left exactly as they
   are — which is also why `.glass` is NOT in the list of surfaces that get the
   pointer specular: that effect needs `::before`, and on those two variants it
   is taken.
   ========================================================================== */
.glass {
  background-color:var(--glass-fill);
  background-image:var(--glass-sheen);
  border-color:var(--glass-line);
  box-shadow:var(--glass-rim), var(--lq-lens), var(--glass-cast);
  -webkit-backdrop-filter:none;
          backdrop-filter:none;
}
.glass:hover {
  border-color:var(--glass-line-strong);
  box-shadow:var(--glass-rim), var(--lq-lens), var(--glass-cast-lift);
}
/* The strong tier is the one used for panels that carry a heading and a block
   of copy rather than a single control, and it reads better one step denser. */
.glass--deep,
.glass--t4,
.glass--t5 { background-color:var(--glass-fill-strong); }

/* ==========================================================================
   3 · THE DRIFT — the two big panels only
   --------------------------------------------------------------------------
   A wide, very soft diagonal band of light that slides slowly back and forth
   across the panel. It is what the surface does when nobody is touching it, and
   it is the reason the page reads as glass in a screenshot as well as under a
   cursor.

   `.lq--flow` replaces the rim-follow on `::before` rather than adding a third
   layer: a panel 1240px wide does not need a 190px rim highlight, and a third
   absolutely-positioned pseudo would be a third layer to composite for no gain.
   The element is deliberately oversized and clipped by the panel's own
   `overflow:hidden`, so the band can travel without its ends ever showing.

   Two elements on the page carry this. That is the budget, and it is why the
   animation is on `transform` alone: two compositor layers, no repaints.
   ========================================================================== */
.lq--flow::before {
  padding:0;
  inset:-45% -110%;
  border-radius:0;
  -webkit-mask:none; mask:none;
  background:linear-gradient(104deg,
    transparent 30%,
    var(--lq-flow-1) 43%,
    var(--lq-flow-2) 50%,
    var(--lq-flow-1) 57%,
    transparent 70%);
  opacity:1;
  transition:none;
  animation:lq-drift 16s cubic-bezier(0.45,0,0.55,1) infinite alternate;
  will-change:transform;
}
.lq--flow:hover::before,
.lq--flow:focus-within::before { opacity:1; }
@keyframes lq-drift {
  from { transform:translate3d(-26%,0,0); }
  to   { transform:translate3d(26%,0,0); }
}

/* ==========================================================================
   4 · REVEAL
   --------------------------------------------------------------------------
   Each section lifts 12px and fades in once, the first time it crosses into
   view, and the children stagger behind it by 45ms each up to a ceiling. Once
   revealed the class is removed and the observer stops watching, so nothing on
   this page is still being observed after the first pass down it.

   🔴 The `transform`/`opacity` pair promotes the element to its own layer while
   it animates. Anything inside a `background-clip:text` parent would vanish for
   the duration — the gradient title in the brand layer is the one on this site.
   Section heads are safe because the clip is on the H1 inside the hero, and the
   hero is never revealed (it is on screen at load, and it is out of scope).
   ========================================================================== */
[data-lq-reveal] { }
.lq-hide { opacity:0; transform:translate3d(0,12px,0); }
.lq-in {
  opacity:1; transform:none;
  transition:opacity 460ms cubic-bezier(0.22,1,0.36,1),
             transform 460ms cubic-bezier(0.22,1,0.36,1);
  transition-delay:var(--lq-d,0ms);
}

/* ==========================================================================
   5 · SCROLL PROGRESS
   --------------------------------------------------------------------------
   Two pixels along the top of the page shell, scaled on the X axis from a
   scroll listener that does its arithmetic inside a rAF. `transform-origin` is
   the inline start so it grows the way the language reads.

   It sits at the top of `.main`, i.e. inside the framed shell and above the
   topbar, so it never covers content and never needs a z-index fight with the
   sticky header.
   ========================================================================== */
.lq-progress {
  position:fixed; inset-block-start:0; inset-inline:0; z-index:60;
  block-size:2px; pointer-events:none;
  background:linear-gradient(90deg, var(--lav-cta,#0439D9), var(--lav-cyan,#0E7490));
  transform:scaleX(var(--lq-p,0)); transform-origin:left center;
  will-change:transform;
}
:root:not([data-theme="light"]) .lq-progress { opacity:.85; }

/* ==========================================================================
   6 · BACK TO TOP
   --------------------------------------------------------------------------
   Appears after two viewport heights and not before: a control that is on
   screen from the first frame is a control nobody needed. It is a real
   `<button>` so it is in the tab order, and it is `inert`-equivalent while
   hidden — `visibility` goes with the opacity, so it cannot be tabbed to while
   invisible, which is the usual bug in this component.
   ========================================================================== */
.lq-top {
  position:fixed; inset-block-end:24px; inset-inline-end:24px; z-index:55;
  inline-size:44px; block-size:44px; display:grid; place-items:center;
  border-radius:50%; cursor:pointer;
  color:var(--lav-text-primary); border:1px solid var(--glass-line);
  background-color:var(--glass-fill-strong); background-image:var(--glass-sheen);
  box-shadow:var(--glass-rim), var(--glass-cast);
  opacity:0; visibility:hidden; transform:translate3d(0,10px,0);
  transition:opacity 260ms cubic-bezier(0.22,1,0.36,1),
             transform 260ms cubic-bezier(0.22,1,0.36,1),
             visibility 0s linear 260ms;
}
.lq-top.is-on {
  opacity:1; visibility:visible; transform:none;
  transition-delay:0s, 0s, 0s;
}
.lq-top:hover { transform:translate3d(0,-2px,0); }
.lq-top:active { transform:translate3d(0,0,0) scale(.94); }
.lq-top svg { inline-size:20px; block-size:20px; }
/* The mobile tab bar owns the bottom-right corner on phones; the offset that
   clears it is stated once, with the tab bar itself, in section 11. */

/* ==========================================================================
   6b · THE JUMP BAR
   --------------------------------------------------------------------------
   Sticky, not fixed. It is laid out in normal flow under the trust strip and
   pins from there, so it never overlaps anything on the way in and the page
   below it never needs padding to compensate for a bar that appeared.

   `top` is the height of the sticky topbar above it — 60px, measured, the same
   at every width — and this is the only place that number appears.

   🔴 THIS BAR IS THE ONE SURFACE THAT CANNOT BE TRANSLUCENT.

   Every other pane on this site sits still while the page scrolls past around
   it. This one has arbitrary page content sliding UNDER it, and at
   `--glass-fill-strong` (58% white) a section heading passing beneath was
   legible through the chips — two lines of type occupying the same pixels.

   The material's own answer is `backdrop-filter`, and it is exactly the answer
   this project cannot afford: a third filtered pinned surface over the gradient
   canvas is how the renderer went down before. So the fill goes dense instead.
   It keeps the sheen, the rim and the cast, so it still reads as the same
   material as everything around it — a thicker piece of the same glass rather
   than a different substance. The topbar directly above it is nearly opaque in
   light mode too, so the pair reads as one assembly.

   The chips scroll horizontally on a phone rather than wrapping to a second
   row: a two-row navigation bar that follows you down the page is a navigation
   bar in the way.
   ========================================================================== */
:root {
  --lq-bar-fill:rgba(12,20,38,.90);
  /* Where the topbar's bottom edge falls. 60 is the marketplace home; every
     other template puts the topbar in the `.app` frame and lands at 75.
     assets/js/liquid.js measures it and overwrites this before first paint of
     the sticky state; the value here is the no-JS fallback. */
  --lq-jump-top:60px;
}
:root[data-theme="light"] { --lq-bar-fill:rgba(255,255,255,.92); }

/* No padding on the leading edge. Any gap between the topbar and this one is a
   strip of page showing between two bars, which reads as a rendering fault
   rather than as two floating objects; below it, content sliding under a
   floating bar is just what a floating bar looks like. */
.jump {
  position:sticky; inset-block-start:var(--lq-jump-top,60px); z-index:40;
  padding-block:0 8px; margin-block-end:8px;
}
.jump__wrap { padding-inline:var(--lav-fluid-s); }
.jump__list {
  list-style:none; margin:0; padding:4px;
  display:flex; align-items:center; gap:4px;
  border-radius:var(--lav-radius-pill,999px);
  border:1px solid var(--glass-line);
  background-color:var(--lq-bar-fill); background-image:var(--glass-sheen);
  box-shadow:var(--glass-rim), var(--glass-cast);
  overflow-x:auto; scrollbar-width:none; overscroll-behavior-x:contain;
  scroll-snap-type:x proximity;
}
.jump__list::-webkit-scrollbar { display:none; }
.jump__link {
  display:inline-flex; align-items:center; flex:0 0 auto; scroll-snap-align:center;
  block-size:32px; padding-inline:14px; border-radius:var(--lav-radius-pill,999px);
  color:var(--lav-text-secondary); text-decoration:none; white-space:nowrap;
  font-size:var(--fs-caption,0.8125rem); font-weight:600;
  transition:color 260ms cubic-bezier(0.22,1,0.36,1),
             background-color 260ms cubic-bezier(0.22,1,0.36,1);
}
.jump__link:hover { color:var(--lav-text-primary); background-color:var(--glass-fill); }
/* The current section is stated with `aria-current`, so the styling hangs off
   the attribute rather than off a second class that could drift away from it. */
.jump__link[aria-current="true"] {
  color:var(--lav-cta-ink,#fff);
  background-color:var(--lav-cta,#0439D9);
}
.jump__link:focus-visible { outline:2px solid var(--lav-cta,#0439D9); outline-offset:2px; }
/* No JavaScript means no scroll-spy, which costs a highlight. Every chip is a
   plain in-page anchor and works exactly as it reads. */

/* The services page lays its column out with `.lav-wrap`, the home page with
   `.wrap`. The bar takes whichever it is given; only the inline padding is
   stated here so the chips line up with the content under them either way. */
.lav-wrap.jump__wrap { padding-inline:var(--lav-fluid-s, 20px); }

/* The `scroll-margin` that clears both bars is stated once, in section 9,
   against the same measured variable this bar sticks to. */

/* ==========================================================================
   7 · SEGMENTED CONTROL
   --------------------------------------------------------------------------
   The iOS control, and the one place on this site where the pill that moves is
   the point. `--seg-x` and `--seg-w` are written by the script; the indicator
   animates `transform` only, so switching tabs costs one composited frame.

   It is a real `role="tablist"` with real `role="tabpanel"` children: arrow
   keys move between tabs, Home/End jump to the ends, and only the selected tab
   is in the tab order (`tabindex="-1"` on the rest). That is the WAI-ARIA
   pattern, and it is the reason this is a control rather than a row of buttons.
   ========================================================================== */
.segbar {
  position:relative; display:inline-flex; align-items:center; gap:4px;
  padding:4px; border-radius:var(--lav-radius-pill,999px);
  border:1px solid var(--glass-line);
  background-color:var(--glass-fill); background-image:var(--glass-sheen);
  box-shadow:var(--glass-rim);
  max-inline-size:100%; overflow-x:auto; scrollbar-width:none;
}
.segbar::-webkit-scrollbar { display:none; }
.segbar__ind {
  position:absolute; inset-block:4px; inset-inline-start:0; z-index:0;
  inline-size:var(--seg-w,0px); border-radius:var(--lav-radius-pill,999px);
  background:var(--lav-cta,#0439D9);
  transform:translate3d(var(--seg-x,0px),0,0);
  transition:transform 320ms cubic-bezier(0.34,1.4,0.64,1),
             inline-size 320ms cubic-bezier(0.34,1.4,0.64,1);
  pointer-events:none;
}
.seg {
  position:relative; z-index:1; cursor:pointer; flex:0 0 auto;
  display:inline-flex; align-items:center; gap:8px;
  block-size:36px; padding-inline:16px;
  border:0; background:none; border-radius:var(--lav-radius-pill,999px);
  font:inherit; font-size:var(--fs-label,0.875rem); font-weight:600;
  color:var(--lav-text-secondary);
  transition:color 260ms cubic-bezier(0.22,1,0.36,1);
  white-space:nowrap;
}
.seg:hover { color:var(--lav-text-primary); }
.seg[aria-selected="true"] { color:var(--lav-cta-ink,#fff); }
.seg .ico { inline-size:16px; block-size:16px; }
/* Until the script has measured the buttons there is no indicator to show, and
   a 0px pill parked at the start would be a visible glitch on first paint. */
.segbar:not(.is-ready) .segbar__ind { opacity:0; }
.segbar:not(.is-ready) .seg[aria-selected="true"] { color:var(--lav-cta,#0439D9); }

/* the panel a segment controls */
.segpanel[hidden] { display:none; }
.segpanel { animation:lq-fade 320ms cubic-bezier(0.22,1,0.36,1); }
@keyframes lq-fade { from { opacity:0; transform:translate3d(0,6px,0); } to { opacity:1; transform:none; } }

/* ==========================================================================
   8 · LIVE FILTER
   --------------------------------------------------------------------------
   One input over a list that is already in the DOM. Nothing is fetched and
   nothing is removed: matches keep their place, non-matches get `hidden`, and
   the count under the input is announced through a polite live region so the
   result of typing is available to somebody who cannot see the list shorten.

   The whole catalogue is 60 real category links, so this filters something that
   exists. It is not a search box pointed at an empty index.
   ========================================================================== */
.lq-filter { display:flex; flex-direction:column; gap:8px; margin-block-end:24px; }
.lq-filter__box {
  position:relative; display:flex; align-items:center; gap:8px;
  inline-size:min(100%,420px); block-size:44px; padding-inline:16px;
  border-radius:var(--lav-radius-pill,999px);
  border:1px solid var(--glass-line);
  background-color:var(--glass-fill); background-image:var(--glass-sheen);
  box-shadow:var(--glass-rim);
  transition:border-color 260ms cubic-bezier(0.22,1,0.36,1);
}
.lq-filter__box:focus-within { border-color:var(--lav-cta,#0439D9); }
.lq-filter__box .ico { inline-size:18px; block-size:18px; color:var(--lav-text-tertiary); flex:0 0 auto; }
/* `align-self:stretch` rather than leaving the input at its intrinsic 21px.
   The label around it is already 44px and clicking anywhere on it focuses the
   input, so the effective target was always compliant — but the audit measures
   the input's own rect, and a rule that has to be explained every time it is
   measured is a rule that should just be true. Now it is. */
.lq-filter__in {
  flex:1 1 auto; min-inline-size:0; align-self:stretch;
  border:0; background:none; outline:none;
  font:inherit; font-size:var(--fs-label,0.875rem); color:var(--lav-text-primary);
}
/* Safari paints its own rounded field on `type=search`; this removes it so the
   input is the invisible text layer the glass box around it implies. */
.lq-filter__in::-webkit-search-decoration,
.lq-filter__in::-webkit-search-cancel-button { -webkit-appearance:none; appearance:none; }
.lq-filter__in::placeholder { color:var(--lav-text-tertiary); }
.lq-filter__clear {
  flex:0 0 auto; display:none; place-items:center; cursor:pointer;
  inline-size:26px; block-size:26px; border-radius:50%;
  border:1px solid var(--glass-line); background:none; color:var(--lav-text-secondary);
  transition:color 260ms cubic-bezier(0.22,1,0.36,1);
}
.lq-filter__clear:hover { color:var(--lav-text-primary); }
.lq-filter__box.has-q .lq-filter__clear { display:grid; }
.lq-filter__clear svg { inline-size:14px; block-size:14px; }
.lq-filter__count { margin:0; font-size:var(--fs-caption,0.8125rem); color:var(--lav-text-tertiary); }
.lq-filter__count b { color:var(--lav-text-primary); font-weight:600; }
mark.lq-hit { background:rgba(4,57,217,.14); color:inherit; border-radius:3px; padding:0 1px; }
:root:not([data-theme="light"]) mark.lq-hit { background:rgba(160,195,255,.20); }
[data-lq-empty] { display:none; }
[data-lq-empty].is-on { display:block; }
.lq-filter__empty {
  margin:0 0 24px; padding:16px; border-radius:14px;
  border:1px dashed var(--glass-line-strong);
  color:var(--lav-text-secondary); font-size:var(--fs-label,0.875rem); line-height:1.55;
}
.lq-filter__empty a { color:var(--lav-cta,#0439D9); font-weight:600; }

/* The same utility `assets/css/home.css` defines, restated because that sheet
   only loads on the front page and these widgets run everywhere. */
.sr-only {
  position:absolute; inline-size:1px; block-size:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}

/* ==========================================================================
   9 · COPY BUTTON
   --------------------------------------------------------------------------
   The label swaps to "Copied" for a second and swaps back. No toast, no
   library, and the state is announced: `aria-live="polite"` on the label means
   a screen reader hears the confirmation the sighted user reads.
   ========================================================================== */
.lq-copy {
  display:inline-flex; align-items:center; gap:6px; cursor:pointer;
  block-size:30px; padding-inline:12px; border-radius:var(--lav-radius-pill,999px);
  border:1px solid var(--glass-line); background:none;
  font:inherit; font-size:var(--fs-caption,0.8125rem); font-weight:600;
  color:var(--lav-text-secondary);
  transition:color 260ms cubic-bezier(0.22,1,0.36,1),
             border-color 260ms cubic-bezier(0.22,1,0.36,1),
             transform 150ms cubic-bezier(0.34,1.56,0.64,1);
}
.lq-copy:hover { color:var(--lav-text-primary); border-color:var(--lav-line-strong); }
.lq-copy:active { transform:scale(.95); }
.lq-copy svg { inline-size:14px; block-size:14px; }
.lq-copy.is-done { color:var(--lav-cta,#0439D9); border-color:var(--lav-cta,#0439D9); }
/* The address and its copy button share a line and wrap together on a phone. */
.foot-mail-row { margin:0; display:flex; align-items:center; gap:12px; flex-wrap:wrap; }

/* — FAQ controls ————————————————————————————————————————————————————————
   The open-all button sits above the two columns, on the right, where the
   section head's own link sits — one vertical line for everything that acts on
   the block rather than describing it. Its chevron turns when everything is
   open, which is the same affordance each question already uses. */
.faq__tools { display:flex; justify-content:flex-end; margin-block-end:12px; }
.faq__all svg { transition:transform 260ms cubic-bezier(0.22,1,0.36,1); }
.faq__all[aria-expanded="true"] svg { transform:rotate(180deg); }

.faq__foot { margin:0; padding:0 16px 16px; }
/* 🔴 QUIET, NOT FADED.
   The first version dimmed this to `opacity:.55` at rest and brought it back on
   hover. That is a contrast failure with extra steps: the audit composites what
   is actually on screen, and tertiary ink at 55% over a pale pane is nowhere
   near 4.5:1. Recessing a control is a job for COLOUR — one step down the ink
   ramp, which is a value that was chosen to pass — never for opacity. */
.faq__perma {
  block-size:26px; padding-inline:10px;
  color:var(--lav-text-tertiary); border-color:transparent;
}
.faq__perma:hover { color:var(--lav-text-primary); border-color:var(--glass-line); }

/* — anchors clear the two sticky bars ————————————————————————————————————
   The topbar is 60px and the jump bar under it is about 48px. Without this, a
   link to `#faq` lands with the section heading behind both of them, which
   reads as the link having gone to the wrong place. `.sec` is a home-page class
   (every use of it is a `template-parts/section-*.php`), so this is scoped by
   construction rather than by a selector. */
.sec, .faq__item, .lav-sec, .lav-faq__item, .pkg, .segpanel {
  scroll-margin-block-start:calc(var(--lq-jump-top,60px) + 56px);
}

/* ==========================================================================
   10 · FOCUS
   --------------------------------------------------------------------------
   Every control this file introduces takes the same ring. Stated once here
   rather than per component, because the one thing worse than no focus ring is
   four different ones.
   ========================================================================== */
.seg:focus-visible,
.lq-copy:focus-visible,
.lq-top:focus-visible,
.lq-filter__clear:focus-visible {
  outline:2px solid var(--lav-cta,#0439D9);
  outline-offset:2px;
}

/* ==========================================================================
   11 · THE FLOATING TAB BAR
   --------------------------------------------------------------------------
   The bar used to be welded to the bottom edge: full-bleed, square, with a
   hairline along its top. That is the 2014 pattern, and it has one thing wrong
   with it that is not a matter of taste — an edge-to-edge bar reads as the end
   of the window, so the content above it looks cut off rather than scrolled.

   Detached, it reads as an object floating OVER the page, which is what it is:
   the page keeps going underneath, and the shadow says so. It is also the one
   surface on this site where the material is doing its real job, because
   arbitrary page content genuinely slides beneath it — which is why it keeps
   the `backdrop-filter` it has always had. It is small, it is pinned, and it is
   one of the two places the rule in the header of this file allows one.

   Geometry, all of it from the spacing grid: 12px in from each side, 12px up
   from the safe area, pill radius. The safe-area inset is added rather than
   substituted so the bar clears the home indicator on a notched phone and sits
   at 12px on everything else.
   ========================================================================== */
@media (max-width:860px) {
  .tabbar {
    inset-inline:12px;
    inset-block-end:calc(12px + env(safe-area-inset-bottom));
    padding-block-end:0;
    border:1px solid var(--glass-line);
    border-radius:var(--lav-radius-pill,999px);
    /* Dense, for the same reason as the jump bar: the page runs underneath it.
       See section 6b — this is the second of the two surfaces on the site that
       content slides beneath, and the only one that keeps a backdrop-filter. */
    background-color:var(--lq-bar-fill);
    background-image:var(--glass-sheen);
    box-shadow:var(--glass-rim), var(--glass-cast-lift);
    overflow:hidden;
  }
  .tabbar__list { padding:6px; gap:2px; }

  /* 🔴 The active marker changes shape, not just position.

     The old marker was a 2px rule pinned to the top edge of the bar. On a
     rounded, floating bar that rule sits inside the curve and reads as damage.
     A filled pill behind the whole tab replaces it — and it still satisfies the
     reason the rule existed in the first place, which was never decoration: the
     active tab must be distinguishable by something other than colour alone.
     A filled shape is that something, and it is a stronger one. */
  .tabbar__list > li > .tab {
    min-height:46px;
    border-radius:var(--lav-radius-pill,999px);
    transition:background-color 260ms cubic-bezier(0.22,1,0.36,1),
               color 260ms cubic-bezier(0.22,1,0.36,1);
  }
  .tabbar__list > li > .tab[aria-current="page"]::before { content:none; }
  .tabbar__list > li > .tab[aria-current="page"] {
    background-color:var(--lav-cta,#0439D9);
  }
  :root[data-theme="light"] .tabbar__list > li > .tab[aria-current="page"],
  .tabbar__list > li > .tab[aria-current="page"] { color:var(--lav-cta-ink,#fff); }
  .tabbar__list > li > .tab:active { transform:scale(.95); }

  /* Clearance under the page: the bar's own height, plus the 12px it floats by,
     plus the safe area, plus 12px so the last line of the page is not tucked
     right under it. */
  body { padding-block-end:calc(82px + env(safe-area-inset-bottom)); }
  body:has(.tabbar) .sidebar { margin-block-end:calc(88px + env(safe-area-inset-bottom)); }

  /* Back-to-top clears the floating bar rather than the old flush one. */
  .lq-top { inset-block-end:calc(84px + env(safe-area-inset-bottom)); inset-inline-end:16px; }

  /* — TOUCH FEEDBACK ————————————————————————————————————————————————————
     A phone has no hover, so every hover state written for the desktop is a
     state that never happens here. What a native app gives instead is a press:
     the thing you are touching moves under your finger and comes back. It is
     the only confirmation a touch device can offer before the navigation
     happens, and without it a tap on a card feels like a tap on a picture of
     a card.

     `transform` only, 120ms out and back on the platform's own spring, and no
     layout is touched — a scale is the one visual change that cannot reflow
     anything around it. `-webkit-tap-highlight-color` goes with it: the grey
     box Android paints over the element would fight the animation and land a
     frame late. */
  .bento__link, .ccard, .b-card, .card, .path, .pillar, .svc-card,
  .jump__link, .faq__q, .btn-solid, .lq-copy, .seg {
    -webkit-tap-highlight-color:transparent;
    transition:transform 120ms cubic-bezier(0.34,1.56,0.64,1),
               border-color 260ms cubic-bezier(0.22,1,0.36,1),
               background-color 260ms cubic-bezier(0.22,1,0.36,1);
  }
  .bento__link:active, .ccard:active, .b-card:active, .card:active,
  .path:active, .pillar:active, .svc-card:active, .faq__q:active {
    transform:scale(.975);
  }
  .jump__link:active, .lq-copy:active, .seg:active { transform:scale(.94); }

  /* The specular has no pointer to follow on a touch screen, so the two
     hover-only layers never appear. Stated rather than left implicit: they
     cost nothing when they never open, and it is the reason the pointer
     handler in liquid.js checks `hover: hover` before it binds anything. */
  .lq::after, .lq::before { opacity:0; }
  .lq--flow::before { opacity:1; }
}

/* ==========================================================================
   11a · TAP TARGETS THE AUDIT FOUND ON PAGES NOBODY HAD MEASURED
   --------------------------------------------------------------------------
   "Forgot password?" was 104x20 — under the 24px floor of WCAG 2.5.8, and not
   an inline link inside a sentence, so the exemption does not apply. It is also
   the one control on that page a person reaches for while already annoyed.

   A `min-block-size` with `inline-flex` grows the target without moving the
   text: the link keeps its baseline next to the "Remember me" checkbox beside
   it, and gains the missing four pixels above and below.
   ========================================================================== */
.la-link,
.lav-auth .la-link {
  display:inline-flex; align-items:center; min-block-size:26px;
}

/* 🔴 AND THE ONE THE AUDIT COULD NOT MEASURE, WHICH IS WHY IT SURVIVED.
   The inner footer's two-line buttons set their small top line as
   `rgba(233,237,247,.5)` — near-white ink at half alpha. The audit reports
   semi-transparent TEXT as "unmeasured" rather than as a failure, because it
   cannot composite an alpha it does not know the backdrop of, so this sat
   through every previous pass as a warning nobody chased. Computed by hand
   against the dark glass under it, it lands at about 4.5:1 — a label inside a
   button, sitting exactly on the line.
   The fix is not a higher alpha, it is no alpha: `--lav-text-tertiary` is a
   value that was chosen against these surfaces and measured. */
.app-btn .l1 { color:var(--lav-text-tertiary); }

/* ==========================================================================
   11b · SERVICE RESULTS IN THE BLOG CARD
   --------------------------------------------------------------------------
   Site search returns `bnrt_service` results now, rendered through the blog
   card. A service has no featured image, so the media slot carries the
   service's own icon on the glass fill instead of a placeholder logo — the same
   icon that labels it on `/services/` and in the studio band, so the result is
   recognisable before the title is read.
   ========================================================================== */
.pthumb.is-svc {
  display:grid; place-items:center;
  background-color:var(--glass-fill-strong); background-image:var(--glass-sheen);
}
.pthumb__icon { display:grid; place-items:center; color:var(--lav-cta,#0439D9); }
.pthumb__icon svg { inline-size:40px; block-size:40px; }
.bpost--svc .pfoot .read { font-weight:700; color:var(--lav-text-primary); }

/* ==========================================================================
   12 · ONE DISPLAY FACE
   --------------------------------------------------------------------------
   🔴 THE SITE WAS SETTING ITS HEADINGS IN TWO DIFFERENT TYPEFACES DEPENDING ON
   WHICH PAGE YOU WERE ON.

   The marketplace home renders headings in Clash Display, via
   `--lav-font-display`. Every other template renders them in Oswald, via a
   separate variable — `--display`, declared in `assets/css/main.css` and in
   `assets/css/sections/global.root.css`, both inherited from the theme this one
   grew out of. Click "Shop" and the type changes.

   That is the same fault the fourth round of the home rebuild spent its time
   on, at a larger scale: nineteen Unicode glyphs from six blocks were removed
   because mixing sets is the most reliable way to make an interface look
   unowned. Two display faces is that, in the one element on a page that carries
   the most weight.

   The repair is one variable, not a hunt through selectors. Everything on the
   inner pages already routes through `--display`; pointing it at the brand
   stack moves all of them at once, and any component added later inherits the
   fix rather than needing it. `--sans` goes the same way, off Inter and onto
   the Satoshi the rest of the site reads in.

   It is restated on the four subtrees that re-declare tokens locally
   (bineret-brand.css §3): `:root` alone does not reach inside them.

   Vazirmatn stays in both stacks. It is the Persian fallback, and dropping it
   would silently degrade every non-Latin string on the site to a default face.
   ========================================================================== */
:root,
.app,
.main,
.topbar,
.sidebar {
  --display:"Clash Display","Satoshi","Vazirmatn",-apple-system,BlinkMacSystemFont,"SF Pro Display","Segoe UI",system-ui,sans-serif;
  --sans:"Satoshi","Vazirmatn",-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI",system-ui,sans-serif;
}

/* ==========================================================================
   12b · THE WORK PAGES
   --------------------------------------------------------------------------
   Built out of the services page's components — `.lav-sec`, `.lav-wrap`,
   `.svc-card`, `.glass` — so the studio's two halves read as one place. What is
   here is only the handful of objects a case study needs and a service page
   does not: the paired before/after figure, the decision block with its
   trade-off, and the results table.

   The results TABLE is a table on purpose. These are paired numbers sharing a
   unit of comparison, which is what tables are for, and it is the only markup
   that makes a screen reader announce "Internal links, before 40, after 207"
   instead of three unrelated numbers in a row.
   ========================================================================== */

/* `auto-fill`, not `auto-fit`, and the difference matters at exactly the size
   this page is today. `auto-fit` collapses the empty tracks, so a single case
   study would stretch to the full 1240px column — a 1,240px-wide card holding
   four lines of text. `auto-fill` keeps the empty tracks, so the first study
   sits at a card's width and the grid fills in behind it as more are written. */
.work-grid { grid-template-columns:repeat(auto-fill,minmax(340px,1fr)); }

/* — the before → after pair, on a card ——————————————————————————————————— */
.work-card__client {
  margin:0; font-size:var(--fs-caption,0.8125rem); color:var(--lav-text-tertiary);
}
.work-card__figs {
  list-style:none; margin:12px 0 0; padding:0;
  display:flex; flex-direction:column; gap:8px;
}
.work-card__figs li { display:flex; flex-direction:column; gap:2px; }
.work-card__fig { display:flex; align-items:baseline; gap:6px; flex-wrap:wrap; }
/* The BEFORE is struck through and recessed, the AFTER carries the weight.
   Two treatments rather than one colour, so the direction of the change
   survives being read in greyscale or by somebody who cannot see the green /
   blue distinction most dashboards rely on. */
.work-card__was {
  font-size:var(--fs-caption,0.8125rem); color:var(--lav-text-tertiary);
  text-decoration:line-through; text-decoration-thickness:1px;
}
.work-card__to { color:var(--lav-text-tertiary); font-size:var(--fs-caption,0.8125rem); }
.work-card__now {
  font-family:var(--lav-font-display); font-weight:700;
  font-size:1.05rem; color:var(--lav-cta,#0439D9); line-height:1.1;
}
.work-card__lab {
  font-size:var(--fs-caption,0.8125rem); color:var(--lav-text-secondary); line-height:1.4;
}

/* — the single page ——————————————————————————————————————————————————— */
.work-crumb { margin:0 0 12px; font-size:var(--fs-caption,0.8125rem); color:var(--lav-text-tertiary); }
.work-crumb a { color:var(--lav-text-secondary); text-decoration:none; }
.work-crumb a:hover { color:var(--lav-cta,#0439D9); }
.work-crumb span { margin-inline:6px; }

.work-headline {
  margin:12px 0 0; max-inline-size:34ch;
  font-family:var(--lav-font-display); font-weight:600;
  font-size:clamp(1.25rem,1.05rem + .9vw,1.6rem); line-height:1.25;
  color:var(--lav-cta,#0439D9);
}

.work-facts {
  margin:24px 0 0; padding:16px; border-radius:14px;
  display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:16px;
}
.work-fact dt {
  margin:0 0 4px; font-size:var(--fs-caption,0.8125rem);
  text-transform:uppercase; letter-spacing:var(--lav-track-wide,.06em);
  color:var(--lav-text-tertiary);
}
.work-fact dd { margin:0; font-size:var(--fs-label,0.875rem); color:var(--lav-text-primary); line-height:1.45; }

.work-live { margin:24px 0 0; display:flex; align-items:center; gap:16px; flex-wrap:wrap; }
.work-live__note { font-size:var(--fs-caption,0.8125rem); color:var(--lav-text-tertiary); }

.work-h2 {
  margin:0 0 8px; font-family:var(--lav-font-display); font-weight:600;
  font-size:var(--fs-h2,clamp(1.5rem,1.3rem + .9vw,1.875rem)); line-height:1.15;
}
.work-lede { margin:0 0 24px; max-inline-size:62ch; color:var(--lav-text-secondary); font-size:var(--fs-label,0.875rem); line-height:1.6; }
.work-body { margin:0; max-inline-size:62ch; color:var(--lav-text-secondary); font-size:var(--fs-body,1rem); line-height:1.7; }

.work-decisions {
  list-style:none; margin:0; padding:0; display:grid; gap:16px;
  grid-template-columns:repeat(auto-fit,minmax(300px,1fr));
}
.work-decision { position:relative; padding:24px; border-radius:14px; }
.work-decision__n {
  display:grid; place-items:center; inline-size:24px; block-size:24px;
  border-radius:50%; margin-block-end:12px;
  border:1px solid var(--glass-line-strong); color:var(--lav-cta,#0439D9);
  font-size:var(--fs-caption,0.8125rem); font-weight:700;
}
.work-decision__t { margin:0 0 8px; font-size:var(--fs-body,1rem); font-weight:600; line-height:1.3; }
.work-decision__b { margin:0; color:var(--lav-text-secondary); font-size:var(--fs-label,0.875rem); line-height:1.6; }
/* The trade-off is set apart with a rule and a label rather than a colour: it
   is the part of the block that must not be skimmed past. */
.work-decision__c {
  margin:12px 0 0; padding-block-start:12px;
  border-block-start:1px solid var(--glass-line);
  color:var(--lav-text-tertiary); font-size:var(--fs-label,0.875rem); line-height:1.6;
}
.work-decision__ck {
  display:block; margin-block-end:4px;
  font-size:var(--fs-caption,0.8125rem); font-weight:700;
  text-transform:uppercase; letter-spacing:var(--lav-track-wide,.06em);
  color:var(--lav-text-secondary);
}

/* The table scrolls inside its own box rather than widening the page — the one
   thing the audit checks on every view is that the document never scrolls
   sideways. */
.work-results-wrap { overflow-x:auto; -webkit-overflow-scrolling:touch; border-radius:14px; }
.work-results { inline-size:100%; border-collapse:collapse; border-radius:14px; overflow:hidden; }
.work-results th, .work-results td { padding:12px 16px; text-align:start; vertical-align:top; }
.work-results thead th {
  font-size:var(--fs-caption,0.8125rem); font-weight:700;
  text-transform:uppercase; letter-spacing:var(--lav-track-wide,.06em);
  color:var(--lav-text-tertiary);
  border-block-end:1px solid var(--glass-line-strong);
  white-space:nowrap;
}
.work-results tbody th {
  font-weight:500; font-size:var(--fs-label,0.875rem);
  color:var(--lav-text-secondary); line-height:1.5; max-inline-size:34ch;
}
.work-results tbody tr + tr th,
.work-results tbody tr + tr td { border-block-start:1px solid var(--glass-line); }
.work-results__was {
  font-size:var(--fs-label,0.875rem); color:var(--lav-text-tertiary);
  text-decoration:line-through; text-decoration-thickness:1px; white-space:nowrap;
}
.work-results__now {
  font-family:var(--lav-font-display); font-weight:700;
  font-size:var(--fs-body,1rem); color:var(--lav-cta,#0439D9); white-space:nowrap;
}

.work-quote { padding:24px; border-radius:22px; margin:0; }
.work-quote blockquote { margin:0; }
.work-quote blockquote p {
  margin:0; font-size:var(--fs-lead,1.125rem); line-height:1.6;
  color:var(--lav-text-primary);
}
.work-quote figcaption { margin-block-start:16px; display:flex; flex-direction:column; gap:2px; }
.work-quote__by { font-weight:600; font-size:var(--fs-label,0.875rem); }
.work-quote__role { font-size:var(--fs-caption,0.8125rem); color:var(--lav-text-tertiary); }

.work-stack { list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:8px; }
.work-stack li {
  padding:6px 12px; border-radius:var(--lav-radius-pill,999px);
  border:1px solid var(--glass-line); background-color:var(--glass-fill);
  font-size:var(--fs-caption,0.8125rem); color:var(--lav-text-secondary);
}

.work-empty { padding:32px; border-radius:22px; text-align:center; }
.work-empty svg { color:var(--lav-cta,#0439D9); margin-inline:auto; }
.work-empty h2 { margin:16px 0 8px; font-family:var(--lav-font-display); font-weight:600; font-size:var(--fs-title,1.375rem); }
.work-empty p { margin:0 auto 24px; max-inline-size:56ch; color:var(--lav-text-secondary); font-size:var(--fs-label,0.875rem); line-height:1.6; }

@media (max-width:700px) {
  .work-decisions { grid-template-columns:1fr; }
  .work-results th, .work-results td { padding:12px; }
}

/* ==========================================================================
   13 · REDUCED MOTION
   --------------------------------------------------------------------------
   Everything above turns off. Not "turns down": the drift stops, the reveal
   never hides anything in the first place, the indicator jumps instead of
   sliding, and the pointer specular stays — it is a static gradient that
   appears on hover, which is a state change rather than motion, and removing it
   would take away the material rather than the movement.

   The brand layer already collapses every transition to 0.001ms globally under
   the same query. This block exists for the two things that query cannot reach:
   an infinite animation (which it shortens rather than stops) and an element
   that starts life invisible waiting for a script.
   ========================================================================== */
@media (prefers-reduced-motion:reduce) {
  .lq--flow::before { animation:none; transform:none; }
  .lq-hide { opacity:1; transform:none; }
  .lq-in { transition:none; }
  .segpanel { animation:none; }
  .lq-top { transition:opacity 1ms linear, visibility 0s linear 1ms; }
}
