/* ===========================================================================
   InCode Label — "NOCTURNE / DAYLIGHT"
   ---------------------------------------------------------------------------
   A white-and-cyan site that goes dark in the places where going dark means
   something.

   The brief, in the owner's words: white and cyan is the main combination,
   with dark stretches in between, and the whole thing cinematic. Those two
   things sound like they fight. They do not — this is exactly how a good
   showroom is lit. The walls are white. The product sits under a lamp. Two
   or three moments in the room are deliberately in shadow so the lit things
   read as lit. A page that is dark everywhere has no contrast left to spend;
   a page that is white everywhere has no drama. This one alternates.

   THE ARCHITECTURE, and it is worth understanding before editing anything:

     The palette is a set of custom properties on :root — --paper, --ink,
     --line, --cyan and so on. Every component below is written in terms of
     those names and NEVER in terms of a literal colour.

     A dark band is then one thing only: an element that RE-DECLARES the
     same property names with dark values. Custom properties inherit, so
     every card, button, chip, input and heading inside that element picks
     up its dark form automatically, with not one duplicated rule and no
     `.card--dark` variants to keep in sync. Add `class="dark"` to a section
     and it becomes a cinematic band. Remove it and it is white again.

     There is one exception on purpose: --plate, the near-white surface that
     product photographs sit on, does NOT flip. A white label photographed
     on white must be shown on white in both modes, or the buyer cannot
     judge the stock. Inside a dark band that plate becomes a lit sample
     tray, which is the best the product has ever looked.

   THE RHYTHM as shipped:
     home        dark hero → white features → soft categories →
                 DARK gallery band → soft order band → dark footer
     inner pages white hero → white content → DARK closing band →
                 dark footer
     detail      white page, DARK order panel (the thing the page is for)
     contact     white form, DARK contact card beside it
     admin       light throughout — it is a desk, not a showroom.
                 See the note at the top of admin.html.

   Every class the pages and scripts rely on is preserved.
   ========================================================================= */

/* ── The daylight palette ────────────────────────────────────────────── */
:root{
  --paper:#FFFFFF;
  --soft:#F3F9FB;
  --soft-2:#E7F3F7;

  /* The plate. Deliberately NOT flipped by dark bands — see the note above.
     Not pure white either: a #FFF slab inside a dark band is a glare
     source, and this is the largest element on a product card. */
  --plate:#EFF6F8;
  --plate-2:#DAE8ED;

  --ink:#06171F;      /* headings                */
  --ink-2:#2E4550;    /* body copy               */
  --ink-3:#56727E;    /* secondary copy          */
  --muted:#6C8894;    /* captions, timestamps    */

  --line:#DCE9ED;
  --line-strong:#BCD6DE;

  --cyan:#0797AE;         /* the working ink                              */
  --cyan-ink:#046074;     /* cyan that must be READ on this ground        */
  --cyan-deep:#046074;
  --cyan-bright:#12C4DE;  /* cyan used as light                           */
  --cyan-light:#7FE6F5;
  --cyan-wash:#E6F8FB;

  /* Surfaces. Components reference these instead of picking a colour, so a
     dark band restyles all of them at once. */
  --card-bg:#FFFFFF;
  --card-bg-2:#FFFFFF;
  --card-border:var(--line);
  --field-bg:#FFFFFF;
  --chip-bg:var(--soft);
  --glass:none;

  /* The primary button. Ink-dark on white; it becomes a cyan lamp on dark. */
  --btn-bg:linear-gradient(180deg,#0D2531,#06171F);
  --btn-fg:#FFFFFF;
  --btn-border:transparent;
  --btn-shadow:0 12px 26px -16px rgba(6,23,31,.7);
  --btn-shadow-hover:0 18px 34px -16px rgba(6,23,31,.8);

  /* Text that sits ON a cyan fill. Cyan is a light hue: white on it fails
     contrast, so it is dark ink — the choice a road sign makes. */
  --on-cyan:#02171C;

  /* Glow is a token so daylight can switch it off entirely. Nothing on a
     white ground should be emitting light; that is what makes a light
     theme look cheap. */
  --glow:none;
  --glow-ring:none;
  --text-glow:none;

  --shadow:0 1px 2px rgba(6,23,31,.045), 0 12px 26px -18px rgba(6,23,31,.28);
  --shadow-lg:0 2px 5px rgba(6,23,31,.05), 0 30px 60px -32px rgba(6,23,31,.34);
  --shadow-cyan:0 14px 32px -16px rgba(7,151,174,.5);
  --ring:0 0 0 3px rgba(7,151,174,.24);

  /* admin.html consumes these four, so the names stay. */
  --grad:linear-gradient(90deg,var(--cyan-deep),var(--cyan));
  --grad-line:linear-gradient(90deg,transparent,var(--cyan-bright),transparent);
  --grad-ink:linear-gradient(165deg,#0E2531 0%,#08161E 55%,#050F15 100%);

  /* Film grain, at two strengths. This is the single ingredient that makes
     a large dark area read as a lit room rather than a flat rectangle. */
  --grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.4'/%3E%3C/svg%3E");
  --grain-soft:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.09'/%3E%3C/svg%3E");

  --ease:cubic-bezier(.2,.7,.2,1);
  --ease-out:cubic-bezier(.16,1,.3,1);
  --ease-soft:cubic-bezier(.4,.1,.2,1);

  --font-display:'Roboto Condensed','Roboto',system-ui,-apple-system,'Segoe UI',sans-serif;
  --font-body:'Roboto',system-ui,-apple-system,'Segoe UI',Helvetica,Arial,sans-serif;
  --font-mono:ui-monospace,'SFMono-Regular',Menlo,Consolas,monospace;

  --radius-sm:4px;
  --radius:8px;
  --radius-lg:14px;
  --radius-xl:20px;

  --shell:1720px;
  --gut:clamp(20px,4.5vw,54px);

  /* Written by JavaScript at runtime; declared so the values a visitor sees
     with JS off are still deliberate ones.
       --mx / --my : the pointer inside a .has-spot band, as a percentage
       --i         : a word's place in the kinetic headline */
  --mx:50%;
  --my:50%;
  --i:0;
}

/* ── The dark bands ──────────────────────────────────────────────────
   ONE declaration block. Everything inside any of these elements — cards,
   buttons, inputs, chips, headings, hairlines — becomes its dark form
   because it was written against these names in the first place.

   To make a new section cinematic, add `class="dark"` to it in the HTML.
   That is the whole procedure. */
.hero,
.section.dark,
.order-panel,
.contact-note,
.chat-head,
footer{
  --paper:#0A1922;
  --soft:rgba(255,255,255,.055);
  --soft-2:rgba(255,255,255,.09);

  --ink:#FFFFFF;
  --ink-2:#A9C6D1;
  --ink-3:#8DAEBA;
  --muted:#7699A6;

  --line:rgba(127,230,245,.15);
  --line-strong:rgba(127,230,245,.32);

  --cyan:#12C4DE;
  --cyan-ink:#4EE2F5;      /* cyan text has to be brighter to be read here */
  --cyan-bright:#4EE2F5;
  --cyan-light:#9DEFFB;
  --cyan-wash:rgba(18,196,222,.12);

  --card-bg:linear-gradient(168deg,rgba(255,255,255,.075),rgba(255,255,255,.025));
  --card-bg-2:rgba(255,255,255,.05);
  --card-border:rgba(127,230,245,.15);
  --field-bg:rgba(255,255,255,.05);
  --chip-bg:rgba(255,255,255,.05);
  --glass:blur(14px);

  --btn-bg:linear-gradient(180deg,#4EE2F5,#12C4DE);
  --btn-fg:#02171C;
  --btn-border:rgba(157,239,251,.6);
  --btn-shadow:0 12px 38px -14px rgba(18,196,222,.75), 0 0 0 1px rgba(18,196,222,.3);
  --btn-shadow-hover:0 18px 54px -12px rgba(18,196,222,.9), 0 0 0 1px rgba(157,239,251,.55);

  --glow:0 0 60px -18px rgba(18,196,222,.5);
  --glow-ring:0 0 0 1px rgba(18,196,222,.28);
  --text-glow:0 0 22px rgba(18,196,222,.4);

  --shadow:0 1px 0 rgba(255,255,255,.05) inset, 0 20px 44px -28px rgba(0,0,0,.9);
  --shadow-lg:0 1px 0 rgba(255,255,255,.06) inset, 0 44px 96px -42px rgba(0,0,0,.95);
  --shadow-cyan:0 12px 44px -12px rgba(18,196,222,.6), 0 0 0 1px rgba(18,196,222,.35);
  --ring:0 0 0 3px rgba(18,196,222,.32);
}

/* The dark ground itself: grain on top, two cyan lights behind it, deep ink
   beneath. Written as one background stack rather than pseudo-elements,
   because several of these hosts already use ::before and ::after for the
   registration ticks, the seam and the corner marks. */
.hero,
.section.dark,
footer{
  background-color:#071219;
  background-image:
    var(--grain-soft),
    radial-gradient(1100px 620px at 82% -18%, rgba(18,196,222,.20), transparent 62%),
    radial-gradient(880px 560px at 4% 108%,  rgba(4,96,116,.34),  transparent 62%),
    linear-gradient(178deg,#0B1B24 0%,#071219 58%,#050E14 100%);
  background-size:160px 160px, auto, auto, auto;
  background-repeat:repeat, no-repeat, no-repeat, no-repeat;
  color:var(--ink-2);
}

/* ── Reset ───────────────────────────────────────────────────────────── */
*{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;background:var(--paper)}
body{
  margin:0;background:var(--paper);color:var(--ink-2);
  font-family:var(--font-body);font-size:16.5px;line-height:1.75;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
img{max-width:100%;display:block}
a{color:var(--cyan-ink);text-decoration:none}
button{font:inherit}
:focus-visible{outline:2px solid var(--cyan);outline-offset:3px;border-radius:var(--radius-sm)}
::selection{background:rgba(7,151,174,.22)}

.container{width:min(var(--shell),100%);margin:0 auto;padding:0 var(--gut)}

h1,h2,h3,h4{
  font-family:var(--font-display);color:var(--ink);margin:0;font-weight:700;
  letter-spacing:-.025em;line-height:1.05;text-wrap:balance;
}
h1{font-size:clamp(38px,5.4vw,74px)}
h2{font-size:clamp(28px,3.6vw,48px)}
h3{font-size:clamp(18px,1.5vw,21px);letter-spacing:-.012em;line-height:1.3}

.lead{font-size:clamp(16.5px,1.35vw,19px);color:var(--ink-3);line-height:1.75;max-width:56ch;margin:20px 0 0}
.muted{color:var(--muted);font-size:15px;line-height:1.7}
.tiny{font-size:12.5px;color:var(--muted);line-height:1.6}
.small{font-size:13px;color:var(--muted);line-height:1.6}

/* On white the accent is flat printed ink. In a dark band --text-glow
   switches on and the same word becomes a light source. One rule, both
   behaviours — nothing on a white ground should be emitting light. */
.accent{color:var(--cyan);text-shadow:var(--text-glow)}

/* The verbatim contract: exactly what the admin typed, to the space. */
.verbatim{white-space:pre-wrap;overflow-wrap:break-word;word-break:break-word}

/* ── `hidden` must actually hide ─────────────────────────────────────
   The browser's own `[hidden] { display: none }` is the weakest rule there
   is, so ANY class that sets a display beats it. Several elements here are
   shown and hidden from JavaScript with `el.hidden = true` — the chat's
   unread badge, the phone-only order bar, the chat call row, the product
   card on the contact form, the detail-page eyebrow. Each one also carries
   a class that sets display, so each one stayed on screen while its script
   believed it was hidden. The unread badge sat there reading "0".

   This is the sweep that keeps that from happening again: whatever the
   class says, `hidden` wins. */
[hidden]{display:none !important}

/* ── Scroll progress ─────────────────────────────────────────────────── */
.scroll-rail{
  position:fixed;left:0;top:0;height:2px;width:100%;z-index:100;
  pointer-events:none;background:rgba(7,151,174,.08);
}
.scroll-rail i{
  display:block;height:100%;width:0;
  background:linear-gradient(90deg,var(--cyan-deep),#12C4DE);
}

/* ── The pointer light ───────────────────────────────────────────────
   Behind the words, never over them — a wash that tints text is the one
   thing a light must not do. Every host sets isolation:isolate, so this
   negative layer can never escape its own band. It is stronger inside a
   dark band, where there is room for it to be seen. */
.spotlight{
  position:absolute;inset:0;pointer-events:none;z-index:-1;
  opacity:0;transition:opacity .6s var(--ease);
  background:radial-gradient(480px circle at var(--mx,50%) var(--my,50%),
    rgba(7,151,174,.09), transparent 70%);
}
.hero .spotlight,.section.dark .spotlight,footer .spotlight{
  background:radial-gradient(520px circle at var(--mx,50%) var(--my,50%),
    rgba(18,196,222,.15), transparent 68%);
}
.has-spot:hover .spotlight{opacity:1}

/* ── Sections ────────────────────────────────────────────────────────
   Divided by registration ticks — the marks a press prints in the margin
   to line up a run. Borrowed from the customer's own product rather than
   invented as decoration. Under them, a light seam that brightens in the
   middle and fades at both ends. */
.section{padding:clamp(66px,7vw,108px) 0;position:relative;isolation:isolate}
.section.tight{padding:clamp(44px,5vw,70px) 0}
.section::before{
  content:"";position:absolute;left:0;right:0;top:0;height:8px;
  pointer-events:none;opacity:.45;
  background-image:repeating-linear-gradient(90deg,
    var(--line-strong) 0 1px, transparent 1px 34px);
  mask-image:linear-gradient(90deg,transparent,#000 16%,#000 84%,transparent);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 16%,#000 84%,transparent);
}
.section + .section::after{
  content:"";position:absolute;left:0;right:0;top:0;height:1px;
  pointer-events:none;background:var(--grad-line);opacity:.45;
}
.section.soft{background:var(--soft)}
/* A dark band paints its own ground (above), so it must not also inherit
   the soft wash, and its ticks need to be lit rather than printed. */
.section.dark.soft{background-image:
  var(--grain-soft),
  radial-gradient(1100px 620px at 82% -18%, rgba(18,196,222,.20), transparent 62%),
  radial-gradient(880px 560px at 4% 108%,  rgba(4,96,116,.34),  transparent 62%),
  linear-gradient(178deg,#0B1B24 0%,#071219 58%,#050E14 100%);
}

.section-head{
  display:flex;align-items:flex-end;justify-content:space-between;gap:28px;
  flex-wrap:wrap;margin-bottom:clamp(32px,4vw,54px);
}
.section-head h2{margin-top:12px}

/* An eyebrow is the plate number on a specimen sheet. */
.eyebrow{
  display:inline-flex;align-items:center;gap:10px;
  font-family:var(--font-mono);
  font-size:11px;font-weight:700;letter-spacing:.22em;text-transform:uppercase;
  color:var(--cyan-ink);
}
.eyebrow::before{content:"";width:18px;height:1px;flex:none;background:var(--cyan)}
.hero .eyebrow::before,.section.dark .eyebrow::before,footer .eyebrow::before{
  box-shadow:0 0 10px rgba(78,226,245,.9);
}

.eyebrow-pill{
  display:inline-flex;align-items:center;gap:10px;
  padding:7px 15px;border:1px solid var(--line-strong);
  border-radius:999px;background:var(--card-bg-2);
  font-family:var(--font-mono);
  font-size:10.5px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;
  color:var(--cyan-ink);
}
.eyebrow-pill::before{content:"";width:14px;height:1px;flex:none;background:var(--cyan)}
.hero .eyebrow-pill::before{box-shadow:0 0 10px rgba(78,226,245,.9)}

/* ── Buttons ─────────────────────────────────────────────────────────── */
.btn,.btn-call{
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  padding:15px 28px;border-radius:var(--radius);border:1px solid transparent;
  font-family:var(--font-display);
  font-size:14.5px;font-weight:700;letter-spacing:.02em;
  cursor:pointer;text-align:center;position:relative;overflow:hidden;
  transition:background .24s var(--ease),color .24s var(--ease),
             border-color .24s var(--ease),box-shadow .3s var(--ease),
             transform .24s var(--ease);
}
/* A single sweep of light crosses the button on hover. One pass, not a
   loop — a loop is a novelty, a single pass is a material. */
.btn::after,.btn-call::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(105deg,transparent 30%,rgba(255,255,255,.28) 50%,transparent 70%);
  transform:translateX(-140%);
}
.btn:hover::after,.btn-call:hover::after{transition:transform .75s var(--ease-out);transform:translateX(140%)}

.btn.primary{background:var(--btn-bg);color:var(--btn-fg);border-color:var(--btn-border);box-shadow:var(--btn-shadow)}
.btn.primary:hover{transform:translateY(-2px);box-shadow:var(--btn-shadow-hover)}
.btn.ghost{
  background:var(--card-bg-2);color:var(--ink);border-color:var(--line-strong);
  backdrop-filter:var(--glass);-webkit-backdrop-filter:var(--glass);
}
.btn.ghost:hover{border-color:var(--cyan);color:var(--cyan-ink);background:var(--cyan-wash)}
.btn.on-ink{background:transparent;color:var(--cyan-light);border-color:rgba(127,230,245,.38)}
.btn.on-ink:hover{background:rgba(127,230,245,.13);border-color:var(--cyan-bright);color:#fff}
.btn.block{width:100%}

.btn-call{
  background:var(--cyan-wash);color:var(--cyan-ink);
  border-color:var(--cyan);
  backdrop-filter:var(--glass);-webkit-backdrop-filter:var(--glass);
}
.btn-call:hover{
  background:var(--cyan);color:#fff;border-color:var(--cyan);
  transform:translateY(-2px);box-shadow:var(--shadow-cyan);
}
.hero .btn-call:hover,.section.dark .btn-call:hover,footer .btn-call:hover{color:var(--on-cyan)}
.btn-call svg{flex:none}

/* ── Header ──────────────────────────────────────────────────────────
   Frosted white glass. It sits over a DARK hero on the home page and over
   white on every other page, which is why it is translucent rather than
   solid: it takes the colour of whatever is passing under it, and only
   commits to opaque white once you have scrolled far enough that there is
   real text underneath needing separation. */
.site-header{
  position:sticky;top:0;z-index:60;
  background:rgba(255,255,255,.72);
  backdrop-filter:blur(22px) saturate(180%);
  -webkit-backdrop-filter:blur(22px) saturate(180%);
  border-bottom:1px solid rgba(220,233,237,.7);
  transition:background .4s var(--ease),border-color .4s var(--ease),box-shadow .4s var(--ease);
}
.site-header.is-stuck{
  background:rgba(255,255,255,.94);
  border-bottom-color:var(--line-strong);
  box-shadow:0 14px 30px -26px rgba(6,23,31,.55);
}
.site-header .nav{display:flex;align-items:center;justify-content:space-between;gap:26px;height:76px}

/* The brand is the word. No mark anywhere on the site; the only picture
   identity is the browser-tab favicon. */
.brand{
  display:inline-flex;align-items:center;
  font-family:var(--font-display);
  font-size:24px;font-weight:700;letter-spacing:-.03em;color:var(--ink);
}
.brand .accent{color:var(--cyan)}

.site-header nav{display:flex;gap:2px;align-items:center}
.site-header nav a{
  position:relative;padding:10px 16px;border-radius:var(--radius-sm);
  font-family:var(--font-display);
  font-size:14.5px;font-weight:600;color:var(--ink-2);
  transition:color .22s var(--ease),background .22s var(--ease);
}
.site-header nav a::after{
  content:"";position:absolute;left:16px;right:16px;bottom:4px;height:2px;
  background:var(--cyan);transform:scaleX(0);transform-origin:left;
  transition:transform .32s var(--ease);
}
.site-header nav a:hover{color:var(--ink);background:rgba(7,151,174,.06)}
.site-header nav a:hover::after{transform:scaleX(1)}

.menu{
  display:none;width:42px;height:42px;border:1px solid var(--line-strong);
  border-radius:var(--radius);background:var(--paper);
  font-size:17px;color:var(--ink);cursor:pointer;line-height:1;
}

/* ── Hero — the one fully cinematic stage ────────────────────────────── */
.hero{
  position:relative;overflow:hidden;isolation:isolate;
  padding:clamp(74px,7.5vw,124px) 0 clamp(74px,7.5vw,124px);
}
.hero-bg{position:absolute;inset:0;z-index:-2;pointer-events:none;overflow:hidden}
/* A perspective floor grid fading up into the dark. It gives the hero a
   FLOOR, which is what makes the specimen card read as standing in a room
   rather than floating in a void. */
.hero-bg::before{
  content:"";position:absolute;inset:0;
  background-image:
    linear-gradient(rgba(127,230,245,.06) 1px,transparent 1px),
    linear-gradient(90deg,rgba(127,230,245,.06) 1px,transparent 1px);
  background-size:78px 78px;
  mask-image:radial-gradient(120% 92% at 50% 108%,#000 5%,transparent 72%);
  -webkit-mask-image:radial-gradient(120% 92% at 50% 108%,#000 5%,transparent 72%);
}
/* One slow shaft of light from the upper right, the way a single lamp falls
   across a studio. It drifts; it never visibly loops. */
.hero-bg::after{
  content:"";position:absolute;top:-40%;right:-10%;width:70%;height:180%;
  background:linear-gradient(200deg,rgba(18,196,222,.14),transparent 55%);
  transform:rotate(-14deg);filter:blur(38px);
  animation:beam 22s var(--ease-soft) infinite alternate;
}
@keyframes beam{
  from{opacity:.55;transform:rotate(-14deg) translateY(-2%)}
  to  {opacity:1;  transform:rotate(-10deg) translateY(3%)}
}

.orb{position:absolute;pointer-events:none;border-radius:50%;filter:blur(70px)}
.orb-cyan{
  left:-10%;bottom:-24%;width:min(54vw,620px);aspect-ratio:1;
  background:radial-gradient(circle,rgba(18,196,222,.28),transparent 66%);
  animation:breathe 15s var(--ease-soft) infinite alternate;
}
.orb-cyan-soft{
  right:-12%;top:-26%;width:min(48vw,560px);aspect-ratio:1;
  background:radial-gradient(circle,rgba(4,96,116,.40),transparent 68%);
  animation:breathe 19s var(--ease-soft) infinite alternate-reverse;
}
@keyframes breathe{
  from{transform:scale(1) translate3d(0,0,0);opacity:.75}
  to  {transform:scale(1.14) translate3d(2%,-3%,0);opacity:1}
}
/* Scan lines: a very faint horizontal rhythm over the hero only. This is
   the "film" in cinematic — almost invisible, and instantly missed if it
   were removed. */
.grid-lines{
  display:block;position:absolute;inset:0;z-index:-1;pointer-events:none;opacity:.5;
  background-image:repeating-linear-gradient(180deg,
    rgba(255,255,255,.026) 0 1px, transparent 1px 4px);
  mask-image:linear-gradient(180deg,transparent,#000 30%,#000 70%,transparent);
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 30%,#000 70%,transparent);
}

.hero-grid{
  display:grid;grid-template-columns:1.08fr .92fr;
  gap:clamp(36px,4.5vw,74px);align-items:center;
}
.hero h1{
  color:#fff;letter-spacing:-.035em;
  font-size:clamp(40px,5.6vw,76px);
  margin-top:26px;max-width:15ch;
}
.hero .lead{color:var(--ink-3);max-width:52ch}
.hero-actions{display:flex;gap:14px;flex-wrap:wrap;margin-top:36px}

/* Kinetic headline — each word rises once, in sequence. */
.kin{
  display:inline-block;overflow:hidden;vertical-align:bottom;
  /* The mask that hides a word before it rises is this box's own overflow,
     clipped exactly at the baseline — which shears the tail off every g, y
     and p. Pad down, then pull the same amount back out of the line box so
     the leading is untouched. */
  padding-bottom:.14em;margin-bottom:-.14em;
}
.kin > span{
  display:inline-block;transform:translateY(105%);opacity:0;
  transition:transform .9s var(--ease-out),opacity .7s var(--ease-out);
  transition-delay:calc(var(--i,0) * 58ms);
}
.kin-go .kin > span{transform:translateY(0);opacity:1}

/* The accent word in the hero, and the bloom behind it.
   The bloom has to hang on `.accent` and NOT on `.kin`, because .kin clips:
   its `overflow:hidden` is the mask the rising-word animation needs, and a
   wide text-shadow inside a clipping box does not fall off — it floods the
   box and paints a bright RECTANGLE around the word. A blurred radial on a
   sibling of that box falls off in every direction the way a real light
   does. `isolation:isolate` keeps the negative layer inside the word.
   The colour is re-stated on the split spans because background-clip and
   inherited colour do not both survive the extra nesting the split adds.

   NOTE the text-shadow is `none` here and the glow comes ONLY from the
   pseudo-element. Even a 22px shadow was enough to show its clipped edges:
   invisible on a desktop screen, a plainly visible cyan box around the word
   on a 2x phone. The bloom does the whole job. */
.hero h1 .accent{
  color:var(--cyan-bright);position:relative;isolation:isolate;
  text-shadow:none;
}
.hero h1 .accent::before{
  content:"";position:absolute;z-index:-1;pointer-events:none;
  left:-6%;right:-6%;top:6%;bottom:14%;
  background:radial-gradient(closest-side,rgba(18,196,222,.42),transparent 78%);
  filter:blur(30px);
}
.hero h1 .accent .kin > span{color:var(--cyan-bright)}

/* The trust row reads as a materials list. */
.trust{display:flex;gap:0;flex-wrap:wrap;margin-top:40px;border-top:1px solid var(--line)}
.trust span{
  display:inline-flex;align-items:center;gap:9px;
  padding:16px 22px 0 0;margin-right:22px;
  font-family:var(--font-mono);
  font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-3);
}
.trust svg{color:var(--cyan-bright);filter:drop-shadow(0 0 6px rgba(18,196,222,.7))}

/* ── The specimen card — a label under a lamp ────────────────────────── */
.hero-card{position:relative;isolation:isolate}
.hero-glow{
  position:absolute;inset:-14%;z-index:-1;pointer-events:none;
  background:radial-gradient(circle at 50% 45%,rgba(18,196,222,.34),transparent 62%);
  filter:blur(46px);
  animation:breathe 12s var(--ease-soft) infinite alternate;
}
.label-demo{
  position:relative;
  background:linear-gradient(165deg,rgba(255,255,255,.11),rgba(255,255,255,.035));
  border:1px solid var(--line-strong);border-radius:var(--radius-lg);
  padding:28px 26px 24px;
  backdrop-filter:blur(18px) saturate(140%);
  -webkit-backdrop-filter:blur(18px) saturate(140%);
  box-shadow:var(--shadow-lg), 0 0 0 1px rgba(255,255,255,.04) inset;
  transform:perspective(1400px) rotateY(-5deg) rotateX(2deg);
  transition:transform .8s var(--ease-out);
}
.hero-card:hover .label-demo{transform:perspective(1400px) rotateY(0deg) rotateX(0deg)}
/* Corner registration marks — the press mark, here as a lit edge. */
.label-demo::before,.label-demo::after{
  content:"";position:absolute;width:16px;height:16px;pointer-events:none;
  border:1px solid var(--cyan-bright);box-shadow:0 0 12px rgba(18,196,222,.7);
}
.label-demo::before{top:-1px;left:-1px;border-right:0;border-bottom:0;border-top-left-radius:var(--radius-lg)}
.label-demo::after{bottom:-1px;right:-1px;border-left:0;border-top:0;border-bottom-right-radius:var(--radius-lg)}

.label-demo .tiny{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--cyan-light);font-weight:700;
}
.sku-chip{
  padding:4px 10px;border:1px solid rgba(18,196,222,.5);border-radius:999px;
  color:var(--cyan-bright);font-size:9.5px;letter-spacing:.16em;
  background:rgba(18,196,222,.1);
}
/* A real barcode — black on WHITE, because that is the only way a barcode
   exists. Bars on the dark card would have been the prettier lie; no
   scanner would read it, and neither would a buyer who knows the trade. So
   it gets its own lit plate. */
.barcode-real{
  position:relative;overflow:hidden;
  height:104px;margin:22px 0 16px;border-radius:var(--radius-sm);
  background-color:#F7FBFC;
  background-image:repeating-linear-gradient(90deg,
    #06131A 0 2px, #F7FBFC 2px 5px, #06131A 5px 6px, #F7FBFC 6px 11px,
    #06131A 11px 14px, #F7FBFC 14px 16px, #06131A 16px 17px, #F7FBFC 17px 23px);
  box-shadow:0 0 34px -6px rgba(18,196,222,.45), 0 10px 30px -14px rgba(0,0,0,.9);
}
/* A slow scanner sweep across the barcode — the one animation on the whole
   page that is not just atmosphere, it is the product doing its job. */
.barcode-real::after{
  content:"";position:absolute;inset:0;width:46%;
  background:linear-gradient(100deg,transparent,rgba(18,196,222,.55) 45%,rgba(255,255,255,.5) 50%,rgba(18,196,222,.55) 55%,transparent);
  mix-blend-mode:screen;transform:translateX(-140%);
  animation:barcodeScan 4.6s cubic-bezier(.5,0,.25,1) infinite;
}
@keyframes barcodeScan{
  0%,58%{transform:translateX(-140%)}
  86%,100%{transform:translateX(240%)}
}
@media (prefers-reduced-motion: reduce){ .barcode-real::after{animation:none;display:none} }
.sku{
  font-family:var(--font-mono);font-size:11px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--ink-2);font-weight:700;text-align:center;
}

/* ── Page hero — white, with the cyan light kept low ─────────────────── */
.page-hero{
  position:relative;overflow:hidden;isolation:isolate;
  padding:clamp(54px,6vw,92px) 0 clamp(38px,4vw,58px);
  background:var(--paper);
}
.page-hero::before{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:
    radial-gradient(900px 420px at 84% -34%, rgba(7,151,174,.13), transparent 62%),
    linear-gradient(rgba(7,151,174,.05) 1px,transparent 1px),
    linear-gradient(90deg,rgba(7,151,174,.05) 1px,transparent 1px);
  background-size:auto, 78px 78px, 78px 78px;
  mask-image:linear-gradient(180deg,#000,transparent 86%);
  -webkit-mask-image:linear-gradient(180deg,#000,transparent 86%);
}
.page-hero h1{margin-top:20px;font-size:clamp(33px,4.4vw,58px)}
.page-hero .lead{margin-top:16px}
/* An inner page's hero already ends in generous space; the section after it
   was adding a full pad on top, opening a hole about 200px tall between the
   sentence and the first thing you can click. */
.page-hero + .section{padding-top:clamp(38px,4vw,62px)}

.breadcrumb{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  font-family:var(--font-mono);font-size:11.5px;letter-spacing:.07em;
  color:var(--muted);margin-bottom:28px;
}
.breadcrumb a{color:var(--cyan-ink);font-weight:700}
.breadcrumb span{color:var(--line-strong)}

/* ── Cards ───────────────────────────────────────────────────────────
   White sheets with a hairline on the light pages; panes of smoked glass
   inside a dark band. Same rule, both results — see --card-bg. */
.feature-card,.category-card,.product-card,.cta-band-inner,.pf-stat,.gallery-card,.inq-card{
  background:var(--card-bg);border:1px solid var(--card-border);
  border-radius:var(--radius-lg);
  backdrop-filter:var(--glass);-webkit-backdrop-filter:var(--glass);
}

.feature-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(238px,1fr));gap:16px}
.feature-card{
  padding:30px 28px 32px;position:relative;overflow:hidden;
  transition:border-color .3s var(--ease),transform .3s var(--ease),box-shadow .3s var(--ease);
}
/* The lit cap: a short bar on the top edge that grows to the full width of
   the card when you point at it. */
.feature-card::before{
  content:"";position:absolute;left:0;top:0;height:2px;width:34px;
  background:var(--cyan);transition:width .45s var(--ease-out);
}
.feature-card:hover{
  border-color:var(--line-strong);transform:translateY(-3px);
  box-shadow:var(--shadow),var(--glow);
}
.feature-card:hover::before{width:100%}
.feature-card h3{margin-bottom:10px}

.category-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(268px,1fr));gap:16px}
.category-card{
  display:block;padding:28px 26px 30px;position:relative;overflow:hidden;
  transition:border-color .3s var(--ease),transform .3s var(--ease),box-shadow .3s var(--ease);
}
.category-card::after{
  content:"→";position:absolute;right:24px;bottom:24px;
  font-size:18px;color:var(--cyan);opacity:0;transform:translateX(-8px);
  transition:opacity .28s var(--ease),transform .28s var(--ease);
}
.category-card:hover{
  border-color:var(--cyan);transform:translateY(-3px);
  box-shadow:var(--shadow),var(--glow);
}
.category-card:hover::after{opacity:1;transform:none}
.category-card h3{color:var(--ink);margin-bottom:8px}

/* ── Products ────────────────────────────────────────────────────────── */
.category-tabs,.portfolio-tabs{display:flex;gap:9px;flex-wrap:wrap;margin-bottom:32px}
.tab-pill{
  padding:10px 19px;border:1px solid var(--line-strong);border-radius:999px;
  background:var(--card-bg-2);color:var(--ink-2);cursor:pointer;
  font-family:var(--font-display);font-size:13.5px;font-weight:600;
  backdrop-filter:var(--glass);-webkit-backdrop-filter:var(--glass);
  transition:.24s var(--ease);
}
.tab-pill:hover{border-color:var(--cyan);color:var(--cyan-ink);background:var(--cyan-wash)}
.tab-pill.active{background:var(--btn-bg);border-color:var(--btn-border);color:var(--btn-fg);box-shadow:var(--btn-shadow)}

/* Four per row on desktop (was three) — narrower cards, so gap tightens a
   touch to keep them from feeling cramped. auto-fit rather than auto-fill:
   a half-empty last row of an odd product count stretches to fill the
   width instead of leaving a phantom empty column. The three breakpoints
   below step it down to 3 / 2 / 1 rather than letting minmax() reflow it
   unpredictably at in-between widths. */
/* --products-per-row is set on the #productGrid element by app.js from the
   admin's Settings choice (falls back to 4 if JS hasn't run yet). The
   breakpoints below still narrow the grid on smaller screens regardless of
   that choice, so it never gets cramped on tablet/mobile. */
.product-grid{display:grid;grid-template-columns:repeat(var(--products-per-row,4),1fr);gap:18px}
@media (max-width:1180px){.product-grid{grid-template-columns:repeat(min(var(--products-per-row,4),3),1fr)}}
@media (max-width:820px){.product-grid{grid-template-columns:repeat(min(var(--products-per-row,4),2),1fr)}}
@media (max-width:520px){.product-grid{grid-template-columns:1fr}}
.product-card{
  display:flex;flex-direction:column;overflow:hidden;
  transition:border-color .3s var(--ease),box-shadow .3s var(--ease),transform .3s var(--ease);
}
.product-card:hover{
  border-color:var(--cyan);transform:translateY(-4px);
  box-shadow:var(--shadow-lg),var(--glow);
}

/* THE PLATE.
   Product photographs in this trade are white labels on white backgrounds.
   The plate is near-white in BOTH modes and does not flip with the band —
   on a white page it is a quiet tray, inside a dark band it becomes a lit
   sample tray, and in both cases the buyer sees the true colour of the
   stock, which is the one thing a label photo is for.
   object-fit:contain, always: the whole label shows, never cropped. */
.product-image{
  display:grid;place-items:center;aspect-ratio:4/3;
  background:transparent;
  border-bottom:1px solid var(--card-border);padding:18px;position:relative;
}
/* The lamp falls from the upper left, so the lower right of the plate is
   where it runs out. One inset shadow there is the whole trick. */
.product-image::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  box-shadow:inset -14px -18px 40px -30px rgba(3,8,11,.7);
}
.product-image img{width:100%;height:100%;object-fit:contain;transition:transform .6s var(--ease-epic)}
.product-card:hover .product-image img{transform:scale(1.06)}
.product-placeholder{display:grid;place-items:center;width:100%;height:100%;font-size:36px;color:#B7CBD2}
.product-body{padding:22px 24px 24px;display:flex;flex-direction:column;gap:10px;flex:1}
.product-body h3 a{color:var(--ink)}
.product-body h3 a:hover{color:var(--cyan-ink)}
.product-body .card-call{margin-top:auto}

.price-row{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.price{
  font-family:var(--font-display);font-size:27px;font-weight:700;
  letter-spacing:-.025em;color:var(--ink);font-variant-numeric:tabular-nums;
}
.price-note{font-size:12.5px;color:var(--muted)}
/* The unit badge — Pcs / Roll / Yds / Set / Dzn — set from the admin's
   fixed dropdown, never free-typed, so it reads identically on every
   product. Sits right after the price in every price-row on the site. */
.price-unit{
  display:inline-flex;align-items:center;padding:3px 10px;border-radius:999px;
  background:var(--cyan-wash);border:1px solid var(--line-strong);
  font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;color:var(--cyan-ink);
}
.price-ask{
  font-family:var(--font-display);font-size:16px;font-weight:700;color:var(--cyan-ink);
  display:inline-flex;align-items:center;gap:6px;transition:color .2s var(--ease);
}
.price-ask::after{content:"→";transition:transform .24s var(--ease)}
.price-ask:hover{color:var(--cyan-bright)}
.price-ask:hover::after{transform:translateX(3px)}

.spec-chips{display:flex;gap:7px;flex-wrap:wrap}
.spec-chips span{
  padding:5px 11px;border:1px solid var(--line);border-radius:999px;
  background:var(--chip-bg);
  font-family:var(--font-mono);font-size:11px;color:var(--ink-3);
}

.card-call{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  padding:12px 17px;border-radius:var(--radius);
  background:var(--cyan-wash);border:1px solid var(--cyan);color:var(--cyan-ink);
  font-family:var(--font-display);font-size:13.5px;font-weight:700;
  transition:.24s var(--ease);
}
.card-call:hover{background:var(--cyan);color:#fff;box-shadow:var(--shadow-cyan)}
.section.dark .card-call:hover{color:var(--on-cyan)}

/* ── Card action row (Call + WhatsApp side by side, always in one line —
   never wrap the second button under the first, even on the narrowest
   card in a 4-up desktop grid). Each button shares the row equally and is
   allowed to shrink below its own text width (min-width:0), with a
   slightly tighter size than the standalone buttons elsewhere so both
   labels keep fitting comfortably. ──────────────────────────────────── */
.card-actions{display:flex;gap:8px;flex-wrap:nowrap;margin-top:auto}
.card-actions .card-call{margin-top:0}
.card-actions .card-call,
.card-actions .card-whatsapp{
  flex:1 1 0;min-width:0;
  padding:11px 10px;gap:6px;
  font-size:12.5px;
}
.card-actions .card-call svg,
.card-actions .card-whatsapp svg{width:14px;height:14px;flex:none}
.card-actions .card-call .call-slide{min-width:0;width:100%}

/* ── WhatsApp order/inquiry button ───────────────────────────────────── */
/* A deliberately quieter, more restrained green than WhatsApp's own bright
   brand color — it sits next to the cyan Call button without fighting it,
   and reads as one more premium action rather than a third-party badge. */
.btn-whatsapp,.card-whatsapp{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  padding:12px 17px;border-radius:var(--radius);
  background:rgba(19,145,86,.08);border:1px solid rgba(19,145,86,.55);color:#0F7A46;
  font-family:var(--font-display);font-size:13.5px;font-weight:700;
  transition:.24s var(--ease);white-space:nowrap;
}
.btn-whatsapp:hover,.card-whatsapp:hover{background:#128C4A;color:#fff;border-color:#128C4A;box-shadow:0 10px 24px -12px rgba(18,140,74,.55)}
.section.dark .btn-whatsapp,.section.dark .card-whatsapp{color:#4FE39B;border-color:rgba(79,227,155,.4);background:rgba(79,227,155,.08)}
.section.dark .btn-whatsapp:hover,.section.dark .card-whatsapp:hover{background:#17A85B;color:#04180E;border-color:#17A85B}

/* ── Stock status badge ──────────────────────────────────────────────── */
.stock-badge{
  display:inline-block;padding:4px 11px;border-radius:999px;
  font-family:var(--font-display);font-size:10.5px;font-weight:800;letter-spacing:.04em;
  text-transform:uppercase;vertical-align:middle;white-space:nowrap;max-width:none;
}
.stock-ready{background:rgba(15,122,70,.1);color:#0F7A46;border:1px solid rgba(15,122,70,.35)}
.stock-limited{background:rgba(184,134,11,.12);color:#8A6200;border:1px solid rgba(184,134,11,.4)}
.stock-out{background:rgba(192,57,43,.1);color:#B02A24;border:1px solid rgba(192,57,43,.35)}
.product-image .stock-badge{position:absolute;top:12px;right:12px;z-index:2;backdrop-filter:blur(10px) saturate(160%);background:rgba(255,255,255,.86)}
h1 .stock-badge{margin-left:12px;vertical-align:middle;transform:translateY(-4px)}

/* ── Product detail ──────────────────────────────────────────────────── */
.product-detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:clamp(30px,4vw,62px);align-items:start}
.product-detail-image{
  display:grid;place-items:center;aspect-ratio:1;padding:28px;position:relative;
  background:transparent;
  border:1px solid var(--line-strong);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-lg);
}
.product-detail-image::before,.product-detail-image::after{
  content:"";position:absolute;width:17px;height:17px;border:1px solid var(--cyan);opacity:.8;
}
.product-detail-image::before{top:-1px;left:-1px;border-right:0;border-bottom:0}
.product-detail-image::after{bottom:-1px;right:-1px;border-left:0;border-top:0}
.product-detail-image img{width:100%;height:100%;object-fit:contain}
.product-detail h1{margin:16px 0 0;font-size:clamp(29px,3.4vw,46px)}

.detail-desc{
  margin:26px 0 0;padding:22px 24px;
  background:var(--soft);border:1px solid var(--line);
  border-radius:var(--radius-lg);border-left:2px solid var(--cyan);
}
.detail-desc-head{
  display:block;font-family:var(--font-mono);
  font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--cyan-ink);font-weight:700;margin-bottom:11px;
}
.detail-desc .verbatim{color:var(--ink-2);font-size:15.5px;line-height:1.8}

/* The order panel is a dark band on a white page — the thing the whole page
   exists for, lit like the hero. */
.order-panel{
  margin-top:28px;padding:28px;border-radius:var(--radius-lg);
  background:var(--grad-ink);color:var(--ink-2);position:relative;overflow:hidden;
  border:1px solid rgba(127,230,245,.22);
  box-shadow:var(--shadow-lg), 0 0 60px -28px rgba(18,196,222,.5);
}
.order-panel::before{content:"";position:absolute;left:0;right:0;top:0;height:1px;background:var(--grad-line)}
/* A soft light in the corner, so the price sits in a pool of it rather than
   on a flat rectangle. */
.order-panel::after{
  content:"";position:absolute;right:-20%;top:-60%;width:60%;height:180%;pointer-events:none;
  background:radial-gradient(circle,rgba(18,196,222,.18),transparent 62%);filter:blur(30px);
}
.order-panel > *{position:relative;z-index:1}
.order-label{
  display:block;font-family:var(--font-mono);
  font-size:10.5px;letter-spacing:.22em;text-transform:uppercase;
  color:var(--cyan-bright);font-weight:700;
}
.order-price{
  font-family:var(--font-display);font-size:clamp(34px,3.8vw,50px);font-weight:700;
  letter-spacing:-.03em;color:#fff;margin-top:8px;font-variant-numeric:tabular-nums;
  text-shadow:0 0 50px rgba(18,196,222,.35);
}
/* Call + WhatsApp always sit together on their own first line (equal
   width, never one above the other); the "Send an enquiry" link — a
   longer label, and a different action — gets its own full-width line
   underneath rather than fighting the other two for space. */
.order-actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:22px}
.order-actions .btn-call,
.order-actions .btn-whatsapp{flex:1 1 0;min-width:0}
.order-actions .btn-call .call-slide{min-width:0;width:100%}
.order-actions .btn.on-ink{flex:1 1 100%}
.order-hint{font-size:13px;color:var(--muted);margin:16px 0 0;line-height:1.65}
.order-price .price-unit{
  background:rgba(18,196,222,.14);border-color:rgba(18,196,222,.42);color:var(--cyan-bright);
  vertical-align:middle;letter-spacing:.04em;
}

/* ── The sliding number ──────────────────────────────────────────────── */
.call-slide{position:relative;display:grid;overflow:hidden;align-items:center;justify-items:start}
.call-slide > span{grid-area:1/1;white-space:nowrap;transition:transform .44s var(--ease),opacity .34s var(--ease)}
.call-number,.call-copied{transform:translateY(115%);opacity:0}
.call-number{font-variant-numeric:tabular-nums;letter-spacing:.03em}
.is-revealed .call-slide .call-label{transform:translateY(-115%);opacity:0}
.is-revealed .call-slide .call-number{transform:translateY(0);opacity:1}
.is-copied .call-slide .call-number{transform:translateY(-115%);opacity:0}
.is-copied .call-slide .call-copied{transform:translateY(0);opacity:1}

/* ── CTA bands ───────────────────────────────────────────────────────── */
.cta-band-inner{
  display:flex;align-items:center;justify-content:space-between;gap:30px;
  flex-wrap:wrap;padding:clamp(32px,3.8vw,50px);position:relative;
  isolation:isolate;overflow:hidden;border-color:var(--line-strong);
}
.cta-band-inner::before{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(700px 300px at 8% 122%,var(--cyan-wash),transparent 62%);
}
.cta-band-inner::after{
  content:"";position:absolute;left:0;top:0;bottom:0;width:2px;
  background:linear-gradient(180deg,transparent,var(--cyan),transparent);
}
.section.dark .cta-band-inner::after{box-shadow:0 0 20px rgba(18,196,222,.8)}
.cta-band-inner > *{position:relative;z-index:1}

/* ── Gallery ─────────────────────────────────────────────────────────── */
.pf-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:14px;margin-bottom:34px}
.pf-stat{padding:24px 26px;position:relative;overflow:hidden}
.pf-stat::before{
  content:"";position:absolute;left:0;top:0;height:2px;width:100%;
  background:var(--grad-line);opacity:.8;
}
.pf-stat strong{
  display:block;font-family:var(--font-display);font-size:34px;font-weight:700;
  letter-spacing:-.035em;color:var(--ink);
}
.pf-stat span{
  display:block;font-family:var(--font-mono);font-size:10.5px;
  letter-spacing:.18em;text-transform:uppercase;color:var(--muted);
  font-weight:700;margin-top:6px;
}

.gallery-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(292px,1fr));gap:18px}
.gallery-card{
  overflow:hidden;display:flex;flex-direction:column;
  transition:border-color .3s var(--ease),box-shadow .3s var(--ease),transform .3s var(--ease);
}
.gallery-card:hover{border-color:var(--cyan);transform:translateY(-3px);box-shadow:var(--shadow-lg),var(--glow)}
/* Same plate as the product cards, same reason. */
.gallery-card img{
  width:100%;aspect-ratio:4/3;object-fit:contain;
  background:transparent;
  border-bottom:1px solid var(--card-border);padding:16px;
}
.gallery-cap{padding:18px 20px 20px;display:flex;flex-direction:column;gap:8px;font-size:14px;color:var(--ink-2)}
.pf-tag{
  align-self:flex-start;padding:4px 11px;
  border:1px solid var(--cyan);border-radius:999px;background:var(--cyan-wash);
  font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--cyan-ink);font-weight:700;
}

/* ── Contact ─────────────────────────────────────────────────────────── */
.contact-grid{display:grid;grid-template-columns:.85fr 1.15fr;gap:clamp(28px,4vw,58px);align-items:start}
.contact-note{
  padding:30px 28px;border-radius:var(--radius-lg);
  background:var(--grad-ink);color:var(--ink-2);position:relative;overflow:hidden;
  border:1px solid rgba(127,230,245,.22);
  box-shadow:var(--shadow-lg), 0 0 60px -30px rgba(18,196,222,.5);
}
.contact-note::before{content:"";position:absolute;left:0;right:0;top:0;height:1px;background:var(--grad-line)}
.contact-note::after{
  content:"";position:absolute;left:-25%;bottom:-60%;width:80%;height:150%;pointer-events:none;
  background:radial-gradient(circle,rgba(18,196,222,.16),transparent 62%);filter:blur(34px);
}
.contact-note > *{position:relative;z-index:1}
.contact-note strong{
  display:block;font-family:var(--font-mono);font-size:10.5px;
  letter-spacing:.22em;text-transform:uppercase;color:var(--cyan-bright);
}
.contact-note > span{display:block;margin-top:12px;font-size:14.5px;line-height:1.75;color:var(--ink-3)}
.contact-channels{display:flex;flex-direction:column;gap:11px;margin-top:24px}
.contact-channel{
  display:flex;align-items:center;gap:14px;padding:14px 16px;
  border:1px solid var(--line);border-radius:var(--radius);
  background:rgba(255,255,255,.035);
  transition:background .24s var(--ease),border-color .24s var(--ease),transform .24s var(--ease);
}
.contact-channel:hover{background:rgba(18,196,222,.11);border-color:rgba(18,196,222,.45);transform:translateX(3px)}
.cc-icon{
  width:38px;height:38px;flex:none;display:grid;place-items:center;
  border:1px solid rgba(18,196,222,.35);border-radius:var(--radius-sm);
  background:rgba(18,196,222,.09);color:var(--cyan-bright);
}
.cc-text{display:flex;flex-direction:column;min-width:0}
.cc-text small{font-family:var(--font-mono);font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--muted)}
.cc-text b{color:#fff;font-size:15px;word-break:break-all}

.contact-form{display:flex;flex-direction:column;gap:18px}
.contact-form > label{
  display:flex;flex-direction:column;gap:9px;
  font-family:var(--font-display);font-size:13.5px;font-weight:600;color:var(--ink-2);
}
.contact-form input,.contact-form textarea{
  padding:15px 16px;border-radius:var(--radius);
  border:1px solid var(--line-strong);background:var(--field-bg);
  font-size:15.5px;color:var(--ink);font-family:var(--font-body);
  transition:border-color .24s var(--ease),box-shadow .24s var(--ease);
}
.contact-form input::placeholder,.contact-form textarea::placeholder{color:var(--muted)}
.contact-form input:focus,.contact-form textarea:focus{outline:none;border-color:var(--cyan);box-shadow:var(--ring)}
.contact-form textarea{resize:vertical;min-height:150px;line-height:1.75}
.form-status{font-size:14px;color:var(--cyan-ink);min-height:1.2em}

/* ---- Incode Label AI — inquiry writer on the Contact form ---- */
.ai-writer{
  border:1px solid var(--line-strong);border-radius:var(--radius);padding:16px 18px;display:grid;gap:10px;
  background:
    radial-gradient(420px 200px at 100% 0%, rgba(6,188,214,.10), transparent 62%),
    linear-gradient(180deg,var(--cyan-wash),var(--field-bg));
}
.ai-writer-head strong{display:block;font-family:var(--font-display);font-size:14px;letter-spacing:-.02em;color:var(--ink)}
.ai-writer-head .small{display:block;margin-top:3px;color:var(--ink-3);font-size:12.5px}
.ai-writer-row{display:flex;gap:8px;flex-wrap:wrap}
.ai-writer-row .btn{font-size:13px;white-space:nowrap;padding:9px 16px}
.ai-writer-row .btn:disabled{opacity:.5;cursor:not-allowed}
.ai-writer-row .btn[data-ai-lang].is-active{border-color:var(--cyan);color:var(--cyan-ink);box-shadow:var(--ring)}
.ai-writer p{margin:0;font-size:12.5px;color:var(--ink-3)}

/* The honeypot: out of the layout and out of the accessibility tree, but
   NOT display:none and NOT type=hidden — the bots worth catching skip both
   of those. */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

/* "You are asking about this product" */
#inquiryProduct[hidden]{display:none}
.inq-card{display:grid;grid-template-columns:108px 1fr;gap:18px;align-items:center;padding:16px}
.inq-thumb{
  display:grid;place-items:center;width:108px;height:108px;
  border:1px solid var(--line);border-radius:var(--radius);
  background:linear-gradient(168deg,#F8FCFD,var(--plate-2));overflow:hidden;
}
.inq-thumb img{width:100%;height:100%;object-fit:contain}
.inq-body h3{margin:8px 0 0;font-size:18px}
.inq-body h3 a{color:var(--ink)}
.inq-body h3 a:hover{color:var(--cyan-ink)}
.inq-body .price-row{margin-top:8px}
.inq-body .tiny{margin-top:9px}
@media (max-width:560px){
  .inq-card{grid-template-columns:1fr}
  .inq-thumb{width:100%;height:auto;aspect-ratio:4/3}
}

/* "Write with InCode Label AI" — sits under the Description textarea,
   only shown once a Subject has been typed (see app.js). */
.ai-write-btn{
  align-self:flex-start;display:inline-flex;align-items:center;gap:8px;
  padding:9px 16px;border-radius:999px;border:1px solid var(--cyan);
  background:var(--cyan-wash);color:var(--cyan-ink);font-size:13px;font-weight:700;
  font-family:var(--font-display);cursor:pointer;
  transition:background .22s var(--ease),color .22s var(--ease);
}
.ai-write-btn:hover{background:var(--cyan);color:#fff}
.ai-write-btn:disabled{opacity:.55;cursor:default}
.ai-write-btn svg{flex:none}
.ai-write-status{margin-top:-2px}

/* ── Sticky order bar (phones) ───────────────────────────────────────── */
.order-bar{
  position:fixed;left:0;right:0;bottom:0;z-index:80;display:none;
  align-items:center;justify-content:space-between;gap:14px;
  padding:12px 16px calc(12px + env(safe-area-inset-bottom));
  background:rgba(255,255,255,.94);
  backdrop-filter:blur(20px) saturate(160%);-webkit-backdrop-filter:blur(20px) saturate(160%);
  border-top:1px solid var(--line-strong);
  box-shadow:0 -14px 30px -24px rgba(6,23,31,.6);
}
.ob-label{
  font-family:var(--font-mono);font-size:10px;letter-spacing:.18em;
  text-transform:uppercase;color:var(--muted);font-weight:700;
}
.ob-price{font-family:var(--font-display);font-size:21px;font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums}

/* ── Footer — the page closes in the dark ────────────────────────────── */
footer{
  position:relative;overflow:hidden;isolation:isolate;
  padding:74px 0 0;
}
footer::before{content:"";position:absolute;left:0;right:0;top:0;height:1px;background:var(--grad-line);opacity:.8}
/* One last pool of light, so the document ends on the accent rather than
   fading into nothing. */
footer::after{
  content:"";position:absolute;left:50%;bottom:-58%;width:90%;height:120%;
  transform:translateX(-50%);pointer-events:none;z-index:-1;
  background:radial-gradient(circle,rgba(18,196,222,.15),transparent 62%);filter:blur(46px);
}
.footer-grid{display:grid;grid-template-columns:1.7fr 1fr 1fr;gap:44px;padding-bottom:48px}
.footer-grid .brand{color:#fff;margin-bottom:18px}
.footer-grid .brand .accent{color:var(--cyan-bright)}
.footer-grid p{font-size:14.5px;line-height:1.8;color:var(--ink-3);max-width:44ch;margin:0}
.footer-grid strong{
  display:block;font-family:var(--font-mono);font-size:10.5px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--muted);margin-bottom:14px;
}
.footer-grid > div > a{
  display:block;padding:6px 0;color:var(--ink-2);font-size:14.5px;
  transition:color .22s var(--ease),transform .22s var(--ease);
}
.footer-grid > div > a:hover{color:var(--cyan-bright);transform:translateX(3px)}
.footer-contact{display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;gap:12px}
.footer-contact a{display:inline-flex;align-items:center;gap:10px;color:#fff;font-weight:700;font-size:15px}
.footer-contact a:hover{color:var(--cyan-bright)}
.footer-bottom{
  display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;
  padding:20px 0 24px;border-top:1px solid var(--line);
  font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--muted);
}
/* The chat bubble is fixed over the bottom-right corner and the last line
   of the footer ran straight underneath it. Leave it the room it takes —
   60px of bubble, 24px of offset, and a gap. */
.footer-bottom > :last-child{padding-right:96px}
@media (max-width:760px){ .footer-bottom > :last-child{padding-right:0} }

/* ── Reveal on scroll ────────────────────────────────────────────────
   CONTENT IS VISIBLE BY DEFAULT. The hiding is gated behind the `js` class,
   which a two-line inline script in every page's <head> adds before the
   first paint. `.in-view` is only ever added by app.js, so hiding by
   default would leave the whole page blank if that file never ran —
   JavaScript off, an ad-blocker, one bad upload. `.reveal-all` is the same
   script's backstop when nothing has been revealed after a few seconds. */
.js .reveal{
  opacity:0;transform:translateY(20px);
  transition:opacity .8s var(--ease-out),transform .8s var(--ease-out);
}
.reveal.in-view,.reveal-all .reveal{opacity:1;transform:none}
.reveal:nth-child(2){transition-delay:.07s}
.reveal:nth-child(3){transition-delay:.14s}
.reveal:nth-child(4){transition-delay:.21s}

/* Loading skeletons — the shape of what is coming, so nothing jumps. */
.skeleton{
  border-radius:var(--radius-lg);border:1px solid var(--line);
  background:linear-gradient(100deg,var(--soft) 30%,var(--paper) 50%,var(--soft) 70%);
  background-size:220% 100%;animation:shimmer 1.6s linear infinite;min-height:240px;
}
.section.dark .skeleton{
  background:linear-gradient(100deg,rgba(255,255,255,.04) 30%,rgba(18,196,222,.11) 50%,rgba(255,255,255,.04) 70%);
  background-size:220% 100%;
}
@keyframes shimmer{from{background-position:140% 0}to{background-position:-40% 0}}
.skeleton.sk-card{min-height:300px}
.skeleton.sk-detail{min-height:420px;grid-column:1/-1}
.skeleton.sk-inquiry{min-height:136px;margin-bottom:20px}
@media (prefers-reduced-motion: reduce){
  .skeleton{animation:none;background:var(--soft)}
}

/* ── Chat widget ─────────────────────────────────────────────────────
   The widget is a POSITIONING SHELL, not a surface. The closed panel keeps
   its layout box, which stretches this container to roughly 376x550 — an
   invisible slab over the right-hand third of every page. Left hit-testable
   it silently ate clicks on whatever sat underneath: a Call For Order
   button, the right column of product cards, footer links. So the shell
   passes every pointer straight through, and only the two things really
   there take clicks back. */
.chat-widget{
  position:fixed;right:24px;bottom:24px;z-index:90;
  display:flex;flex-direction:column;align-items:flex-end;gap:14px;
  pointer-events:none;
}
.chat-bubble{pointer-events:auto}
.chat-panel.open{pointer-events:auto}
.chat-whatsapp-fab{pointer-events:auto}

.chat-whatsapp-fab{
  width:48px;height:48px;border-radius:var(--radius-lg);
  background:#25D366;color:#fff;
  display:grid;place-items:center;
  box-shadow:0 10px 24px -10px rgba(18,140,74,.6);
  transition:transform .26s var(--ease),box-shadow .3s var(--ease);
}
.chat-whatsapp-fab:hover{transform:translateY(-3px) scale(1.03);box-shadow:0 14px 30px -10px rgba(18,140,74,.75)}
.chat-whatsapp-fab svg{display:block}

.chat-bubble{
  position:relative;width:60px;height:60px;border-radius:var(--radius-lg);
  border:1px solid var(--cyan-deep);cursor:pointer;
  background:linear-gradient(180deg,#12C4DE,#0797AE);
  color:#fff;font-size:24px;line-height:1;
  display:grid;place-items:center;box-shadow:var(--shadow-cyan);
  transition:transform .26s var(--ease),box-shadow .3s var(--ease);
}
.chat-bubble:hover{transform:translateY(-3px) scale(1.03);box-shadow:0 18px 46px -14px rgba(7,151,174,.75)}
.chat-bubble-icon{line-height:1}
.chat-badge{
  position:absolute;top:-7px;right:-7px;min-width:22px;height:22px;padding:0 6px;
  border-radius:999px;background:#C0392B;color:#fff;
  font-family:var(--font-mono);font-size:11px;font-weight:700;
  display:grid;place-items:center;border:1px solid #fff;
}

.chat-panel{
  width:min(94vw,376px);
  /* Anchored to the BOTTOM, so an over-tall panel grows off the TOP of the
     screen and takes its own header and close button with it. On a
     landscape phone the old 76vh plus the bubble, the gap and the sticky
     order bar came to more than the whole viewport. */
  height:min(548px, calc(100dvh - 200px));
  min-height:240px;
  display:flex;flex-direction:column;overflow:hidden;
  border-radius:var(--radius-lg);background:var(--paper);
  border:1px solid var(--line-strong);box-shadow:var(--shadow-lg);
  opacity:0;visibility:hidden;transform:translateY(14px) scale(.985);
  transition:opacity .34s var(--ease),transform .34s var(--ease),visibility .34s;
}
.chat-panel.open{opacity:1;visibility:visible;transform:none}

/* The chat HEAD is a dark band (it re-declares the palette at the top of
   this file); the body below it stays light, so a long conversation reads
   like a conversation and not like a terminal. */
.chat-head{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:15px 17px;background:var(--grad-ink);color:#fff;
  position:relative;overflow:hidden;
}
.chat-head::before{content:"";position:absolute;left:0;right:0;top:0;height:1px;background:var(--grad-line)}
.chat-head-id{display:flex;align-items:center;gap:12px;position:relative;z-index:1;min-width:0}
.chat-head .chat-close{position:relative;z-index:1}
.chat-avatar{
  width:38px;height:38px;flex:none;display:grid;place-items:center;
  border-radius:var(--radius);
  background:linear-gradient(180deg,#4EE2F5,#12C4DE);color:var(--on-cyan);
  font-family:var(--font-mono);font-size:12px;font-weight:700;letter-spacing:.06em;
  box-shadow:0 0 20px -6px rgba(18,196,222,.85);
}
.chat-head strong{display:block;font-family:var(--font-display);font-size:15px;font-weight:700;color:#fff}
.chat-status{
  display:flex;align-items:center;gap:8px;
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:.07em;
  color:var(--cyan-light);margin-top:3px;
}
/* The live dot is a pseudo-element on purpose: chat.js writes .textContent
   into this span, which would delete any real child. */
.chat-status::before{
  content:"";width:6px;height:6px;border-radius:50%;flex:none;background:#3EE08F;
  animation:livedot 2.4s var(--ease) infinite;
}
@keyframes livedot{
  0%{box-shadow:0 0 0 0 rgba(62,224,143,.55)}
  70%,100%{box-shadow:0 0 0 7px rgba(62,224,143,0)}
}
.chat-close{
  width:32px;height:32px;border-radius:var(--radius-sm);
  border:1px solid rgba(127,230,245,.3);background:rgba(255,255,255,.05);color:#fff;
  cursor:pointer;font-size:13px;line-height:1;flex:none;transition:background .22s;
}
.chat-close:hover{background:rgba(255,255,255,.16)}

/* Only appears once a returning visitor's thread actually has something
   older than this page load to reveal — see chat.js. Toggled "active"
   (cyan fill) while the earlier conversation is showing, so it reads the
   same way an unmuted/pressed icon does elsewhere on the site. */
.chat-history-btn{
  width:32px;height:32px;border-radius:var(--radius-sm);position:relative;z-index:1;
  border:1px solid rgba(127,230,245,.3);background:rgba(255,255,255,.05);color:#fff;
  cursor:pointer;line-height:1;flex:none;display:grid;place-items:center;transition:background .22s,color .22s;
}
.chat-history-btn svg{display:block}
.chat-history-btn:hover{background:rgba(255,255,255,.16)}
.chat-history-btn.active{background:linear-gradient(180deg,#4EE2F5,#12C4DE);border-color:transparent;color:var(--on-cyan)}

.chat-body{
  flex:1;overflow-y:auto;padding:18px;display:flex;flex-direction:column;gap:12px;
  background:var(--soft);scroll-behavior:smooth;
}
/* The opening greeting is a message from your team, so it is shaped like
   one — at the top, on the left, in a bubble. Centred in an empty panel it
   read as a system notice rather than somebody saying hello. */
.chat-empty{
  align-self:flex-start;max-width:88%;
  font-size:14.5px;line-height:1.65;color:var(--ink-2);
  padding:14px 17px;border-radius:var(--radius-lg);
  background:var(--paper);border:1px solid var(--line);
}
.chat-empty::before{
  content:"InCode Label";display:block;
  font-family:var(--font-mono);font-size:10px;font-weight:700;
  letter-spacing:.18em;text-transform:uppercase;color:var(--cyan-ink);margin-bottom:7px;
}
.chat-msg{max-width:82%;display:flex;flex-direction:column;gap:5px}
.chat-msg-who{
  font-family:var(--font-mono);font-size:10px;font-weight:700;
  letter-spacing:.18em;text-transform:uppercase;color:var(--cyan-ink);
}
.chat-msg-text{
  padding:12px 16px;border-radius:var(--radius-lg);font-size:14.5px;line-height:1.62;
  white-space:pre-wrap;overflow-wrap:break-word;
}
.chat-msg.me{align-self:flex-end;align-items:flex-end}
.chat-msg.me .chat-msg-text{
  background:linear-gradient(180deg,#12C4DE,#0797AE);color:#fff;
  box-shadow:0 8px 22px -14px rgba(7,151,174,.9);
}
.chat-msg.me .chat-msg-who{color:var(--muted)}
.chat-msg.ai .chat-msg-text,.chat-msg.admin .chat-msg-text{
  background:var(--paper);border:1px solid var(--line);color:var(--ink-2);
}
.chat-typing{max-width:82%}
.chat-dots{
  display:inline-flex;gap:5px;padding:14px 17px;
  background:var(--paper);border:1px solid var(--line);border-radius:var(--radius-lg);
}
.chat-dots i{width:6px;height:6px;border-radius:50%;background:var(--cyan);animation:blink 1.3s infinite}
.chat-dots i:nth-child(2){animation-delay:.18s}
.chat-dots i:nth-child(3){animation-delay:.36s}
@keyframes blink{0%,60%,100%{opacity:.25}30%{opacity:1}}

/* One scrolling row. Wrapped onto four stacked rows the starter chips ate a
   third of the panel and squeezed the conversation into a slot too short to
   read a greeting in. */
.chat-suggest{
  display:flex;gap:8px;padding:0 18px 13px;background:var(--soft);
  overflow-x:auto;scrollbar-width:none;
}
.chat-suggest::-webkit-scrollbar{display:none}
.chat-suggest button{
  flex:none;white-space:nowrap;padding:9px 14px;border-radius:999px;
  border:1px solid var(--line-strong);background:var(--paper);color:var(--ink-2);
  font-size:12.5px;font-weight:600;cursor:pointer;transition:.24s var(--ease);
}
.chat-suggest button:hover{border-color:var(--cyan);color:var(--cyan-ink);background:var(--cyan-wash)}

.chat-callrow{
  display:flex;align-items:center;gap:12px;padding:12px 18px;
  border-top:1px solid var(--line);background:var(--cyan-wash);
  font-size:13px;color:var(--cyan-ink);font-weight:600;
}
.chat-callrow a{
  margin-left:auto;display:inline-flex;align-items:center;gap:8px;
  padding:9px 15px;border-radius:var(--radius);
  background:linear-gradient(180deg,#12C4DE,#0797AE);color:#fff;
  font-weight:700;font-size:13px;font-variant-numeric:tabular-nums;
}
.chat-callrow a:hover{filter:brightness(1.06)}

.chat-input-row{display:flex;gap:10px;padding:14px 16px;border-top:1px solid var(--line);background:var(--paper)}
.chat-input-row textarea{
  flex:1;min-width:0;padding:12px 15px;border-radius:var(--radius);
  border:1px solid var(--line-strong);background:var(--paper);
  font-size:14.5px;color:var(--ink);font-family:var(--font-body);
}
.chat-input-row textarea::placeholder{color:var(--muted)}
.chat-input-row textarea:focus{outline:none;border-color:var(--cyan);box-shadow:var(--ring)}
.chat-send{
  width:44px;flex:none;border-radius:var(--radius);border:1px solid var(--cyan);
  background:linear-gradient(180deg,#12C4DE,#0797AE);color:#fff;cursor:pointer;font-size:15px;
  transition:filter .22s var(--ease),transform .22s var(--ease);
}
.chat-send:hover{filter:brightness(1.06);transform:translateY(-1px)}
.chat-local-notice{opacity:.95}

/* ── Responsive ──────────────────────────────────────────────────────── */
@media (max-width:1000px){
  .hero-grid{grid-template-columns:1fr;gap:46px}
  .hero h1{max-width:none}
  .label-demo{transform:none}
  .product-detail-grid{grid-template-columns:1fr}
  .contact-grid{grid-template-columns:1fr}
  .footer-grid{grid-template-columns:1fr 1fr;gap:32px}
}
@media (max-width:760px){
  body{font-size:16px}
  .menu{display:grid;place-items:center}
  .site-header nav{
    position:absolute;left:0;right:0;top:100%;
    flex-direction:column;align-items:stretch;gap:0;
    background:rgba(255,255,255,.97);
    backdrop-filter:blur(22px);-webkit-backdrop-filter:blur(22px);
    border-bottom:1px solid var(--line-strong);
    padding:8px var(--gut) 18px;
    opacity:0;visibility:hidden;transform:translateY(-8px);
    transition:opacity .25s var(--ease),transform .25s var(--ease),visibility .25s;
  }
  .site-header nav.open{opacity:1;visibility:visible;transform:none}
  .site-header nav a{padding:14px 0;border-bottom:1px solid var(--line);border-radius:0}
  .site-header nav a::after{display:none}
  .footer-grid{grid-template-columns:1fr}
  .cta-band-inner{flex-direction:column;align-items:flex-start}
  .feature-grid{grid-template-columns:1fr}
  .pf-stats{grid-template-columns:1fr 1fr}
  body.has-order-bar .order-bar{display:flex}
  body.has-order-bar{padding-bottom:78px}
  body.has-order-bar .chat-widget{bottom:92px}
  /* Blur is the most expensive thing on a mid-range phone and is doing the
     least work on the smallest screen. Every surface below already has an
     opaque-enough colour under it to carry the panel without it. */
  .feature-card,.category-card,.product-card,.cta-band-inner,
  .pf-stat,.gallery-card,.inq-card,.tab-pill,.eyebrow-pill,
  .btn.ghost,.btn-call,.detail-desc{
    backdrop-filter:none;-webkit-backdrop-filter:none;
  }
}
@media (max-width:420px){
  .hero-actions .btn,.hero-actions .btn-call{width:100%}
}

/* ── Motion preferences ──────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce){
  *{animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;scroll-behavior:auto !important}
  .js .reveal{opacity:1;transform:none}
  .kin > span{transform:none;opacity:1}
  .spotlight{display:none}
  .label-demo{transform:none}
}


/* ═══════════════════════════════════════════════════════════════════════
   MACHINED METAL
   ═══════════════════════════════════════════════════════════════════════
   The brief was "heavier, more metallic, glassier". Metal on a screen is
   not a texture you paste on; it is a specific behaviour of light, and it
   comes down to three things that are cheap to draw and unmistakable when
   they are all present:

     A BEVEL. Every real machined edge is lit on the side facing the lamp
     and dark on the side away from it. One inset hairline of white along
     the top of a panel and one of black along the bottom is the whole
     illusion — it is what separates "a rectangle with a border" from "a
     piece of something".

     ANISOTROPY. Brushed aluminium scatters light along the direction it
     was brushed, so the highlight is a smeared band rather than a point.
     A very shallow repeating gradient, at a few percent opacity, does it.

     A SPECULAR EDGE. The one bright line where the bevel catches the lamp
     directly. Here it is cyan, because cyan is the light in this room.

   All three are applied through ONE class, `.metal`, plus the existing
   card selectors, so nothing needs new markup and the light-mode surfaces
   are untouched — brushed metal in daylight just reads as dirty.
   ═══════════════════════════════════════════════════════════════════════ */

/* The bevel + brush, on every panel inside a dark band. */
.hero .label-demo,
.section.dark .feature-card, .section.dark .category-card,
.section.dark .product-card, .section.dark .gallery-card,
.section.dark .pf-stat, .section.dark .cta-band-inner,
.order-panel, .contact-note{
  position:relative;
}
.hero .label-demo::after{ /* keeps the corner mark; adds the lit top edge */ }

.order-panel, .contact-note,
.section.dark .cta-band-inner, .section.dark .feature-card,
.section.dark .category-card, .section.dark .pf-stat{
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.16),          /* lit top bevel      */
    inset 0 -1px 0 rgba(0,0,0,.55),               /* shadowed underside */
    var(--shadow-lg);
  background-image:
    /* the brush: a shallow, almost invisible anisotropic grain */
    repeating-linear-gradient(101deg,
      rgba(255,255,255,.030) 0 1px,
      rgba(255,255,255,0)    1px 3px),
    /* the specular band that makes it read as a curved surface */
    linear-gradient(168deg,
      rgba(255,255,255,.10) 0%,
      rgba(255,255,255,.02) 38%,
      rgba(0,0,0,.10)       100%),
    var(--grad-ink);
}
.section.dark .cta-band-inner, .section.dark .feature-card,
.section.dark .category-card, .section.dark .pf-stat{
  background-image:
    repeating-linear-gradient(101deg,
      rgba(255,255,255,.030) 0 1px,
      rgba(255,255,255,0)    1px 3px),
    linear-gradient(168deg, rgba(255,255,255,.085), rgba(255,255,255,.02));
}

/* The hero specimen becomes a milled plate: thicker glass, a brighter top
   edge, and a cyan rim light down one side. */
.hero .label-demo{
  box-shadow:
    inset 0 1px 0 rgba(210,248,255,.24),
    inset 1px 0 0 rgba(120,225,245,.10),
    inset 0 -1px 0 rgba(0,0,0,.6),
    var(--shadow-lg),
    0 0 70px -26px rgba(18,196,222,.5);
  background-image:
    repeating-linear-gradient(101deg, rgba(255,255,255,.035) 0 1px, rgba(255,255,255,0) 1px 3px),
    linear-gradient(165deg, rgba(255,255,255,.13), rgba(255,255,255,.03));
}

/* On WHITE the same idea, inverted and far quieter: polished steel, not
   brushed. A white card gets one hairline of pure white along the top and
   the faintest grey beneath, which is what stops a flat white card on a
   flat white page from disappearing. */
.feature-card, .category-card, .product-card,
.gallery-card, .pf-stat, .cta-band-inner, .inq-card{
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    inset 0 -1px 0 rgba(6,23,31,.045),
    var(--shadow);
}

/* The topbar and the header get a machined underside. */
.site-header.is-stuck{
  box-shadow:
    inset 0 -1px 0 rgba(255,255,255,.7),
    0 14px 30px -26px rgba(6,23,31,.55);
}

/* ── The magnifier ────────────────────────────────────────────────────
   A round loupe that follows the pointer over a product image. The circle
   is empty; its BACKGROUND is the same picture scaled up, with the
   background-position moved so the point under the cursor sits in the
   middle. Nothing is downloaded twice and no canvas is involved.

   `will-change:transform` is deliberately NOT set: the lens moves by
   left/top on a rafThrottle, once per frame, and promoting it to its own
   layer costs more than it saves at this size. */
[data-zoom]{cursor:zoom-in}
.zoom-lens{
  position:absolute;left:0;top:0;width:190px;height:190px;
  margin:-95px 0 0 -95px;                 /* centre it on the cursor */
  border-radius:50%;pointer-events:none;z-index:3;
  background-repeat:no-repeat;background-color:transparent;
  border:1px solid rgba(255,255,255,.85);
  box-shadow:
    0 0 0 1px rgba(6,23,31,.28),
    inset 0 1px 0 rgba(255,255,255,.9),
    inset 0 -14px 26px -18px rgba(6,23,31,.6),
    0 22px 46px -18px rgba(6,23,31,.55);
  opacity:0;transform:scale(.9);
  transition:opacity .18s var(--ease),transform .22s var(--ease-out);
}
[data-zoom].zooming .zoom-lens{opacity:1;transform:scale(1)}

/* ── The full-screen image ────────────────────────────────────────────
   Where a phone goes when the picture is tapped: a dark ground, the image
   as large as it will go, and the browser's own pinch-to-zoom left to do
   the zooming. Deliberately not a custom pinch implementation — the
   browser's is better than any hand-written one and it is the gesture the
   person's thumbs already know. */
.lightbox{
  position:fixed;inset:0;z-index:120;display:none;
  align-items:center;justify-content:center;padding:24px;
  background:rgba(3,8,11,.94);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  opacity:0;transition:opacity .22s var(--ease);
}
.lightbox.open{display:flex;opacity:1}
.lightbox img{
  max-width:100%;max-height:100%;object-fit:contain;
  background:transparent;border-radius:var(--radius-lg);
  box-shadow:0 40px 90px -30px rgba(0,0,0,.9), 0 0 90px -30px rgba(18,196,222,.45);
}
.lightbox-close{
  position:absolute;top:20px;right:20px;width:44px;height:44px;
  border-radius:50%;cursor:pointer;font-size:16px;line-height:1;
  color:#EAF6F9;background:rgba(255,255,255,.08);
  border:1px solid rgba(127,230,245,.32);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  transition:background .2s var(--ease),transform .2s var(--ease);
}
.lightbox-close:hover{background:rgba(18,196,222,.22);transform:scale(1.06)}
body.lightbox-open{overflow:hidden}

/* ── Chat starters that are all reachable ─────────────────────────────
   They used to sit in one row that scrolled sideways with the scrollbar
   hidden, so in a 376px panel the third chip was cut in half and the
   fourth was off-screen with nothing to hint that sideways was a direction
   you could go. They wrap now. The chips were shortened to two or three
   words each (see chat.js) so all four fit on two rows without eating the
   conversation, which is what made wrapping unworkable the first time. */
.chat-suggest{
  flex-wrap:wrap;overflow-x:visible;
  max-height:none;
}
.chat-suggest button{white-space:nowrap}


/* ═══════════════════════════════════════════════════════════════════════
   APERTURE — the instrument layer
   ═══════════════════════════════════════════════════════════════════════
   The brief: one theme in which light and dark are combined, not two
   themes with a switch. Heavier, more metallic, glassier, cinematic, and
   not recognisable as any other website.

   The idea this layer commits to is that the page is the DISPLAY OF A
   PRECISION INSTRUMENT — the thing a label house actually is. That gives
   a reason for every decision below, and it is where the light/dark answer
   comes from:

     An instrument is never all one tone. It has a machined dark chassis and
     a lit readout, and they sit INSIDE each other. So here, light and dark
     do not alternate down the page in bands — they interlock inside every
     single component. A white section carries a dark milled chip. A light
     product plate sits above a dark data rail. A light chat body hangs
     under a dark instrument head. At every scale, both.

   Four devices carry it, and nothing else is added:

     1. THE FRAME. A hairline inset from the viewport with corner brackets,
        always there, never scrolling. It turns the browser window into a
        housing and every page into something mounted inside it.
     2. THE CHIP. Every section eyebrow becomes a small dark machined plate
        with a cyan monospace legend. This is the dark element that appears
        in even the whitest section.
     3. THE RAIL. Cards get a dark data rail carrying the number and the
        action, under a lit plate carrying the product.
     4. THE BEVEL. Lit top edge, shadowed underside, shallow brushed grain.
        Metal is not a texture, it is a behaviour of light.

   Everything here is layered ON TOP of the palette above, so nothing that
   already works is rewritten and the dark bands keep their own behaviour.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. THE FRAME ─────────────────────────────────────────────────────
   Fixed, ignores the pointer, and drawn in four corner brackets rather
   than a full box — a closed rectangle around a web page reads as a
   border, four brackets read as an instrument's housing. It is drawn with
   gradients on one element so it costs one paint and no markup.

   It hides below 900px: on a phone the viewport IS the housing, and 14px
   of chrome on each side is 8% of the screen spent on decoration. */
/* The chat clears the frame. The HEADER deliberately does not.

   Offsetting a sticky header to sit inside the frame leaves a 13px slot
   above it that the page scrolls through — a sliver of moving text over
   the top of the screen, which looks like a rendering fault rather than a
   design. The frame is drawn at a higher layer instead, so its top
   hairline simply crosses the header. A housing that the instrument's own
   fascia passes behind is what a real one looks like anyway. */
@media (min-width:900px){
  .chat-widget{right:36px;bottom:36px}
}

/* An eyebrow is a small plate, never a full-width bar. Inside the card
   rails and the contact column it sits in a stretching flex column, which
   was pulling it to the whole width of the card. */
.eyebrow,.eyebrow-pill{align-self:flex-start}

/* ── 2. THE CHIP ──────────────────────────────────────────────────────
   The single most repeated element on the site, and now the place where
   dark meets light in every section. A milled dark plate, cyan monospace
   legend, lit top edge, seated with a shadow so it reads as a physical
   part fixed to the page rather than coloured text.

   Inside a dark band it inverts to a lit plate on the dark ground, so the
   same component keeps doing the same job — being the other tone. */
.eyebrow{
  padding:6px 13px 6px 11px;border-radius:6px;
  background:linear-gradient(178deg,#12242E,#08161D);
  color:var(--cyan-bright);
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.20),
    inset 0 -1px 0 rgba(0,0,0,.6),
    0 6px 16px -10px rgba(6,23,31,.85);
  letter-spacing:.2em;
}
.eyebrow::before{
  width:12px;background:var(--cyan-bright);
  box-shadow:0 0 9px rgba(78,226,245,.9);
}
/* On a dark ground the chip becomes the lit one. */
.hero .eyebrow,.section.dark .eyebrow,footer .eyebrow,
.hero .eyebrow-pill{
  background:linear-gradient(178deg,rgba(255,255,255,.13),rgba(255,255,255,.045));
  box-shadow:
    inset 0 1px 0 rgba(210,248,255,.28),
    inset 0 -1px 0 rgba(0,0,0,.45),
    0 0 26px -12px rgba(18,196,222,.8);
}
.eyebrow-pill{
  background:linear-gradient(178deg,#12242E,#08161D);
  color:var(--cyan-bright);border-color:rgba(127,230,245,.2);
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.20),
    inset 0 -1px 0 rgba(0,0,0,.6),
    0 8px 20px -12px rgba(6,23,31,.9);
}
.eyebrow-pill::before{background:var(--cyan-bright);box-shadow:0 0 9px rgba(78,226,245,.9)}

/* ── 3. THE RAIL ──────────────────────────────────────────────────────
   A product card is now two materials stacked: a lit plate holding the
   photograph, and a dark machined rail holding the number and the button.
   That is what a scale, a caliper or a label printer looks like, and it
   does the job a card has to do — the eye goes to the picture, then drops
   to the figure, because the figure is on a different material. */
.product-card .product-body{
  background:linear-gradient(178deg,#0E2029,#081820);
  color:var(--ink-2);
  box-shadow:inset 0 1px 0 rgba(190,240,250,.14);
  background-image:
    repeating-linear-gradient(101deg, rgba(255,255,255,.028) 0 1px, rgba(255,255,255,0) 1px 3px),
    linear-gradient(178deg,#0E2029,#081820);
}
.product-card .product-body h3 a{color:#fff}
.product-card .product-body h3 a:hover{color:var(--cyan-bright)}
.product-card .price{color:#fff}
.product-card .price-note,.product-card .muted{color:#7EA0AC}
.product-card .price-unit{background:rgba(18,196,222,.14);border-color:rgba(18,196,222,.4);color:var(--cyan-bright)}
.product-card .price-ask{color:var(--cyan-bright)}
.product-card .spec-chips span{
  background:rgba(255,255,255,.05);border-color:rgba(127,230,245,.15);color:#9FC0CB;
}
.product-card .card-call{
  background:rgba(18,196,222,.12);border-color:rgba(18,196,222,.45);color:var(--cyan-bright);
}
.product-card .card-call:hover{
  background:linear-gradient(180deg,#4EE2F5,#12C4DE);color:#02171C;
  border-color:rgba(157,239,251,.6);box-shadow:0 10px 30px -12px rgba(18,196,222,.85);
}
/* Same dark-plate glass treatment as the Call button above, in the
   WhatsApp green, so the two sit as one matched pair on the card instead
   of one looking like a premium pill and the other a flat tag. */
.product-card .card-whatsapp{
  background:rgba(79,227,155,.12);border-color:rgba(79,227,155,.45);color:#4FE39B;
}
.product-card .card-whatsapp:hover{
  background:linear-gradient(180deg,#6BEEB0,#17A85B);color:#02180F;
  border-color:rgba(178,247,212,.6);box-shadow:0 10px 30px -12px rgba(23,168,91,.85);
}
.product-card{border-color:rgba(6,23,31,.14)}
.product-card:hover{border-color:var(--cyan)}

/* The gallery caption is the same rail, shorter. */
.gallery-card .gallery-cap{
  background:linear-gradient(178deg,#0E2029,#081820);
  color:#A9C6D1;box-shadow:inset 0 1px 0 rgba(190,240,250,.14);
}
.gallery-card .pf-tag{
  background:rgba(18,196,222,.13);border-color:rgba(18,196,222,.42);color:var(--cyan-bright);
}

/* A feature card gets its index milled into the corner instead. */
.feature-card{padding-top:34px}
.feature-card::after{
  content:"";position:absolute;right:0;top:0;width:44px;height:26px;
  background:linear-gradient(178deg,#12242E,#08161D);
  border-bottom-left-radius:10px;
  box-shadow:inset 0 1px 0 rgba(190,240,250,.18);
  pointer-events:none;
}

/* ── 4. THE HEADER, as a machined rail ────────────────────────────────
   Dark, so the page opens with the chassis and the content sits lit
   beneath it. Smoked, so the hero's light moves behind it. */
.site-header{
  /* Opaque enough that the page does not smear through it.

     At .72 this was properly frosted over the dark hero and a muddy grey
     smudge over the white pages, with headings legibly ghosting through
     the fascia as they scrolled under it. Dark glass over a light ground
     needs far more body than dark glass over a dark one, and the header
     has to work over both. The blur and the saturation stay — they are
     what keep it glass rather than a painted bar. */
  background:rgba(6,18,24,.94);
  backdrop-filter:blur(24px) saturate(170%);-webkit-backdrop-filter:blur(24px) saturate(170%);
  border-bottom:1px solid rgba(127,230,245,.14);
  box-shadow:inset 0 1px 0 rgba(190,240,250,.13), inset 0 -1px 0 rgba(0,0,0,.5);
}
.site-header.is-stuck{
  background:rgba(4,13,18,.97);
  border-bottom-color:rgba(127,230,245,.24);
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.16),
    inset 0 -1px 0 rgba(0,0,0,.6),
    0 22px 44px -34px rgba(0,0,0,.9);
}
.site-header .brand{color:#fff}
.site-header .brand .accent{color:var(--cyan-bright);text-shadow:0 0 22px rgba(18,196,222,.65)}
.site-header nav a{color:#A9C6D1}
.site-header nav a::after{background:var(--cyan-bright);box-shadow:0 0 10px rgba(78,226,245,.9)}
.site-header nav a:hover{color:#fff;background:rgba(127,230,245,.08)}
.menu{
  background:rgba(255,255,255,.06);border-color:rgba(127,230,245,.24);color:#EAF6F9;
}
@media (max-width:760px){
  .site-header nav{
    background:rgba(6,18,24,.97);border-bottom-color:rgba(127,230,245,.22);
  }
  .site-header nav a{border-bottom-color:rgba(127,230,245,.12)}
}

/* ── 5. THE PLATE, lit from within ────────────────────────────────────
   The product image plate gains an inner rim light along its top edge, so
   it reads as a lit surface set into the card rather than a pale
   rectangle painted on it. */
.product-image::before,
.product-detail-image::before{
  content:"";position:absolute;inset:0;pointer-events:none;z-index:1;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.95), inset 0 0 0 1px rgba(6,23,31,.06);
}
.product-detail-image::before{border-radius:inherit}

/* ── 6. TYPE, tightened ───────────────────────────────────────────────
   The one change that reads as "expensive" before any colour does:
   headlines set tighter and heavier, micro-labels opened right out. */
h1{letter-spacing:-.038em}
h2{letter-spacing:-.032em}
.hero h1{letter-spacing:-.042em}
.price,.order-price,.pf-stat strong,.ob-price{letter-spacing:-.04em}

/* ── 7. THE CHAT, as an instrument ────────────────────────────────────
   Dark machined head, lit conversation beneath it — the same two materials
   as everything else, at the smallest scale on the page. */
.chat-bubble{
  border-radius:16px;
  background:linear-gradient(178deg,#12242E,#07151C);
  border:1px solid rgba(127,230,245,.3);
  color:var(--cyan-bright);
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.22),
    inset 0 -1px 0 rgba(0,0,0,.6),
    0 14px 34px -16px rgba(6,23,31,.9),
    0 0 30px -14px rgba(18,196,222,.7);
}
.chat-bubble:hover{
  color:#fff;border-color:var(--cyan-bright);
  box-shadow:
    inset 0 1px 0 rgba(210,248,255,.3),
    0 18px 44px -16px rgba(6,23,31,.9),
    0 0 46px -12px rgba(18,196,222,.95);
}
.chat-bubble-icon{display:grid;place-items:center}
.chat-bubble-icon svg{display:block}
.chat-badge{box-shadow:0 0 0 2px rgba(8,22,29,.9), 0 6px 14px -6px rgba(0,0,0,.8)}
.chat-whatsapp-fab{
  border-radius:var(--radius-lg);
  background:linear-gradient(178deg,#1FAE55,#0F7A3E);
  border:1px solid rgba(79,227,155,.4);
  box-shadow:
    inset 0 1px 0 rgba(190,250,220,.22),
    inset 0 -1px 0 rgba(0,0,0,.5),
    0 12px 28px -14px rgba(6,23,31,.9);
}
.chat-whatsapp-fab:hover{
  border-color:#4FE39B;
  box-shadow:
    inset 0 1px 0 rgba(210,255,235,.3),
    0 16px 36px -14px rgba(6,23,31,.9),
    0 0 30px -12px rgba(37,211,102,.8);
}
.chat-panel{
  border-radius:16px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.85),
    var(--shadow-lg),
    0 0 70px -34px rgba(18,196,222,.5);
}
.chat-head{
  background-image:
    repeating-linear-gradient(101deg, rgba(255,255,255,.03) 0 1px, rgba(255,255,255,0) 1px 3px),
    var(--grad-ink);
  box-shadow:inset 0 1px 0 rgba(190,240,250,.18), inset 0 -1px 0 rgba(0,0,0,.5);
}
.chat-send,.chat-close{display:grid;place-items:center}
.chat-send svg,.chat-close svg{display:block}

/* ── 8. THE SCROLL FILAMENT ───────────────────────────────────────────
   Thicker, lit, and seated in a dark channel so it reads as a gauge
   rather than a coloured line. */
.scroll-rail{height:3px;background:rgba(8,22,29,.5)}
.scroll-rail i{
  background:linear-gradient(90deg,var(--cyan-deep),#4EE2F5);
  box-shadow:0 0 14px rgba(18,196,222,.9);
}

/* ── 9. BUTTONS, machined ─────────────────────────────────────────────
   The primary is a milled dark key with a lit rim; on a dark band the
   palette already flips it to a cyan lamp. */
.btn.primary{
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.22),
    inset 0 -1px 0 rgba(0,0,0,.55),
    var(--btn-shadow);
}
.btn.ghost,.btn-call{
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9), inset 0 -1px 0 rgba(6,23,31,.05);
}
.hero .btn.ghost,.hero .btn-call,
.section.dark .btn.ghost,.section.dark .btn-call{
  box-shadow:inset 0 1px 0 rgba(190,240,250,.18), inset 0 -1px 0 rgba(0,0,0,.4);
}

/* ── 10. THE FOOTER PLINTH ────────────────────────────────────────────
   The page ends on the chassis, with one lit hairline where it meets the
   content above it. */
footer::before{height:1px;opacity:1;box-shadow:0 0 18px rgba(18,196,222,.55)}

@media (prefers-reduced-motion: reduce){
  .chat-bubble,.btn,.btn-call,.chat-whatsapp-fab{transition:none !important}
}

/* ═══════════════════════════════════════════════════════════════════════
   CINEMATIC GLASS — v2 UPGRADE LAYER
   ---------------------------------------------------------------------------
   The owner's brief: nothing about the previous look should survive as-is.
   Heavier, more high-end, more "next level" — full cinematic glassmorphism
   everywhere, on the white pages as much as the dark ones, white+cyan the
   whole way through, light and dark united in one epic material.

   This layer does not rewrite the file above — every existing class and
   every script-generated element keeps working exactly as before. It turns
   the same tokens up: real frosted glass on white pages (previously off on
   purpose), a permanent ambient cyan atmosphere behind every page, stronger
   glow on every card, and a purpose-built cinematic treatment for the
   product page — the page a buyer actually decides on. Because it is
   appended last, it wins the cascade without needing !important anywhere.
   ═══════════════════════════════════════════════════════════════════════ */

:root{
  /* Frosted glass switched ON for the light pages too — the single change
     that turns every .feature-card/.category-card/.product-card/.gallery-
     card/.cta-band-inner/.tab-pill/.btn.ghost/.btn-call into real glass,
     because they already read this token. */
  --glass:blur(24px) saturate(190%);

  /* Cards become translucent panes instead of flat white paper, so the
     ambient atmosphere below shows through them like light through a
     showroom window. */
  --card-bg:linear-gradient(166deg,rgba(255,255,255,.86),rgba(255,255,255,.58));
  --card-bg-2:rgba(255,255,255,.62);
  --card-border:rgba(18,196,222,.22);
  --field-bg:rgba(255,255,255,.72);
  --chip-bg:linear-gradient(166deg,rgba(255,255,255,.75),rgba(231,243,247,.45));

  /* Glow switched on everywhere, at a level judged for a WHITE ground —
     enough to feel lit, never enough to muddy the paper. */
  --glow:0 0 1px rgba(18,196,222,.05),0 30px 70px -26px rgba(7,151,174,.42);
  --glow-ring:0 0 0 1px rgba(18,196,222,.22);
  --text-glow:0 0 18px rgba(7,151,174,.22);

  --shadow-lg:0 2px 5px rgba(6,23,31,.05), 0 46px 90px -36px rgba(6,23,31,.4);
  --radius-lg:16px;
  --radius-xl:24px;
}

/* ── The atmosphere ─────────────────────────────────────────────────────
   Fixed behind the entire document — three slow cyan blooms and a hairline
   of film grain. This is what the new frosted glass actually blurs; a
   glass pane over flat white paper is just a soft-edged rectangle, a glass
   pane over light is a window. Sits at the very back of the stack, drawn
   once, never repainted on scroll. */
body{position:relative;background:#FBFEFF}
body::before{
  content:"";position:fixed;inset:0;z-index:-2;pointer-events:none;
  background:
    radial-gradient(1100px 620px at 88% -12%, rgba(18,196,222,.16), transparent 60%),
    radial-gradient(820px 560px at -8% 22%,  rgba(7,151,174,.11),  transparent 58%),
    radial-gradient(900px 620px at 46% 112%, rgba(4,96,116,.13),   transparent 60%),
    linear-gradient(180deg,#FFFFFF 0%,#F8FCFD 100%);
  animation:atmoDrift 26s var(--ease-soft) infinite alternate;
}
body::after{
  content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
  background-image:var(--grain-soft);background-size:160px 160px;
  opacity:.35;mix-blend-mode:multiply;
}
@keyframes atmoDrift{
  from{background-position:0 0,0 0,0 0,0 0}
  to  {background-position:2% -2%,-2% 2%,-1% 1%,0 0}
}
@media (min-width:900px){ body::before{z-index:-2} }
@media (prefers-reduced-motion: reduce){ body::before{animation:none} }

/* ── Every card is now a true glass pane ─────────────────────────────── */
.feature-card,.category-card,.product-card,.cta-band-inner,.pf-stat,
.gallery-card,.inq-card,.tab-pill,.eyebrow-pill,.skeleton{
  box-shadow:0 1px 0 rgba(255,255,255,.7) inset, 0 26px 54px -34px rgba(6,23,31,.28);
}
.feature-card::after,.category-card::after,.product-card::after,.gallery-card::after{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:1;
  background:linear-gradient(155deg,rgba(255,255,255,.5) 0%,transparent 26%);
  mix-blend-mode:overlay;
}
.category-card::after{content:"→";background:none;mix-blend-mode:normal} /* restore the arrow glyph */
.feature-card:hover,.category-card:hover,.product-card:hover,.gallery-card:hover{
  box-shadow:var(--shadow-lg),var(--glow), 0 1px 0 rgba(255,255,255,.8) inset;
}

/* ── Product listing plate — a touch more premium ─────────────────────── */
.product-image::before{
  content:"";position:absolute;inset:0;pointer-events:none;z-index:1;
  background:linear-gradient(155deg,rgba(255,255,255,.55),transparent 40%);
  mix-blend-mode:overlay;
}
.price{text-shadow:0 0 24px rgba(7,151,174,.12)}

/* ── Buttons — deeper glass, sharper light sweep ───────────────────────── */
.btn.ghost,.btn-call,.btn-whatsapp{box-shadow:var(--glow-ring),inset 0 1px 0 rgba(255,255,255,.85),0 18px 40px -26px rgba(6,23,31,.35)}
.btn.primary{box-shadow:var(--btn-shadow),0 0 44px -18px rgba(18,196,222,.4)}

/* ═══════════════════════════════════════════════════════════════════════
   THE PRODUCT PAGE — full cinematic treatment
   The page a customer actually converts on. It gets its own atmosphere,
   a genuinely epic glass specimen frame, a description card that reads
   like a spec sheet under glass, and an order panel that behaves like the
   hero: it breathes.
   ═══════════════════════════════════════════════════════════════════════ */

.section[data-page="product-detail"]{
  position:relative;isolation:isolate;overflow:hidden;
  padding-top:clamp(52px,6vw,88px);
}
.section[data-page="product-detail"]::before{
  /* replaces the printed registration ticks on this page only, with a lit
     floor-grid that fades toward the reader — the hero's language, brought
     to a white page. */
  background-image:
    linear-gradient(rgba(7,151,174,.07) 1px,transparent 1px),
    linear-gradient(90deg,rgba(7,151,174,.07) 1px,transparent 1px);
  background-size:64px 64px;opacity:1;
  mask-image:radial-gradient(120% 70% at 50% 0%,#000 0%,transparent 78%);
  -webkit-mask-image:radial-gradient(120% 70% at 50% 0%,#000 0%,transparent 78%);
}
.section[data-page="product-detail"] .container{position:relative;z-index:1}
.section[data-page="product-detail"]::after{
  content:"";position:absolute;z-index:0;pointer-events:none;
  right:-14%;top:-22%;width:64%;aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle,rgba(18,196,222,.20),transparent 66%);
  filter:blur(70px);
  animation:breathe 16s var(--ease-soft) infinite alternate;
}

.breadcrumb{
  padding:8px 16px;border-radius:999px;display:inline-flex;width:fit-content;
  background:var(--card-bg-2);border:1px solid var(--card-border);
  backdrop-filter:var(--glass);-webkit-backdrop-filter:var(--glass);
}

/* The specimen frame — glass, corner-lit, floating. */
.product-detail-image{
  background:rgba(255,255,255,.06);
  backdrop-filter:blur(26px) saturate(190%);-webkit-backdrop-filter:blur(26px) saturate(190%);
  border:1px solid rgba(18,196,222,.3);border-radius:var(--radius-xl);
  box-shadow:
    0 1px 0 rgba(255,255,255,.8) inset,
    0 40px 90px -34px rgba(6,23,31,.34),
    0 0 100px -34px rgba(18,196,222,.5);
  transition:transform .7s var(--ease-out),box-shadow .5s var(--ease);
}
.product-detail-image::before,.product-detail-image::after{
  width:22px;height:22px;border-color:var(--cyan-bright);
  box-shadow:0 0 16px rgba(18,196,222,.75);opacity:1;
}
.product-detail-image:hover{
  transform:translateY(-6px);
  box-shadow:
    0 1px 0 rgba(255,255,255,.85) inset,
    0 54px 110px -32px rgba(6,23,31,.4),
    0 0 130px -30px rgba(18,196,222,.65);
}

.product-detail h1{
  background:linear-gradient(180deg,var(--ink),var(--ink-2));
  -webkit-background-clip:text;background-clip:text;color:var(--ink);
}
.product-detail .spec-chips span{
  background:var(--chip-bg);border-color:rgba(18,196,222,.32);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  font-weight:700;letter-spacing:.16em;color:var(--cyan-ink);
  box-shadow:0 8px 20px -14px rgba(6,23,31,.25);
}

/* The description — a glass spec sheet, not a flat tinted box. */
.detail-desc{
  background:linear-gradient(165deg,rgba(255,255,255,.78),rgba(231,243,247,.42));
  backdrop-filter:blur(22px) saturate(170%);-webkit-backdrop-filter:blur(22px) saturate(170%);
  border:1px solid rgba(18,196,222,.26);border-left:3px solid var(--cyan-bright);
  border-radius:var(--radius-xl);
  box-shadow:0 1px 0 rgba(255,255,255,.8) inset, 0 30px 60px -34px rgba(6,23,31,.3);
  position:relative;overflow:hidden;
}
.detail-desc::before{
  content:"";position:absolute;left:-30%;top:-140%;width:60%;height:260%;pointer-events:none;
  background:radial-gradient(circle,rgba(18,196,222,.16),transparent 65%);filter:blur(30px);
}

/* The order panel now breathes like the hero specimen card — the same
   material, the same behaviour, on the page that matters most. */
.order-panel{
  border-radius:var(--radius-xl);
  box-shadow:
    0 1px 0 rgba(255,255,255,.06) inset,
    var(--shadow-lg),
    0 0 90px -30px rgba(18,196,222,.55);
  animation:panelBreathe 7s var(--ease-soft) infinite alternate;
}
@keyframes panelBreathe{
  from{box-shadow:0 1px 0 rgba(255,255,255,.06) inset,var(--shadow-lg),0 0 80px -32px rgba(18,196,222,.48)}
  to  {box-shadow:0 1px 0 rgba(255,255,255,.08) inset,var(--shadow-lg),0 0 120px -26px rgba(18,196,222,.68)}
}
.order-panel::before{height:2px;box-shadow:0 0 16px rgba(18,196,222,.7)}
.order-price{text-shadow:0 0 60px rgba(18,196,222,.55),0 0 16px rgba(78,226,245,.5)}
.order-actions .btn,.order-actions .btn-call,.order-actions .btn-whatsapp{backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%)}

.gallery-grid .gallery-card{backdrop-filter:var(--glass);-webkit-backdrop-filter:var(--glass)}

@media (max-width:760px){
  .section[data-page="product-detail"]::after{display:none}
}

/* ── Print — the atmosphere layer must never reach paper ───────────────── */
@media print{
  body::before,body::after{display:none !important}
  .section[data-page="product-detail"]::after{display:none !important}
}

/* ── Print ───────────────────────────────────────────────────────────
   Paper is white and toner is expensive, so the dark bands are inverted
   back for print. A dark band sent to a printer is a solid black page. */
@media print{
  .site-header,.chat-widget,.order-bar,footer,.hero-bg,.grid-lines,
  .scroll-rail,.spotlight,.hero-glow{display:none !important}
  body::before{display:none !important}
  html,body{background:#fff !important}
  body{font-size:12pt;color:#000}
  .hero,.section.dark{background:#fff !important;background-image:none !important}
  h1,h2,h3,h4,.price,.order-price,.pf-stat strong,.brand,.hero h1{color:#000 !important;text-shadow:none !important}
  .lead,.muted,.tiny,.small,p{color:#222 !important}
  .accent,.hero h1 .accent{color:#046074 !important;text-shadow:none !important}
  .hero h1 .accent::before{display:none !important}
  .section{padding:14pt 0;border-top:1px solid #ccc}
  .section::before,.section::after{display:none !important}
  .feature-card,.category-card,.product-card,.cta-band-inner,.pf-stat,
  .gallery-card,.inq-card,.order-panel,.contact-note,.detail-desc,
  .tab-pill,.btn,.eyebrow-pill,.pf-stats,.label-demo{
    background:#fff !important;background-image:none !important;
    border-color:#999 !important;color:#000 !important;
    box-shadow:none !important;backdrop-filter:none !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   MONUMENTAL — v3 EPIC CINEMATIC UPGRADE
   ---------------------------------------------------------------------------
   Brief: nothing about the current look survives as-is. Heavier, more
   high-end, more "next level" than the v2 glass pass — full cinematic
   glassmorphism pushed to a monumental, mesmerizing register, white+cyan
   the entire way through, one unified theme where the light stretches and
   the dark stretches are two moods of the same material, not two themes.

   Appended last, so it wins the cascade with zero !important. Nothing
   below removes a class or a script hook — every element the pages and
   app.js depend on keeps its name and its job. This layer only makes the
   same names heavier, deeper, and lit.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. Monumental scale & type ────────────────────────────────────────── */
:root{
  --radius-lg:18px;
  --radius-xl:28px;
  --ease-epic:cubic-bezier(.16,1,.3,1);
}
h1{font-size:clamp(42px,6.4vw,92px);letter-spacing:-.035em}
.hero h1{letter-spacing:-.045em;line-height:1.02}
.page-hero h1{font-size:clamp(36px,5vw,66px)}
h2{font-size:clamp(30px,4vw,54px)}
.lead{font-size:clamp(17px,1.5vw,20.5px)}

/* Custom, cinematic scrollbar — thin cyan filament in a dark channel. */
html{scrollbar-width:thin;scrollbar-color:#12C4DE rgba(6,23,31,.15)}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:rgba(6,23,31,.06)}
::-webkit-scrollbar-thumb{
  background:linear-gradient(180deg,#4EE2F5,#0797AE);border-radius:99px;
  border:2px solid rgba(255,255,255,.6);
}

/* ── 2. The atmosphere, turned into a full cinematic environment ────────
   Bigger blooms, a third depth layer, a slow light sweep that crosses the
   whole document, and a permanent soft vignette that pulls the eye to the
   centre column the way a lens does. */
body{background:#FBFEFF}
body::before{
  background:
    radial-gradient(1300px 760px at 90% -16%, rgba(18,196,222,.19), transparent 60%),
    radial-gradient(980px 680px at -10% 18%,  rgba(7,151,174,.13),  transparent 58%),
    radial-gradient(1000px 700px at 50% 118%, rgba(4,96,116,.15),   transparent 60%),
    radial-gradient(1600px 900px at 50% 50%,  transparent 55%, rgba(4,26,34,.05) 100%),
    linear-gradient(180deg,#FFFFFF 0%,#F7FCFD 100%);
  animation:atmoDrift 30s var(--ease-epic) infinite alternate;
}
@keyframes lightSweep{
  0%{transform:translateX(-120%) skewX(-12deg)}
  100%{transform:translateX(220%) skewX(-12deg)}
}
.hero::after{
  content:"";position:absolute;inset:-10% -40%;z-index:-1;pointer-events:none;
  width:36%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.05),transparent);
  animation:lightSweep 9s linear infinite;
}
@media (prefers-reduced-motion: reduce){ .hero::after{animation:none;display:none} }

/* ── 3. Header — the machined rail, final finish ─────────────────────────
   Earlier drafts of this pass reused the pale-glass fascia from the very
   first version of the header and it read as washed-out white-on-white —
   the brand and nav labels are set in white / pale cyan-grey specifically
   for the dark rail below, so the rail has to stay dark and near-opaque
   on every page, not just the home hero. Fixed here: same premium finish
   (deeper blur, glowing seam, heavier shadow) but back on its dark ground,
   so "InCodeLabel" and the nav links are crisp on every page. */
.site-header{
  background:rgba(5,15,20,.88);
  backdrop-filter:blur(30px) saturate(200%);
  -webkit-backdrop-filter:blur(30px) saturate(200%);
  border-bottom:1px solid rgba(127,230,245,.16);
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.14),
    inset 0 -1px 0 rgba(0,0,0,.5),
    0 24px 60px -44px rgba(0,0,0,.6);
}
.site-header::after{
  content:"";position:absolute;left:0;right:0;bottom:-1px;height:1px;
  background:linear-gradient(90deg,transparent,rgba(78,226,245,.75),transparent);
  opacity:.9;
}
.site-header.is-stuck{
  background:rgba(4,12,17,.96);
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.16),
    inset 0 -1px 0 rgba(0,0,0,.6),
    0 30px 70px -40px rgba(0,0,0,.75);
}
.site-header .nav{height:82px}
.site-header .brand{font-size:25px;letter-spacing:-.035em;color:#fff}
.site-header .brand .accent{color:var(--cyan-bright);text-shadow:0 0 26px rgba(18,196,222,.7)}
.site-header nav a{font-size:14px;letter-spacing:.01em;color:#A9C6D1;font-weight:600}
.site-header nav a:hover{color:#fff;background:rgba(127,230,245,.08)}
.site-header nav a::after{
  height:2px;border-radius:2px;
  background:var(--cyan-bright);
  box-shadow:0 0 10px rgba(78,226,245,.9);
}
.site-header nav a.is-active{color:#fff}
.site-header nav a.is-active::after{transform:scaleX(1)}
.menu{
  background:rgba(255,255,255,.06);border-color:rgba(127,230,245,.24);color:#EAF6F9;
  backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
  box-shadow:inset 0 1px 0 rgba(190,240,250,.14), 0 10px 26px -18px rgba(0,0,0,.4);
  transition:border-color .25s var(--ease),box-shadow .25s var(--ease),background .25s var(--ease);
}
.menu:hover{border-color:var(--cyan-bright);background:rgba(127,230,245,.1)}

/* ── 4. Hero — the monumental stage ──────────────────────────────────────
   A cinema-scale opener: deeper floor, brighter orbs with slow parallax
   drift, a heavier eyebrow chip, kinetic accent word with a moving glint,
   and a hero card that reads as a lit specimen suspended in the room. */
.hero{
  padding:clamp(92px,10vw,168px) 0 clamp(88px,9vw,150px);
}
.orb-cyan{
  box-shadow:0 0 240px 100px rgba(18,196,222,.30);
  animation-duration:16s;
}
.orb-cyan-soft{
  box-shadow:0 0 300px 130px rgba(4,96,116,.22);
}
.eyebrow-pill{
  padding:9px 18px;font-size:11.5px;letter-spacing:.2em;
  border-radius:999px;
  background:linear-gradient(160deg,rgba(255,255,255,.14),rgba(255,255,255,.03));
  border:1px solid rgba(127,230,245,.32);
  backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
  box-shadow:0 1px 0 rgba(255,255,255,.12) inset, 0 14px 30px -20px rgba(0,0,0,.6);
}
.hero h1 .accent{
  text-shadow:0 0 36px rgba(18,196,222,.55),0 0 90px rgba(18,196,222,.25);
}
.hero .lead{font-size:clamp(17.5px,1.5vw,21px);max-width:52ch}

.hero-card{filter:drop-shadow(0 40px 90px rgba(0,0,0,.55))}
.hero-glow{
  box-shadow:0 0 220px 80px rgba(18,196,222,.35);
}
.label-demo{
  border-radius:var(--radius-xl);
  box-shadow:
    0 1px 0 rgba(255,255,255,.14) inset,
    0 50px 100px -34px rgba(0,0,0,.75),
    0 0 130px -30px rgba(18,196,222,.6);
  backdrop-filter:blur(30px) saturate(200%);-webkit-backdrop-filter:blur(30px) saturate(200%);
}
.label-demo::before,.label-demo::after{
  width:26px;height:26px;border-color:rgba(157,239,251,.9);
  box-shadow:0 0 18px rgba(18,196,222,.8);
}
.barcode-real{box-shadow:0 0 46px -6px rgba(18,196,222,.6), 0 14px 34px -14px rgba(0,0,0,.95)}

.trust{gap:22px}
.trust span{
  display:inline-flex;align-items:center;gap:7px;
  padding:6px 12px;border-radius:999px;
  background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
}

/* ── 5. Buttons — liquid glass with a moving sheen ───────────────────────
   The primary key becomes a lit ingot; the ghost/call pill a true glass
   capsule. Same classes, same click targets, dramatically heavier finish. */
.btn,.btn-call{
  border-radius:999px;padding:15px 30px;font-size:14.5px;letter-spacing:.02em;
  transition:transform .45s var(--ease-epic),box-shadow .45s var(--ease-epic),
             border-color .3s var(--ease),background .3s var(--ease);
}
.btn.primary{
  background:var(--btn-bg);position:relative;overflow:hidden;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.3),
    inset 0 -2px 6px rgba(0,0,0,.35),
    var(--btn-shadow),0 0 60px -20px rgba(18,196,222,.5);
}
.btn.primary:hover{transform:translateY(-3px) scale(1.015)}
.btn.ghost,.btn-call{
  backdrop-filter:blur(22px) saturate(190%);-webkit-backdrop-filter:blur(22px) saturate(190%);
  border-width:1.5px;
}
.btn.ghost:hover,.btn-call:hover{transform:translateY(-3px)}
.card-call{border-radius:999px}
/* WhatsApp gets the exact same liquid-glass upgrade as Call, so the pair
   reads as one design, not two different button styles. */
.btn-whatsapp{
  border-radius:999px;padding:15px 30px;font-size:14.5px;letter-spacing:.02em;
  backdrop-filter:blur(22px) saturate(190%);-webkit-backdrop-filter:blur(22px) saturate(190%);
  border-width:1.5px;
  transition:transform .45s var(--ease-epic),box-shadow .45s var(--ease-epic),
             border-color .3s var(--ease),background .3s var(--ease);
}
.btn-whatsapp:hover{transform:translateY(-3px)}
.card-whatsapp{border-radius:999px}

/* ── 6. Cards — cursor-lit glass panes, corner-braced, floating on hover ─
   Every card family now tracks the pointer via the same --mx/--my the
   spotlight already writes, so the highlight sits exactly under the
   cursor — the one touch that makes glass feel physically present. */
.feature-card,.category-card,.product-card,.cta-band-inner,.pf-stat,
.gallery-card,.inq-card,.order-panel,.contact-note,.detail-desc{
  border-radius:var(--radius-lg);
  border-width:1.5px;
  transition:transform .55s var(--ease-epic),box-shadow .5s var(--ease-epic),border-color .35s var(--ease);
}
.feature-card::before{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:0;
  opacity:0;transition:opacity .45s var(--ease);
  background:radial-gradient(340px circle at var(--mx,50%) var(--my,50%),
    rgba(18,196,222,.16),transparent 70%);
}
.feature-card:hover::before,.category-card:hover::before{opacity:1}
.feature-card:hover,.category-card:hover,.product-card:hover,
.gallery-card:hover,.inq-card:hover{
  transform:translateY(-8px);
  border-color:rgba(18,196,222,.45);
}
.pf-stat:hover{transform:translateY(-4px)}
/* Corner braces — the registration-mark language, shrunk onto every card. */
.feature-card,.category-card,.product-card,.gallery-card,.cta-band-inner{position:relative}
.feature-card::after,.category-card::after,.product-card::after,.gallery-card::after,.cta-band-inner::after{
  --bl:16px;
}
.product-detail-image::before,.product-detail-image::after,
.label-demo::before,.label-demo::after{
  transition:box-shadow .5s var(--ease);
}

/* Chips — glass capsules with a lit rim */
.spec-chips span,.eyebrow-pill,.tab-pill{
  backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);
}

/* ── 7. Footer — the closing plinth, monumental version ──────────────────
   Deeper ink, a brighter seam, and glass panels for the contact links so
   the footer reads as one more lit surface rather than a flat end-cap. */
footer{padding:clamp(64px,7vw,96px) 0 clamp(36px,4vw,48px)}
footer::before{
  height:2px;
  background:linear-gradient(90deg,transparent,#4EE2F5,transparent);
  box-shadow:0 0 26px rgba(78,226,245,.8);
}
.footer-contact a{
  display:inline-flex;align-items:center;gap:9px;padding:10px 16px;border-radius:999px;
  background:rgba(255,255,255,.05);border:1px solid rgba(127,230,245,.22);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  transition:border-color .3s var(--ease),background .3s var(--ease),transform .3s var(--ease);
}
.footer-contact a:hover{border-color:var(--cyan-bright);background:rgba(127,230,245,.1);transform:translateY(-2px)}
.footer-grid .brand{text-shadow:0 0 30px rgba(18,196,222,.25)}

/* The bottom row as one glass strip, instead of two lines of text floating
   on bare ink — a copyright line is not decoration, but it reads better
   sitting on something. */
.footer-bottom{
  position:relative;margin-top:8px;padding:16px 22px !important;border-radius:16px;
  background:rgba(255,255,255,.04);border:1px solid rgba(127,230,245,.14);border-top:1px solid rgba(127,230,245,.14) !important;
  backdrop-filter:blur(14px) saturate(160%);-webkit-backdrop-filter:blur(14px) saturate(160%);
}

/* Back-to-top floating button: removed site-wide by request. Only the
   chat widget (WhatsApp fab + chat bubble) remains in the bottom-right
   corner, styled exactly as it already was. */

/* ── 8. Forms — glass fields with a lit focus ring ───────────────────────
   Contact and admin inputs get the same frosted material as the cards,
   so the whole page — not just its cards — feels machined from one glass. */
.contact-form input,.contact-form textarea{
  border-radius:var(--radius);border-width:1.5px;
  background:var(--field-bg);
  backdrop-filter:blur(14px) saturate(180%);-webkit-backdrop-filter:blur(14px) saturate(180%);
  transition:border-color .3s var(--ease),box-shadow .3s var(--ease),background .3s var(--ease);
}
.contact-form input:focus,.contact-form textarea:focus{
  box-shadow:var(--ring),0 0 30px -14px rgba(18,196,222,.5);
  background:var(--paper);
}

/* ── 9. Page hero (Products / Gallery / Contact) — same monumental floor
   the home hero stands on, brought to the white pages. ──────────────────── */
.page-hero{padding:clamp(70px,7.5vw,116px) 0 clamp(48px,5vw,72px)}
.page-hero::before{
  background:
    radial-gradient(1100px 520px at 84% -34%, rgba(18,196,222,.18), transparent 62%),
    radial-gradient(700px 420px at 8% 10%, rgba(7,151,174,.09), transparent 60%),
    linear-gradient(rgba(7,151,174,.06) 1px,transparent 1px),
    linear-gradient(90deg,rgba(7,151,174,.06) 1px,transparent 1px);
  background-size:auto, auto, 78px 78px, 78px 78px;
}
.breadcrumb{
  padding:7px 16px;border-radius:999px;
  background:var(--card-bg-2);border:1px solid var(--card-border);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  width:fit-content;
}

/* ── 10. Product detail — pushed one more level past the v2 pass ────────
   A brighter atmosphere ring, a specimen frame with a slow internal glint,
   a price that reads like a marquee number, and an order panel with
   layered corner braces to match the rest of the card family. */
.section[data-page="product-detail"]::after{
  width:74%;filter:blur(86px);
  background:radial-gradient(circle,rgba(18,196,222,.26),transparent 66%);
}
.product-detail-image{border-radius:var(--radius-xl)}
.product-detail-image::before{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:2;
  background:linear-gradient(120deg,transparent 30%,rgba(255,255,255,.35) 46%,transparent 62%);
  background-size:220% 220%;
  animation:glintSweep 7s var(--ease-epic) infinite;
  mix-blend-mode:overlay;
}
@keyframes glintSweep{
  0%,100%{background-position:120% -20%}
  50%{background-position:-20% 120%}
}
.order-price{
  font-size:clamp(38px,4.5vw,56px);
  text-shadow:0 0 70px rgba(18,196,222,.6),0 0 20px rgba(78,226,245,.55);
}
.order-panel{border-radius:var(--radius-xl)}

@media (prefers-reduced-motion: reduce){
  .product-detail-image::before{animation:none}
}

/* ── 11. Reveal-in — a touch more cinematic than a plain fade ────────────
   Rises and softly de-blurs into place instead of only fading, without
   touching the class app.js toggles. */
.js .reveal{filter:blur(6px);transition:opacity .8s var(--ease-out),transform .8s var(--ease-out),filter .9s var(--ease-out)}
.reveal.in-view,.reveal-all .reveal{filter:blur(0)}

@media print{
  .hero::after{display:none !important}
}

/* ═══════════════════════════════════════════════════════════════════════
   CHAT — CONVERSATION HISTORY & COMPOSER, A PREMIUM PASS
   ---------------------------------------------------------------------------
   The bubble, panel and header already got their "machined instrument"
   finish above (§7) and their glass upgrade in the v2/v3 layers. This pass
   is about what happens ONCE the panel is open: the thread itself should
   read the way ChatGPT / Gemini / Claude's own history does — grouped by
   day, timestamped, sender bursts collapsed into one turn instead of a
   label per line, new messages arriving with a soft lift instead of a
   hard cut, and a composer that grows with what you're typing instead of
   scrolling sideways inside one fixed-height line.

   Nothing here touches the privacy behaviour in chat.js (a fresh page load
   still starts empty on a shared computer, History still has to be
   tapped) — this only changes how that same data is drawn.

   Appended last, so it wins the cascade with zero !important.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── The composer — an auto-growing textarea, not a fixed single line ──
   Enter sends, Shift+Enter starts a new line (see chat.js); this is just
   the shape it grows into as chat.js adjusts its height. */
.chat-input-row{align-items:flex-end}
.chat-input-row textarea{
  flex:1;min-width:0;resize:none;
  height:44px;min-height:44px;max-height:116px;
  padding:11px 15px;border-radius:var(--radius);
  border:1px solid var(--line-strong);background:var(--field-bg,var(--paper));
  font-size:14.5px;line-height:1.5;color:var(--ink);font-family:var(--font-body);
  transition:border-color .22s var(--ease),box-shadow .22s var(--ease);
}
.chat-input-row textarea::placeholder{color:var(--muted)}
.chat-input-row textarea:focus{outline:none;border-color:var(--cyan);box-shadow:var(--ring)}
.chat-send{align-self:flex-end;transition:filter .22s var(--ease),transform .22s var(--ease),opacity .22s var(--ease)}
.chat-send:disabled{opacity:.4;cursor:not-allowed;transform:none !important;filter:none !important}

/* ── The body becomes a positioning shell, so a "jump to latest" button
   can float over it without disturbing the flex column beneath (head,
   body, suggestions, input row). ─────────────────────────────────────── */
.chat-body-wrap{position:relative;flex:1;min-height:0;display:flex}
.chat-body-wrap .chat-body{flex:1}

.chat-scroll-btn{
  position:absolute;right:14px;bottom:14px;z-index:2;width:34px;height:34px;
  border-radius:50%;display:grid;place-items:center;cursor:pointer;
  background:var(--card-bg-2,var(--paper));border:1px solid var(--card-border,var(--line-strong));
  color:var(--cyan-ink);box-shadow:var(--shadow);
  backdrop-filter:var(--glass);-webkit-backdrop-filter:var(--glass);
  opacity:0;visibility:hidden;transform:translateY(8px) scale(.92);
  transition:opacity .22s var(--ease),transform .22s var(--ease),visibility .22s,border-color .22s var(--ease),color .22s var(--ease);
}
.chat-scroll-btn.show{opacity:1;visibility:visible;transform:none}
.chat-scroll-btn:hover{border-color:var(--cyan);color:var(--cyan)}
.chat-scroll-btn svg{display:block}

/* ── Your conversations panel — a small ChatGPT/Claude-style thread list
   that slides over the message body without disturbing the header or the
   composer beneath it. ──────────────────────────────────────────────── */
.chat-threads-panel{
  position:absolute;inset:0;z-index:3;display:flex;flex-direction:column;
  background:var(--paper);
}
.chat-threads-head{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:14px 16px 10px;font-family:var(--font-display);font-weight:700;font-size:14px;color:var(--ink);
}
.chat-threads-close{
  width:28px;height:28px;border-radius:var(--radius-sm);flex:none;
  border:1px solid var(--line);background:transparent;color:var(--ink-2);
  cursor:pointer;display:grid;place-items:center;transition:background .2s,color .2s;
}
.chat-threads-close:hover{background:var(--soft);color:var(--ink)}
.chat-thread-new{
  margin:0 16px 10px;display:flex;align-items:center;justify-content:center;gap:8px;
  padding:10px 14px;border-radius:var(--radius);cursor:pointer;
  font-family:var(--font-display);font-weight:700;font-size:13.5px;
  background:linear-gradient(180deg,#4EE2F5,#12C4DE);color:var(--on-cyan);border:none;
  transition:filter .2s;
}
.chat-thread-new:hover{filter:brightness(1.06)}
.chat-thread-list{flex:1;overflow-y:auto;padding:0 10px 12px;display:flex;flex-direction:column;gap:4px}
.chat-thread-item{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  width:100%;text-align:left;padding:11px 12px;border-radius:var(--radius-sm);
  background:transparent;border:1px solid transparent;cursor:pointer;
  transition:background .2s,border-color .2s;
}
.chat-thread-item:hover{background:var(--soft)}
.chat-thread-item.active{background:var(--soft);border-color:var(--line-strong)}
.chat-thread-preview{
  font-size:13.5px;color:var(--ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.chat-thread-item.active .chat-thread-preview{color:var(--ink);font-weight:600}
.chat-thread-when{font-family:var(--font-mono);font-size:10.5px;color:var(--muted);flex:none}

/* ── Day dividers — "Today / Yesterday / 12 Aug", centred and quiet ────── */
.chat-date-sep{
  align-self:stretch;display:flex;align-items:center;gap:10px;
  margin:6px 0 2px;color:var(--muted);
  font-family:var(--font-mono);font-size:9.5px;font-weight:700;
  letter-spacing:.12em;text-transform:uppercase;
}
.chat-date-sep::before,.chat-date-sep::after{content:"";flex:1;height:1px;background:var(--line)}

/* ── The "you're viewing full history" marker — shown once, the first
   message in from before this page load, only while History is open. ── */
.chat-history-marker{
  align-self:center;display:flex;align-items:center;gap:6px;
  margin:0 0 8px;padding:6px 14px;border-radius:999px;
  background:var(--cyan-wash);color:var(--cyan-ink);
  border:1px solid rgba(7,151,174,.18);
  font-family:var(--font-mono);font-size:9.5px;font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;
}

/* ── Sender bursts — the avatar + name only repeats when the sender
   actually changes, so three replies in a row read as one turn. Grouped
   bubbles sit closer together than a fresh turn does. ──────────────────── */
.chat-msg.grouped{margin-top:-6px}
.chat-msg-meta{display:flex;align-items:center;gap:7px;margin-bottom:1px}
.chat-msg-avatar{
  width:19px;height:19px;flex:none;border-radius:50%;display:grid;place-items:center;
  font-family:var(--font-mono);font-size:8px;font-weight:700;letter-spacing:.01em;
}
.chat-msg-avatar.ai{background:linear-gradient(180deg,#4EE2F5,#12C4DE);color:var(--on-cyan)}
.chat-msg-avatar.admin{background:linear-gradient(180deg,#0D2531,#06171F);color:#fff}

/* ── A quiet timestamp under every bubble — right-aligned for the
   visitor's own messages, left-aligned for the reply, exactly like the
   thread it's modelled on. ──────────────────────────────────────────── */
.chat-msg-time{
  font-family:var(--font-mono);font-size:9px;color:var(--muted);opacity:.8;
}

/* ── New bubbles arrive with a soft lift, not a hard cut. Only messages
   chat.js has never painted before get this class, so scrolling or a
   background snapshot never replays it across the whole thread. ──────── */
@keyframes chatMsgIn{
  from{opacity:0;transform:translateY(7px) scale(.985)}
  to{opacity:1;transform:none}
}
.chat-msg-enter{animation:chatMsgIn .32s var(--ease-out,var(--ease)) both}

/* ── Starter chips arrive with a small stagger instead of popping in as
   one block. ─────────────────────────────────────────────────────────── */
@keyframes chatChipIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
.chat-suggest button{opacity:0;animation:chatChipIn .3s var(--ease-out,var(--ease)) both}
.chat-suggest button:nth-child(1){animation-delay:.02s}
.chat-suggest button:nth-child(2){animation-delay:.07s}
.chat-suggest button:nth-child(3){animation-delay:.12s}
.chat-suggest button:nth-child(4){animation-delay:.17s}

/* ── A quiet breathing glow on the header avatar and a slightly springier
   open, so the panel feels less like a modal snapping open and more like
   a surface arriving. ─────────────────────────────────────────────────── */
@keyframes chatAvatarGlow{
  from{box-shadow:0 0 20px -6px rgba(18,196,222,.85)}
  to{box-shadow:0 0 28px -4px rgba(18,196,222,1)}
}
.chat-avatar{animation:chatAvatarGlow 3.2s var(--ease-soft,var(--ease)) infinite alternate}
.chat-panel{transition:opacity .3s var(--ease),transform .4s cubic-bezier(.34,1.56,.64,1),visibility .3s}

/* ── The bubble pulses gently while something's waiting to be read,
   the same notification language the rest of the site uses. ──────────── */
@keyframes chatBubblePulse{
  0%{transform:scale(.92);opacity:.85}
  70%,100%{transform:scale(1.38);opacity:0}
}
.chat-bubble.has-unread::before{
  content:"";position:absolute;inset:-6px;z-index:-1;border-radius:inherit;
  border:2px solid rgba(18,196,222,.55);
  animation:chatBubblePulse 1.9s var(--ease) infinite;
}

@media (prefers-reduced-motion: reduce){
  .chat-msg-enter,.chat-suggest button,.chat-avatar,.chat-bubble.has-unread::before{animation:none !important}
  .chat-msg-enter{opacity:1;transform:none}
  .chat-suggest button{opacity:1}
  .chat-panel{transition:opacity .01s linear,visibility .01s linear}
}

@media (max-width:480px){
  /* One line of chip text less to scroll past on a narrow phone keyboard
     view — the composer's own growth already eats the same space. */
  .chat-input-row textarea{max-height:96px}
}

/* ═══════════════════════════════════════════════════════════════════════
   CHATBOX — EPIC CINEMATIC UPGRADE
   ---------------------------------------------------------------------------
   The rest of the site already runs the "dark band under white paper"
   showroom logic (see the note at the top of this file) and the v2 glass
   layer above turned that into real frosted glass everywhere. The widget
   itself was left in its first, flatter pass: a plain dark header sat on a
   flat --soft body with no glass, no ambient glow, no depth. This section
   brings the chat bubble in line with everything around it — one more
   light-and-dark material, lit like the rest of the room, appended last so
   it wins the cascade without a single !important. Nothing here removes a
   class or a behaviour; every selector chat.js already writes keeps
   working exactly as before. ═══════════════════════════════════════════ */

/* An ambient cyan atmosphere breathing behind the whole widget — the same
   trick the hero and the gallery band use, at chat-bubble scale. Sits
   behind everything (z-index -1) and never intercepts a click. */
.chat-widget::before{
  content:"";position:absolute;right:-40px;bottom:-40px;width:260px;height:260px;
  background:radial-gradient(circle,rgba(18,196,222,.32),transparent 70%);
  filter:blur(30px);pointer-events:none;z-index:-1;
  opacity:0;transition:opacity .5s var(--ease);
}
.chat-widget:has(.chat-panel.open)::before{opacity:1}

/* The launcher: a real machined lamp, not a flat circle. A slow, always-on
   breathing ring (independent of the unread-pulse, which still layers on
   top) plus a glassy top highlight. */
.chat-bubble{
  position:relative;overflow:visible;
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
}
.chat-bubble::after{
  content:"";position:absolute;inset:-3px;z-index:-1;border-radius:inherit;
  background:conic-gradient(from 180deg,rgba(18,196,222,.55),rgba(78,226,245,.15),rgba(18,196,222,.55));
  opacity:.55;filter:blur(7px);
  animation:chatLampBreathe 4.2s var(--ease-soft,var(--ease)) infinite;
}
@keyframes chatLampBreathe{
  0%,100%{opacity:.35;transform:scale(.97)}
  50%{opacity:.7;transform:scale(1.04)}
}
@media (prefers-reduced-motion: reduce){ .chat-bubble::after{animation:none} }

/* The small WhatsApp launcher above it gets the same machined-lamp
   treatment — matched radius, glass blur, breathing glow ring — just in
   WhatsApp's own green, so the two read as one lit pair instead of one
   upgraded button sitting next to a flat leftover one. */
.chat-whatsapp-fab{
  position:relative;overflow:visible;
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
}
.chat-whatsapp-fab::after{
  content:"";position:absolute;inset:-3px;z-index:-1;border-radius:inherit;
  background:conic-gradient(from 180deg,rgba(37,211,102,.6),rgba(79,227,155,.18),rgba(37,211,102,.6));
  opacity:.5;filter:blur(6px);
  animation:chatLampBreathe 4.2s var(--ease-soft,var(--ease)) infinite;
}
@media (prefers-reduced-motion: reduce){ .chat-whatsapp-fab::after{animation:none} }

/* The panel itself: frosted glass over the dark/light materials that were
   already there, wrapped in a soft gradient rim and a wider ambient glow
   than the flat shadow it had before. */
.chat-panel{
  backdrop-filter:var(--glass,blur(20px) saturate(180%));
  -webkit-backdrop-filter:var(--glass,blur(20px) saturate(180%));
  border-color:rgba(127,230,245,.55);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.85),
    var(--shadow-lg),
    0 0 90px -30px rgba(18,196,222,.55),
    0 0 0 1px rgba(18,196,222,.12);
}

/* Header: the dark band gets a slow-moving sheen across it, like light
   sweeping a lacquered surface — cinematic, not distracting, and it stops
   for anyone who asked for reduced motion. */
.chat-head{
  background-size:220% 220%;
  animation:chatHeadSheen 9s var(--ease-soft,var(--ease)) infinite;
}
@keyframes chatHeadSheen{
  0%,100%{background-position:0% 0%}
  50%{background-position:100% 60%}
}
@media (prefers-reduced-motion: reduce){ .chat-head{animation:none} }

/* Body: still light, on purpose — a wall of dark bubbles is exhausting to
   read — but no longer a flat tint. A faint cyan wash in the top corner and
   the same film-grain the dark bands use (at whisper strength) give it the
   same "lit room" depth as the rest of the page, while staying legible. */
.chat-body{
  background-color:var(--soft);
  background-image:var(--grain-soft),
    radial-gradient(120% 70% at 100% 0%,rgba(18,196,222,.10),transparent 60%),
    radial-gradient(90% 60% at 0% 100%,rgba(7,151,174,.06),transparent 60%);
  background-blend-mode:overlay,normal,normal;
}
/* A slim, lit scroll channel instead of the browser default — the same
   language as .scroll-rail elsewhere on the site. */
.chat-body{scrollbar-width:thin;scrollbar-color:rgba(7,151,174,.45) transparent}
.chat-body::-webkit-scrollbar{width:6px}
.chat-body::-webkit-scrollbar-thumb{background:rgba(7,151,174,.4);border-radius:99px}
.chat-body::-webkit-scrollbar-thumb:hover{background:rgba(7,151,174,.65)}

/* Message bubbles: more glass, more depth, still exactly the same markup
   chat.js already produces. */
.chat-empty,
.chat-msg.ai .chat-msg-text,.chat-msg.admin .chat-msg-text,
.chat-dots{
  background:rgba(255,255,255,.72);
  backdrop-filter:blur(10px) saturate(160%);-webkit-backdrop-filter:blur(10px) saturate(160%);
  border-color:rgba(7,151,174,.18);
  box-shadow:0 6px 18px -14px rgba(6,23,31,.4),inset 0 1px 0 rgba(255,255,255,.9);
}
.chat-msg.me .chat-msg-text{
  background:linear-gradient(160deg,#4EE2F5 0%,#12C4DE 46%,#0797AE 100%);
  box-shadow:
    0 10px 26px -14px rgba(7,151,174,.85),
    inset 0 1px 0 rgba(255,255,255,.55),
    inset 0 -10px 18px -12px rgba(4,96,116,.55);
}
.chat-msg-enter{will-change:opacity,transform}

/* Suggestion chips and the composer pick up the same glass, so the whole
   panel reads as one material rather than three different eras of the
   file stacked on top of each other. */
.chat-suggest button{
  background:rgba(255,255,255,.65);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
}
.chat-suggest button:hover{
  box-shadow:0 8px 18px -12px rgba(7,151,174,.5);
  transform:translateY(-1px);
}
.chat-input-row{
  background:rgba(255,255,255,.6);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
}
.chat-input-row textarea{background:rgba(255,255,255,.7)}
.chat-input-row textarea:focus{box-shadow:var(--ring),0 0 22px -10px rgba(18,196,222,.6)}
.chat-send{box-shadow:0 8px 20px -12px rgba(7,151,174,.7)}
.chat-send:hover{box-shadow:0 10px 26px -12px rgba(7,151,174,.85)}

/* Fallback for the handful of browsers with no backdrop-filter support at
   all: fall back to a solid surface rather than a see-through panel with
   no legible background underneath it. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .chat-panel,.chat-body,.chat-input-row,.chat-suggest button,
  .chat-empty,.chat-msg.ai .chat-msg-text,.chat-msg.admin .chat-msg-text,.chat-dots{
    background:var(--paper) !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: OBSIDIAN — the last pass over the whole machine
   ═══════════════════════════════════════════════════════════════════════
   Everything below is deliberately last in the file, because it is the
   layer that decides how the site FEELS rather than how it works. It adds
   four things and changes nothing about the markup:

     1. the Original / Local corner ribbon,
     2. the gallery loupe frame,
     3. the image guard (no drag, no selection, no long-press sheet),
     4. a heavier, deeper cut on the cards, the buttons and every icon.

   The palette is unchanged — white and cyan carrying the page, dark used
   as weight, never as a second theme. What changes is the depth: more
   contrast between the lit face and the shadowed underside of every
   surface, so the page reads as milled parts sitting in light rather than
   as rectangles with borders. */

/* ── 1. The Original / Local ribbon ───────────────────────────────────
   A corner ribbon rather than a pill, and cut across the top-left where
   nothing else lives — the stock badge already owns the top right, the
   loupe owns the middle. Rotated 45° through a clipped corner, which is
   why the container needs overflow hidden and the band needs to be wider
   than the corner it crosses.

   Original is cut in the brand's own cyan-on-ink: this is the claim the
   business is proudest of, and it should look like part of the machine.
   Local is brass — warm, deliberately different, and never a lesser
   version of the same colour, because "Local" is a different product,
   not a downgraded one. */
/* The containers are deliberately NOT clipping boxes. The loupe is a
   190px circle centred on the pointer and is half outside the picture at
   every edge — clipping the container cuts it into a crescent. The ribbon
   does its own clipping instead: .origin-tag is a small square box with
   overflow hidden, and the band is clipped to that. */

.origin-tag{
  position:absolute;top:0;left:0;width:132px;height:132px;
  z-index:3;pointer-events:none;overflow:hidden;
}
.origin-tag > span{
  position:absolute;top:24px;left:-40px;width:170px;
  transform:rotate(-45deg);
  display:block;text-align:center;padding:6px 0;
  font-family:var(--font-display);font-size:10.5px;font-weight:800;
  letter-spacing:.18em;text-transform:uppercase;white-space:nowrap;
  box-shadow:
    0 10px 22px -12px rgba(6,23,31,.75),
    inset 0 1px 0 rgba(255,255,255,.4),
    inset 0 -8px 14px -10px rgba(0,0,0,.5);
}
/* The two little folded ends that make a ribbon read as cloth over a
   corner rather than a stripe painted on top of it. */
.origin-tag::before,.origin-tag::after{
  content:"";position:absolute;width:0;height:0;
  border:5px solid transparent;
}
.origin-tag::before{top:62px;left:0;border-right-color:rgba(6,23,31,.55);border-top-color:rgba(6,23,31,.55)}
.origin-tag::after{top:0;left:62px;border-left-color:rgba(6,23,31,.55);border-top-color:rgba(6,23,31,.55)}

.origin-original > span{
  background:linear-gradient(135deg,#12C4DE 0%,#0797AE 48%,#04566A 100%);
  color:#00212A;
  text-shadow:0 1px 0 rgba(255,255,255,.35);
}
/* There used to be a second, brass-coloured ribbon here for "Local Made".
   It is gone by request: the tag now has exactly two states, on or absent,
   and the absence of a ribbon makes no claim of its own. */
/* On the big product page the ribbon scales with the plate. */
.product-detail-image .origin-tag{width:170px;height:170px}
.product-detail-image .origin-tag > span{top:34px;left:-48px;width:210px;font-size:12px;padding:8px 0}
.product-detail-image .origin-tag::before{top:82px}
.product-detail-image .origin-tag::after{left:82px}

/* The admin table chip for the same thing, so the owner can scan a list of
   forty products and see which ones carry a claim. */
.chip-tag.origin{
  background:rgba(7,151,174,.12);color:var(--cyan-ink);
  border:1px solid rgba(7,151,174,.4);
}

/* ── 2. The gallery loupe frame ───────────────────────────────────────
   The gallery now magnifies exactly like a product photo does, so the
   picture needs to be its own box: a lens that wanders over the caption
   text underneath looks like a fault, not a feature. */
.gallery-shot{
  display:block;position:relative;
  background:linear-gradient(168deg,var(--plate) 0%,var(--plate-2) 100%);
  border-bottom:1px solid var(--card-border);
}
.gallery-shot img{
  display:block;width:100%;aspect-ratio:4/3;object-fit:contain;
  padding:16px;background:transparent;border-bottom:0;
  transition:transform .6s var(--ease-epic);
}
.gallery-card:hover .gallery-shot img{transform:scale(1.05)}
/* Same falling light as the product plate — one inset shadow in the
   corner the lamp does not reach. */
.gallery-shot::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  box-shadow:inset -14px -18px 40px -30px rgba(3,8,11,.7);
}

/* ── 3. The image guard ───────────────────────────────────────────────
   The CSS half of it. The JavaScript half (protectImages() in app.js)
   stops the right-click menu, the drag and the copy; this half stops the
   selection highlight and the long-press "Save image" sheet on a phone,
   and takes the pictures out of the pointer's reach entirely so that a
   right-click never lands on an <img> in the first place.

   pointer-events is safe to remove here because every hover, lens and
   click handler is bound to the BOX around the picture, never to the
   picture itself. The lightbox image is deliberately left alone: that is
   where a phone pinch-zooms, and it must stay touchable. */
.product-image img,.product-detail-image img,.gallery-shot img,.gallery-card img{
  pointer-events:none;
  -webkit-user-drag:none;-khtml-user-drag:none;-moz-user-drag:none;user-drag:none;
  -webkit-touch-callout:none;
  user-select:none;-webkit-user-select:none;-ms-user-select:none;
}
/* The lens shows the picture as a background image, so there is nothing
   there to right-click either. */
.zoom-lens{-webkit-touch-callout:none;user-select:none;-webkit-user-select:none}

/* ── 4. The last cut ──────────────────────────────────────────────────
   Depth, not colour. Every rule below either fixes a corner the new
   clipping boxes squared off, or deepens something that was already
   there — a lit top edge, a shadowed underside, a longer travel on hover.
   No new hues: white and cyan carry the page, dark is the weight. */

/* Clipping a corner must not un-round it. .product-image and .gallery-shot
   became clipping boxes when the ribbon arrived, and a clipping box with
   square corners cuts the card's own radius off at the top. */
.product-image{border-top-left-radius:inherit;border-top-right-radius:inherit}
.gallery-shot{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}
.product-detail-image{border-radius:var(--radius-lg)}

/* The ribbon, tightened. Smaller box, band pulled up towards the corner so
   there is no bare notch of plate above it. */
.origin-tag{width:114px;height:114px}
.origin-tag > span{top:19px;left:-42px;width:152px;padding:5px 0;font-size:10px}
.origin-tag::before,.origin-tag::after{display:none}
.product-detail-image .origin-tag{width:152px;height:152px}
.product-detail-image .origin-tag > span{top:27px;left:-46px;width:192px;font-size:11.5px;padding:7px 0}

/* ── The stock chip, re-cut ───────────────────────────────────────────
   It was a pale pill in a colour that belongs to no other part of this
   site. Now it is the same machined dark plate the eyebrow chips are cut
   from, with a single lit dot carrying the colour — green, amber or red
   reduced to 7px of light instead of a whole green badge shouting next to
   a cyan headline. The status still reads instantly; it just stops
   fighting the brand for attention. */
.stock-badge{
  display:inline-flex;align-items:center;gap:8px;
  padding:6px 13px 6px 11px;border-radius:999px;
  background:linear-gradient(178deg,#13252F,#08161D);
  border:1px solid rgba(127,230,245,.16);
  color:#CFE9F0;font-size:10px;letter-spacing:.14em;
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.14),
    0 12px 24px -16px rgba(6,23,31,.8);
}
.stock-badge::before{
  content:"";width:7px;height:7px;border-radius:50%;flex:none;
  background:currentColor;
}
.stock-ready{color:#5FE3A8}
.stock-limited{color:#F2C14E}
.stock-out{color:#FF8B7E}
/* The word itself stays quiet; only the dot carries the colour. */
.stock-badge{color:#CFE9F0}
.stock-ready::before{background:#3FD68F;box-shadow:0 0 10px 1px rgba(63,214,143,.75)}
.stock-limited::before{background:#F2C14E;box-shadow:0 0 10px 1px rgba(242,193,78,.7)}
.stock-out::before{background:#FF7A6A;box-shadow:0 0 10px 1px rgba(255,122,106,.7)}
.product-image .stock-badge{
  position:absolute;top:12px;right:12px;z-index:3;
  background:linear-gradient(178deg,rgba(19,37,47,.92),rgba(8,22,29,.94));
  backdrop-filter:blur(10px) saturate(160%);-webkit-backdrop-filter:blur(10px) saturate(160%);
}

/* ── Cards: a lit top edge and a heavier underside ────────────────────
   One hairline of cyan light along the top of every card, dim at rest and
   full when the pointer is on it. It is the cheapest way to make a flat
   rectangle read as a machined part catching a lamp, and it costs one
   pseudo-element that was already there for the hover glow. */
.product-card,.gallery-card,.feature-card,.category-card{
  position:relative;
  transition:
    transform .5s var(--ease-epic),
    box-shadow .5s var(--ease-epic),
    border-color .35s var(--ease);
}
.product-card::before,.gallery-card::before{
  content:"";position:absolute;left:14%;right:14%;top:0;height:1px;z-index:4;
  background:linear-gradient(90deg,transparent,var(--cyan-bright),transparent);
  opacity:.35;transition:opacity .4s var(--ease),left .5s var(--ease-epic),right .5s var(--ease-epic);
  pointer-events:none;
}
.product-card:hover::before,.gallery-card:hover::before{opacity:1;left:4%;right:4%}
.product-card:hover,.gallery-card:hover{
  transform:translateY(-6px);
  box-shadow:
    0 2px 6px rgba(6,23,31,.06),
    0 44px 84px -40px rgba(6,23,31,.45),
    0 0 90px -34px rgba(18,196,222,.55);
}

/* The plate under a product photograph gets a real gradient and a floor
   line, so the picture sits ON something instead of floating in a box. */
.product-image{
  background:
    radial-gradient(120% 90% at 22% 8%, rgba(255,255,255,.95) 0%, rgba(255,255,255,0) 62%),
    linear-gradient(168deg,var(--plate) 0%,var(--plate-2) 100%);
}

/* ── Buttons: one bevel, everywhere ───────────────────────────────────
   Same lit top edge, same shadowed underside, same 2px lift. Whatever the
   button says and wherever it sits, it is stamped from the same metal. */
.btn,.btn-call,.card-call,.tab-pill,.btn-whatsapp,.card-whatsapp{
  transition:
    transform .28s var(--ease-out),
    box-shadow .28s var(--ease-out),
    background-color .28s var(--ease),
    border-color .28s var(--ease),
    color .28s var(--ease);
}
.btn:hover,.btn-call:hover,.card-call:hover,.tab-pill:hover{transform:translateY(-2px)}
.btn:active,.btn-call:active,.card-call:active,.tab-pill:active{transform:translateY(0) scale(.985)}
.btn.primary,.tab-pill.active{
  box-shadow:
    var(--btn-shadow),
    inset 0 1px 0 rgba(255,255,255,.18),
    inset 0 -10px 18px -14px rgba(0,0,0,.85);
}
.btn.primary:hover,.tab-pill.active:hover{
  box-shadow:
    var(--btn-shadow-hover),
    inset 0 1px 0 rgba(255,255,255,.24),
    0 0 46px -18px rgba(18,196,222,.7);
}
/* Every icon inside a button inherits the label's colour and its weight,
   so an icon can never be a different grey from the word beside it. */
.btn svg,.btn-call svg,.card-call svg,.btn-whatsapp svg,.card-whatsapp svg,
.tab-pill svg,.chat-toggle svg,.lightbox-close svg{
  stroke-linecap:round;stroke-linejoin:round;
}

/* ── The keyboard ring ────────────────────────────────────────────────
   One ring, on everything, and never removed without a replacement — a
   focus style you cannot see is an accessibility bug wearing a design
   decision's clothes. */
a:focus-visible,button:focus-visible,input:focus-visible,
select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible{
  outline:2px solid var(--cyan-bright);outline-offset:3px;border-radius:6px;
}

/* Everything decorative above is motion. If the visitor asked for less of
   it, they get the depth and none of the movement. */
@media (prefers-reduced-motion: reduce){
  .product-card,.gallery-card,.feature-card,.category-card,
  .btn,.btn-call,.card-call,.tab-pill,.product-image img,.gallery-shot img{
    transition:none !important;
  }
  .product-card:hover,.gallery-card:hover,
  .btn:hover,.btn-call:hover,.card-call:hover,.tab-pill:hover{transform:none}
  .product-card:hover .product-image img,
  .gallery-card:hover .gallery-shot img{transform:none}
}

/* ── 5. The corner marks come off ─────────────────────────────────────
   Two separate things, both of them small crop-marks in a corner, and the
   owner asked for both to go.

   The first was a fixed hairline frame with cyan brackets at the four
   corners of the viewport. Removed above, at source.

   The second is subtler and was a real fault, not a taste question. The
   product plate's ::before was doing two jobs at once: an early rule made
   it a 17px corner brace, a much later rule reused the SAME pseudo-element
   for the full-plate light sweep. Pseudo-element declarations merge rather
   than replace, so what actually rendered was the sweep squeezed into a
   22px rounded box pinned to the top-left corner — the little white blob
   sitting on the plate. Giving the sweep back its full size is what fixes
   it, and it retires the braces at the same time. */
.product-detail-image::before{
  width:auto;height:auto;border:0;opacity:1;
  inset:0;border-radius:inherit;
  box-shadow:none;
}
.product-detail-image::after{display:none}
/* The card ::after pseudo-elements are deliberately left alone. They were
   nearly removed here as "more crop-marks", which would have been a bad
   trade: on a category card that pseudo-element is the hover arrow, on a
   product and gallery card it is the diagonal glass sheen, and on a CTA
   band it is the cyan rail down the left edge. Only two things were ever
   the complaint — the frame around the viewport and the blob on the
   product plate — and both are gone above. */

/* ── 6. Corrections found in review ───────────────────────────────────
   Three of these are the kind of bug that only shows up when you put a
   pointer on the thing: an absolutely positioned child measures from its
   parent's PADDING box, not its border box, so a "corner" ribbon on a
   plate with 18px of padding sits 18px away from the corner it is meant
   to be folded over. */
/* On a card the ribbon is a child of .product-card itself, not of the
   padded image box inside it — the card has no padding, so top:0/left:0
   really is the corner, and the card's own overflow:hidden and radius clip
   the band to the rounded corner for free. On the product page there is no
   outer element to hang it on, so it stays inside the plate and the plate's
   28px of padding is cancelled by hand. */
.product-detail-image .origin-tag{top:-28px;left:-28px}
.origin-tag{border-top-left-radius:inherit}
.product-detail-image .origin-tag{border-top-left-radius:var(--radius-xl)}

/* The gallery photo's padding belongs to the frame, not to the <img>.
   With the padding on the image, the element's box is 32px larger than the
   picture inside it, which is exactly the offset the loupe was reading. */
.gallery-shot{padding:16px}
.gallery-shot img{padding:0}

/* zoom-in is a promise. A product with no photograph renders a placeholder
   glyph, initMagnifier() bails, and the plate would still be advertising a
   loupe that will never open. The cursor now waits for the class that says
   a loupe was actually attached. */
[data-zoom]{cursor:default}
[data-zoom].zoom-ready{cursor:zoom-in}
[data-zoom].zoom-tap{cursor:pointer}

/* "LOCAL MADE" is four characters longer than "ORIGINAL" and was running
   out of the clipped corner. Slightly smaller type and a slightly bigger
   corner fits every word the owner is likely to type, in Bangla too. */
.origin-tag{width:126px;height:126px}
.origin-tag > span{top:22px;left:-46px;width:170px;font-size:9.5px;letter-spacing:.14em}
.product-detail-image .origin-tag{width:168px;height:168px}
.product-detail-image .origin-tag > span{top:30px;left:-52px;width:214px;font-size:11px}

/* ── 7. Two buttons, one line, nothing cut ────────────────────────────
   With a WhatsApp number set a product card carries both "Call For Order"
   and "WhatsApp Order", and they must sit SIDE BY SIDE — that is the
   layout the owner designed the card around, and the card keeps its width.

   The bug was never the arrangement, it was the fit: at the narrowest
   card the two labels needed a few pixels more than the row had, and the
   words were cut off mid-letter ("Call For Orde"). Dropping one button
   onto a second line fixed the clipping and broke the design, so this
   does the opposite — the row stays on one line and the buttons are
   trimmed just enough to fit inside it.

   The numbers: a 292px card leaves 244px of inner width, so each button
   gets 118px. Icon 14 + gap 5 + padding 16 leaves 83px for the label, and
   "WhatsApp Order" at 11.8px measures about 80px. It fits, with the
   narrowest card the grid can produce. Wider cards simply have room to
   spare. */
.card-actions{flex-wrap:nowrap}
.card-actions .card-call,
.card-actions .card-whatsapp{
  flex:1 1 0;min-width:0;
  padding:11px 8px;gap:5px;
  font-size:11.8px;letter-spacing:0;
  justify-content:center;white-space:nowrap;
}
/* The product page has more room, so its three buttons keep full size and
   wrap only if the window is genuinely too narrow for them. */
.order-actions{flex-wrap:wrap}
.order-actions > *{flex:1 1 auto;justify-content:center}

/* ── 8. Making both labels fit on the narrow cards ────────────────────
   The row must stay on one line — that is the layout the cards are
   designed around — and the card must keep its width. So the only thing
   left to give is the icons.

   On a card narrower than about 320px there is not enough room for two
   icons AND both labels, and the label is the part that carries meaning:
   nobody reads a phone glyph and knows it means "Call For Order". Below
   that width the two glyphs step aside and the words get the space.

   A container query, not a media query, because what matters is how wide
   the CARD is, not the window — the same card is 390px wide in a
   four-column grid on a wide screen and 261px in a three-column one on a
   laptop. */
.product-card{container-type:inline-size}
@container (max-width: 322px){
  .card-actions .card-call svg,
  .card-actions .card-whatsapp svg{display:none}
  .card-actions .card-call,
  .card-actions .card-whatsapp{padding:11px 6px;gap:0}
}
/* Browsers without container queries fall back to the icons staying put;
   the labels are then tight on the narrowest cards but still readable,
   which is the same place this started rather than anything worse. */
/* And one step further for the very narrowest card the grid produces —
   about 261px, which is what a three-column layout gives on a 900px
   laptop. A hair smaller again, and both words still fit whole. */
@container (max-width: 288px){
  .card-actions .card-call,
  .card-actions .card-whatsapp{font-size:11px;padding:11px 4px}
}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: ATELIER — the motion system and the last of the luxury
   ═══════════════════════════════════════════════════════════════════════
   Two things live here and nothing else:

     A. THE MOTION CONTRACT. One set of durations and one set of easing
        curves, named for what they are for. Before this, every component
        picked its own timing, so a card settled in 500ms next to a button
        that settled in 280ms and the page felt assembled rather than
        designed. Anything that moves from here on refers to these.

     B. THE LAST OF THE LUXURY. Depth, not colour: a three-part shadow
        ladder, a rim of light on every raised surface, tilt and magnetism
        composed into single transforms, and a real page-to-page
        transition where the browser supports one.

   The palette does not change. White and cyan carry the page; dark is
   weight. Everything below is about how light behaves on it.
   ═════════════════════════════════════════════════════════════════════ */

:root{
  /* ── The five speeds ──────────────────────────────────────────────
     Named for the job. A tooltip and a page transition should not share
     a number just because 300ms once looked right for both. */
  --m-tap:120ms;      /* a press: it must feel instantaneous          */
  --m-quick:220ms;    /* hover, focus, a chip lighting up             */
  --m-base:380ms;     /* the default — a card lifting, a panel opening*/
  --m-slow:620ms;     /* something arriving on screen                 */
  --m-epic:1100ms;    /* the overture, a full-plate light sweep       */

  /* ── The four curves ──────────────────────────────────────────────
     `enter` overshoots nothing and lands soft; `exit` leaves briskly,
     because nobody wants to wait for something to go away. `glide` is
     for anything the pointer is steering — it must feel attached to the
     hand, so it is nearly linear at the start. `spring` is the only one
     that overshoots, and it is reserved for a press releasing. */
  --e-enter:cubic-bezier(.16,1,.3,1);
  --e-exit:cubic-bezier(.4,0,1,1);
  --e-glide:cubic-bezier(.22,.61,.36,1);
  --e-spring:cubic-bezier(.34,1.56,.64,1);

  /* ── The shadow ladder ────────────────────────────────────────────
     Three parts to every raised surface, because that is how a real one
     behaves: a tight CONTACT shadow where it meets the ground, a wide
     soft AMBIENT one for the room light, and a hairline RIM along the
     top edge where the light actually lands. Most web shadows are only
     the middle one, which is why most cards look printed on rather than
     placed on. */
  --lift-1:
    0 1px 2px rgba(6,23,31,.06),
    0 8px 20px -12px rgba(6,23,31,.28),
    inset 0 1px 0 rgba(255,255,255,.85);
  --lift-2:
    0 2px 4px rgba(6,23,31,.06),
    0 22px 48px -24px rgba(6,23,31,.34),
    inset 0 1px 0 rgba(255,255,255,.9);
  --lift-3:
    0 3px 8px rgba(6,23,31,.07),
    0 48px 92px -40px rgba(6,23,31,.42),
    0 0 100px -36px rgba(18,196,222,.55),
    inset 0 1px 0 rgba(255,255,255,.95);
}

/* ── A. PAGE TO PAGE ───────────────────────────────────────────────────
   This site is five separate HTML documents, so every menu click has
   always been a white flash and a redraw. A cross-document view
   transition removes that: the browser holds the old page, paints the
   new one, and dissolves between them — the thing that separates a site
   that feels like an application from a set of pages.

   The header is given a name so it is treated as the SAME element across
   the two documents and simply stays where it is while the body changes
   under it. Nothing here is a polyfill: where the browser does not
   support it, navigation behaves exactly as it did before. */
@view-transition{ navigation:auto }
.site-header{view-transition-name:site-header}
.chat-widget{view-transition-name:chat-widget}
::view-transition-old(root){animation:vtOut var(--m-quick) var(--e-exit) both}
::view-transition-new(root){animation:vtIn var(--m-slow) var(--e-enter) both}
@keyframes vtOut{to{opacity:0;transform:translateY(-6px) scale(.994)}}
@keyframes vtIn{from{opacity:0;transform:translateY(10px) scale(.994)}}

/* ── B. TILT AND MAGNETISM, COMPOSED ───────────────────────────────────
   Lift and tilt both want `transform`, and two rules cannot share one
   property — the second silently wins and the first appears broken. So
   each is a variable, and the transform is written once, here.

   `translateZ(0)` is not a hack for a repaint bug; it is what puts the
   card on its own compositor layer so the rotation costs nothing per
   frame. `transform-style:flat` keeps the children from being lifted out
   of the plane with it. */
/* The reveal animation ALSO sets `transform` on these very elements —
   every card carries `.reveal` — and `.js .reveal` outranks a bare
   `.feature-card`, so the composed transform below never applied to a
   single card on the page. The tilt was dead code and the hover lift was
   coming from the reveal rule's `transform:none`.

   Fixing it by raising specificity alone would only move the fight. The
   reveal's offset becomes a variable too, and every transform on these
   elements is written once, here, out of all three. */
.js .reveal{--rev-y:20px}
.reveal.in-view,.reveal-all .reveal{--rev-y:0px}
.js .product-card.reveal,.js .feature-card.reveal,
.js .category-card.reveal,.js .pf-stat.reveal,
.product-card,.feature-card,.category-card,.pf-stat{
  --lift:0px; --rx:0deg; --ry:0deg;
  transform:
    perspective(1100px)
    translateY(calc(var(--rev-y,0px) + var(--lift)))
    rotateX(var(--rx))
    rotateY(var(--ry))
    translateZ(0);
  transform-style:flat;
  transition:
    transform var(--m-base) var(--e-glide),
    box-shadow var(--m-base) var(--e-glide),
    border-color var(--m-quick) var(--e-glide);
  box-shadow:var(--lift-1);
}
.product-card:hover,.feature-card:hover,.category-card:hover,.pf-stat:hover{
  --lift:-7px;
  box-shadow:var(--lift-3);
}
/* The timing has to carry the same specificity as the transform, for the
   same reason the transform did: `.js .reveal` also declares a transition
   on these elements, at .8s, and it outranks a bare `.feature-card`. Left
   at one class, the motion contract above was being written and then
   quietly overruled — the cards settled at the reveal's speed and the
   tokens described a system nothing obeyed. */
.js .product-card.reveal,.js .feature-card.reveal,
.js .category-card.reveal,.js .pf-stat.reveal,
.product-card,.feature-card,.category-card,.pf-stat{
  transition-property:transform,box-shadow,border-color,opacity;
  transition-duration:var(--m-base),var(--m-base),var(--m-quick),var(--m-slow);
  transition-timing-function:var(--e-glide),var(--e-glide),var(--e-glide),var(--e-enter);
}

/* The buttons drift; the drift is a variable so the press can still
   scale on top of it. */
.btn.primary,.btn-call,.card-call,.btn-whatsapp,.card-whatsapp{
  --mag-x:0px; --mag-y:0px; --press:1;
  transform:translate3d(var(--mag-x),calc(var(--mag-y) + var(--btn-lift,0px)),0) scale(var(--press));
  transition:
    transform var(--m-quick) var(--e-glide),
    box-shadow var(--m-quick) var(--e-glide),
    background-color var(--m-quick) var(--e-glide),
    border-color var(--m-quick) var(--e-glide),
    color var(--m-quick) var(--e-glide);
}
.btn.primary:hover,.btn-call:hover,.card-call:hover,
.btn-whatsapp:hover,.card-whatsapp:hover{--btn-lift:-2px}
.btn.primary:active,.btn-call:active,.card-call:active,
.btn-whatsapp:active,.card-whatsapp:active{
  --press:.975;--btn-lift:0px;
  transition-duration:var(--m-tap);
}

/* ── C. THE OVERTURE ───────────────────────────────────────────────────
   The hero arrives as one sequence rather than six separate fades. `--i`
   is the element's place in the queue, set once in JavaScript; the delay
   is arithmetic from there, so the rhythm is defined in exactly one
   place and adding a seventh element needs no new CSS.

   70ms between steps: close enough to read as one movement, far enough
   apart that the eye can follow the order. */
.js .overture{
  opacity:0;
  transform:translateY(16px);
  filter:blur(7px);
  transition:
    opacity var(--m-slow) var(--e-enter),
    transform var(--m-slow) var(--e-enter),
    filter calc(var(--m-slow) + 120ms) var(--e-enter);
  transition-delay:calc(var(--i,0) * 70ms + 60ms);
}
.js .overture-go .overture{opacity:1;transform:none;filter:blur(0)}

/* ── D. LIGHT ON A SURFACE ─────────────────────────────────────────────
   A single specular band that crosses the dark order panel once, slowly,
   and then waits. It is the one piece of ambient motion on the product
   page, and it exists because a large flat dark rectangle is the hardest
   thing on a page to make look like a material. */
/* `.order-panel::after` is ALREADY the soft corner light this panel sits
   in, and `::before` is already its top filament — both pseudo-elements
   are spoken for. A new "sheen" declared on ::after would not be a third
   effect; it would merge into the corner light and replace its geometry,
   quietly deleting the pool of light the price sits in.

   So the light moves instead of being replaced. Adding only an animation
   to the existing rule is merge-safe by construction: nothing about its
   size, position or colour is touched, and the panel gets its slow drift
   without losing anything it had. */
.order-panel{position:relative;overflow:hidden}
.order-panel::after{animation:panelDrift 14s var(--e-glide) infinite alternate}
@keyframes panelDrift{
  from{transform:translate3d(0,0,0) scale(1)}
  to  {transform:translate3d(-14%,8%,0) scale(1.12)}
}

/* ── E. THE PRICE, GIVEN ITS WEIGHT ────────────────────────────────────
   The number is the single most-read thing on a product page, and it was
   set at the same optical weight as a heading. Tighter tracking, a
   slightly heavier cut, and tabular figures so a column of prices lines
   up digit under digit — which is what makes a price list look like it
   came from a real supplier. */
.order-price,.price{
  font-variant-numeric:tabular-nums;
  letter-spacing:-.02em;
  font-feature-settings:"tnum" 1;
}

/* ── F. RHYTHM ─────────────────────────────────────────────────────────
   More air between the bands. Luxury on a page is mostly the confidence
   to leave space empty, and this page was working slightly too hard. */
.section{padding-block:clamp(64px,8.5vw,124px)}
.hero{padding-block:clamp(72px,10vw,150px)}

/* ── G. TYPE ───────────────────────────────────────────────────────────
   Roboto Condensed stays — it is the brand. What changes is the optical
   treatment at display sizes: negative tracking that scales with the
   type, and balanced wrapping so a two-line headline never leaves one
   orphan word on the second line. */
h1,h2,.order-price{text-wrap:balance;letter-spacing:-.022em}
h3,.card h3{text-wrap:balance;letter-spacing:-.012em}
.lead,.hero .lead{text-wrap:pretty}

/* ── H. FOCUS, WITH THE SAME CARE AS HOVER ─────────────────────────────
   A keyboard user should get the best-looking state on the page, not the
   leftover one. */
a:focus-visible,button:focus-visible,
input:focus-visible,select:focus-visible,textarea:focus-visible{
  outline:2px solid var(--cyan-bright);
  outline-offset:3px;
  transition:outline-offset var(--m-tap) var(--e-spring);
}

/* ── I. THE OFF SWITCH ─────────────────────────────────────────────────
   One block, and it must stay complete: every animation added above is
   named here. A motion system that honours the setting only in the
   places somebody remembered is not honouring it. */
@media (prefers-reduced-motion: reduce){
  .product-card,.feature-card,.category-card,.pf-stat,
  .btn.primary,.btn-call,.card-call,.btn-whatsapp,.card-whatsapp{
    transition:none !important;
    --lift:0px;--rx:0deg;--ry:0deg;--mag-x:0px;--mag-y:0px;--press:1;--btn-lift:0px;
  }
  .product-card:hover,.feature-card:hover,
  .category-card:hover,.pf-stat:hover{--lift:0px}
  /* Named individually and deliberately: `panelBreathe` predates this
     pass and was never switched off, which is exactly the failure mode a
     motion system is supposed to prevent — the setting honoured only in
     the places somebody remembered. */
  .order-panel,.order-panel::before,.order-panel::after{animation:none !important}
  .js .overture{opacity:1;transform:none;filter:none;transition:none}
  ::view-transition-old(root),::view-transition-new(root){animation:none}
  @view-transition{ navigation:none }
}

/* ── J. THE ACCENT BLOOM, AGAIN ────────────────────────────────────────
   A later layer had put `text-shadow` back onto `.hero h1 .accent`, and
   the comment three hundred lines above this one explains precisely why
   that cannot work: the accent contains the `.kin` word boxes, whose
   `overflow:hidden` is the mask the rising-word animation depends on. A
   text-shadow inherited into a clipping box does not fall off at the
   edges — it fills the box and paints a bright rectangle behind the word.
   It is visible in any screenshot of the hero.

   The glow belongs on the blurred pseudo-element, which is a SIBLING of
   the clipping boxes and therefore free to fall off in every direction
   the way light actually does. Turned off here for good, and the bloom
   given back the brightness the text-shadow was borrowing. */
.hero h1 .accent{text-shadow:none}
.hero h1 .accent::before{
  left:-9%;right:-9%;top:2%;bottom:8%;
  background:radial-gradient(closest-side,rgba(18,196,222,.55),rgba(18,196,222,.16) 58%,transparent 80%);
  filter:blur(34px);
}

/* ── K. THE CARD, GIVEN A MATERIAL ─────────────────────────────────────
   The shadow ladder above put the cards on the page properly, but it also
   left them as plain white rectangles — and white-on-white is the one
   combination that cannot look expensive, because there is no light on
   it to see.

   Three things fix that, and all three are about light rather than
   colour: a surface that is fractionally cooler at the bottom than the
   top, so the eye reads a plane tilted towards a lamp; a rim that is
   bright along the top edge and dark along the bottom, which is what the
   edge of a real panel does; and a cyan filament across the top that is
   dim at rest and lit under the pointer. */
.feature-card,.category-card{
  position:relative;overflow:hidden;
  background:
    linear-gradient(180deg,rgba(255,255,255,.96) 0%,rgba(240,249,251,.92) 100%),
    var(--card-bg);
  border:1px solid var(--line);
  box-shadow:
    var(--lift-1),
    inset 0 -1px 0 rgba(6,23,31,.05);
}
.feature-card::before,.category-card::before{
  content:"";position:absolute;left:12%;right:12%;top:0;height:1px;z-index:2;
  background:linear-gradient(90deg,transparent,var(--cyan-bright),transparent);
  opacity:.3;pointer-events:none;
  transition:opacity var(--m-base) var(--e-glide),
             left var(--m-base) var(--e-glide),
             right var(--m-base) var(--e-glide);
}
.feature-card:hover::before,.category-card:hover::before{opacity:1;left:3%;right:3%}
.feature-card:hover,.category-card:hover{
  border-color:rgba(7,151,174,.4);
  background:
    linear-gradient(180deg,#FFFFFF 0%,rgba(233,247,251,.96) 100%),
    var(--card-bg);
}

/* Inside a dark band the same card inverts through the tokens it already
   reads, so this only has to restate the light — not the colour. */
.section.dark .feature-card,.section.dark .category-card,
.hero .feature-card,.hero .category-card{
  background:linear-gradient(180deg,rgba(255,255,255,.06) 0%,rgba(255,255,255,.02) 100%);
  border-color:rgba(127,230,245,.16);
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.14),
    0 30px 64px -34px rgba(0,0,0,.85);
}

/* ── L. THE SECTION HEAD ───────────────────────────────────────────────
   The eyebrow chip and the heading were separated by the same gap as any
   two paragraphs, so a band opened without an obvious first line. Tying
   them together and giving the pair real space above turns each band
   into a chapter rather than a continuation. */
.section > .container > .eyebrow,
.section > .container > .eyebrow-pill{margin-bottom:14px}
.section h2{margin-top:0}

/* ── M. THE STAT TILES ─────────────────────────────────────────────────
   These are the gallery's headline numbers and they were the quietest
   thing on the page. Bigger figure, tabular so the row aligns, and the
   label pushed down into a caption where it belongs. */
.pf-stat strong{
  display:block;
  font-family:var(--font-display);font-weight:800;
  font-size:clamp(30px,3.4vw,44px);line-height:1.05;
  letter-spacing:-.03em;font-variant-numeric:tabular-nums;
  color:var(--ink);
}
.pf-stat span{
  display:block;margin-top:8px;
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--muted);
}

/* ── N. THE HORIZON ────────────────────────────────────────────────────
   Where a dark band meets a light one there was a hard horizontal line
   across the full width — the single most "web page" thing left on the
   site. In a film that cut would be a dissolve.

   Two elements do it. A gradient bleed carries the dark band's ground a
   little way into the light one so the change happens over sixty pixels
   instead of none, and a hairline of cyan sits exactly on the seam, at
   full strength in the middle and gone at the edges, which is what an
   edge catching a light looks like. Both are drawn by the dark band, so
   any section that becomes dark gets its horizon automatically. */
.hero,.section.dark,.cta-band{position:relative}
/* NOTE the hero is deliberately absent from the bleed below.
   `.hero::after` is already the slow light sweep that crosses the hero,
   and pseudo-element declarations MERGE rather than replace — adding a
   second use of it here would not give the hero a bleed, it would give
   the light sweep a 78px height and pin it to the bottom edge. The hero
   keeps the hairline on ::before, which nothing else claims, and that is
   the half of the effect that does the work. */
.section.dark::after{
  content:"";position:absolute;left:0;right:0;bottom:-1px;height:78px;
  pointer-events:none;z-index:2;
  background:linear-gradient(180deg,transparent,rgba(255,255,255,.06) 100%);
  mask-image:linear-gradient(180deg,transparent,#000);
  -webkit-mask-image:linear-gradient(180deg,transparent,#000);
}
.hero::before,.section.dark::before{
  content:"";position:absolute;left:0;right:0;bottom:0;height:1px;z-index:3;
  pointer-events:none;
  background:linear-gradient(90deg,
    transparent,
    rgba(18,196,222,.22) 18%,
    rgba(18,196,222,.75) 50%,
    rgba(18,196,222,.22) 82%,
    transparent);
}

/* ── O. THE VIGNETTE ───────────────────────────────────────────────────
   A whisper of shade in the far corners of the viewport, fixed, behind
   everything. It is what a lens does, and it is the reason the centre
   column of a film frame reads as the subject rather than as the middle
   of a rectangle. Barely visible on its own; take it away and the page
   goes flat. */
@media (min-width:1100px){
  body::after{
    background-image:var(--grain-soft),
      radial-gradient(120% 90% at 50% 50%, transparent 52%, rgba(4,26,34,.09) 100%);
    background-size:160px 160px, cover;
  }
}

/* ── P. THE FEATURE CARD'S RULE ────────────────────────────────────────
   A short cyan rule between the heading and the text. It does one useful
   thing — it gives the eye a place to stop before the paragraph — and it
   is the same mark the section eyebrows use, so the page reads as one
   drawing rather than a set of components. */
.feature-card h3,.category-card h3{margin-bottom:14px}
.feature-card h3::after,.category-card h3::after{
  content:"";display:block;width:26px;height:2px;margin-top:12px;border-radius:2px;
  background:linear-gradient(90deg,var(--cyan),rgba(7,151,174,0));
  transition:width var(--m-base) var(--e-glide);
}
.feature-card:hover h3::after,.category-card:hover h3::after{width:52px}

/* ── Q. TWO SMALL THINGS THE FULL-PAGE VIEW SHOWED UP ──────────────────
   A gallery card whose caption is one line short left a bare white strip
   along its bottom edge, because the caption block stopped where its text
   did and the card's own ground showed underneath. Every card in a row is
   the height of the tallest, so this happens to most of them. The caption
   fills what is left. */
.gallery-card{display:flex;flex-direction:column}
.gallery-cap{flex:1 1 auto}

/* And the WhatsApp label sat noticeably dimmer than the Call label beside
   it inside the dark order panel — the restrained green that reads well
   on white is too quiet on ink. Lifted just on that ground, and nowhere
   else, so the button stays the calmer of the two on the light cards. */
.order-panel .btn-whatsapp{color:#6FE3A6;border-color:rgba(111,227,166,.42)}
.order-panel .btn-whatsapp:hover{color:#0A2418;background:#5BD895;border-color:#5BD895}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: THE PRESS — motion graphics on the hero
   ═══════════════════════════════════════════════════════════════════════
   Not ambient drift. A nine-second scene that shows what this business
   actually does, playing on a loop in the corner of the home page:

        PRINT  →  SETTLE  →  SCAN  →  VERIFY  →  hold  →  again

   A thermal print head crosses the plate and the barcode appears behind
   it. The label sits for a moment. A scanner beam reads it. A ring pulses
   out to say it read clean. Then it holds, and starts over.

   Everything is drawn with gradients and masks — no images, no canvas, no
   library, nothing extra to download. And every part of it is named in
   the reduced-motion block at the bottom, because a nine-second loop in
   the corner of the screen is precisely what somebody who turns that
   setting on is turning it on for.
   ═════════════════════════════════════════════════════════════════════ */

/* ── The plate ─────────────────────────────────────────────────────────
   The bars move OFF the container and onto their own layer, because the
   container also carries the plate colour and its shadow — masking the
   container would erase the white paper along with the ink, and a barcode
   printing onto nothing is not what a printer does. */
.barcode-real{
  background-image:none;              /* the bars live on .bc-bars now */
  isolation:isolate;
}
.bc-bars{
  position:absolute;inset:0;display:block;pointer-events:none;
  background-image:repeating-linear-gradient(90deg,
    #06131A 0 2px, #F7FBFC 2px 5px, #06131A 5px 6px, #F7FBFC 6px 11px,
    #06131A 11px 14px, #F7FBFC 14px 16px, #06131A 16px 17px, #F7FBFC 17px 23px);
  /* A mask twice the element's width: black on its left half, clear on
     its right. Sliding it from right-aligned to left-aligned wipes the
     ink on from the left, which is the direction a thermal head travels.
     Plain `mask-position` — no @property registration needed, so it
     interpolates in every browser that can mask at all. */
  -webkit-mask-image:linear-gradient(90deg,#000 0 50%,transparent 50% 100%);
  mask-image:linear-gradient(90deg,#000 0 50%,transparent 50% 100%);
  -webkit-mask-size:200% 100%;mask-size:200% 100%;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  animation:bcPrint 9s linear infinite;
}
@keyframes bcPrint{
  /* print */    0%{mask-position:100% 0;-webkit-mask-position:100% 0}
                22%{mask-position:0% 0;-webkit-mask-position:0% 0}
  /* hold  */   100%{mask-position:0% 0;-webkit-mask-position:0% 0}
}

/* ── The print head ────────────────────────────────────────────────────
   A hot filament, brightest at its centre, with the faint scorch of heat
   trailing behind it. It only exists while it is printing. */
.bc-head{
  position:absolute;top:-8%;bottom:-8%;left:0;width:34px;display:block;
  pointer-events:none;z-index:2;
  background:
    linear-gradient(90deg,transparent,rgba(18,196,222,.30) 55%,transparent),
    linear-gradient(90deg,transparent 46%,#EAFDFF 49%,#7FE6F5 51%,transparent 54%);
  filter:blur(.3px) drop-shadow(0 0 10px rgba(18,196,222,.9));
  opacity:0;
  animation:bcHead 9s linear infinite;
}
@keyframes bcHead{
  0%{transform:translateX(-120%);opacity:0}
  3%{opacity:1}
  20%{opacity:1}
  22%{transform:translateX(2000%);opacity:0}
  100%{transform:translateX(2000%);opacity:0}
}

/* ── The scanner ───────────────────────────────────────────────────────
   The sweep that was already here, re-timed to run AFTER the print rather
   than on its own unrelated clock. A scanner reading a label that has not
   finished printing is the kind of detail a label house would notice. */
.barcode-real::after{
  animation:bcScan 9s cubic-bezier(.45,0,.2,1) infinite;
}
@keyframes bcScan{
  0%,40%{transform:translateX(-140%)}
  62%{transform:translateX(240%)}
  100%{transform:translateX(240%)}
}

/* ── The verify pulse ──────────────────────────────────────────────────
   One ring, expanding out of the plate and fading, at the moment the scan
   clears the last bar. It is the "beep". */
.bc-verify{
  position:absolute;inset:0;display:block;pointer-events:none;z-index:3;
  border-radius:var(--radius-sm);
  box-shadow:0 0 0 0 rgba(63,214,143,.85);
  opacity:0;
  animation:bcVerify 9s var(--e-enter) infinite;
}
@keyframes bcVerify{
  0%,62%{opacity:0;box-shadow:0 0 0 0 rgba(63,214,143,.85)}
  66%{opacity:1;box-shadow:0 0 0 0 rgba(63,214,143,.85)}
  78%{opacity:0;box-shadow:0 0 0 14px rgba(63,214,143,0)}
  100%{opacity:0}
}
/* The corner registration marks flash green with it — the same beat, so
   the card reads as one machine rather than two effects. */
.hero-card .label-demo::before,.hero-card .label-demo::after{
  animation:markVerify 9s var(--e-enter) infinite;
}
@keyframes markVerify{
  0%,62%{border-color:var(--cyan-bright);box-shadow:0 0 12px rgba(18,196,222,.7)}
  68%{border-color:#4FE3A4;box-shadow:0 0 20px rgba(79,227,164,.95)}
  82%,100%{border-color:var(--cyan-bright);box-shadow:0 0 12px rgba(18,196,222,.7)}
}

/* ── The room ──────────────────────────────────────────────────────────
   Behind the card, three things, all very quiet. The floor grid drifts
   towards the viewer so the room has depth rather than a printed pattern
   on it. A wide soft bar of light crosses the whole hero every twenty
   seconds. And three hairlines run left to right along the floor at
   different speeds — data moving through a machine, which is the one
   thing a barcode business is actually selling. */
.hero-bg::before{
  animation:floorDrift 26s linear infinite;
}
@keyframes floorDrift{
  from{background-position:0 0,0 0}
  to  {background-position:0 78px,78px 0}
}

.hero-sweep{
  position:absolute;top:0;bottom:0;left:0;width:38%;display:block;
  pointer-events:none;z-index:-1;
  background:linear-gradient(100deg,transparent,rgba(127,230,245,.055) 45%,rgba(127,230,245,.09) 50%,rgba(127,230,245,.055) 55%,transparent);
  transform:translateX(-120%) skewX(-10deg);
  animation:heroSweep 20s cubic-bezier(.5,0,.3,1) infinite;
}
@keyframes heroSweep{
  0%,70%{transform:translateX(-120%) skewX(-10deg)}
  100%{transform:translateX(320%) skewX(-10deg)}
}

.data-rail{position:absolute;inset:0;display:block;pointer-events:none;z-index:-1;overflow:hidden}
.data-rail i{
  position:absolute;left:0;height:1px;width:22%;display:block;
  background:linear-gradient(90deg,transparent,rgba(18,196,222,.55),transparent);
  transform:translateX(-120%);
}
.data-rail i:nth-child(1){top:34%;animation:railRun 11s linear infinite}
.data-rail i:nth-child(2){top:58%;width:14%;opacity:.7;animation:railRun 15s linear infinite 3.5s}
.data-rail i:nth-child(3){top:76%;width:30%;opacity:.45;animation:railRun 19s linear infinite 7s}
@keyframes railRun{
  0%{transform:translateX(-120%)}
  55%,100%{transform:translateX(560%)}
}

/* ── OFF ───────────────────────────────────────────────────────────────
   Every animation this layer introduced, named. The scene stops on its
   finished frame: the barcode fully printed, which is the state that
   makes sense as a still picture. */
@media (prefers-reduced-motion: reduce){
  .bc-bars{
    animation:none;
    -webkit-mask-image:none;mask-image:none;    /* stop on the printed frame */
  }
  .bc-head,.bc-verify,.hero-sweep,.data-rail{display:none}
  .data-rail i{animation:none}
  .barcode-real::after{animation:none;display:none}
  .hero-card .label-demo::before,.hero-card .label-demo::after{animation:none}

  /* The hero's older ambient motion — the light beam, the two breathing
     orbs and the glow behind the specimen card — predates this pass and
     was never switched off. Three separate authors each added a loop and
     none of them came back to this block, which is exactly the drift a
     single named off-switch exists to stop. Everything inside the hero is
     covered now, by selector rather than by memory. */
  .hero-bg::before,.hero-bg::after,
  .orb,.orb-cyan,.orb-cyan-soft,.grid-lines,.hero-glow,
  .hero,.hero *,.hero *::before,.hero *::after{
    animation:none !important;
  }
}
/* And nothing of it exists on paper. */
@media print{
  .bc-head,.bc-verify,.hero-sweep,.data-rail{display:none !important}
  .bc-bars{-webkit-mask-image:none;mask-image:none;animation:none}
}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: FLOOR — corrections from the live site, and motion where it can
   actually be seen
   ═════════════════════════════════════════════════════════════════════ */

/* ── 1. "Call For Order", centred ──────────────────────────────────────
   On a wide card the icon and label sat hard against the left edge with
   a stretch of empty pill after them, so the button looked like a label
   with a border rather than a control. Centred as a pair. */
.card-call,.btn-call{justify-content:center}
.card-call .call-slide,.btn-call .call-slide{
  width:auto;min-width:0;text-align:center;
}
.card-actions .card-call .call-slide{width:auto}

/* ── 2. The ORIGINAL ribbon, readable ──────────────────────────────────
   Near-black type on a mid-cyan band is the lowest-contrast pairing in
   the whole palette, and at 9.5px on a diagonal it was a smudge. White,
   and 2.5pt larger. The band grows with it so the word still fits whole
   with the corner clipping it. */
.origin-tag > span{
  font-size:12.5px;letter-spacing:.1em;
  color:#FFFFFF;
  text-shadow:0 1px 2px rgba(0,33,42,.55);
  padding:6px 0;
}
.origin-original > span{
  background:linear-gradient(135deg,#0FB6CE 0%,#0784A0 48%,#03485A 100%);
}
.origin-tag{width:140px;height:140px}
.origin-tag > span{top:24px;left:-48px;width:186px}
.product-detail-image .origin-tag{width:184px;height:184px}
.product-detail-image .origin-tag > span{top:33px;left:-54px;width:232px;font-size:14px}

/* ── 3. The footer WhatsApp goes under the number ──────────────────────
   Side by side, the two read as alternatives of equal weight. Stacked,
   they read as one contact block: the number first, the second way to
   reach the same people underneath it. Both stretch to the same width so
   the column has an edge. */
.footer-contact{flex-direction:column;align-items:stretch;gap:10px}
.footer-contact a{justify-content:flex-start}

/* ── 4. Motion where it is actually seen ───────────────────────────────
   The nine-second scene lives on the home page hero. Every other page —
   the one a customer spends their time on — had motion only on hover,
   which on a phone is no motion at all.

   So the product plate gets the same idea the hero card has, reduced to
   its quietest form: a scan line crosses each photograph, once, slowly,
   and the cards go one after another rather than together. It is the
   business's own gesture, it needs no pointer, and at eight seconds
   apart it never becomes wallpaper.

   The stagger is by position in the grid, so a row reads left to right
   the way the eye already moves. */
/* Its OWN element, not a pseudo-element.

   The first attempt hung this on `.product-image::before`, which already
   carries the plate's inner highlight — and inherited that rule's
   `mix-blend-mode:overlay` along with it. Overlay blending a pale cyan
   band onto a near-white plate produces almost exactly nothing, which is
   why the sweep did not appear. Switching the blend mode would have
   erased the highlight instead. Both pseudo-elements on this box are
   already spoken for, so the honest fix is one span in the markup.

   The band is CYAN and it MULTIPLIES, because the plate is white paper:
   a white sweep on white shows nothing, and a scanner line on a label is
   a darker line, not a brighter one. */
.plate-scan{
  position:absolute;top:0;bottom:0;left:0;width:22%;
  display:block;pointer-events:none;z-index:2;
  mix-blend-mode:multiply;
  /* A soft cyan wash with one hard bright line down its middle — a
     scanner beam is a line with a glow, not a gradient. */
  background:linear-gradient(100deg,
    transparent,
    rgba(7,151,174,.10) 34%,
    rgba(7,151,174,.34) 47%,
    rgba(4,96,116,.72) 50%,
    rgba(7,151,174,.34) 53%,
    rgba(7,151,174,.10) 66%,
    transparent);
  transform:translateX(-120%);
  animation:plateScan 8s cubic-bezier(.45,0,.25,1) infinite;
}
/* Moved with `transform`, not `background-position`.

   The first version animated the position of an over-sized background,
   and percentage background-position on a background wider than its box
   does not mean what it looks like it means: it is a fraction of
   (box − image), which is negative. The sweep did cross the plate — for
   about a quarter of a second out of eight, somewhere nobody was looking.
   A translated element crosses in a straight line at the speed you asked
   for, and it is the same mechanism the hero sweep already uses. */
@keyframes plateScan{
  0%,58%{transform:translateX(-120%)}
  92%,100%{transform:translateX(430%)}
}
/* Six steps of stagger, then it repeats — enough that no two cards in a
   row ever scan together, few enough to stay a rhythm rather than noise. */
.product-card:nth-child(6n+2) .plate-scan{animation-delay:1.3s}
.product-card:nth-child(6n+3) .plate-scan{animation-delay:2.6s}
.product-card:nth-child(6n+4) .plate-scan{animation-delay:3.9s}
.product-card:nth-child(6n+5) .plate-scan{animation-delay:5.2s}
.product-card:nth-child(6n+6) .plate-scan{animation-delay:6.5s}

/* The stock badge's dot is a live indicator, so it behaves like one —
   a slow pulse rather than a painted circle. Only the "ready" state
   pulses: a warning that blinks is an alarm, and this is not one. */
.stock-ready::before{animation:stockPulse 2.6s var(--e-glide) infinite}
@keyframes stockPulse{
  0%,100%{box-shadow:0 0 10px 1px rgba(63,214,143,.75)}
  50%{box-shadow:0 0 16px 3px rgba(63,214,143,.95)}
}

/* The price, once, as the card arrives — the figure lights up and settles.
   Tied to `.in-view`, so it happens when the card is actually looked at
   and never again. */
.product-card.in-view .price,.product-card.in-view .price-ask{
  animation:priceLand var(--m-epic) var(--e-enter) both;
}
@keyframes priceLand{
  from{opacity:0;transform:translateY(6px);text-shadow:0 0 24px rgba(18,196,222,.9)}
  to  {opacity:1;transform:none;text-shadow:none}
}

/* ── OFF ───────────────────────────────────────────────────────────────
   Named, as always. */
@media (prefers-reduced-motion: reduce){
  .plate-scan{display:none}
  .stock-ready::before{animation:none}
  .product-card.in-view .price,.product-card.in-view .price-ask{animation:none}
}
@media print{
  .plate-scan{display:none !important}
}
/* The product page's plate gets the same beam, slower — it is one large
   picture the visitor is studying, not a grid they are skimming. */
.product-detail-image .plate-scan{
  width:16%;
  animation-duration:11s;
}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: PRISM — liquid glass, shiny type, grain
   ═══════════════════════════════════════════════════════════════════════
   The techniques here come from a reference design ("Aura") the owner
   liked. Its PALETTE does not: that page is near-black with a navy-to-ice
   gradient, and this is a white-and-cyan brand. So each technique is
   re-derived from the tokens already at the top of this file, and every
   colour below traces back to --paper, --ink, --cyan, --cyan-bright or
   --cyan-deep. Nothing above this line is renamed, redefined or removed;
   the whole layer is additive and namespaced.

   Four techniques:
     .liquid-glass  a translucent pane with a gradient rim
     .shiny-text    a headline word with a gradient that travels through it
     .noise-grain   a film of grain over a large surface
     .watermark     the section's own word, huge, behind its content

   Plus .stagger, which gives any grid the entrance rhythm the hero has.
   ═════════════════════════════════════════════════════════════════════ */

/* ── The glass tokens ──────────────────────────────────────────────────
   Declared here on :root and re-declared on the SAME selector list the
   dual palette uses, so a pane inside a dark band becomes its dark self
   with no extra class — the mechanism this stylesheet was built around.

   Note the rim is not white. On a white ground a white rim is invisible;
   what reads as glass here is a rim that runs from near-white at the top
   edge, through the brand cyan at the shoulders, to almost nothing across
   the middle — the way light actually catches a curved edge. */
:root{
  --glass-fill:rgba(255,255,255,.66);
  --glass-fill-hover:rgba(255,255,255,.80);
  --glass-blur:blur(22px) saturate(180%);
  --glass-rim:linear-gradient(180deg,
    rgba(255,255,255,.98) 0%,
    rgba(7,151,174,.34) 16%,
    rgba(7,151,174,.07) 40%,
    rgba(7,151,174,.07) 60%,
    rgba(7,151,174,.30) 84%,
    rgba(255,255,255,.95) 100%);
  --glass-rim-hot:linear-gradient(180deg,
    rgba(255,255,255,1) 0%,
    rgba(18,196,222,.75) 14%,
    rgba(18,196,222,.28) 42%,
    rgba(18,196,222,.28) 58%,
    rgba(18,196,222,.70) 86%,
    rgba(255,255,255,1) 100%);
  --glass-inset:inset 0 1px 1px rgba(255,255,255,.85);
  --glass-radius:22px;
  --glass-radius-xl:40px;
}
.hero,
.section.dark,
.order-panel,
.contact-note,
.chat-head,
footer{
  --glass-fill:rgba(255,255,255,.055);
  --glass-fill-hover:rgba(255,255,255,.10);
  --glass-rim:linear-gradient(180deg,
    rgba(190,240,250,.60) 0%,
    rgba(18,196,222,.34) 18%,
    rgba(18,196,222,.06) 42%,
    rgba(18,196,222,.06) 58%,
    rgba(18,196,222,.30) 82%,
    rgba(190,240,250,.55) 100%);
  --glass-rim-hot:linear-gradient(180deg,
    rgba(230,250,255,.95) 0%,
    rgba(18,196,222,.85) 16%,
    rgba(18,196,222,.34) 42%,
    rgba(18,196,222,.34) 58%,
    rgba(18,196,222,.80) 84%,
    rgba(230,250,255,.9) 100%);
  --glass-inset:inset 0 1px 1px rgba(190,240,250,.22);
}

/* ── .liquid-glass ─────────────────────────────────────────────────────
   The reference draws its gradient rim with a pseudo-element and
   mask-composite. That cannot be copied here: EVERY card in this
   stylesheet already uses both ::before and ::after — for the lit top
   rail, the hover arrow, the diagonal sheen, the corner tab. A third use
   would not stack, it would merge into one of them and silently replace
   its geometry. That exact fault has already been fixed twice in this
   file.

   So the rim is painted with two backgrounds instead: the fill clipped to
   the padding box, the gradient clipped to the border box, with a
   transparent border between them. Same ring of light, no pseudo-element,
   nothing to collide with. It is also less code and better supported.

   `overflow` is deliberately NOT set. The reference hides it; here the
   panes carry the Original ribbon, the stock chip and the magnifier lens,
   all of which are meant to sit outside the box. */
.liquid-glass{
  border:1.4px solid transparent;
  border-radius:var(--glass-radius);
  background:
    linear-gradient(var(--glass-fill),var(--glass-fill)) padding-box,
    var(--glass-rim) border-box;
  backdrop-filter:var(--glass-blur);
  -webkit-backdrop-filter:var(--glass-blur);
  box-shadow:var(--glass-inset),var(--lift-1);
  transition:
    background var(--m-base) var(--e-glide),
    box-shadow var(--m-base) var(--e-glide),
    transform var(--m-base) var(--e-glide);
}
.liquid-glass:hover{
  background:
    linear-gradient(var(--glass-fill-hover),var(--glass-fill-hover)) padding-box,
    var(--glass-rim-hot) border-box;
  box-shadow:var(--glass-inset),var(--lift-3);
}
/* The large panels — the order panel, the closing band, the contact note.
   The reference uses 44px on cards nearly six hundred pixels tall; at that
   radius a 260px product card reads as a lozenge, so the big radius is
   kept for the big surfaces and the cards get a restrained 22. */
.liquid-glass.glass-xl{border-radius:var(--glass-radius-xl)}
@media (max-width:600px){
  .liquid-glass.glass-xl{border-radius:26px}
}
/* Where backdrop-filter is unavailable the pane must not become a
   see-through hole with unreadable text over it. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .liquid-glass{
    background:
      linear-gradient(var(--card-bg),var(--card-bg)) padding-box,
      var(--glass-rim) border-box;
  }
}

/* ── .shiny-text ───────────────────────────────────────────────────────
   A gradient wider than the text, travelling through it on a six-second
   loop. Built from the four cyans this brand already owns, with the ink
   at each end so the light reads as a highlight passing over a dark
   letter rather than the whole word flashing.

   `background-clip:text` needs a colour of `transparent`, which means the
   word is INVISIBLE if the gradient fails to paint. The @supports guard
   is not politeness — without it, a browser that cannot clip a background
   to text shows an empty space where the headline should be. */
@supports ((-webkit-background-clip:text) or (background-clip:text)){
  .shiny-text{
    background-image:linear-gradient(100deg,
      var(--cyan-deep) 0%,
      var(--cyan) 18%,
      var(--cyan-bright) 34%,
      #EAFEFF 50%,
      var(--cyan-bright) 66%,
      var(--cyan) 82%,
      var(--cyan-deep) 100%);
    background-size:220% auto;
    /* NOT `no-repeat`. With the background clipped to the text and the
       colour set to transparent, any part of a word the gradient does not
       cover has nothing behind it — and a letter with no background and no
       colour is INVISIBLE. Sliding a 220% gradient across a heading with
       no-repeat therefore ate the first half of every headline it was put
       on. The gradient starts and ends on the same cyan, so tiling it is
       seamless and the text can never be left uncovered. */
    background-repeat:repeat;
    -webkit-background-clip:text;background-clip:text;
    color:transparent;-webkit-text-fill-color:transparent;
    animation:shinySweep 6s linear infinite;
  }
  /* The hero's accent word is split into per-word boxes at runtime by
     initKineticHeadline(), and those boxes are what the rising animation
     clips. A background on the parent does not show through a child box,
     so the gradient is handed down to the words themselves. With a
     one-word accent — which is what this headline has — that is
     indistinguishable from the parent carrying it. */
  .shiny-text .kin,
  .shiny-text .kin > span{
    background-image:inherit;
    background-size:inherit;
    background-repeat:repeat;
    -webkit-background-clip:text;background-clip:text;
    color:transparent;-webkit-text-fill-color:transparent;
    animation:shinySweep 6s linear infinite;
  }
}
@keyframes shinySweep{
  0%{background-position:-160% center}
  100%{background-position:160% center}
}

/* ── .noise-grain ──────────────────────────────────────────────────────
   A film of grain over a surface. This file already carries a
   fractal-noise texture as `--grain-soft` — the same feTurbulence the
   reference builds an SVG filter for, already inlined as a data URI and
   already used on the page ground. Reusing it costs nothing, cannot fail
   to resolve, and keeps one grain across the whole site rather than two
   that almost match.

   An overlay element rather than a filter on the box: `filter` on a
   card would blur its shadow and create a containing block that the
   ribbon and the badge are positioned against. */
.noise-grain{position:relative}
.noise-grain > .grain{
  position:absolute;inset:0;display:block;pointer-events:none;z-index:1;
  border-radius:inherit;
  background-image:var(--grain-soft);
  background-size:160px 160px;
  opacity:.5;mix-blend-mode:multiply;
}
.hero .noise-grain > .grain,
.section.dark .noise-grain > .grain,
.order-panel > .grain{mix-blend-mode:overlay;opacity:.7}

/* ── .watermark ────────────────────────────────────────────────────────
   The section's own word, set enormous and low-contrast behind its
   content. The word comes from a data attribute rather than a second copy
   of the text in the markup, so there is nothing for a screen reader to
   read twice and nothing for the owner to have to edit in two places.

   It sits at -1 and the section's content is lifted above it. The SVG
   grain filter is applied HERE and only here — this is the one place a
   real filter earns its cost, because a flat 9rem gradient letterform is
   the most obviously computer-generated thing a page can contain, and
   grain is what makes it read as printed. */
/* A real element, not a pseudo-element.

   The first attempt used `.watermark::after{content:attr(data-watermark)}`
   — and rendered nothing, because the sections it was put on are also
   `.section.dark`, whose ::after is the band bleed and whose ::before is
   the horizon hairline. Both are more specific than a single class, so
   the bleed's `content:""` won and the word never existed. Every card and
   band in this stylesheet has both pseudo-elements spoken for; that is
   simply the shape of this file, and the third use always has to be a
   real element.

   `aria-hidden` because it is a picture of a word, not a word: it repeats
   text already in the heading beside it, and a screen reader announcing
   the section twice is worse than not seeing the backdrop at all. */
.watermark{position:relative;isolation:isolate;overflow:hidden}
.watermark > .watermark-word{
  position:absolute;left:50%;top:50%;
  transform:translate(-50%,-50%);
  z-index:0;pointer-events:none;user-select:none;
  font-family:var(--font-display);
  font-weight:800;font-size:clamp(72px,16vw,220px);
  line-height:.86;letter-spacing:-.055em;
  white-space:nowrap;
  background-image:linear-gradient(100deg,
    var(--cyan-deep) 0%,
    var(--cyan) 34%,
    var(--cyan-bright) 62%,
    var(--cyan-light) 100%);
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  opacity:.13;
  filter:url(#icl-grain-soft);
}
/* The content has to sit above it. These bands centre one panel, so
   lifting that panel is enough. */
.watermark > .container{position:relative;z-index:1}
.hero .watermark > .watermark-word,
.section.dark .watermark-word{opacity:.20}

/* No filter, smaller and quieter on a phone — a 220px letterform behind a
   340px column is not a backdrop, it is a collision. */
@media (max-width:900px){
  .watermark > .watermark-word{font-size:clamp(52px,14vw,96px);filter:none;opacity:.10}
}
@supports not ((-webkit-background-clip:text) or (background-clip:text)){
  .watermark > .watermark-word{display:none}
}

/* ── .stagger ──────────────────────────────────────────────────────────
   The hero gets its entrance sequence from JavaScript, which writes an
   index onto each element. A grid does not need that: `:nth-child` knows
   the index already. Eight steps, then it repeats — long enough for every
   grid on this site, short enough that a long list never waits a second
   and a half for its last row. */
.stagger > .reveal:nth-child(1){transition-delay:0ms}
.stagger > .reveal:nth-child(2){transition-delay:70ms}
.stagger > .reveal:nth-child(3){transition-delay:140ms}
.stagger > .reveal:nth-child(4){transition-delay:210ms}
.stagger > .reveal:nth-child(5){transition-delay:280ms}
.stagger > .reveal:nth-child(6){transition-delay:350ms}
.stagger > .reveal:nth-child(7){transition-delay:420ms}
.stagger > .reveal:nth-child(8){transition-delay:490ms}
.stagger > .reveal:nth-child(n+9){transition-delay:560ms}

/* ── OFF ─────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce){
  .watermark > .watermark-word{filter:none}
  .shiny-text,.shiny-text .kin,.shiny-text .kin > span{
    animation:none;
    /* Stopped where the highlight sits on the word, not at the dark end
       of the gradient — a headline frozen on its own shadow reads as a
       rendering fault. */
    background-position:50% center;
  }
  .liquid-glass{transition:none}
  .stagger > .reveal{transition-delay:0ms !important}
}
@media print{
  .liquid-glass{
    background:#fff !important;border:1px solid #ccc !important;
    backdrop-filter:none !important;box-shadow:none !important;
  }
  .watermark > .watermark-word,.noise-grain > .grain{display:none !important}
  .shiny-text,.shiny-text .kin,.shiny-text .kin > span{
    -webkit-text-fill-color:#04606F !important;color:#04606F !important;
    background:none !important;animation:none !important;
  }
}

/* ── A pane that IS a dark card, not a pane inside a dark band ─────────
   `.contact-note`, `.order-panel` and `.chat-head` are in the dual
   palette's dark list — but for a different reason from `.hero` and
   `.section.dark`. Those are dark SECTIONS, and a glass pane sitting
   inside one is correctly a white 5% film over a dark ground. These three
   are dark CARDS on a LIGHT page: their text is white because their own
   background is dark.

   Handing them the same white 5% fill replaced that dark background with
   something nearly transparent, and left white text sitting on a white
   page — the contact card's phone number and email were effectively
   invisible. The fill has to be dark for these, so the glass adds a rim
   and a blur to the card they already were instead of erasing it. */
.contact-note.liquid-glass,
.order-panel.liquid-glass,
.chat-head.liquid-glass{
  --glass-fill:rgba(9,26,35,.86);
  --glass-fill-hover:rgba(11,31,41,.90);
  --glass-blur:blur(18px) saturate(150%);
}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: CLEAN PLATE — nothing lies on top of a product photograph
   ═══════════════════════════════════════════════════════════════════════
   The owner's instruction, verbatim: "product er card ar upore ba image ar
   upore kono effect ba kichui porbe na — already ekta diye dicho."

   He is right, and the reason is not taste. A product photograph on this
   site is a COLOUR SAMPLE. A buyer looks at a ribbon picture to decide
   whether that is the blue they want. Every film laid over it — a white
   diagonal sheen, a travelling cyan scanner beam, a corner vignette —
   changes the colour he is judging. The card already carries one mark
   that means something (the ORIGINAL ribbon) and one that carries data
   (the stock chip). Everything else was decoration on top of the one
   thing the page exists to show.

   So four earlier effects are switched off here, by name, each with the
   line it overrides. Nothing is deleted above: this layer is the record
   of the decision, and reversing it means deleting this block.          */

/* 1. The travelling scanner beam (LAYER: FLOOR, ~line 3926). The span is
      also gone from the card template in app.js — this rule is what
      protects a visitor whose browser still has the old app.js cached. */
.plate-scan{display:none !important}

/* 2. The diagonal white sheen across the whole card (~line 2000). It was
      written for four card families at once; only the product card is
      taken out of it, because only the product card contains a colour
      sample. Feature, category and gallery cards keep their sheen. */
.product-card::after{display:none}

/* 3. The same sheen again, painted directly on the plate (~line 2011).
      The rule at ~line 1817 that shares this pseudo-element is the plate's
      1px inner rim light — an EDGE, not a film — and it stays, which is
      why this switches off the background rather than the element. */
.product-image::before{background:none;mix-blend-mode:normal}

/* 4. The corner vignette on the plate (~line 757). Subtle, but it darkened
      the lower right of every photograph. */
.product-image::after{display:none}

/* 5. The hover zoom. Switched off here first, then asked for back — and
      the owner is right both times, because the two requests are about
      different things. What he objected to was something moving over the
      picture on its own; a zoom that happens because HE moved the mouse is
      a response, not an effect.

      It is safe at this size, and the reason is worth writing down. The
      plate has 18px of padding and the photo inside it is
      `object-fit:contain`, so the drawn image is already smaller than its
      box. Scaling the img element by 4% grows it INTO that padding, not
      past the plate — about 13px per side on a 660px plate against 18px of
      room. Nothing is cropped, which keeps the rule that has governed this
      plate since it was written. Go much past 1.05 and that stops being
      true. */
.product-image img{
  transition:transform .75s var(--e-glide);
  will-change:transform;
}
.product-card:hover .product-image img,
.product-card:focus-within .product-image img{transform:scale(1.04)}
@media (prefers-reduced-motion: reduce){
  .product-card:hover .product-image img,
  .product-card:focus-within .product-image img{transform:none}
}

/* What deliberately stays: the ORIGINAL ribbon, the stock chip, the
   plate's own gradient (which is BEHIND the picture, not over it), the
   1px rim light at the plate's edge, the card's lit top rail on hover and
   the card's lift. None of those touch the photograph. */

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: FINALE — the closing CTA and the footer, as one continuous shot
   ═══════════════════════════════════════════════════════════════════════
   The reference is a black page with a Mux HLS video of a flowing liquid
   ribbon behind a centred headline, two pill buttons, and a footer bar
   welded onto the bottom of the same section.

   Three things are taken from it and one is refused.

   TAKEN
     · the composition — eyebrow, one large headline, one line of subtext,
       two buttons side by side, footer immediately beneath, all centred;
     · the top and bottom ground fades that let a moving background die
       into the page instead of ending on a hard edge;
     · `.liquid-glass-strong`: the mask-composite rim, which draws a
       hairline that is bright at the top and bottom of a shape and gone
       across its middle — the way light actually catches a curved edge.

   REFUSED
     · the video. It is 2.4 MB of HLS that needs hls.js on every browser
       but Safari, it is somebody else's purple, and this site is plain
       JS with zero dependencies. The same flowing light is built here
       from `.silk` — a conic gradient in this brand's own cyan, blurred
       and turned slowly — sitting inside the hero's existing furniture:
       `.orb`, `.grid-lines`, `.hero-sweep` and `.data-rail`, reused by
       name so the closing shot and the opening shot are the same room.

   Every colour below resolves through the tokens at the top of this file.
   Because custom properties are substituted where they are USED and not
   where they are declared, `--rim-strong` can be declared once on :root
   with `var(--cyan-light)` inside it and still come out in the dark
   palette's brighter cyan when it lands inside a dark band. That is the
   mechanism this stylesheet is built on, used one more time.           */

:root{
  /* The ground the dark bands already paint (see the .hero/.section.dark/
     footer rule near the top of this file). Named here so the fades and
     the footer join can be written against it instead of repeating it. */
  --ground:#071219;

  /* The rim. Bright at the very top edge, gone across the middle, bright
     again at the bottom — the reference's shape, this brand's colour.
     The rgba values ARE --cyan-light (127,230,245) and --cyan-bright
     (18,196,222); the @supports block below restates them as color-mix so
     the token literally derives from the variables. */
  --rim-strong:linear-gradient(180deg,
    rgba(127,230,245,.55) 0%,
    rgba(127,230,245,.22) 20%,
    rgba(127,230,245,0)   40%,
    rgba(18,196,222,0)    60%,
    rgba(18,196,222,.26)  80%,
    rgba(18,196,222,.62)  100%);
  --rim-strong-hot:linear-gradient(180deg,
    rgba(157,239,251,.95) 0%,
    rgba(127,230,245,.50) 18%,
    rgba(18,196,222,.10)  42%,
    rgba(18,196,222,.10)  58%,
    rgba(18,196,222,.55)  82%,
    rgba(157,239,251,.98) 100%);

  --glass-strong-fill:rgba(255,255,255,.02);
  --glass-strong-blur:blur(50px) saturate(160%);
  --glass-strong-shadow:
    4px 4px 4px rgba(6,23,31,.05),
    inset 0 1px 1px rgba(127,230,245,.18);
}
@supports (color:color-mix(in srgb,red 50%,transparent)){
  :root{
    --rim-strong:linear-gradient(180deg,
      color-mix(in srgb,var(--cyan-light) 55%,transparent) 0%,
      color-mix(in srgb,var(--cyan-light) 22%,transparent) 20%,
      transparent 40%,
      transparent 60%,
      color-mix(in srgb,var(--cyan-bright) 26%,transparent) 80%,
      color-mix(in srgb,var(--cyan-bright) 62%,transparent) 100%);
    --rim-strong-hot:linear-gradient(180deg,
      color-mix(in srgb,var(--cyan-light) 95%,transparent) 0%,
      color-mix(in srgb,var(--cyan-light) 50%,transparent) 18%,
      color-mix(in srgb,var(--cyan-bright) 12%,transparent) 42%,
      color-mix(in srgb,var(--cyan-bright) 12%,transparent) 58%,
      color-mix(in srgb,var(--cyan-bright) 55%,transparent) 82%,
      color-mix(in srgb,var(--cyan-light) 98%,transparent) 100%);
    --glass-strong-shadow:
      4px 4px 4px rgba(6,23,31,.05),
      inset 0 1px 1px color-mix(in srgb,var(--cyan-light) 18%,transparent);
  }
}

/* ── .liquid-glass-strong ──────────────────────────────────────────────
   A heavier sibling of `.liquid-glass`. That one paints its rim with two
   background-clips because every CARD in this file already has both
   pseudo-elements spoken for. A BUTTON does not: `.btn`/`.btn-call` use
   `::after` for the hover sweep and nothing at all for `::before`, so
   here the reference's mask-composite trick fits with nothing to
   collide with, and it is the better rim — it can fade along its own
   length, which a border-box gradient cannot.

   `background-blend-mode:luminosity` is kept from the reference: it makes
   the 2% fill take the BRIGHTNESS of what is behind it while dropping
   that colour, which is why the glass looks like glass over a moving
   background instead of a grey film. */
.liquid-glass-strong{
  position:relative;overflow:hidden;
  border:none;
  background-color:var(--glass-strong-fill);
  background-blend-mode:luminosity;
  backdrop-filter:var(--glass-strong-blur);
  -webkit-backdrop-filter:var(--glass-strong-blur);
  box-shadow:var(--glass-strong-shadow);
}
.liquid-glass-strong::before{
  content:"";position:absolute;inset:0;z-index:2;
  border-radius:inherit;padding:1.4px;
  background:var(--rim-strong);
  -webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);
          mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);
  -webkit-mask-composite:xor;
          mask-composite:exclude;
  pointer-events:none;
  transition:background var(--m-base) var(--e-glide);
}
.liquid-glass-strong:hover::before{background:var(--rim-strong-hot)}
/* Where mask-composite is missing the rim would paint as a solid slab
   over the button's own label. Better no rim than a filled rectangle. */
@supports not ((-webkit-mask-composite:xor) or (mask-composite:exclude)){
  .liquid-glass-strong::before{display:none}
  .liquid-glass-strong{border:1.4px solid var(--line-strong)}
}
/* And where there is no backdrop-filter, a 2% fill is a hole. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .liquid-glass-strong{background-color:rgba(9,26,35,.72)}
}

/* ── The closing section ───────────────────────────────────────────────
   It is still `.section.cta-band` and it still carries every data-txt it
   carried before; what changed is that it is now DARK and full-bleed, so
   the page descends into the dark and stays there through the footer. */
.cta-close{
  padding:clamp(92px,10.5vw,164px) 0 clamp(88px,9.5vw,148px);
  overflow:hidden;isolation:isolate;
}
/* No watermark word here. Every other band on this page can carry one
   because its headline sits in a panel off to one side; this headline is
   centred and 84px tall, so a 220px letterform behind it lands directly
   across the words — the two collided into an unreadable smear the first
   time this was built. The moving ground is the depth this section needs;
   a second background device is one too many. */
/* The registration ticks and the seam hairline are the divider between
   this band and the dark band above it — without them two dark sections
   read as one accident. Lifted above the top fade so the fade cannot
   swallow them. */
.cta-close::before{z-index:1}

/* ── The moving ground ─────────────────────────────────────────────── */
.close-bg{
  position:absolute;inset:0;z-index:-1;
  display:block;overflow:hidden;pointer-events:none;
}
/* The liquid ribbon. A conic gradient in the brand cyan, given an organic
   outline by an asymmetric border-radius, blurred until the bands become
   light rather than stripes, and turned very slowly. Two of them at
   different speeds and directions is what stops it reading as a spinning
   wheel: where they cross, the light gathers, and that crossing point
   drifts because their periods do not divide into each other. */
.silk{
  position:absolute;display:block;pointer-events:none;
  width:132%;aspect-ratio:1;left:-16%;
  border-radius:46% 54% 58% 42% / 52% 44% 56% 48%;
  filter:blur(66px);
  background:conic-gradient(from 210deg at 50% 50%,
    transparent 0deg,
    rgba(4,96,116,.50)    38deg,
    rgba(18,196,222,.58)  96deg,
    rgba(157,239,251,.38) 138deg,
    transparent           198deg,
    rgba(7,151,174,.40)   282deg,
    transparent           360deg);
  will-change:transform;
}
.silk-1{top:-30%;opacity:.55;animation:silkTurn 38s linear infinite}
.silk-2{
  bottom:-36%;left:-26%;width:124%;opacity:.38;
  animation:silkCounter 61s linear infinite;
}
@keyframes silkTurn{
  from{transform:rotate(0deg) scale(1)}
  50% {transform:rotate(180deg) scale(1.08)}
  to  {transform:rotate(360deg) scale(1)}
}
@keyframes silkCounter{
  from{transform:rotate(360deg) translate3d(0,0,0)}
  50% {transform:rotate(180deg) translate3d(3%,-2%,0)}
  to  {transform:rotate(0deg) translate3d(0,0,0)}
}

/* ── The two ground fades ──────────────────────────────────────────────
   The reference's exact device: 200px of the page's own ground at each
   edge, fading to nothing, so a moving background has no visible top or
   bottom. Here the bottom one matters twice over — it is also the joint
   between this section and the footer, and both end on --ground. */
.close-fade{
  position:absolute;left:0;right:0;z-index:0;
  display:block;pointer-events:none;
  height:clamp(108px,15vw,208px);
}
.close-fade-top{
  top:0;
  background:linear-gradient(to bottom,var(--ground) 0%,rgba(7,18,25,.82) 32%,rgba(7,18,25,0) 100%);
}
.close-fade-bottom{
  bottom:0;
  background:linear-gradient(to top,var(--ground) 0%,rgba(7,18,25,.82) 32%,rgba(7,18,25,0) 100%);
}

/* ── The content ───────────────────────────────────────────────────────
   Centred, and NOT `.cta-band-inner`: that class is a left-aligned flex
   row with a lit left edge and a panel behind it. This band has no panel
   at all — the point of the reference is text standing directly in the
   light. `.watermark > .container` already lifts it to z-index 1. */
.close-inner{
  position:relative;z-index:1;
  display:flex;flex-direction:column;align-items:center;text-align:center;
}
/* `.eyebrow,.eyebrow-pill{align-self:flex-start}` (~line 1668) is right
   everywhere else on the site, where an eyebrow sits above a left-aligned
   heading. It is the one rule a centred column has to undo — without this
   the pill sat hard against the left gutter while the headline under it
   was perfectly centred, which looked like a bug because it was one. */
.close-inner > .eyebrow,
.close-inner > .eyebrow-pill{align-self:center;justify-content:center}
.close-h2{
  font-size:clamp(36px,6vw,84px);
  letter-spacing:-.045em;line-height:.95;
  max-width:19ch;margin:20px auto 0;
}
.close-lead{
  max-width:54ch;margin:16px auto 0;
  font-size:clamp(15.5px,1.25vw,18px);line-height:1.7;
}
.close-actions{
  display:flex;align-items:center;justify-content:center;flex-wrap:wrap;
  gap:clamp(14px,1.7vw,24px);
  margin-top:clamp(28px,3.4vw,46px);
}
/* The diagonal arrow — the reference's ArrowUpRight, drawn inline rather
   than pulled in with an icon package. It leans further out as the button
   is approached, which is the entire animation. */
.arrow-ur{flex:none;transition:transform var(--m-quick) var(--e-glide)}
.close-actions a:hover .arrow-ur{transform:translate(2px,-2px)}

/* The call button as glass. Its own rules fill it solid cyan on hover,
   which would put out the glass, so hover here is a lift in brightness
   and a ring of light instead — scoped to this band only, so the call
   button everywhere else on the site behaves exactly as it always has. */
.cta-close .btn-call.liquid-glass-strong{
  color:#fff;
  padding:16px 32px;
}
.cta-close .btn-call.liquid-glass-strong:hover{
  background-color:rgba(18,196,222,.16);
  color:#fff;
  box-shadow:
    var(--glass-strong-shadow),
    0 0 0 1px rgba(157,239,251,.30),
    0 22px 60px -22px rgba(18,196,222,.75);
  transform:translateY(-3px);
}
.cta-close .btn.primary{padding:16px 32px}

/* On a phone the two buttons stack and each takes the full width, which
   is the only arrangement where a 44px touch target survives. */
@media (max-width:560px){
  .close-actions{flex-direction:column;align-items:stretch;width:100%}
  .close-actions > a{width:100%}
}
/* One ribbon on a phone, less blur: two 130%-wide blurred layers turning
   at once is the single most expensive thing on this page. */
@media (max-width:760px){
  .silk-2{display:none}
  .silk-1{filter:blur(50px);width:180%;left:-40%}
  /* And a shallower backdrop on the button. A 50px backdrop-filter has to
     re-blur whatever is behind it on every frame, and what is behind it
     here is a moving ribbon — the one combination that is genuinely
     expensive on a mid-range phone. At 22px it still reads as glass and
     the phone stops working for it. */
  .liquid-glass-strong{--glass-strong-blur:blur(22px) saturate(150%)}
}

/* ── The footer, welded on ─────────────────────────────────────────────
   In the reference the footer is not a separate component; it is the
   bottom of the same section. Here it stays a real <footer> — the year
   script, paintFooterWhatsapp() and the whole footer-grid depend on it —
   and is joined instead: the same ground, no seam, no gap.

   `.footer-close` is on index.html only. Every other page keeps the
   footer it has, including its lit top hairline. */
.footer-close{
  padding-top:clamp(46px,5vw,72px);
  /* Starts on exactly the colour the fade above it ends on, so the join
     is invisible. The shared dark-band background starts at #0B1B24,
     which is lighter than --ground and drew a visible band across the
     seam. */
  background-image:
    var(--grain-soft),
    radial-gradient(900px 520px at 50% 118%, rgba(18,196,222,.13), transparent 62%),
    linear-gradient(180deg,#071219 0%,#050E14 100%);
  background-size:160px 160px, auto, auto;
  background-repeat:repeat, no-repeat, no-repeat;
}
/* No hairline at the top: there is nothing to divide. The glow the owner
   asked for moves down to the line that still separates something — the
   rule above the copyright bar. */
.footer-close::before{display:none}
.footer-close .footer-bottom{
  position:relative;border-top:0;
  padding-top:24px;
}
.footer-close .footer-bottom::before{
  content:"";position:absolute;left:0;right:0;top:0;height:1px;
  pointer-events:none;
  background:var(--grad-line);
  box-shadow:0 0 18px rgba(18,196,222,.45);
}
/* Column on a phone, row from tablet up — the reference's
   `flex-col md:flex-row`, written as the one media query it actually is. */
@media (max-width:760px){
  .footer-close .footer-bottom{
    flex-direction:column;align-items:center;text-align:center;gap:10px;
  }
}

/* ── OFF ─────────────────────────────────────────────────────────────
   Named, as always. Under reduced motion the ribbon stops on a frame
   that is still a composition rather than snapping to zero degrees. */
@media (prefers-reduced-motion: reduce){
  .silk{animation:none;transform:rotate(24deg)}
  .arrow-ur{transition:none}
  .liquid-glass-strong::before{transition:none}
}
@media print{
  .close-bg,.close-fade{display:none !important}
  .liquid-glass-strong{
    background:#fff !important;backdrop-filter:none !important;
    border:1px solid #ccc !important;box-shadow:none !important;
  }
  .liquid-glass-strong::before{display:none !important}
}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: STAGE — the floating HUD header and the full-screen hero
   ═══════════════════════════════════════════════════════════════════════
   Two things the brief asked for that this site did not have: a navbar
   that floats over the hero as a glass HUD, and a hero that fills the
   screen.

   Both are scoped to `body[data-page="home"]` and to viewports wider than
   the mobile-menu breakpoint (760px). That is not caution for its own
   sake:

     · A floating HUD over a WHITE page is wrong — it has nothing to float
       over and reads as a bar that forgot to attach. Products, gallery
       and contact keep the header they have.
     · The mobile menu is `position:absolute; top:100%` on the header. Turn
       the header into a centred capsule and the dropdown detaches from
       the screen edges. Below 760px the header stays exactly as it is.

   WHAT IS NOT HERE, deliberately: the brief asks for an "authentication
   CTA" on the right of the navbar. This site has no public login, and the
   owner's own standing rule is that no admin path appears on a customer
   page. So the capsule is brand-left / nav-right — the arrangement a
   premium site uses when it has no account button — rather than a fake
   one invented to fill the space.
   ═══════════════════════════════════════════════════════════════════════ */

@media (min-width:761px){

  /* ── The HUD ─────────────────────────────────────────────────────────
     `fixed`, not `sticky`: sticky still occupies 76px at the top of the
     flow, and the whole point is that the hero starts at the top of the
     screen and the header floats ON it.

     Note what is NOT redeclared below. This header is already a dark rail
     on every page of this site — white brand, #A9C6D1 links, cyan accent,
     documented near line 2232 as a deliberate choice. The first version of
     this layer forgot that, painted the scrolled state white, and left
     white text on a white bar: "InCode" and the active link simply
     vanished. The HUD therefore changes the header's SHAPE, not its
     colours, and the scrolled state hands the shape straight back. */
  body[data-page="home"] .site-header{
    position:fixed;left:0;right:0;top:0;
    transition:padding-top var(--m-base) var(--e-glide);
  }
  /* The header itself paints nothing on the home page, in EITHER state.
     `backdrop-filter` is the reason: it does not interpolate, so leaving
     the blur on the header meant the whole strip snapped from clear to
     frosted the instant the class landed. The rail is drawn on
     `::before` instead and cross-faded. */
  body[data-page="home"] .site-header{
    background:transparent;
    border-bottom-color:transparent;
    box-shadow:none;
    backdrop-filter:none;-webkit-backdrop-filter:none;
  }
  body[data-page="home"] .site-header:not(.is-stuck){
    padding-top:clamp(12px,1.5vw,20px);
  }
  body[data-page="home"] .site-header::before{
    content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
    background:rgba(4,12,17,.96);
    backdrop-filter:blur(30px) saturate(200%);
    -webkit-backdrop-filter:blur(30px) saturate(200%);
    border-bottom:1px solid rgba(127,230,245,.16);
    box-shadow:
      inset 0 1px 0 rgba(190,240,250,.16),
      inset 0 -1px 0 rgba(0,0,0,.6),
      0 30px 70px -40px rgba(0,0,0,.75);
    opacity:0;
    transition:opacity var(--m-epic) var(--e-glide);
  }
  body[data-page="home"] .site-header.is-stuck::before{opacity:1}
  body[data-page="home"] .site-header::after{opacity:0}
  body[data-page="home"] .site-header.is-stuck::after{opacity:.9}

  /* GEOMETRY on the element, PAINT on a pseudo-element. That split is the
     whole reason this transition works in both directions.

     A gradient background does not interpolate: going from the capsule's
     two-layer gradient to no background at all is a hard cut, and a hard
     cut is exactly what the owner saw scrolling down. Width, padding and
     radius interpolate perfectly, so they stay on `.nav`; the fill, the
     rim, the blur and the shadow move to `.nav::before`, whose OPACITY
     interpolates. The capsule now opens out and dissolves at the same
     speed, and it does the same thing in reverse on the way back up. */
  body[data-page="home"] .site-header .nav{
    position:relative;
    transition:
      width var(--m-epic) var(--e-glide),
      padding var(--m-epic) var(--e-glide),
      min-height var(--m-epic) var(--e-glide),
      border-radius var(--m-epic) var(--e-glide);
  }
  body[data-page="home"] .site-header .nav::before{
    content:"";position:absolute;inset:0;z-index:-1;
    border-radius:inherit;
    border:1.4px solid transparent;
    background:
      linear-gradient(rgba(8,24,32,.62),rgba(8,24,32,.62)) padding-box,
      linear-gradient(180deg,
        rgba(190,240,250,.55) 0%,
        rgba(18,196,222,.30) 18%,
        rgba(18,196,222,.05) 42%,
        rgba(18,196,222,.05) 58%,
        rgba(18,196,222,.26) 82%,
        rgba(190,240,250,.50) 100%) border-box;
    backdrop-filter:blur(28px) saturate(180%);
    -webkit-backdrop-filter:blur(28px) saturate(180%);
    box-shadow:
      inset 0 1px 0 rgba(190,240,250,.18),
      0 24px 54px -32px rgba(0,0,0,.95);
    opacity:1;pointer-events:none;
    transition:opacity var(--m-epic) var(--e-glide);
  }
  body[data-page="home"] .site-header.is-stuck .nav::before{opacity:0}

  body[data-page="home"] .site-header:not(.is-stuck) .nav{
    width:min(940px,calc(100% - 2*var(--gut)));
    margin-inline:auto;
    height:auto;min-height:60px;
    padding:8px 12px 8px 26px;
    border-radius:999px;
  }
  /* The scrolled geometry is written out rather than left to fall back to
     `.container`, because a transition needs two stated ends to run
     between. Left implicit, the capsule snapped. */
  body[data-page="home"] .site-header.is-stuck .nav{
    width:min(var(--shell),100%);
    margin-inline:auto;
    min-height:82px;
    padding:0 var(--gut);
    border-radius:0;
  }
  /* The active page is marked in the HUD the way a HUD marks things — a
     lit pill, not an underline that disappears against the video. */
  body[data-page="home"] .site-header:not(.is-stuck) nav a[aria-current]{
    color:#02171C;background:linear-gradient(180deg,#4EE2F5,#12C4DE);
    box-shadow:0 8px 24px -12px rgba(18,196,222,.9);
  }
  body[data-page="home"] .site-header:not(.is-stuck) nav a[aria-current]::after{opacity:0}

  /* Scrolled: the capsule opens back out into the dark rail this site
     already has on every other page. Nothing is restyled here — the
     padding goes to zero and every rule above line 2260 takes over again,
     which is why the two states cannot drift apart. */
  body[data-page="home"] .site-header.is-stuck{padding-top:0}
}

/* ── The hero, full screen ──────────────────────────────────────────── */
body[data-page="home"] .hero{
  /* svh, not vh. On a phone `100vh` is the viewport WITHOUT the address
     bar, so a 100vh hero is taller than the screen and its bottom is cut
     off until you scroll. `svh` is the small viewport — the one that is
     actually visible. The vh line is the fallback for browsers that do
     not know svh. */
  min-height:100vh;
  min-height:100svh;
  display:grid;align-items:center;
  padding-top:clamp(104px,13vh,168px);
  padding-bottom:clamp(84px,10vh,132px);
}
@media (max-width:760px){
  /* A full-screen hero on a phone pushes every product below two screens
     of scrolling. 88svh leaves the next section peeking, which is what
     tells a thumb there is more. */
  body[data-page="home"] .hero{
    min-height:88svh;
    padding-top:clamp(48px,10vh,74px);
    padding-bottom:clamp(56px,9vh,86px);
  }
}

/* ── The video slot ──────────────────────────────────────────────────
   The CSS scene (orbs, grid, sweep, rail, the printing label card) is NOT
   replaced by the video — it stays underneath. That means the hero is
   complete and cinematic with no video at all, and a clip is an addition
   rather than a dependency: if the file is missing, slow, blocked, or the
   visitor is on a data saver, nothing is broken and nothing is empty.

   `poster` is what LCP measures, so it must exist. `preload="none"` keeps
   the clip off the critical path entirely. */
.hero-video{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center;
  z-index:-1;pointer-events:none;
  opacity:0;transition:opacity 1.2s var(--e-glide);
}
.hero-video[data-ready]{opacity:.55}
/* A wash over the clip, so white headline type keeps its contrast no
   matter what frame is showing. Without this a hero video is a legibility
   accident waiting for the wrong frame. */
.hero-video-veil{
  position:absolute;inset:0;z-index:-1;display:block;pointer-events:none;
  background:
    linear-gradient(90deg,rgba(7,18,25,.92) 0%,rgba(7,18,25,.72) 38%,rgba(7,18,25,.30) 72%,rgba(7,18,25,.55) 100%),
    linear-gradient(180deg,rgba(7,18,25,.85) 0%,transparent 26%,transparent 62%,rgba(7,18,25,.95) 100%);
}
@media (max-width:760px){
  /* No video on a phone. A 2–4 MB autoplaying clip on mobile data, to
     decode on a mid-range phone, behind text that now covers most of it —
     the cost is real and the benefit is nearly zero. The poster frame
     stays, so the composition is intact. */
  .hero-video{display:none}
}
@media (prefers-reduced-motion: reduce){
  .hero-video{display:none}
}

/* ── The scroll cue ──────────────────────────────────────────────────
   A full-screen hero hides the rest of the page, so it has to say that
   there IS a rest of the page. It is a real link to the products section,
   not a decorative chevron. */
.hero-cue{
  position:absolute;left:50%;bottom:clamp(18px,3.4vh,34px);
  transform:translateX(-50%);
  display:inline-flex;flex-direction:column;align-items:center;gap:7px;
  padding:8px 14px 10px;border-radius:999px;
  color:var(--ink-3);
  transition:color var(--m-quick) var(--e-glide),transform var(--m-quick) var(--e-glide);
}
.hero-cue:hover{color:var(--cyan-bright)}
.hero-cue .cue-rail{
  display:block;width:1px;height:34px;
  background:linear-gradient(180deg,transparent,rgba(127,230,245,.75));
  position:relative;overflow:hidden;
}
.hero-cue .cue-rail::after{
  content:"";position:absolute;left:0;top:-40%;width:1px;height:40%;
  background:linear-gradient(180deg,transparent,#9DEFFB);
  animation:cueRun 2.6s var(--e-glide) infinite;
}
@keyframes cueRun{
  0%{transform:translateY(0);opacity:0}
  25%{opacity:1}
  100%{transform:translateY(340%);opacity:0}
}
.hero-cue svg{opacity:.8}
@media (max-width:760px){ .hero-cue{display:none} }
@media (prefers-reduced-motion: reduce){ .hero-cue .cue-rail::after{animation:none} }

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: EDITORIAL — the product page reads like a spread, not a form
   ═══════════════════════════════════════════════════════════════════════
   This is the page a customer actually converts on, and the brief asked
   for it to be editorial: one large picture, a strong name, then price,
   description and specifications in a clear typographic order.

   Everything here is CSS. `renderProductDetail()` in app.js is untouched —
   same template, same ids, same events, same order panel, same sticky
   order bar. What changes is the rhythm.
   ═══════════════════════════════════════════════════════════════════════ */

@media (min-width:1001px){
  /* The picture is the constant. A product page is read by scrolling the
     TEXT — specs, description, price — and an image that scrolls away with
     it makes the reader hold the product in their head. Stick it. */
  .product-detail-grid{
    grid-template-columns:1.05fr .95fr;
    align-items:start;
  }
  .product-detail-image{
    position:sticky;
    /* Clear of the header rail, which is 82px on this build. */
    top:calc(82px + 26px);
  }
}

/* ── The name ────────────────────────────────────────────────────────
   Larger and tighter, and the stock chip stops competing with it. The
   chip is inside the <h1> in the template — moving it out would mean
   editing app.js, so instead it is set as what it actually is: a
   superscript tag on the name, not a second headline. */
.product-detail-grid h1{
  font-size:clamp(30px,3.5vw,52px);
  letter-spacing:-.038em;line-height:1.02;
  max-width:17ch;
}
.product-detail-grid h1 .stock-badge{
  vertical-align:.42em;
  transform:scale(.86);transform-origin:left center;
  margin-left:2px;
}

/* ── The specification rail ──────────────────────────────────────────
   Chips floating loose under a headline read as tags. Ruled top and
   bottom with the chips spaced along it, the same chips read as a
   specification strip — which is what they are. */
.product-detail-grid .spec-chips{
  margin:22px 0 0;padding:14px 0;
  border-top:1px solid var(--line);
  border-bottom:1px solid var(--line);
  gap:9px;
}

/* ── The description ─────────────────────────────────────────────────
   It was inside a bordered, tinted card. A card says "this is a separate
   object"; a description is the page's body copy. So the chrome comes off
   and what is left is a measure narrow enough to read and a label above
   it. `.detail-desc` picks up card styling from two earlier layers, which
   is why the border, the fill and the radius all have to be named. */
.product-detail-grid .detail-desc{
  background:none;
  border:0;border-radius:0;
  box-shadow:none;
  backdrop-filter:none;-webkit-backdrop-filter:none;
  padding:26px 0 0;
  max-width:62ch;
}
.product-detail-grid .detail-desc .verbatim{
  font-size:16px;line-height:1.85;color:var(--ink-2);
}
.product-detail-grid .detail-desc-head{
  margin-bottom:12px;
}

/* ── The order panel ─────────────────────────────────────────────────
   It stays a dark card — that is the brand's order moment and the one
   place on a white page that should feel like a decision. What changes is
   that it stops shouting: the label and the figure share a baseline
   instead of stacking into a poster, and the buttons get room. */
.product-detail-grid .order-panel{
  margin-top:34px;
  padding:clamp(24px,2.4vw,32px);
}
.product-detail-grid .order-label{
  margin-bottom:6px;
}
.product-detail-grid .order-price{
  font-size:clamp(30px,3.2vw,42px);
  letter-spacing:-.03em;line-height:1.05;
}
.product-detail-grid .order-actions{
  margin-top:24px;gap:11px;
}
/* On a narrow column two side-by-side buttons clip their labels long
   before the viewport is narrow. Container query, so the decision is made
   by the panel's own width rather than the screen's. */
@container (max-width:420px){
  .product-detail-grid .order-actions .btn-call,
  .product-detail-grid .order-actions .btn-whatsapp{flex:1 1 100%}
}
.product-detail-grid .order-panel{container-type:inline-size}

/* ── The plate ───────────────────────────────────────────────────────
   One large picture, given the room the brief asked for. The corner
   braces on ::before/::after stay — they are the registration marks that
   tie this page to the rest of the site. */
.product-detail-image{
  aspect-ratio:1/1;
  padding:clamp(22px,2.6vw,40px);
}
@media (max-width:1000px){
  .product-detail-image{aspect-ratio:4/3}
}

/* Two things the sticky column needed, found by measuring rather than by
   reading — both invisible in the CSS above.

   1. `.section[data-page="product-detail"]` sets `overflow:hidden` (line
      ~2031) to contain its atmosphere layers. `overflow:hidden` creates a
      scroll container, and `position:sticky` inside a scroll container
      sticks to THAT box, not to the viewport — so the sticky image simply
      scrolled away. `overflow:clip` clips exactly the same way without
      creating one. Guarded, because the fallback has to stay `hidden`
      rather than become `visible`. */
@supports (overflow:clip){
  .section[data-page="product-detail"]{overflow:clip}
}

/*  2. `.detail-desc::before` paints a cyan radial wash — the "card under
      glass" the earlier layer describes. With the card chrome removed the
      wash was left behind as a floating tinted rectangle with nothing
      around it. */
.product-detail-grid .detail-desc::before{display:none}

/* ── The hero, composed for a phone ──────────────────────────────────
   Measured before it was written. At 390px the hero came out 1261px tall
   against an 844px screen — one and a half screens of hero before a
   visitor sees a single product. The parts, in order of how much room
   they were taking:

     trust chips   187px   three pills, each on its own line
     h1            204px
     label card    240px
     actions       129px
     lead          123px
     eyebrow        60px   the label wraps to two lines at this width
     padding       150px
     row gap        46px

   None of that is wrong on a desktop. On a phone it is a desktop hero
   with the columns stacked, which is exactly what the brief said not to
   ship. So each part is re-sized for the width it is actually being read
   at, rather than inheriting a size chosen for 1440px. */
@media (max-width:760px){
  body[data-page="home"] .hero{padding-top:clamp(38px,7vh,58px);padding-bottom:clamp(40px,6.5vh,62px)}
  .hero-grid{gap:26px}

  /* The eyebrow is a 34-character line of tracked-out mono. At 390px it
     wraps, and a two-line eyebrow above a headline reads as a paragraph. */
  .hero .eyebrow-pill{font-size:9.5px;letter-spacing:.13em;padding:6px 12px}

  .hero h1{font-size:clamp(29px,8.4vw,39px);letter-spacing:-.034em;margin-top:16px}
  .hero .lead{font-size:15px;line-height:1.65;margin-top:12px}
  .hero-actions{margin-top:20px;gap:10px}

  /* Three pills on three lines is a list. Three pills on one or two lines
     is a row of credentials, which is what they are. */
  /* `.trust span` is a DESCENDANT selector, and the markup nests a span
     inside each chip — so the pill treatment was being applied twice, one
     pill drawn inside another, and each chip came out ~300px wide. Three
     of those cannot share a 390px line, which is why they stacked. The
     outer span keeps the pill; the inner one goes back to being text. */
  .trust{gap:6px;margin-top:20px;padding-top:14px}
  .trust > span{padding:5px 9px;font-size:9.5px;letter-spacing:.08em;gap:5px}
  .trust > span > span{
    padding:0;background:none;border:0;border-radius:0;
    backdrop-filter:none;-webkit-backdrop-filter:none;
    font-size:inherit;letter-spacing:inherit;
  }
  .trust svg{width:11px;height:11px}

  /* The specimen card is the hook, but at 240px it was a second screen of
     its own. The barcode is the part that has to stay legible; the frame
     around it is what gives way. */
  .label-demo{padding:18px 18px 16px;border-radius:var(--radius-lg)}
  .barcode-real{height:74px;margin:14px 0 12px}
}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: SLOW — the header changes state in slow motion, everywhere
   ═══════════════════════════════════════════════════════════════════════
   The owner's words: scrolling DOWN the header changed "hut kore" — all at
   once — and scrolling up it was smooth. He is right, and it was one
   property doing it.

   `.site-header` declares its transition near the top of this file
   (background, border-color, box-shadow at .4s). Two later layers then
   redefine the header's background and shadow but not its transition, so
   that .4s was still in force — a quarter-second is fast enough to read as
   a snap when the colour change is large. And on the home page the STAGE
   layer replaced the transition list outright with `padding-top` alone, so
   the background had no transition at all going down.

   Both are fixed the same way: one transition, every property that
   actually differs between the two states, at --m-epic (1.1s), on every
   page. Symmetric by construction — the same declaration runs the change
   in both directions, so up and down cannot drift apart again.

   The seam gets one too, because `.site-header::after` is a lit hairline
   that was appearing instantly under a bar that was still fading in.      */
.site-header{
  transition:
    background-color var(--m-epic) var(--e-glide),
    border-bottom-color var(--m-epic) var(--e-glide),
    box-shadow var(--m-epic) var(--e-glide),
    padding-top var(--m-epic) var(--e-glide);
}
.site-header::after{transition:opacity var(--m-epic) var(--e-glide)}

/* The scroll rail across the top of the page is the other thing that
   moves on every scroll; at .4s it lagged the header it sits on. */
.scroll-rail i{transition:width var(--m-base) linear}

@media (prefers-reduced-motion: reduce){
  .site-header,.site-header::before,.site-header::after,
  .site-header .nav,.site-header .nav::before{transition:none}
}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: RESPONSE — the cards answer the pointer, all of them
   ═══════════════════════════════════════════════════════════════════════ */

/* THE bug, and it is a good one. The owner reported that only the FIRST
   product card animated on hover; the rest did nothing.

   Nothing was wrong with the hover. `.stagger > .reveal:nth-child(n)` sets
   a `transition-delay` so a grid arrives one card after another — 0ms,
   70ms, 140ms … 490ms. `transition-delay` is not scoped to the transition
   that needed it: it applies to EVERY transition on that element, forever.
   So card 1 (0ms) responded instantly and card 8 sat still for half a
   second after the pointer arrived, which is indistinguishable from being
   broken.

   The delay is only ever wanted for the entrance. Cancelling it while the
   pointer is on the card leaves the entrance staggered and makes every
   card answer the moment it is touched. */
.stagger > .reveal:hover,
.stagger > .reveal:focus-within{transition-delay:0ms}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: NO GLINT — nothing crosses a product photograph. Again.
   ═══════════════════════════════════════════════════════════════════════
   The white band travelling diagonally across the product page's picture
   every seven seconds — the "white glitch". It is `glintSweep` on
   `.product-detail-image::before` (~line 2471): a 35%-white diagonal
   gradient in `mix-blend-mode:overlay`, animated across the plate.

   The CLEAN PLATE layer switched off the four films on the product CARD
   and missed this one, because it lives on the product PAGE and is
   painted by a pseudo-element whose other jobs had already been retired
   further down the file (~line 3127 strips its border, size and shadow).
   That leaves the glint as the pseudo-element's only remaining content,
   so it can go entirely rather than be neutralised property by property.

   The rule the owner set stands: a product photograph is a colour sample,
   and nothing lies on top of it. */
.product-detail-image::before{display:none}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: FILM — the hero video the owner shot
   ═══════════════════════════════════════════════════════════════════════
   369 KB for ten seconds of 720p, because the frame is nearly black. That
   is small enough that it does not need to be switched off on a phone —
   the whole rest of the home page is 103 KB, so this is not the usual
   "hero video costs more than the site" trade.

   It sits UNDER the CSS scene, not instead of it. The orbs, the grid, the
   sweep and the data rail still run on top, which is what ties the film
   to the rest of the site rather than leaving it looking pasted in.     */
.hero-video[data-ready]{opacity:.82}

/* The veil is what keeps white type readable over a moving picture. The
   film's subject — the hand and the label — sits centre-right, and the
   headline sits left, so the veil is heaviest on the left and lets the
   right side through. */
.hero-video-veil{
  background:
    linear-gradient(90deg,
      rgba(7,18,25,.94) 0%,
      rgba(7,18,25,.80) 34%,
      rgba(7,18,25,.34) 66%,
      rgba(7,18,25,.20) 82%,
      rgba(7,18,25,.55) 100%),
    linear-gradient(180deg,
      rgba(7,18,25,.80) 0%,
      transparent 24%,
      transparent 58%,
      rgba(7,18,25,.96) 100%);
}
@media (max-width:760px){
  /* On a phone the columns stack and the text runs the full width, so a
     left-weighted veil protects nothing. Vertical instead: dark where the
     words are, clear across the middle where the label floats. */
  .hero-video{display:block}
  .hero-video[data-ready]{opacity:.66}
  .hero-video-veil{
    background:linear-gradient(180deg,
      rgba(7,18,25,.93) 0%,
      rgba(7,18,25,.86) 42%,
      rgba(7,18,25,.55) 68%,
      rgba(7,18,25,.90) 100%);
  }
}
/* A film of a hand moving is motion, and the switch has to mean it. */
@media (prefers-reduced-motion: reduce){
  .hero-video{display:none}
}
@media print{
  .hero-video,.hero-video-veil{display:none !important}
}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: DRIFT — scroll is the transport, and the page floats
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. The boxes come off the film ──────────────────────────────────
   Two separate grids were drawn over the hero: `.hero-bg::before`, a
   78px perspective floor, and `.grid-lines`, a scan rhythm. Both were
   built to give an EMPTY dark stage a floor and a texture. There is a
   filmed room in there now, and drawing graph paper over a photograph of
   a hand is the fastest way to make a real thing look synthetic.

   The `.grid-lines` element is gone from the hero's markup; the floor is
   a pseudo-element, so it is switched off here. The closing CTA band
   keeps both — it has no film behind it. */
body[data-page="home"] .hero-bg::before{display:none}

/* ── 2. Scroll drives the hero ───────────────────────────────────────
   app.js writes `--hero-p`, 0 at the top of the hero and 1 as it leaves,
   from the same rAF that seeks the film. One number, so the picture and
   the words cannot drift out of step with each other.

   Everything below is written to be harmless at `--hero-p: 0` — which is
   what it stays at with JavaScript off, or under reduced motion. */
body[data-page="home"] .hero{--hero-p:0}

body[data-page="home"] .hero-grid > .reveal{
  /* Rises and thins out as the hero is scrolled away, so the reader is
     handing the screen over rather than pushing it off. */
  transform:translate3d(0, calc(var(--hero-p) * -70px), 0);
  opacity:calc(1 - var(--hero-p) * 1.25);
  will-change:transform,opacity;
}
body[data-page="home"] .hero-card{
  /* The specimen card leaves later and further — the parallax that makes
     the two columns read as different distances from the reader. */
  transform:translate3d(0, calc(var(--hero-p) * -128px), 0) scale(calc(1 - var(--hero-p) * .06));
  opacity:calc(1 - var(--hero-p) * 1.15);
  will-change:transform,opacity;
}
body[data-page="home"] .hero-video{
  /* The film pushes in very slightly across the same distance. A locked
     frame under moving text reads as a photograph; this reads as a shot. */
  transform:scale(calc(1 + var(--hero-p) * .07));
}
body[data-page="home"] .hero-cue{opacity:calc(1 - var(--hero-p) * 3)}

/* The scrubbed film is seeked, never played, so it must not be told to
   fade in on `loadeddata` and then sit at 82% over a headline it is
   competing with. Slightly quieter than the autoplay version. */
.hero-video[data-ready]{opacity:.78}

@media (prefers-reduced-motion: reduce){
  body[data-page="home"] .hero-grid > .reveal,
  body[data-page="home"] .hero-card,
  body[data-page="home"] .hero-video{transform:none;opacity:1}
}

/* ── 3. The whole home page floats ───────────────────────────────────
   The owner asked for the header's slow glide to be how EVERYTHING on the
   home page behaves: text, buttons and cards drifting up into place and
   drifting away again, rather than appearing once and then sitting still.

   Scroll-driven animation does this properly — the element's position in
   the viewport IS the timeline, so it comes in on the way down and goes
   out on the way back up, at the speed the reader is scrolling, with no
   observer, no listener and no state to get stuck in.

   It is behind @supports for a reason that matters here: where it is not
   supported the rules simply do not apply, and the existing IntersectionObserver
   reveal carries the page exactly as it does today. There is no path
   where an element is left parked at opacity 0 waiting for a feature the
   browser does not have. */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    /* A drift that does NOT touch `transform`, and the reason is a bug
       this layer shipped with.

       The first version animated `transform` directly. An animation beats
       every declaration, so it replaced the card families' transform —
       and on this site that transform is a contract:
       `perspective() translateY(--lift) rotateX(--rx) rotateY(--ry)`,
       written by the hover lift and by initTilt(). The moment this layer
       landed, hovering a feature card did nothing. That is the third time
       a transform has been silently overwritten in this file.

       `animation-composition: add` fixes the rendering but not the
       problem: the transform is then resolved at paint time and
       `getComputedStyle` no longer reports the whole of it, so nothing
       can verify the contract still holds.

       So the drift is a registered custom property instead. One animation
       moves `--drift`; the ONE transform declaration on the card consumes
       it alongside --lift, --rx and --ry. Nothing is overwritten, nothing
       is hidden from a test, and the contract is extended rather than
       fought. */
    @property --drift{ syntax:'<length>'; inherits:false; initial-value:0px }

    @keyframes floatFade{
      from{opacity:0}
      15%,85%{opacity:1}
      to{opacity:0}
    }
    @keyframes floatDrift{
      from{--drift:44px}
      15%,85%{--drift:0px}
      to{--drift:-30px}
    }
    /* Blocks with no transform of their own can move the ordinary way. */
    @keyframes floatMove{
      from{transform:translate3d(0,42px,0) scale(.985)}
      15%,85%{transform:none}
      to{transform:translate3d(0,-28px,0) scale(.99)}
    }

    /* No `animation-range`. The default — the whole time the element is
       anywhere in the viewport — is exactly right for keyframes that hold
       still between 15% and 85%: the block drifts in as it arrives, sits
       perfectly still for the whole time it is being read, and drifts out
       as it leaves.

       The first version narrowed the range to `entry 8% cover 46%`, which
       put the END of the animation in the middle of the screen — so every
       card reached its exit keyframe, opacity 0, while still sitting in
       plain sight. Cards vanished as you scrolled past them.

       Only the home page, and only below the hero — the hero has its own
       scroll choreography above and must not be animated twice. */
    body[data-page="home"] main > .section .section-head,
    body[data-page="home"] main > .section .cta-band-inner,
    body[data-page="home"] main > .section .close-inner{
      animation:floatFade linear both, floatMove linear both;
      animation-timeline:view(), view();
    }
    body[data-page="home"] main > .section .feature-card,
    body[data-page="home"] main > .section .category-card{
      animation:floatFade linear both, floatDrift linear both;
      animation-timeline:view(), view();
    }
    /* Where this is running, the observer's own reveal must stop fighting
       it: two opacities on one element is a flicker. */
    body[data-page="home"] main > .section .reveal{
      opacity:1;filter:none;transition:none;
    }
    /* The card families keep ONE transform declaration — the shared lift
       and tilt contract, with the scroll drift added into the same
       translate. */
    body[data-page="home"] main > .section .reveal.product-card,
    body[data-page="home"] main > .section .reveal.feature-card,
    body[data-page="home"] main > .section .reveal.category-card{
      transform:
        perspective(1100px)
        translateY(calc(var(--lift,0px) + var(--drift,0px)))
        rotateX(var(--rx,0deg))
        rotateY(var(--ry,0deg));
      transition:transform var(--m-base) var(--e-glide),box-shadow var(--m-base) var(--e-glide);
    }
  }
}

/* ── 4. Everything that can be touched, floats when touched ──────────
   The glass-and-lift language, applied to the things the owner listed:
   every button, every pill, every option on the home page. One rule set,
   so they cannot drift apart. */
body[data-page="home"] .btn,
body[data-page="home"] .btn-call,
body[data-page="home"] .btn-whatsapp,
body[data-page="home"] .card-call,
body[data-page="home"] .card-whatsapp,
body[data-page="home"] .tab-pill,
body[data-page="home"] .eyebrow-pill,
body[data-page="home"] .trust > span{
  transition:
    transform var(--m-slow) var(--e-glide),
    box-shadow var(--m-slow) var(--e-glide),
    background var(--m-base) var(--e-glide),
    border-color var(--m-base) var(--e-glide),
    color var(--m-base) var(--e-glide);
}
body[data-page="home"] .btn:hover,
body[data-page="home"] .btn-call:hover,
body[data-page="home"] .btn-whatsapp:hover,
body[data-page="home"] .tab-pill:hover,
body[data-page="home"] .eyebrow-pill:hover,
body[data-page="home"] .trust > span:hover{
  transform:translateY(-4px);
}
/* The trust chips are not links, so they lift a little and light their
   rim rather than pretending to be pressable. */
body[data-page="home"] .trust > span:hover{
  transform:translateY(-2px);
  border-color:rgba(127,230,245,.34);
  background:rgba(255,255,255,.09);
}
@media (prefers-reduced-motion: reduce){
  body[data-page="home"] .btn:hover,
  body[data-page="home"] .btn-call:hover,
  body[data-page="home"] .btn-whatsapp:hover,
  body[data-page="home"] .tab-pill:hover,
  body[data-page="home"] .eyebrow-pill:hover,
  body[data-page="home"] .trust > span:hover{transform:none}
}

/* ── 5. Two labels now, on purpose ───────────────────────────────────
   This used to stand the drawn specimen card down on a wide screen the
   moment the film actually started playing (app.js adds `has-hero-film`
   once a frame has decoded), on the theory that the film's own label was
   enough. The owner now wants the card ON WITH the film — see the
   "8b" rule further down, which keeps `.hero-card` visible and laid out
   beside the headline regardless of `.has-hero-film`. Nothing to do here
   any more; kept as a note so a future edit doesn't reintroduce the old
   `display:none` by accident. */

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: SEQUENCE — the hero's parts take turns, and the film is clean
   ═══════════════════════════════════════════════════════════════════════ */

/* `--hero-p` has to be a real number for the maths below to work. Left
   unregistered it is only a token, and `calc()` on it is fragile. */
@property --hero-p{ syntax:'<number>'; inherits:true; initial-value:0 }

/* ── 1. The blue cast comes off the film ─────────────────────────────
   The owner is right and there were three separate blue films stacked on
   that clip, none of them obvious on their own:

     · the video sat at 78% opacity, so the section's OWN background —
       which is two cyan radial gradients over blue-black ink — was
       showing through the picture;
     · the veil over it was mixed from rgba(7,18,25), the brand's
       blue-ink, not a neutral;
     · `.orb-cyan` and `.orb-cyan-soft`, two 70px-blurred cyan lamps
       written for an EMPTY stage, were still lit on top of a photograph.

   The film is now fully opaque, the scrim is neutral black and only
   where the words are, and the lamps are out while a film is playing.
   The clip's own cyan is the cyan; nothing is added to it. */
body[data-page="home"] .hero-video,
body[data-page="home"] .hero-video[data-ready]{opacity:1}

/* `display:none`, not `opacity:0`: both lamps carry a drifting keyframe
   animation of their own, and an animation beats a declaration — set to
   zero they simply kept breathing back up to 0.99. Taken out of the box
   there is nothing left to animate. */
body[data-page="home"].has-hero-film .hero-bg .orb,
body[data-page="home"].has-hero-film .hero-bg .hero-sweep{display:none}

body[data-page="home"] .hero-video-veil{
  /* Neutral, not blue-ink — and weighted to the left, where the headline
     is, so the right two-thirds of the frame stay as shot. */
  background:
    linear-gradient(90deg,
      rgba(0,0,0,.88) 0%,
      rgba(0,0,0,.72) 26%,
      rgba(0,0,0,.34) 52%,
      rgba(0,0,0,.06) 74%,
      rgba(0,0,0,.22) 100%),
    linear-gradient(180deg,
      rgba(0,0,0,.62) 0%,
      transparent 22%,
      transparent 66%,
      rgba(4,10,14,.92) 100%);
}
@media (max-width:760px){
  body[data-page="home"] .hero-video-veil{
    background:linear-gradient(180deg,
      rgba(0,0,0,.80) 0%,
      rgba(0,0,0,.74) 46%,
      rgba(0,0,0,.42) 70%,
      rgba(4,10,14,.90) 100%);
  }
}

/* ── 2. The hero's parts leave one at a time ─────────────────────────
   Before this they moved as one block — the whole column sliding up
   together. What the owner drew was five separate boxes: the pill, the
   headline, the line under it, the buttons, the chips. So each one gets
   its own slice of the same scroll: the pill goes first, the headline
   follows, then the line, then the buttons, then the chips — and
   scrolling back brings them in again in reverse.

   `--g` is that group's own 0→1 progress, carved out of `--hero-p`.
   Every group is at 0 while `--hero-p` is 0, which is what keeps the
   hero intact with JavaScript off. */
body[data-page="home"] .hero-grid > .reveal{
  transform:none;opacity:1;   /* the block no longer moves; its parts do */
}
body[data-page="home"] .hero-grid > .reveal:not(.hero-card) > *{
  --g:0;
  transform:translate3d(0, calc(var(--g) * -72px), 0);
  opacity:calc(1 - var(--g));
  will-change:transform,opacity;
}
/* The scroll position is the clock. Nothing may smooth or delay these two
   properties on the way to the screen, or the wheel and the picture come
   apart. Colour and glass still glide, because those answer a POINTER,
   which has no clock of its own. `.overture` is the one exception: while
   the page's entrance is still playing, that transition is the point. */
body[data-page="home"] .hero-grid > .reveal:not(.hero-card) > *:not(.overture),
body[data-page="home"] .hero-grid > .hero-card:not(.overture){
  transition-property:background,border-color,color,box-shadow;
  transition-duration:var(--m-base);
  transition-delay:0s;
}
/* Each of the five is written with the SAME ancestor chain as the reset
   above it. Written short — `.hero h1` — the reset out-ranks them by one
   class and pins every group at --g:0, which is a hero that does not move
   at all. Same chain, later in the file, so these win by order. */
body[data-page="home"] .hero-grid > .reveal:not(.hero-card) > .eyebrow-pill{
  --g:clamp(0, calc((var(--hero-p) - .02) / .16), 1);
}
body[data-page="home"] .hero-grid > .reveal:not(.hero-card) > h1{
  --g:clamp(0, calc((var(--hero-p) - .10) / .22), 1);
}
body[data-page="home"] .hero-grid > .reveal:not(.hero-card) > .lead{
  --g:clamp(0, calc((var(--hero-p) - .20) / .24), 1);
}
body[data-page="home"] .hero-grid > .reveal:not(.hero-card) > .hero-actions{
  --g:clamp(0, calc((var(--hero-p) - .30) / .26), 1);
}
body[data-page="home"] .hero-grid > .reveal:not(.hero-card) > .trust{
  --g:clamp(0, calc((var(--hero-p) - .40) / .28), 1);
}
/* The specimen card, when it is showing at all, goes last and furthest —
   the parallax that keeps the two columns at different distances.

   Written as `.hero-grid > .hero-card`, not plain `.hero-card`: the card
   is itself one of the `.reveal` blocks above, and the block rule that
   holds those still would otherwise out-rank this one and pin it. */
body[data-page="home"] .hero-grid > .hero-card{
  --g:clamp(0, calc((var(--hero-p) - .06) / .5), 1);
  transform:translate3d(0, calc(var(--g) * -120px), 0) scale(calc(1 - var(--g) * .06));
  opacity:calc(1 - var(--g));
  will-change:transform,opacity;
}
@media (prefers-reduced-motion: reduce){
  body[data-page="home"] .hero-grid > .reveal:not(.hero-card) > *,
  body[data-page="home"] .hero-grid > .hero-card{transform:none;opacity:1}
}

/* ── 2b. And on a phone it has to FIT that one screen ────────────────
   "The page stays exactly this size." On a 390×844 phone the hero was
   857px — thirteen over, so the section under it showed a sliver at rest
   and the whole sequence started already scrolled. The thirteen come off
   the three places with the most air in them, not off the type. */
@media (max-width:760px){
  body[data-page="home"] .hero{
    padding-top:clamp(44px,7vh,58px);
    padding-bottom:clamp(28px,4.6vh,44px);
  }
  body[data-page="home"] .hero-grid{gap:18px}
  body[data-page="home"] .hero .trust{margin-top:14px;padding-top:10px}
}

/* ── 3. The footer arrives too ───────────────────────────────────────
   "Reach the very end and the footer comes as well." It is the last
   thing on the page, so it gets the same drift as everything above it
   rather than being the one block that is simply already there. */
/* First, the thing that stopped it working. `footer` carries
   `overflow:hidden` (line ~1110) to clip its own corner glow — and
   `overflow:hidden` makes the footer a SCROLL CONTAINER. A `view()`
   timeline measures its subject against the nearest scroll container,
   so every element inside the footer was being measured against a box
   that never scrolls: the timeline froze at one value and the footer sat
   there, already arrived, at every scroll position.

   `overflow:clip` clips exactly the same pixels and creates no scroll
   container. Same bug, same fix, as the sticky product image. */
@supports (overflow:clip){
  footer{overflow:clip}
}
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    body[data-page="home"] .footer-grid,
    body[data-page="home"] .footer-bottom{
      animation:floatFade linear both, floatMove linear both;
      animation-timeline:view(), view();
    }
  }
}

/* ── 4. Glass and lift, on everything that can be touched ────────────
   Extended past the home page: the same slow float now answers a pointer
   on every card, chip, tab and link on the site, so one page does not
   feel different from the next. */
.feature-card,.category-card,.product-card,.gallery-card,.pf-stat,.inq-card,
.tab-pill,.eyebrow-pill,.breadcrumb,.spec-chips span,.chip-tag,
.site-header nav a,.footer-grid > div > a{
  transition:
    transform var(--m-slow) var(--e-glide),
    box-shadow var(--m-slow) var(--e-glide),
    background var(--m-base) var(--e-glide),
    border-color var(--m-base) var(--e-glide),
    color var(--m-base) var(--e-glide);
}
.tab-pill:hover,.breadcrumb:hover,.spec-chips span:hover{transform:translateY(-3px)}
.gallery-card:hover,.pf-stat:hover,.inq-card:hover{transform:translateY(-5px)}
@media (prefers-reduced-motion: reduce){
  .tab-pill:hover,.breadcrumb:hover,.spec-chips span:hover,
  .gallery-card:hover,.pf-stat:hover,.inq-card:hover{transform:none}
}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: FILM STAGE — the film is the ground, the page is what floats
   ═══════════════════════════════════════════════════════════════════════
   Written last, on purpose. Everything above it describes a site made of
   stacked bands that scroll past a header. This layer replaces that with
   one idea, stated by the owner in three sentences:

     · the film runs on EVERY page except the single-product page, and it
       never moves, never resizes and is never scaled — what the camera
       shot is what the screen shows;
     · the header is one capsule, in the same place, on every page, with
       the scroll line travelling around its rim instead of across the top
       of the window;
     · and on the home page the page itself does not move AT ALL. Scroll
       flies the words, the buttons and the footer away in 3D and brings
       the next set in from the opposite side, by exactly the same amount.

   Nothing above this line was deleted. Rules that no longer have anything
   to match (the two-column hero grid, the scrubbed-video sequence, the
   top scroll rail) simply stop matching, which is the safest way to
   retire a layer in a file this long: if any of this is ever taken out,
   the site underneath it is still whole.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. THE FILM ──────────────────────────────────────────────────────
   One fixed element, one video, painted behind the whole document.

   `position:fixed` and NOT inside any section, so no amount of scrolling
   can move it: the clip occupies exactly the viewport, at exactly the
   same size, from the first pixel of the page to the last.

   THE RESOLUTION RULE. Nothing here scales, zooms, blurs or filters the
   picture. `object-fit:cover` is the only fit applied and it preserves
   the aspect ratio exactly — the frame is cropped to the window, never
   stretched and never enlarged by a transform. The earlier hero layer
   pushed the clip in with `scale(1 + p*.07)`; that is what softened it,
   and it is gone. `backface-visibility` and the translateZ(0) keep it on
   its own compositor layer so the scenes moving above it never force it
   to be re-rasterised. */
.film{
  position:fixed;inset:0;z-index:-3;
  overflow:hidden;pointer-events:none;
  background:#040c11;
  contain:layout paint style;
}
.film-video{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;object-position:center center;
  transform:translateZ(0);          /* layer promotion only — no scaling */
  backface-visibility:hidden;
  border:0;outline:0;
  pointer-events:none;
  /* Lifted from the flat, slightly dim source grade: brighter, a touch
     more contrast and colour so the film reads as premium footage rather
     than a dim phone clip — cheap for the GPU (one compositor-level
     filter, no extra paint) so it costs nothing on low-end devices. The
     veil right below is deepened to match, so text on top does not lose
     any of the legibility it had before this. */
  filter:brightness(1.16) contrast(1.06) saturate(1.12);
}
/* The scrim. It is what lets white type sit on a moving picture and stay
   readable at every frame, and it is the ONLY thing between the viewer
   and the film. Kept neutral black rather than the brand's blue ink, so
   the clip's own colour is the colour. */
.film-veil{
  position:absolute;inset:0;display:block;pointer-events:none;
  background:
    linear-gradient(90deg,
      rgba(0,0,0,.56) 0%,
      rgba(0,0,0,.46) 26%,
      rgba(0,0,0,.28) 52%,
      rgba(0,0,0,.14) 76%,
      rgba(0,0,0,.22) 100%),
    linear-gradient(180deg,
      rgba(0,0,0,.38) 0%,
      rgba(0,0,0,.02) 24%,
      rgba(0,0,0,.02) 64%,
      rgba(2,8,12,.60) 100%);
}
@media (max-width:900px){
  .film-veil{
    background:linear-gradient(180deg,
      rgba(0,0,0,.50) 0%,
      rgba(0,0,0,.40) 40%,
      rgba(0,0,0,.28) 66%,
      rgba(2,8,12,.60) 100%);
  }
}
/* THE SCRIM IS NOT ONE FIXED THING, because the page above it is not.

   The opening scene puts its words on the LEFT and the film's subject —
   the hand, the label — is on the right, so the scrim is heavy on the
   left and lets the right two-thirds through almost untouched. Every
   scene after it uses the full width, and the same left-weighted scrim
   would leave a feature card sitting on a bright frame with nothing
   between them.

   So there are two, cross-faded by `--veil-wide`, which the stage writes
   from its own progress: 0 while the opening is on screen, 1 by the time
   the second scene has arrived. Pages with no stage never set it and take
   the default, 1 — the even scrim, which is the right one for a page of
   cards and forms. */
.film-veil::after{
  content:"";position:absolute;inset:0;display:block;pointer-events:none;
  background:linear-gradient(90deg,
    rgba(0,0,0,.10) 0%,
    rgba(0,0,0,.22) 40%,
    rgba(0,0,0,.32) 100%);
  opacity:var(--veil-wide,1);
}
@media (max-width:900px){
  /* Stacked columns run the full width from the first scene, so a
     left-weighted scrim protects nothing — the base layer below is
     already vertical at this width and this one only deepens it. */
  .film-veil::after{
    background:linear-gradient(180deg,
      rgba(0,0,0,.07) 0%,
      rgba(0,0,0,.17) 46%,
      rgba(0,0,0,.22) 100%);
  }
}
/* A film of a hand moving is motion. The switch has to mean it — but the
   owner's rule is "hero te kono image jeno na thake": there is no poster,
   no jpg, nothing to fall back to. So reduced motion PAUSES the clip on a
   frame (app.js) rather than removing the picture. */
@media print{
  .film{display:none !important}
}

/* ── 2. THE HEADER, one capsule, everywhere ───────────────────────────
   Previously the capsule existed only on the home page and only until you
   scrolled, at which point it opened out into a full-width rail. The owner
   settled that: the capsule IS the header, on every page, in every scroll
   state. Scrolling changes its SIZE — a little smaller, a little tighter —
   and nothing else.

   `position:fixed`, so it floats on the film rather than pushing the page
   down; every page below gives itself the matching top clearance. */
.site-header{
  position:fixed;left:0;right:0;top:0;z-index:70;
  background:transparent;
  border-bottom:0;
  box-shadow:none;
  backdrop-filter:none;-webkit-backdrop-filter:none;
  padding-top:clamp(11px,1.5vw,20px);
  transition:padding-top var(--m-epic,1.1s) var(--e-glide,cubic-bezier(.16,1,.3,1));
}
.site-header::before,
.site-header::after{display:none}
.site-header.is-stuck{
  padding-top:clamp(7px,1vw,13px);
  background:transparent;
  border-bottom:0;
  box-shadow:none;
}

.site-header .nav{
  position:relative;
  display:flex;align-items:center;justify-content:space-between;gap:22px;
  width:min(1010px,calc(100% - 2*var(--gut)));
  margin-inline:auto;
  height:auto;min-height:62px;
  padding:8px 12px 8px 26px;
  border-radius:999px;
  transition:
    width var(--m-epic,1.1s) var(--e-glide,cubic-bezier(.16,1,.3,1)),
    min-height var(--m-epic,1.1s) var(--e-glide,cubic-bezier(.16,1,.3,1)),
    padding var(--m-epic,1.1s) var(--e-glide,cubic-bezier(.16,1,.3,1));
}
/* "just ektu halka boro hobe ar choto hobe" — the only thing scrolling
   does to this header. */
.site-header.is-stuck .nav{
  width:min(930px,calc(100% - 2*var(--gut)));
  min-height:54px;
  padding:6px 10px 6px 22px;
}

/* The glass. On ::before because a gradient background cannot interpolate
   and the capsule has to firm up smoothly as the page moves under it. */
.site-header .nav::before{
  content:"";position:absolute;inset:0;z-index:0;
  border-radius:inherit;
  border:1.4px solid transparent;
  background:
    linear-gradient(rgba(8,24,32,.60),rgba(6,19,26,.66)) padding-box,
    linear-gradient(180deg,
      rgba(190,240,250,.55) 0%,
      rgba(18,196,222,.30) 18%,
      rgba(18,196,222,.05) 42%,
      rgba(18,196,222,.05) 58%,
      rgba(18,196,222,.26) 82%,
      rgba(190,240,250,.50) 100%) border-box;
  backdrop-filter:blur(26px) saturate(180%);
  -webkit-backdrop-filter:blur(26px) saturate(180%);
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.18),
    0 24px 54px -32px rgba(0,0,0,.95);
  pointer-events:none;
  transition:background var(--m-epic,1.1s) var(--e-glide,cubic-bezier(.16,1,.3,1)),
             box-shadow var(--m-epic,1.1s) var(--e-glide,cubic-bezier(.16,1,.3,1));
}
.site-header.is-stuck .nav::before{
  background:
    linear-gradient(rgba(5,15,21,.80),rgba(4,12,17,.86)) padding-box,
    linear-gradient(180deg,
      rgba(190,240,250,.62) 0%,
      rgba(18,196,222,.34) 18%,
      rgba(18,196,222,.07) 42%,
      rgba(18,196,222,.07) 58%,
      rgba(18,196,222,.30) 82%,
      rgba(190,240,250,.58) 100%) border-box;
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.22),
    0 30px 66px -34px rgba(0,0,0,1);
}

/* The wordmark and the burger sit above the glass. The <nav> deliberately
   does NOT get a position here: below 760px it IS the dropdown menu, and
   the responsive rule further up positions it absolutely under the
   capsule. Pinning it `relative` from a later rule put it straight back
   into the flex row, stretched the capsule to 274px tall, and the ring —
   which is drawn from the capsule's measured box — came out as a circle
   the height of a phone's whole header. The ring is a 1.8px rim with
   pointer-events:none, so no link ever needs to be lifted over it. */
.site-header .brand,
.site-header .menu{position:relative;z-index:3}

/* The capsule is a dark rail on every page now, so the type is written for
   a dark rail on every page — no page-scoped exceptions left to drift. */
.site-header .brand{color:#fff}
.site-header .brand .accent{color:var(--cyan-bright,#12C4DE);text-shadow:0 0 22px rgba(18,196,222,.65)}
.site-header nav{display:flex;gap:2px;align-items:center}
.site-header nav a{color:#A9C6D1;border-radius:999px;padding:9px 15px}
.site-header nav a::after{
  background:#4EE2F5;box-shadow:0 0 10px rgba(78,226,245,.9);
  left:15px;right:15px;bottom:5px;
}
.site-header nav a:hover{color:#fff;background:rgba(127,230,245,.09)}
/* The page you are on is marked the way a HUD marks things — a lit pill,
   which survives being over a moving picture. An underline does not. */
.site-header nav a[aria-current]{
  color:#02171C;background:linear-gradient(180deg,#4EE2F5,#12C4DE);
  box-shadow:0 8px 24px -12px rgba(18,196,222,.9);
}
.site-header nav a[aria-current]::after{opacity:0}
.site-header .menu{
  border:1px solid rgba(127,230,245,.28);
  background:rgba(255,255,255,.06);
  color:#fff;
  border-radius:999px;width:42px;height:42px;
}

/* ── 3. THE SCROLL RING ───────────────────────────────────────────────
   The cyan filament used to run left-to-right across the very top of the
   window. The owner asked for it to run around the header instead, and
   that is exactly what it now does: one stroked rounded rectangle
   matching the capsule's own geometry, revealed by stroke-dashoffset from
   0% of the page to 100%.

   The geometry is written in CSS with SVG geometry properties, so the
   ring is correct at any capsule width with no script; app.js only
   measures the path once per resize to know how long the dash has to be.
   Before it does, the running stroke is invisible — a full bright ring on
   a page that has not been scrolled would be a lie. */
.scroll-rail{display:none !important}   /* the old top line, retired */

.nav-ring{
  position:absolute;inset:0;width:100%;height:100%;
  overflow:visible;pointer-events:none;z-index:2;
}
/* rx and ry are CLAMPED INDEPENDENTLY by SVG — rx to half the width, ry
   to half the height. So `rx:999px` on a 1010x62 capsule does not give a
   stadium, it gives rx:505 / ry:31: an enormous lens, which is exactly
   what it drew the first time. The radius has to be the same number in
   both axes, so it is written here as a sane default for the shipped
   capsule height and set exactly by app.js from the measured box. */
.nav-ring rect{
  x:.9px;y:.9px;
  width:calc(100% - 1.8px);height:calc(100% - 1.8px);
  rx:30px;ry:30px;
  fill:none;stroke-width:1.8;
  vector-effect:non-scaling-stroke;
}
.nav-ring .ring-track{stroke:rgba(127,230,245,.13)}
.nav-ring .ring-run{
  stroke:#5FE8F8;
  stroke-linecap:round;
  opacity:0;
  filter:drop-shadow(0 0 5px rgba(78,226,245,.95));
  transition:opacity .5s var(--e-glide,ease);
}
html.js .nav-ring .ring-run{opacity:1}

/* ── 4. FILMED PAGES ──────────────────────────────────────────────────
   `body.filmed` is on every page that carries the film. It does one thing
   and one thing only: it re-declares the dark token block that this file
   already uses for `.hero`, `.section.dark` and `footer`, at body level.

   That is the whole conversion. Cards, tabs, buttons, chips, inputs and
   hairlines were all written against these names in the first place, so
   they become their dark forms with nothing else touched — including
   `--plate`, which is deliberately NOT flipped here for the same reason
   it is not flipped in a dark band: a product photograph is a colour
   sample and its tray stays near-white in both modes. */
body.filmed{
  --paper:#0A1922;
  --soft:rgba(255,255,255,.055);
  --soft-2:rgba(255,255,255,.09);

  --ink:#FFFFFF;
  --ink-2:#A9C6D1;
  --ink-3:#8DAEBA;
  --muted:#7699A6;

  --line:rgba(127,230,245,.15);
  --line-strong:rgba(127,230,245,.32);

  --cyan:#12C4DE;
  --cyan-ink:#4EE2F5;
  --cyan-bright:#4EE2F5;
  --cyan-light:#9DEFFB;
  --cyan-wash:rgba(18,196,222,.12);

  --card-bg:linear-gradient(168deg,rgba(255,255,255,.075),rgba(255,255,255,.025));
  --card-bg-2:rgba(255,255,255,.05);
  --card-border:rgba(127,230,245,.15);
  --field-bg:rgba(255,255,255,.05);
  --chip-bg:rgba(255,255,255,.05);
  --glass:blur(14px);

  --btn-bg:linear-gradient(180deg,#4EE2F5,#12C4DE);
  --btn-fg:#02171C;
  --btn-border:rgba(157,239,251,.6);
  --btn-shadow:0 12px 38px -14px rgba(18,196,222,.75), 0 0 0 1px rgba(18,196,222,.3);
  --btn-shadow-hover:0 18px 54px -12px rgba(18,196,222,.9), 0 0 0 1px rgba(157,239,251,.55);

  --glow:0 0 60px -18px rgba(18,196,222,.5);
  --glow-ring:0 0 0 1px rgba(18,196,222,.28);
  --text-glow:0 0 22px rgba(18,196,222,.4);

  --shadow:0 1px 0 rgba(255,255,255,.05) inset, 0 20px 44px -28px rgba(0,0,0,.9);
  --shadow-lg:0 1px 0 rgba(255,255,255,.06) inset, 0 44px 96px -42px rgba(0,0,0,.95);
  --shadow-cyan:0 12px 44px -12px rgba(18,196,222,.6), 0 0 0 1px rgba(18,196,222,.35);
  --ring:0 0 0 3px rgba(18,196,222,.32);

  color:var(--ink-2);
}

/* The film sits at z-index -3, which paints AFTER the root background but
   BEFORE any in-flow background — so an opaque <body> would bury it. It
   must be transparent, and so must every band that used to paint the page
   white. */
html:has(body.filmed){background:#040c11}
body.filmed{background:transparent}
body.filmed main,
body.filmed .section,
body.filmed .section.soft,
body.filmed .page-hero{background:transparent;background-image:none}
/* The light grid-and-wash the white page hero used to draw. */
body.filmed .page-hero::before{display:none}
/* The dark bands stay bands — but translucent ones, so the film is still
   moving behind them instead of being switched off for 600px. */
body.filmed .section.dark,
body.filmed .section.dark.soft{
  background-color:rgba(4,12,17,.60);
  background-image:var(--grain-soft);
  background-size:160px 160px;
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
}
body.filmed footer{
  background-color:rgba(3,10,14,.74);
  background-image:var(--grain-soft);
  background-size:160px 160px;
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
}
/* A fixed header means the first band has to make its own room. */
body.filmed .page-hero{padding-top:clamp(116px,11.5vw,168px)}
body[data-page="product"] main > .section:first-child{padding-top:clamp(112px,10vw,150px)}

/* Panels that were transparent on white need a floor of their own now that
   a moving picture is underneath them. */
body.filmed .product-card,
body.filmed .category-card,
body.filmed .feature-card,
body.filmed .gallery-card,
body.filmed .pf-stat,
body.filmed .inq-card,
body.filmed .cta-band-inner{
  background-color:rgba(6,18,25,.52);
}
/* …and the photo tray stays a tray. */
body.filmed .product-image,
body.filmed .gallery-shot{
  background:linear-gradient(168deg,var(--plate) 0%,var(--plate-2) 100%);
}

/* ── 5. THE STAGE — the home page never moves ─────────────────────────
   `.stage-wrap` is tall; `.stage` is one screen and sticks to the top of
   it. That is what pins the page: the wrapper provides the scroll
   distance, the stage never leaves the top of the window, and the film
   behind it is fixed, so NOTHING the visitor can see changes position.
   All the scroll does is move a number, and the number flies the scenes.

   sticky rather than fixed on purpose — a fixed stage is outside the flow
   and the page then has to be told how tall it is in two places, which is
   how a pinned section ends up one screen short on a phone. */
.stage-wrap{
  position:relative;
  height:calc(var(--scenes,4) * 100vh);
  height:calc(var(--scenes,4) * 100svh);
}
.stage{
  position:sticky;top:0;
  height:100vh;height:100svh;
  overflow:hidden;
  /* Perspective is declared on BOTH boxes that directly parent a scene —
     `.stage` holds the footer scene, `.stage-deck` holds the other three.
     `perspective` only reaches an element's own children, and `.stage`
     has `overflow:hidden`, which forces its transform-style flat and so
     cannot pass a 3D context down through the deck. Two identical
     declarations is the whole fix, and it needs no preserve-3d anywhere:
     the scenes never have to intersect each other in space, only to be
     seen from the same eye. */
  perspective:1500px;
  perspective-origin:50% 46%;
}
.stage-deck{
  position:absolute;inset:0;
  perspective:1500px;
  perspective-origin:50% 46%;
  /* The deck is a frame, never a target. Only the one live scene inside
     it takes a pointer. */
  pointer-events:none;
}

/* A scene is one full screen of the page, stacked on all the others.
   `--t` is its own signed progress: 0 at rest, +1 fully gone, −1 not yet
   arrived. `--at` is its magnitude, because CSS has no portable abs().
   app.js writes both, once per frame, per scene — that part is unchanged.

   THE CHOREOGRAPHY. This used to be one rule for all four scenes: rise
   straight up and away, arrive straight in from below, every time. The
   owner asked for the opposite — each scene its own entrance and its own
   exit, so the page reads as four considered cuts, not one lever pulled
   four times.

   `--t` is split into two one-way dials that are never both above 0 at
   once: `--t-in` climbs 0→1 while a scene is still arriving, `--t-out`
   climbs 0→1 while it is on its way out. Each scene then supplies its
   own `--in-*` / `--out-*` vectors — how far to move on each axis and
   how far to turn, for that half of the journey only. A scene that only
   ever arrives (the footer, nothing comes after it) or only ever leaves
   (the opening, nothing comes before it) simply never drives the half it
   doesn't use; that dial sits at 0 for the whole visit and costs nothing.

   The defaults below are the ORIGINAL single path — straight up and
   away, straight in from below — so a scene that declares no vectors of
   its own still flies correctly. `.scene-hero`, `.scene-features` and
   `.scene-order` each override the handful of axes that make their leg
   of the trip its own; see "EACH SCENE'S OWN FLIGHT PATH" just below.
   Same budget of travel throughout — the numbers only change which axis
   they're spent on, never how much motion a scene gets. */
.scene{
  --t:0;--at:0;
  --t-in:max(calc(var(--t) * -1), 0);
  --t-out:max(var(--t), 0);
  --in-x:0%;   --in-y:42%;   --in-z:-260px;  --in-rx:18deg;  --in-ry:0deg;
  --out-x:0%;  --out-y:-42%; --out-z:-340px; --out-rx:-24deg;--out-ry:0deg;
  position:absolute;inset:0;
  display:grid;align-content:center;
  padding:clamp(96px,11vh,140px) 0 clamp(56px,8vh,96px);
  transform-origin:50% 50%;
  transform:
    translate3d(
      calc(var(--t-in) * var(--in-x) + var(--t-out) * var(--out-x)),
      calc(var(--t-in) * var(--in-y) + var(--t-out) * var(--out-y)),
      calc(var(--t-in) * var(--in-z) + var(--t-out) * var(--out-z))
    )
    rotateX(calc(var(--t-in) * var(--in-rx) + var(--t-out) * var(--out-rx)))
    rotateY(calc(var(--t-in) * var(--in-ry) + var(--t-out) * var(--out-ry)));
  /* ×1.4, not ×1: a scene has to be completely gone before the next one
     settles, or the previous headline ghosts over the new one at a few
     per cent for the rest of the travel. Distance and angle stay exact
     for whichever half of the trip is running; only the fade finishes
     early, at both ends. */
  opacity:calc(1 - var(--at) * 1.4);
  pointer-events:none;
  backface-visibility:hidden;
}
.scene[data-live="1"]{pointer-events:auto}
.scene[data-moving="1"]{will-change:transform,opacity}
/* A scene that has completely left is not clickable and not reachable by
   the keyboard — app.js sets `inert`, which is what takes it out of the
   accessibility tree. It is deliberately NOT `visibility:hidden`: this is
   the page's own copy, it has to stay indexable, and a layer that is
   already fully transparent costs the compositor nothing to skip. */

/* ── 5a. EACH SCENE'S OWN FLIGHT PATH ───────────────────────────────────
   Four cuts, not one line repeated four times:

     hero      exits only (nothing plays before it, --t is never negative
               here). The classic lift stays — rises, recedes, tilts back
               on X — with a new sideways drift and a slight turn on Y so
               it leaves like a card lifted off a table, not a flat panel
               sliding up.
     features  a horizontal pass. The four cards arrive travelling
               leftward across the frame and simply KEEP travelling left
               as they go — no reversal — with the turn on Y, like a door
               swinging past the camera rather than a nod of the head.
     order     the one full diagonal on the page: in from the lower-left
               corner, out through the upper-right. The widest turn here
               on purpose — the call to order is the moment that earns
               the most motion.
     footer    arrives only (nothing plays after it, --t is never
               positive here) and takes NO override at all — it flies the
               original straight-up-from-below path by inheriting the
               defaults on `.scene`. After three scenes that each turned
               a different way, the page should visibly settle on the one
               meant to be read calmly, not flown past.

   Every vector keeps the same budget the original had — roughly
   40–60% of travel, a few hundred pixels of recession, 20–25° of turn —
   only redirected, so the four scenes read as one considered sequence
   rather than four unrelated experiments. */
.scene-hero{
  --out-x:10%; --out-ry:10deg;
}
.scene-features{
  --in-x:60%;   --in-y:-6%;  --in-z:-260px;  --in-rx:4deg;   --in-ry:24deg;
  --out-x:-60%; --out-y:6%;  --out-z:-340px; --out-rx:-4deg; --out-ry:-24deg;
}
.scene-order{
  --in-x:-50%; --in-y:30%;   --in-z:-260px;  --in-rx:16deg;  --in-ry:18deg;
  --out-x:50%; --out-y:-30%; --out-z:-340px; --out-rx:-16deg;--out-ry:-18deg;
}

/* ── 5b. HIGH-END SCATTER — EVERY ELEMENT ITS OWN LINE ──────────────────
   5a above flies each SCENE as one panel. This layer gives every headline,
   line, card and button group inside a scene a second, smaller flight
   path of its own, so a scene doesn't arrive as one flat block — it
   arrives as several separate things converging on the same point from
   different directions (left, right, above, below, each on its own
   diagonal), and leaves the same way: each piece peeling off its own
   line and drifting up and away, not the whole block sliding off intact.

   `--t-in`/`--t-out` are read straight off the parent `.scene` — custom
   properties inherit, so every element below gets them for free with
   nothing new written to the DOM per frame. `--sc-d-in`/`--sc-d-out` give
   each element its own tiny head start or delay so the pieces move out of
   lockstep — the eyebrow is already settled while the headline is still
   arriving behind it, exactly the way a considered cut reads, not a
   single lever pulled on everything at once. */
.scene-hero-copy > *,
.scene-features .feature-card,
.scene-order .eyebrow-pill,.scene-order h2,.scene-order .lead,.scene-order .scene-actions,
.scene-footer .footer-grid > *{
  --sc-in-x:0px;  --sc-in-y:0px;  --sc-in-z:0px;
  --sc-in-rx:0deg;--sc-in-ry:0deg;--sc-in-rz:0deg;
  --sc-out-x:0px; --sc-out-y:0px; --sc-out-z:0px;
  --sc-out-rx:0deg;--sc-out-ry:0deg;--sc-out-rz:0deg;
  --sc-d-in:0; --sc-d-out:0;
  --sc-t-in:clamp(0, calc((var(--t-in) - var(--sc-d-in)) / (1 - var(--sc-d-in))), 1);
  --sc-t-out:clamp(0, calc((var(--t-out) - var(--sc-d-out)) / (1 - var(--sc-d-out))), 1);
  transform:
    translate3d(
      calc(var(--sc-t-in) * var(--sc-in-x) + var(--sc-t-out) * var(--sc-out-x)),
      calc(var(--sc-t-in) * var(--sc-in-y) + var(--sc-t-out) * var(--sc-out-y)),
      calc(var(--sc-t-in) * var(--sc-in-z) + var(--sc-t-out) * var(--sc-out-z))
    )
    rotateX(calc(var(--sc-t-in) * var(--sc-in-rx) + var(--sc-t-out) * var(--sc-out-rx)))
    rotateY(calc(var(--sc-t-in) * var(--sc-in-ry) + var(--sc-t-out) * var(--sc-out-ry)))
    rotateZ(calc(var(--sc-t-in) * var(--sc-in-rz) + var(--sc-t-out) * var(--sc-out-rz)));
}

/* Opening — eyebrow and paragraph both travel the LEFT side (in and back
   out), the headline and the buttons both travel the RIGHT side. Whichever
   side a piece comes in from, it leaves through that same side — never a
   different one — so the motion reads as things being pulled off-screen
   the way they arrived, not swapping sides mid-flight. */
.scene-hero-copy > .eyebrow-pill{
  --sc-in-x:-130px; --sc-in-y:-60px; --sc-in-z:-130px; --sc-in-ry:-22deg; --sc-in-rz:-10deg;
  --sc-out-x:-170px; --sc-out-y:-90px; --sc-out-rz:-14deg; --sc-d-out:.08;
}
.scene-hero-copy > h1{
  --sc-in-x:170px; --sc-in-y:-40px; --sc-in-z:-200px; --sc-in-ry:26deg; --sc-d-in:.05;
  --sc-out-x:200px; --sc-out-y:-110px; --sc-out-ry:28deg; --sc-d-out:.02;
}
.scene-hero-copy > .lead{
  --sc-in-x:-120px; --sc-in-y:80px; --sc-in-z:-150px; --sc-in-rx:13deg; --sc-d-in:.1;
  --sc-out-x:-150px; --sc-out-y:-70px; --sc-out-rx:10deg; --sc-d-out:.12;
}
.scene-hero-copy > .hero-actions{
  --sc-in-x:140px; --sc-in-y:100px; --sc-in-z:-200px; --sc-in-rx:-11deg; --sc-in-ry:-14deg; --sc-d-in:.15;
  --sc-out-x:170px; --sc-out-y:-90px; --sc-out-rx:-13deg; --sc-d-out:.18;
}

/* Four cards — cards 1 and 3 travel the LEFT side, cards 2 and 4 the
   RIGHT side, each one going back out the same side it came in from. */
.scene-features .feature-card:nth-child(1){
  --sc-in-x:-190px; --sc-in-y:-110px; --sc-in-z:-230px; --sc-in-rx:10deg; --sc-in-ry:-20deg;
  --sc-out-x:-230px; --sc-out-y:-130px; --sc-out-rz:-11deg; --sc-d-out:.15;
}
.scene-features .feature-card:nth-child(2){
  --sc-in-x:190px;  --sc-in-y:-110px; --sc-in-z:-230px; --sc-in-rx:10deg; --sc-in-ry:20deg; --sc-d-in:.06;
  --sc-out-x:230px; --sc-out-y:130px; --sc-out-rz:10deg; --sc-d-out:.1;
}
.scene-features .feature-card:nth-child(3){
  --sc-in-x:-190px; --sc-in-y:110px;  --sc-in-z:-230px; --sc-in-rx:-10deg; --sc-in-ry:-20deg; --sc-d-in:.12;
  --sc-out-x:-230px; --sc-out-y:-90px; --sc-out-rz:12deg; --sc-d-out:.05;
}
.scene-features .feature-card:nth-child(4){
  --sc-in-x:190px;  --sc-in-y:110px;  --sc-in-z:-230px; --sc-in-rx:-10deg; --sc-in-ry:20deg; --sc-d-in:.18;
  --sc-out-x:230px; --sc-out-y:100px; --sc-out-rz:-9deg;
}
/* The pointer-lift hover already sets its own `transform` on `.feature-card`
   (see "6. Cards" above) — scoped narrower here so the lift still layers
   on top of whatever scatter position the card is in, instead of the two
   fighting over the same property. */
.scene-features .feature-card:hover{
  transform:
    translate3d(
      calc(var(--sc-t-in) * var(--sc-in-x) + var(--sc-t-out) * var(--sc-out-x)),
      calc(var(--sc-t-in) * var(--sc-in-y) + var(--sc-t-out) * var(--sc-out-y) - 8px),
      calc(var(--sc-t-in) * var(--sc-in-z) + var(--sc-t-out) * var(--sc-out-z))
    )
    rotateX(calc(var(--sc-t-in) * var(--sc-in-rx) + var(--sc-t-out) * var(--sc-out-rx)))
    rotateY(calc(var(--sc-t-in) * var(--sc-in-ry) + var(--sc-t-out) * var(--sc-out-ry)))
    rotateZ(calc(var(--sc-t-in) * var(--sc-in-rz) + var(--sc-t-out) * var(--sc-out-rz)));
  border-color:rgba(18,196,222,.45);
}

/* The order scene — eyebrow and lead on the LEFT side, headline and
   buttons on the RIGHT, same rule: each leaves through the side it
   arrived from. */
.scene-order .eyebrow-pill{
  --sc-in-x:-90px; --sc-in-y:60px;  --sc-in-z:-130px; --sc-in-rz:-12deg;
  --sc-out-x:-120px; --sc-out-y:-90px; --sc-out-rz:13deg; --sc-d-out:.12;
}
.scene-order h2{
  --sc-in-x:100px;  --sc-in-y:-45px; --sc-in-z:-200px; --sc-in-ry:20deg; --sc-d-in:.05;
  --sc-out-x:150px; --sc-out-y:-130px; --sc-out-ry:-22deg; --sc-d-out:.02;
}
.scene-order .lead{
  --sc-in-x:-85px; --sc-in-y:75px;  --sc-in-z:-150px; --sc-in-rx:11deg; --sc-d-in:.1;
  --sc-out-x:-110px; --sc-out-y:-100px; --sc-d-out:.14;
}
.scene-order .scene-actions{
  --sc-in-x:95px;  --sc-in-y:90px;  --sc-in-z:-190px; --sc-in-rx:-11deg; --sc-d-in:.15;
  --sc-out-x:140px; --sc-out-y:-140px; --sc-d-out:.18;
}

/* The footer only ever arrives (5a leaves it flying the plain, calm
   default path on purpose) — so this stays a light rise-and-settle per
   column, not a scatter, matching "the one meant to be read calmly". */
.scene-footer .footer-grid > *{--sc-in-y:24px; --sc-in-z:-36px}
.scene-footer .footer-grid > *:nth-child(1){--sc-d-in:0}
.scene-footer .footer-grid > *:nth-child(2){--sc-d-in:.06; --sc-in-x:-12px}
.scene-footer .footer-grid > *:nth-child(3){--sc-d-in:.12; --sc-in-x:12px}
.scene-footer .footer-grid > *:nth-child(4){--sc-d-in:.18; --sc-in-x:-10px}
.scene-footer .footer-grid > *:nth-child(5){--sc-d-in:.24; --sc-in-x:10px}

.scene-inner{position:relative;width:min(var(--shell),100%);margin:0 auto;padding:0 var(--gut)}
/* Depth of field. Only where there is a GPU that will not notice — a blur
   on a full screen of type is the one thing that can cost a frame on a
   mid-range phone, and it is the one thing nobody will miss there. */
@media (min-width:901px) and (min-height:620px){
  .scene-inner{filter:blur(calc(var(--at) * 4px))}
}
/* Insurance. The scrim above is measured for the clip that ships, and the
   owner can put ANY clip in from the panel — including a bright one. A
   soft shadow under the headline costs nothing on a dark frame and is the
   difference between readable and not on a light one. */
.scene h1,.scene h2{text-shadow:0 2px 34px rgba(0,0,0,.55)}
.scene .lead,.scene .eyebrow-pill{text-shadow:0 1px 18px rgba(0,0,0,.5)}
.scene-features .feature-card{background-color:rgba(5,15,21,.72)}
/* --muted is a caption colour chosen for a white page. Inside a card that
   is itself sitting on a film, it is the difference between body copy and
   a watermark. These four paragraphs are body copy. */
.scene-features .feature-card p.muted{color:var(--ink-2)}

.scene-center{text-align:center;display:grid;justify-items:center}
.scene-center .lead{margin-left:auto;margin-right:auto}

/* The hero scene. Left-weighted, as shot — the film's subject is on the
   right and the words must not sit on top of it. */
.scene-hero .scene-inner{max-width:min(var(--shell),100%)}
.scene-hero h1{
  margin-top:20px;
  font-size:clamp(36px,5.2vw,74px);
  max-width:15ch;letter-spacing:-.042em;
}
.scene-hero .lead{margin-top:18px;max-width:46ch}
.scene-hero .hero-actions{display:flex;gap:14px;flex-wrap:wrap;margin-top:34px}

/* The four cards. On the stage they are a single row that has to fit one
   screen, so the grid is told how many columns to take rather than being
   left to auto-fit into two rows at an awkward width. */
.scene-features .section-lead{max-width:52ch;margin-top:14px}
.scene-features .feature-grid{margin-top:clamp(26px,3.4vw,46px);grid-template-columns:repeat(4,1fr)}
.scene-features .feature-card{padding:26px 24px 28px}
.scene-features .feature-card h3{font-size:clamp(16px,1.25vw,19px)}
.scene-features .feature-card p{font-size:14px;line-height:1.65}

/* The closing scene. */
.scene-order h2{font-size:clamp(34px,4.6vw,64px);letter-spacing:-.04em;margin-top:18px}
.scene-order .lead{margin-top:16px;max-width:52ch}
.scene-actions{display:flex;gap:14px;flex-wrap:wrap;justify-content:center;margin-top:34px}

/* The footer, as the last thing that flies in. It is still the real
   <footer> element with the real landmark — it is simply the fourth
   scene rather than a block underneath the third. Its flight path is
   deliberately just `.scene`'s defaults — see "EACH SCENE'S OWN FLIGHT
   PATH" above — so no --in-*/--out-* overrides live here. */
.scene-footer{
  padding:clamp(100px,12vh,150px) 0 clamp(28px,4vh,52px);
  background:none;
  overflow:visible;
}
.scene-footer::before,
.scene-footer::after{display:none}
.scene-footer .footer-grid{padding-bottom:34px}
.scene-footer .footer-bottom{border-top:1px solid var(--line)}
/* The view()-timeline drift written for the old scrolling footer must not
   also run in here: inside a pinned stage that timeline never advances,
   so it would park the footer at one opacity forever. */
body[data-page="home"] .scene-footer .footer-grid,
body[data-page="home"] .scene-footer .footer-bottom{animation:none}
/* Same reason: the observer-driven reveal has nothing to observe on a page
   where every scene is already in the viewport. The scene's own progress
   is the only thing allowed to set opacity here. */
body[data-page="home"] .scene .reveal{opacity:1;filter:none;transform:none;transition:none}

/* ── 6. THE STAGE FURNITURE ───────────────────────────────────────────
   A pinned page has to say how much of it is left, or the visitor does
   not know whether to keep scrolling. Four marks on the right and a cue
   at the bottom, both of them real controls. */
.stage-dots{
  position:absolute;right:clamp(14px,2vw,30px);top:50%;
  transform:translateY(-50%);
  display:flex;flex-direction:column;gap:12px;
  z-index:6;
}
.stage-dots button{
  width:26px;height:26px;padding:0;border:0;background:none;cursor:pointer;
  display:grid;place-items:center;border-radius:999px;
}
.stage-dots button span{
  display:block;width:8px;height:8px;border-radius:999px;
  background:rgba(157,239,251,.30);
  box-shadow:0 0 0 1px rgba(127,230,245,.22);
  transition:transform .4s var(--e-glide,ease),background .4s var(--e-glide,ease),box-shadow .4s var(--e-glide,ease);
}
.stage-dots button:hover span{background:rgba(157,239,251,.62)}
.stage-dots button[aria-current="true"] span{
  background:#5FE8F8;transform:scale(1.5);
  box-shadow:0 0 12px rgba(78,226,245,.9),0 0 0 1px rgba(157,239,251,.6);
}
.stage-cue{
  position:absolute;left:50%;bottom:clamp(16px,3vh,34px);
  transform:translateX(-50%);
  display:inline-flex;flex-direction:column;align-items:center;gap:7px;
  padding:8px 14px 10px;border:0;background:none;cursor:pointer;
  color:var(--ink-3,#8DAEBA);z-index:6;
  transition:color var(--m-quick,.24s) var(--e-glide,ease),opacity .5s var(--e-glide,ease);
}
.stage-cue:hover{color:#5FE8F8}
.stage-cue[hidden]{display:none}
.stage-cue .cue-rail{
  display:block;width:1px;height:32px;
  background:linear-gradient(180deg,transparent,rgba(127,230,245,.75));
  position:relative;overflow:hidden;
}
.stage-cue .cue-rail::after{
  content:"";position:absolute;left:0;top:-40%;width:1px;height:40%;
  background:linear-gradient(180deg,transparent,#9DEFFB);
  animation:cueRun 2.6s var(--e-glide,ease) infinite;
}
@media (max-width:900px){
  .stage-dots{
    right:auto;left:50%;top:auto;bottom:calc(env(safe-area-inset-bottom,0px) + 14px);
    transform:translateX(-50%);
    flex-direction:row;gap:10px;
  }
  .stage-cue{display:none}
}

/* ── 7. THE STAGE, ON SMALLER SCREENS ─────────────────────────────────
   Every scene has to fit ONE screen — there is no scrolling out of a
   scene, so anything that does not fit is simply not readable. Each
   breakpoint below is the content being re-cut for the height it actually
   has, not the desktop scene shrunk. */
@media (max-width:1200px){
  .scene-features .feature-grid{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:900px){
  .scene{padding:clamp(84px,13vh,116px) 0 clamp(60px,9vh,86px)}
  .scene-hero h1{font-size:clamp(30px,7.6vw,44px);max-width:none}
  .scene-hero .lead{font-size:15.5px;line-height:1.6;max-width:none;margin-top:14px}
  .scene-hero .hero-actions{margin-top:24px;gap:10px}
  .scene-features .feature-grid{gap:10px;margin-top:20px}
  .scene-features .feature-card{padding:16px 16px 18px}
  .scene-features .feature-card h3{font-size:15px;margin-bottom:6px}
  .scene-features .feature-card p{font-size:12.5px;line-height:1.5}
  .scene-order h2{font-size:clamp(28px,7.4vw,40px)}
  .scene-order .lead{font-size:15px;margin-top:12px}
  .scene-actions{margin-top:24px;gap:10px}
  .scene-footer{padding:clamp(84px,13vh,116px) 0 clamp(52px,8vh,74px)}
  .scene-footer .footer-grid{gap:22px;padding-bottom:20px}
  .scene-footer .footer-grid p{font-size:13px;line-height:1.6}
}
@media (max-width:560px){
  .scene-features .feature-grid{grid-template-columns:1fr 1fr}
  /* Clamped, not deleted. The owner edits these four paragraphs in the
     panel and must not find them missing on the device most of his
     customers are holding. */
  .scene-features .feature-card p{
    font-size:11.5px;line-height:1.45;
    display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;
  }
  .scene-hero .hero-actions .btn,
  .scene-hero .hero-actions .btn-call{width:100%}
  .scene-actions .btn,.scene-actions .btn-call{width:100%}
}
/* A short landscape phone is the hardest case: 360px of height for a whole
   scene. Everything gives at once rather than one part collapsing. */
@media (max-height:560px) and (min-width:561px){
  .scene{padding:clamp(72px,14vh,96px) 0 clamp(34px,6vh,54px)}
  .scene-hero h1{font-size:clamp(26px,3.4vw,38px)}
  .scene-hero .lead,.scene-order .lead{font-size:14.5px;margin-top:10px}
  .scene-features .feature-card p{
    font-size:12px;line-height:1.45;
    display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
  }
  .scene-order h2{font-size:clamp(26px,3.8vw,40px)}
}

/* ── 8. THE OFF SWITCH ────────────────────────────────────────────────
   Two ways the stage stands down, and in BOTH of them the page is a plain,
   complete, scrollable document — never a stack of invisible scenes
   waiting for a script that is not coming.

     · no JavaScript — `html.js` is written by an inline script in the
       <head> before first paint, so its ABSENCE is the reliable signal;
     · reduced motion — a page that flies its own content is exactly what
       that setting is for. */
html:not(.js) .stage-wrap,
.stage-wrap.stage-off{height:auto}
html:not(.js) .stage,
.stage-wrap.stage-off .stage{
  position:static;height:auto;overflow:visible;perspective:none;
}
html:not(.js) .stage-deck,
.stage-wrap.stage-off .stage-deck{position:static}
html:not(.js) .scene,
.stage-wrap.stage-off .scene{
  position:relative;inset:auto;
  min-height:0;
  padding:clamp(64px,8vw,110px) 0;
  transform:none;opacity:1;visibility:visible;pointer-events:auto;
}
html:not(.js) .scene:first-child,
.stage-wrap.stage-off .scene:first-child{padding-top:clamp(128px,14vw,190px)}
html:not(.js) .scene-inner,
.stage-wrap.stage-off .scene-inner{filter:none}
html:not(.js) .stage-dots,html:not(.js) .stage-cue,
.stage-wrap.stage-off .stage-dots,.stage-wrap.stage-off .stage-cue{display:none}

@media (prefers-reduced-motion: reduce){
  .stage-cue .cue-rail::after{animation:none}
  .site-header,.site-header .nav,.site-header .nav::before,
  .nav-ring .ring-run{transition:none}
}

/* ── 8b. HERO OFF BRINGS BACK THE OLDER LOOK ────────────────────────────
   Admin → Settings → "Show the hero video", switched off, now takes the
   3D deck down with it (see disableStage3D() in app.js) and restores the
   opening scene to what it looked like before the film/3D build: the
   headline beside a small animated barcode "specimen" card, sitting on
   the same ambient glow. The header and footer are NOT part of this —
   they stay exactly as the current build has them either way. Everything
   here is hidden unless .stage-off is present — the film staying ON is
   what keeps the card OFF, back to how the owner wants it. */
.scene-hero .hero-bg,.scene-hero .hero-card{display:none}
.stage-wrap.stage-off .scene-hero{isolation:isolate}
.stage-wrap.stage-off .scene-hero .hero-bg{display:block}
.stage-wrap.stage-off .scene-hero .hero-card{display:block;position:relative;isolation:isolate}
.stage-wrap.stage-off .scene-hero .scene-inner{
  display:grid;grid-template-columns:1.08fr .92fr;
  gap:clamp(36px,4.5vw,74px);align-items:center;
}
@media (max-width:900px){
  .stage-wrap.stage-off .scene-hero .scene-inner{grid-template-columns:1fr}
  /* Kept on screen, not hidden: shown small and pinned to the right —
     the way it sat before — instead of stretching the full width of the
     phone under the headline. */
  .stage-wrap.stage-off .scene-hero .hero-card{
    display:block;
    width:min(260px,68%);
    margin:0 0 0 auto;
  }
  .stage-wrap.stage-off .scene-hero .label-demo{
    padding:16px 15px 14px;
  }
  .stage-wrap.stage-off .scene-hero .label-demo .tiny{font-size:9px;letter-spacing:.16em}
  .stage-wrap.stage-off .scene-hero .sku-chip{font-size:8px;padding:3px 8px}
  .stage-wrap.stage-off .scene-hero .barcode-real{height:56px;margin:14px 0 10px}
  .stage-wrap.stage-off .scene-hero .sku{font-size:8.5px;letter-spacing:.16em}
}
@media (max-width:480px){
  .stage-wrap.stage-off .scene-hero .hero-card{width:min(220px,62%)}
  .stage-wrap.stage-off .scene-hero .barcode-real{height:46px}
}

/* ── 9. THE FOOTER'S QR ───────────────────────────────────────────────
   Its own column between the links and the phone number, exactly where
   the owner marked it. It is a real link as well as a code, because half
   the people who see it are on the device that is showing it and cannot
   scan their own screen. */
.footer-grid{grid-template-columns:1.6fr 1fr auto 1.1fr;align-items:start}
.footer-qr{display:flex;flex-direction:column;gap:10px;align-items:flex-start}
.footer-qr a{
  display:block;padding:9px;border-radius:12px;line-height:0;
  background:#fff;
  border:1px solid rgba(127,230,245,.28);
  box-shadow:0 18px 40px -26px rgba(0,0,0,.9);
  transition:transform var(--m-slow,.5s) var(--e-glide,ease),box-shadow var(--m-slow,.5s) var(--e-glide,ease),border-color var(--m-base,.3s) var(--e-glide,ease);
}
.footer-qr a:hover{
  transform:translateY(-4px);
  border-color:rgba(127,230,245,.6);
  box-shadow:0 26px 54px -24px rgba(0,0,0,1),0 0 0 1px rgba(18,196,222,.35);
}
.footer-qr img{width:118px;height:118px;display:block;image-rendering:pixelated}
.footer-qr small{
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--muted);
}
@media (max-width:1100px){
  .footer-grid{grid-template-columns:1fr 1fr;gap:30px}
  .footer-qr{align-items:flex-start}
}
@media (max-width:560px){
  .footer-grid{grid-template-columns:1fr}
  .footer-qr img{width:104px;height:104px}
}
/* On the stage the footer is one screen, so the QR is part of a tighter
   four-across arrangement and shrinks with it. */
.scene-footer .footer-qr img{width:clamp(78px,7vw,104px);height:clamp(78px,7vw,104px)}
@media (max-width:900px){
  .scene-footer .footer-grid{grid-template-columns:1fr 1fr}
  .scene-footer .footer-qr img{width:88px;height:88px}
}

/* ── 10. THE PRODUCT PAGE'S PICTURE, BROUGHT BACK INSIDE THE PAGE ─────
   The owner's report: on a product page the image "onk boro hoye geche,
   page ar bahire chole geche" — it had grown so large it ran off the page.

   Measured rather than guessed. The plate was `aspect-ratio:1/1` inside a
   1.05fr column of a 1720px shell: at 1920×1080 that is a 793px square,
   so the picture alone was taller than the window and the name, the price
   and the call button all sat below the fold. On a 1440×900 laptop it was
   still 590px against 900px of screen.

   Three bounds, all of which have to hold at once:
     · never wider than 520px, so it stays a picture and not a wall;
     · never taller than 58% of the window, so the name and the price are
       always on screen with it;
     · and the column itself narrows, so the reading measure beside it
       gets the room the picture gives up. */
.product-detail-grid{grid-template-columns:minmax(0,.86fr) minmax(0,1.14fr)}
.product-detail-image{
  aspect-ratio:1/1;
  width:100%;
  max-width:520px;
  max-height:min(58svh,560px);
  margin-inline:auto;
  padding:clamp(16px,1.8vw,28px);
}
.product-detail-image img{
  width:100%;height:100%;
  object-fit:contain;         /* the whole label, never cropped */
}
@media (max-width:1000px){
  .product-detail-grid{grid-template-columns:1fr}
  .product-detail-image{
    aspect-ratio:4/3;
    max-width:440px;
    max-height:min(46svh,420px);
    margin-inline:0;
  }
}
@media (max-width:560px){
  .product-detail-image{max-width:100%;max-height:none;aspect-ratio:4/3}
}
/* The gallery strip under a product had the same problem for the same
   reason — no ceiling on the tile. */
.product-detail .gallery-grid{grid-template-columns:repeat(auto-fill,minmax(160px,1fr))}
.product-detail .gallery-card img{aspect-ratio:4/3;object-fit:contain}

/* ── 11. THE PRODUCT PAGE KEEPS ITS DAYLIGHT ──────────────────────────
   "just main product er moddhe cholbe na" — the one page with no film.
   It is the page a purchase is decided on, and a moving picture behind a
   photograph of a white label is the one place motion costs more than it
   gives. It therefore has no `.filmed` class and no `.film` element, and
   the only thing it takes from this layer is the capsule header, the ring
   and the QR — which is why they were all written page-agnostically. */
body[data-page="product"]{background:var(--paper)}

/* ── 12. RETIRING WHAT THIS LAYER REPLACED ────────────────────────────
   Three earlier blocks are now describing a page that no longer exists,
   and two of them out-rank the rules above by specificity rather than by
   being right. They are answered here with their OWN selectors — same
   weight, later in the file — so the override is exact and anybody
   reading either half can see what happened to the other.

   A. THE WHITE ATMOSPHERE. `body::before` (~line 1974, deepened again at
      ~2204) is a FIXED white-to-cyan wash at z-index:-2, and `body::after`
      is a multiply grain at z-index:-1. The film sits at z-index:-3, so
      both of them were painting straight over it: the whole site came out
      as a pale wash with a film nobody could see. On a filmed page the
      atmosphere IS the film, so the two pseudo-elements stand down.

   B. THE HOME-ONLY HUD. The STAGE layer (~line 4788) scoped its header to
      `body[data-page="home"]` and opened the capsule out into a
      full-width rail on scroll. That is precisely what the owner asked to
      stop — "uporer header ... permanently sob jaygy thakbe change hobe
      na" — and at (0,4,3) it beat the page-agnostic rules above. Written
      out again here with the same selectors and the settled geometry.

   C. THE HOME HERO'S BACKGROUND. `.hero` painted its own deep-ink ground;
      the stage has no `.hero`, so nothing here is undone — it is listed
      only so the next reader does not go looking for it.               */

/* A ─ the atmosphere gives way to the film */
body.filmed::before,
body.filmed::after{display:none !important}
body.filmed{position:relative;background:transparent}

/* B ─ one capsule, on the home page too, in both states */
body[data-page="home"] .site-header{
  position:fixed;left:0;right:0;top:0;
  padding-top:clamp(11px,1.5vw,20px);
}
body[data-page="home"] .site-header:not(.is-stuck){padding-top:clamp(11px,1.5vw,20px)}
body[data-page="home"] .site-header.is-stuck{padding-top:clamp(7px,1vw,13px)}
body[data-page="home"] .site-header::before{display:none}
body[data-page="home"] .site-header::after{display:none}
body[data-page="home"] .site-header:not(.is-stuck) .nav,
body[data-page="home"] .site-header .nav{
  width:min(1010px,calc(100% - 2*var(--gut)));
  margin-inline:auto;
  height:auto;min-height:62px;
  padding:8px 12px 8px 26px;
  border-radius:999px;
}
body[data-page="home"] .site-header.is-stuck .nav{
  width:min(930px,calc(100% - 2*var(--gut)));
  margin-inline:auto;
  height:auto;min-height:54px;
  padding:6px 10px 6px 22px;
  border-radius:999px;
}
/* The glass stayed on in the scrolled state — it used to be faded out
   because the capsule was dissolving into a rail. There is no rail now. */
body[data-page="home"] .site-header.is-stuck .nav::before{opacity:1}
/* And the lit pill marks the current page in BOTH states, for the same
   reason: there is only one state's worth of shape to mark it on. */
body[data-page="home"] .site-header nav a[aria-current],
body[data-page="home"] .site-header:not(.is-stuck) nav a[aria-current],
body[data-page="home"] .site-header.is-stuck nav a[aria-current]{
  color:#02171C;background:linear-gradient(180deg,#4EE2F5,#12C4DE);
  box-shadow:0 8px 24px -12px rgba(18,196,222,.9);
}

/* The narrow-screen menu drops out of the capsule, so it is shaped like
   the capsule rather than like the full-width bar it used to hang from. */
@media (max-width:760px){
  .site-header nav{
    left:var(--gut);right:var(--gut);top:calc(100% + 8px);
    z-index:5;
    background:rgba(5,15,21,.92);
    backdrop-filter:blur(26px) saturate(180%);
    -webkit-backdrop-filter:blur(26px) saturate(180%);
    border:1px solid rgba(127,230,245,.20);
    border-radius:18px;
    padding:8px 14px 12px;
    box-shadow:0 30px 66px -34px rgba(0,0,0,1);
  }
  .site-header nav a{
    padding:13px 8px;border-bottom:1px solid rgba(127,230,245,.12);border-radius:10px;
  }
  .site-header nav a:last-child{border-bottom:0}
  /* The capsule is tight at this width; the wordmark gives first. */
  .site-header .nav{padding:7px 8px 7px 16px;gap:10px;min-height:56px}
  .site-header.is-stuck .nav{padding:6px 7px 6px 14px;min-height:50px}
  .site-header .brand{font-size:20px}
  .site-header .menu{width:38px;height:38px;flex:none}
}

/* ── 13. THE REST OF THE DUAL PALETTE, EXTENDED TO A FILMED PAGE ──────
   The colour tokens at §4 were only half of it. This stylesheet has a
   SECOND set of dark-band declarations — the glass tokens at ~line 4052
   and four component rules at ~333, ~388 and ~1693 — and every one of
   them is scoped by name to `.hero, .section.dark, footer`. A filmed page
   is none of those, so the panes on it were still being drawn with the
   DAYLIGHT glass: `--glass-fill: rgba(255,255,255,.66)`, a two-thirds
   white pane. On a white page that is frosted glass. Over a film it is a
   sheet of paper laid on the picture, with #A9C6D1 body copy on it that
   nobody can read — which is exactly how the four cards came out.

   Extending the same declarations to `body.filmed` fixes all of them at
   once, and it stays one source of truth: these are the file's own dark
   values, copied, not new ones invented here.

   `.contact-note`, `.order-panel` and `.chat-head` keep their own darker
   fill (~line 4324) — those are dark CARDS rather than panes inside a
   dark band, and at (0,2,0) they still out-rank this block. */
body.filmed{
  --glass-fill:rgba(255,255,255,.055);
  --glass-fill-hover:rgba(255,255,255,.10);
  --glass-rim:linear-gradient(180deg,
    rgba(190,240,250,.60) 0%,
    rgba(18,196,222,.34) 18%,
    rgba(18,196,222,.06) 42%,
    rgba(18,196,222,.06) 58%,
    rgba(18,196,222,.30) 82%,
    rgba(190,240,250,.55) 100%);
  --glass-rim-hot:linear-gradient(180deg,
    rgba(230,250,255,.95) 0%,
    rgba(18,196,222,.85) 16%,
    rgba(18,196,222,.34) 42%,
    rgba(18,196,222,.34) 58%,
    rgba(18,196,222,.80) 84%,
    rgba(230,250,255,.9) 100%);
  --glass-inset:inset 0 1px 1px rgba(190,240,250,.22);
}
body.filmed .eyebrow::before,
body.filmed .eyebrow-pill::before{box-shadow:0 0 10px rgba(78,226,245,.9)}
body.filmed .eyebrow,
body.filmed .eyebrow-pill{
  background:linear-gradient(178deg,rgba(255,255,255,.13),rgba(255,255,255,.045));
  box-shadow:
    inset 0 1px 0 rgba(210,248,255,.28),
    inset 0 -1px 0 rgba(0,0,0,.45),
    0 0 26px -12px rgba(18,196,222,.8);
}
body.filmed .btn-call:hover{color:var(--on-cyan)}
body.filmed .spotlight{
  background:radial-gradient(520px circle at var(--mx,50%) var(--my,50%),
    rgba(18,196,222,.15), transparent 68%);
}
/* Every pane on a filmed page needs a floor under the 5.5% film, or its
   readability is decided by whichever frame of the clip is behind it. */
body.filmed .feature-card,
body.filmed .category-card,
body.filmed .product-card,
body.filmed .gallery-card,
body.filmed .pf-stat,
body.filmed .inq-card,
body.filmed .cta-band-inner,
body.filmed .contact-form input,
body.filmed .contact-form textarea{background-color:rgba(6,18,25,.70)}
body.filmed .tab-pill{background-color:rgba(6,18,25,.62)}
body.filmed .tab-pill.active{background-color:transparent}

/* ── 14. THE CAPSULE ON A WHITE PAGE ──────────────────────────────────
   The single-product page has no film under it, and the capsule's fill is
   mixed for one: 60% of a near-black over a moving picture is a dark pill,
   but the same 60% over WHITE is a mid grey, and the white wordmark on it
   drops to about 2.5:1. The header does not change shape between pages —
   the owner settled that — so what changes is the one thing that has to:
   how much of the ground it lets through. */
body:not(.filmed) .site-header .nav::before{
  background:
    linear-gradient(rgba(6,19,26,.93),rgba(4,13,19,.95)) padding-box,
    linear-gradient(180deg,
      rgba(190,240,250,.55) 0%,
      rgba(18,196,222,.30) 18%,
      rgba(18,196,222,.05) 42%,
      rgba(18,196,222,.05) 58%,
      rgba(18,196,222,.26) 82%,
      rgba(190,240,250,.50) 100%) border-box;
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.18),
    0 22px 44px -30px rgba(6,23,31,.85);
}
body:not(.filmed) .site-header.is-stuck .nav::before{
  background:
    linear-gradient(rgba(4,14,20,.97),rgba(3,10,15,.98)) padding-box,
    linear-gradient(180deg,
      rgba(190,240,250,.62) 0%,
      rgba(18,196,222,.34) 18%,
      rgba(18,196,222,.07) 42%,
      rgba(18,196,222,.07) 58%,
      rgba(18,196,222,.30) 82%,
      rgba(190,240,250,.58) 100%) border-box;
  box-shadow:
    inset 0 1px 0 rgba(190,240,250,.22),
    0 26px 52px -30px rgba(6,23,31,.9);
}
@media (max-width:760px){
  body:not(.filmed) .site-header nav{background:rgba(4,13,19,.97)}
}

/* ── 15. THE TWO SCREENS THAT DID NOT FIT ─────────────────────────────
   Measured, not guessed. A scene cannot be scrolled out of — that is the
   whole point of the stage — so a scene whose content is taller than the
   screen is content nobody can read. Two viewports failed:

     360x640   the footer scene wanted 603px and had 504
     820x420   the four cards wanted 361px and had 314, and the
               footer wanted 387px

   Both are the same shape of problem — a grid that is right at a normal
   height and one row too tall at a short one — so both get the same
   answer: more columns, less air, and the one paragraph that can be read
   in three lines held to three. Nothing is deleted. */

/* A short, WIDE screen (a laptop with a small window, a phone in
   landscape): put everything on one row rather than two. */
@media (max-height:640px) and (min-width:700px){
  .scene{padding:clamp(66px,15vh,92px) 0 clamp(26px,5vh,44px)}
  .scene-features .feature-grid{grid-template-columns:repeat(4,1fr);gap:10px;margin-top:16px}
  .scene-features .feature-card{padding:14px 14px 16px}
  .scene-features h2,.scene-order h2{font-size:clamp(24px,3.2vw,34px)}
  .scene-footer{padding:clamp(66px,15vh,92px) 0 clamp(20px,4vh,34px)}
  .scene-footer .footer-grid{grid-template-columns:1.5fr 1fr auto 1.1fr;gap:22px;padding-bottom:14px}
  .scene-footer .footer-grid p{
    font-size:12px;line-height:1.5;
    display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;
  }
  .scene-footer .footer-qr img{width:66px;height:66px}
  .scene-footer .footer-bottom{padding:10px 0 8px}
}

/* A short, NARROW screen — the small phone. The footer keeps its two
   columns; what gives is the air between them, the size of the code and
   the length of the address block. */
@media (max-width:560px){
  .scene-footer{padding:clamp(76px,12vh,104px) 0 clamp(20px,3vh,34px)}
  .scene-footer .footer-grid{grid-template-columns:1fr 1fr;gap:14px 18px;padding-bottom:12px}
  .scene-footer .brand{font-size:18px;margin-bottom:10px}
  /* Six lines, not four. The measurement after the tightening above came
     back at 356px of content in 543px of screen, so the owner's address
     block — which is what this paragraph actually holds — fits whole. The
     clamp stays as a ceiling in case he writes a longer one. */
  .scene-footer .footer-grid p{
    font-size:11.5px;line-height:1.55;
    display:-webkit-box;-webkit-line-clamp:6;-webkit-box-orient:vertical;overflow:hidden;
  }
  .scene-footer .footer-grid strong{margin-bottom:8px}
  .scene-footer .footer-grid > div > a{padding:4px 0;font-size:13.5px}
  .scene-footer .footer-qr img{width:74px;height:74px}
  .scene-footer .footer-bottom{padding:10px 0 6px;gap:4px}
  .scene-footer .footer-bottom span{font-size:10px}
  .scene-footer .footer-contact a{font-size:13.5px;gap:7px}
}

/* ═══════════════════════════════════════════════════════════════════════
   LAYER: PERFORMANCE — the same page, cheaper to paint, on hardware that
   cannot afford the full one.

   `html.perf-low` is written once, synchronously, by the inline script at
   the very top of <head> — before this stylesheet's first paint — so
   there is never a flash of the expensive look that then gets stripped
   away a moment later. It is set from real signals (device memory, CPU
   core count, Data Saver, a measured slow connection); see that script
   for the exact test. Nothing here changes layout, only cost:

     · `backdrop-filter` (the frosted-glass look on headers, cards, chips,
       the chat panel) is the single most expensive thing this stylesheet
       does — on a weak GPU it can turn a smooth scroll into a slideshow.
       Turned off wholesale. Every surface that used it already has a
       solid/rgba fill underneath (that is what `--card-bg`, `--field-bg`,
       `--chip-bg` etc. are), so it is still a translucent panel — just
       not a blurred one.
     · The big soft `filter:blur()` lamps (`.orb`, `.orb-cyan*`, the hero
       glow, the grain texture, the SVG-filtered watermark word) are
       decorative and off-screen-adjacent; hidden outright.
     · `.hero-card`'s 90px drop-shadow is the next heaviest single rule on
       the page; flattened to a cheap, ordinary box-shadow.
     · The scroll-scrubbed film itself is NOT touched here — it already
       gets the smallest source file on this same hardware, forced by
       `pickNetworkTier()` in app.js, which is the fix that actually
       matters for a <video> element. This block only clears the space
       around it. */
html.perf-low *{
  backdrop-filter:none !important;
  -webkit-backdrop-filter:none !important;
}
html.perf-low .orb,
html.perf-low .orb-cyan,
html.perf-low .orb-cyan-soft,
html.perf-low .hero-glow,
html.perf-low .watermark > .watermark-word,
html.perf-low .noise-grain > .grain{
  display:none !important;
}
html.perf-low .hero-card{
  filter:none;
  box-shadow:0 24px 48px -20px rgba(0,0,0,.6);
}
