/*
Theme Name: Shape of Light
Theme URI: https://vectalight.com
Author: Vecta Design
Author URI: https://vectadesign.com
Description: Custom one-page showcase theme for the Shape of Light modular LED ceiling luminaire range by Vecta Design. Ported 1:1 from the approved static site design, including the live Formspree + reCAPTCHA contact form. Adds a free, no-plugin EN/ET/DE/IT/ES language switcher (see inc/i18n.php + inc/strings.php). On mobile, the collection cards scroll normally in a plain stacked list instead of the desktop pin/slide effect. The hero now plays a short "lights coming on" video once and holds on its final frame instead of the old two-photo cross-fade. Mira lamp photo updated again (reception lobby installation shot). Hero "Vecta Design Presents" eyebrow line removed; subtitle moved down further (clear of the reception desk edge in the video), sized up and set to a flat 60%-black grey with no text-shadow (title itself untouched). Extra spacing added on mobile, where it was sitting closer to the desk edge than on desktop. Fixed the subtitle's text-shadow actually turning off (it was inheriting the title's glow from the parent instead of truly having none). "About the Collection" restructured per the client's sketch: intro copy now stacks full-width on its own instead of sharing a row with the features list, and the four feature points became their own single-row bordered boxes (matching the full-range lamp cards' layout) that fade/rise into view as the visitor scrolls to them. The old static .collection-banner photo right after it was replaced with a scroll-driven version: a big lamp close-up shrinks and settles into its resting spot over the lobby photo as the visitor scrolls, cables fading in once it's landed and a soft warm glow switching on last, ported 1:1 from the approved standalone scroll-depth prototype (same photo, same quote, same font size as the banner it replaces). Both new sections fall back to a static, fully-visible treatment on mobile and under prefers-reduced-motion. The lamp now opens on a clean white/cream backdrop (the lobby photo only fades in as the visitor scrolls, as originally intended) and starts already near its final height instead of dead-centered in the pinned stage, so there's no longer a wide empty band above it the moment the section appears. Fixed a sitewide font-loading gap: the Google Fonts request only fetched Inter at 200-700, but the philosophy quote and this new lamp-section quote are both set to weight 100 -- browsers were silently substituting the nearest loaded weight (200) instead. Weight 100 is now fetched too, so both quotes actually render at the thin weight the design calls for. Removed the soft warm-glow overlay that used to switch on above/around the lamp once it landed (client found the blur it introduced distracting) -- the lamp now stays sharp against the sharp photo throughout, cables and dust motes unaffected. The static mobile/reduced-motion fallback photo was re-rendered to match (no glow baked in). Restored the photo's "focus pull" (briefly blurred as it fades in, fully sharp by 65% through the section, well before the lamp finishes settling) -- turned out the client liked that part, they'd only objected to the glow, and the two got removed together by mistake last version. Fixed the hero title landing on the reception tablet/stand in hero_video_end.jpg and reading poorly against it: the copy block is now positioned down from the top of the hero (rather than dead-centered) by an amount tuned against the actual photo so the title sits clear of both the ceiling seam above and the tablet stand below; the title is also bigger and no longer carries the text-shadow glow (removed per feedback). Fixed the "lamp settles into the lobby" section looking inert on mobile: the phone/reduced-motion fallback is a single flattened photo with no dust motes in its markup at all (by design, for scroll performance), and that photo's marble wall has its own natural light flecks which were being mistaken for the animated dust having frozen -- added a light, non-scroll-linked set of the same dust motes to that fallback so it's gently alive on phones too, while staying fully static for visitors with reduced-motion enabled. Simplified the hero text position from the last version: that one tuned the offset to the exact pixel position of the reception tablet and looked right in testing, but didn't hold up on a real phone (effective viewport height varies with browser chrome in a way a fixed vh-based offset can't fully account for) and, per direct feedback, was pulled up further than wanted regardless -- sitting near the middle of the reception stand lower down is fine, it was only the seam/table-edge higher up that read poorly. Back to one plain, generous downward shift. Hero text pass 3, per further direct feedback: the subtitle is no longer positioned relative to the title at all -- it's now anchored from the bottom of the hero (a fixed gap above the "SCROLL" indicator) so it reliably lands on the counter's front face, between the counter's top edge and the scroll indicator, regardless of viewport height or how tall the title block is. The title is pushed down further still (it's fine sitting over the reception stand) and has a light text-shadow again (the heavy blurred version was removed a few versions back per feedback; this is a small, plain one, restored per follow-up feedback for legibility now that the title sits over the tablet). Fixed the "lamp settles into the lobby" section's lamp reading as switched off once it lands: the lamp PNG itself is a plain unlit product cutout (the realism came entirely from the wall/lamp glow overlay removed a few versions back over the distracting blur it added) -- added a drop-shadow glow directly on the lamp image instead, timed the same as before, which hugs the fixture's own silhouette rather than bleeding a rectangle across the wall. Tuned that glow per direct follow-up: it was wrapping the whole fixture and reading too yellow. It's now pushed downward only (light pooling under the fixture, the way a real pendant actually casts it, not a halo around it) and much closer to white. Still showing a little at the sides though (a drop-shadow follows the PNG's whole silhouette, so a downward offset alone can't fully avoid that) -- replaced it with a dedicated light-pool shape positioned from the lamp housing's actual measured bounding box within the PNG canvas, so it now only ever appears strictly below the fixture. Per direct follow-up it was pooling in the center and not reaching the ends of the diffuser -- the light-pool shape is now sized to the diffuser's full measured width with a flatter gradient (an ellipse much wider than its own box, so only its even middle ever shows), instead of a box-sized ellipse that naturally brightens in the middle and fades toward its own edges. Fixed the light-pool having a visible hard rectangular edge where it cut off (the oversized-ellipse trick used to flatten it never actually reached zero opacity before hitting its own box's edge) -- it's now shaped with a proper edge mask instead, so it truly fades to nothing on all sides, and stretched a bit further down per follow-up feedback. Hero subtitle nudged up per direct follow-up: it was sitting too low, closer to "SCROLL" than to the counter's top edge -- its bottom-anchored offset now scales with viewport height so it lands right in the middle of that gap on normal screens, while a floor/ceiling on the same value keeps it clear of the title on unusually short or tall windows (verified against the full viewport-size sweep, still zero overlaps). Fixed both contact-section phone numbers dialing wrong: the tel: links had a stray trailing 0 that isn't part of the actual number shown on screen (+372 44 23 023 was dialing ...0230, +372 55 15 394 was dialing ...53940) -- mobile visitors tapping to call were reaching a different number entirely. Both links now dial exactly the digits displayed. Fixed two typos in the technical data table: the fire rating standard was misprinted "EN:3501-1" instead of "EN 13501-1" (a specific, recognisable standard number architects and specifiers check against), and the diffuser description read "Easy accessable" instead of "accessible". Fixed Vega (06/12) being the one model out of 12 missing its "Real Projects" button -- the template's lamp-media block for it was never given the button markup the other 11 models have. Added the button plus its actual gallery: three client-supplied installation photos (a hotel lobby library, an executive boardroom, a hotel lounge near reception) with matching thumbnails, added to the same projects data used by every other model's gallery. Added three architect-facing spec highlights to the hero (CRI ≥ 97, R9 ≥ 89, Class A acoustic) -- a row of small white rounded badges that fade in near the top of the hero once everything else has settled, timed alongside the subtitle/scroll reveal. Anchored from the top of the hero rather than the photo or the title, since that gap is comfortably clear at every viewport size tested; folds to two rows rather than squeezing illegibly on the narrowest phones. Updated the CRI figure everywhere it was still the old "> 90" to match the hero badge's "≥ 97": the per-model Lamping fact (all 12 lamps), the main Technical Data table, and the LED specification table in each model's specs popup (WW/NW/TW rows) all now read the same, corrected number. Made the philosophy statement ("For us, lighting is not an accessory...") a touch darker and one weight step heavier per direct feedback -- #a3a19b to #78766c, weight 100 to 200 (that weight is already fetched from Google Fonts, so it renders as the real cut, not a synthetic/faux bold). Sized the three hero badges up a step per direct feedback -- larger value/label text, more internal padding, roomier gaps -- still verified against the full viewport sweep with zero overlaps into the title. Swapped Crown's main card photo for the client's new hotel-lobby shot (three ring luminaires above the reception desk, full composition visible) -- the previous main photo (retail display table) moved into Crown's own Real Projects gallery as a third image rather than being discarded. Renamed the "Real Projects" button (and its lightbox) to plain "Gallery" across all 12 models and all 5 languages -- some of the interior photos in these galleries are client-supplied visualizations rather than photos of real completed installations, so labelling them "Real Projects" specifically was a false claim; "Gallery" makes no claim either way.
Version: 1.5.23
License: Proprietary -- for Vecta Design's own use only, not for redistribution.
Text Domain: shape-of-light
*/


  :root{
    --bg:#ffffff;
    --bg-deep:#f7f6f3;
    --ink:#17171a;
    --ink-soft:#6b6a66;
    --rule:#e7e5e0;
    --accent:#b3392c;
    --accent-ink:#ffffff;
    --shadow: 0 -28px 70px -20px rgba(23,23,26,.16);
    --overlay: rgba(20,18,16,.42);
  }
  /* Site always uses this fixed white palette, regardless of the visitor's
     OS/browser theme -- Shape of Light's brand system is deliberately
     light-only, not theme-adaptive. */

  *{box-sizing:border-box;}
  html{scroll-behavior:auto;}
  body{
    margin:0;
    background:var(--bg);
    color:var(--ink);
    font-family:"Inter",system-ui,-apple-system,"Segoe UI",sans-serif;
    /* Base weight is ExtraLight -- this is what every plain paragraph/copy
       element on the page (lamp descriptions, section intros, form fields,
       the footer notice, etc.) actually renders at, since none of them set
       their own font-weight and so simply inherit this. Headings, nav
       labels, buttons, table headers and other UI chrome all set their own
       explicit font-weight already (500-700) and are unaffected. */
    font-weight:200;
    -webkit-font-smoothing:antialiased;
  }
  body.lock{overflow:hidden;}
  a{color:inherit;}
  button{font:inherit;}

  /* ---------- header (kept as in the first version) ---------- */
  /* z-index deliberately far above the lamp stack: each pinned lamp block
     gets an ever-increasing inline z-index (zCounter, starting at 1000) as
     it activates, so a low header z-index gets outranked after only a
     handful of navigations. During an 'up'-direction (or otherwise
     negative-translateY) entry/exit transition the transitioning block's
     fixed-position box temporarily extends above y=0, so its visible,
     viewport-clipped sliver spans [0, bottom] -- fully covering the
     header's own [0,64] bounds for most of the transition if the block
     outranks it. The header must always paint above any lamp block, while
     staying below the popup/lightbox overlays (9000/9001/9500) so those
     can still cover it when open. */
  .site-header{
    position:fixed; top:0; left:0; right:0; z-index:8000;
    height:64px;
    display:grid;
    grid-template-columns:1fr auto 1fr;
    align-items:center;
    padding:0 clamp(20px,4vw,48px);
    background:var(--bg);
    border-bottom:1px solid var(--rule);
  }
  .brand-mark{display:flex; align-items:baseline; gap:.5em; white-space:nowrap;}
  .brand-mark .word{font-size:1.05rem; font-weight:600; letter-spacing:.04em;}
  .brand-mark .of{font-size:1.05rem; font-weight:400; color:var(--accent); font-style:normal;}
  .brand-mark .sub{font-size:.62rem; font-weight:600; letter-spacing:.16em; color:var(--ink-soft); margin-left:.6em; padding-left:.6em; border-left:1px solid var(--rule);}
  .main-nav{display:flex; gap:clamp(16px,2.4vw,34px); justify-content:center;}
  .main-nav a{
    font-size:.72rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase;
    text-decoration:none; color:var(--ink); opacity:.75; transition:opacity .15s;
  }
  .main-nav a:hover, .main-nav a:focus-visible{opacity:1;}
  .header-right{display:flex; justify-content:flex-end; align-items:center; gap:clamp(14px,2vw,24px);}
  .header-right img{height:28px; width:auto; display:block;}

  /* ---------- language switcher (free, no-plugin EN/RU/UK toggle --
     see inc/i18n.php + inc/strings.php) ---------- */
  .lang-switch{display:flex; align-items:center; gap:.5em;}
  .lang-switch-link{
    font-size:.68rem; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
    text-decoration:none; color:var(--ink); opacity:.55; transition:opacity .15s, color .15s;
  }
  .lang-switch-link:hover, .lang-switch-link:focus-visible{opacity:.85;}
  .lang-switch-link.is-active{opacity:1; color:var(--accent);}
  .lang-switch-link + .lang-switch-link{border-left:1px solid var(--rule); padding-left:.5em;}
  .lang-switch-mobile{display:none;}

  /* ---------- mobile nav menu ----------
     Below 820px .main-nav is hidden (not enough header width for six
     links), but until now nothing replaced it -- the six section links
     (Collection/Range/Technical/Order/Download/Contact) were simply
     unreachable on a phone. .nav-toggle is the hamburger button that
     appears only at that same breakpoint; #mobileNav is the dropdown
     panel it opens, reusing the exact same hrefs (and therefore the same
     already-verified scroll-landing behavior, including the
     reconcile()/exit-zone handling for the Technical link) as the
     desktop nav -- this is a presentation-only addition, no new
     scroll-target logic. */
  .nav-toggle{
    display:none;
    background:none; border:none; margin-left:14px; padding:8px;
    cursor:pointer;
    flex-direction:column; align-items:center; justify-content:center; gap:5px;
  }
  .nav-toggle span{
    display:block; width:22px; height:2px; background:var(--ink);
    transition:transform .2s ease, opacity .2s ease;
  }
  .nav-toggle[aria-expanded="true"] span:nth-child(1){ transform:translateY(7px) rotate(45deg); }
  .nav-toggle[aria-expanded="true"] span:nth-child(2){ opacity:0; }
  .nav-toggle[aria-expanded="true"] span:nth-child(3){ transform:translateY(-7px) rotate(-45deg); }

  .mobile-nav-overlay{
    position:fixed; top:64px; left:0; right:0; bottom:0;
    background:var(--overlay);
    opacity:0; pointer-events:none; transition:opacity .2s ease;
    z-index:7998;
  }
  .mobile-nav-overlay.open{opacity:1; pointer-events:auto;}

  .mobile-nav{
    position:fixed; top:64px; left:0; right:0; z-index:7999;
    background:var(--bg); border-bottom:1px solid var(--rule);
    display:flex; flex-direction:column;
    max-height:calc(100vh - 64px); max-height:calc(100dvh - 64px); overflow-y:auto;
    transform:translateY(-6px); opacity:0; pointer-events:none;
    transition:transform .2s ease, opacity .2s ease;
    box-shadow:0 20px 40px -20px rgba(0,0,0,.2);
  }
  .mobile-nav.open{ transform:translateY(0); opacity:1; pointer-events:auto; }
  .mobile-nav a{
    display:block; padding:16px clamp(20px,5vw,32px);
    border-top:1px solid var(--rule);
    font-size:.78rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase;
    color:var(--ink); text-decoration:none;
  }
  .mobile-nav a:first-child{ border-top:none; }
  .mobile-nav a:active, .mobile-nav a:focus-visible{ background:var(--bg-deep); }

  @media (max-width:820px){
    .main-nav{display:none;}
    .site-header{grid-template-columns:1fr auto;}
    .nav-toggle{display:inline-flex;}
    /* The header-right lang switch has no room left at this width
       alongside the logo and hamburger button -- it moves into the
       mobile nav dropdown instead (.lang-switch-mobile below). */
    .header-right > .lang-switch{display:none;}
    .lang-switch-mobile{
      display:flex; flex-wrap:wrap; justify-content:center; gap:.5em 1em;
      padding:16px clamp(20px,5vw,32px); border-top:1px solid var(--rule);
    }
    .lang-switch-mobile .lang-switch-link{font-size:.78rem;}
    /* Pre-existing overflow, only exposed now that the toggle button gives
       .header-right something new to fight for room with: "SHAPE of
       LIGHT | VECTA DESIGN" never wraps (.brand-mark is white-space:nowrap)
       and its full width plus the logo already left no room to spare at
       phone widths -- adding the toggle pushed the sum past the header's
       own width, silently rendering it off the right edge of the
       viewport. "VECTA DESIGN" is redundant with the Vecta Design logo
       already sitting in the header, so it's the one dropped here. */
    .brand-mark .sub{display:none;}
  }
  @media (min-width:821px){
    .mobile-nav, .mobile-nav-overlay{display:none;}
  }

  main{padding-top:64px;}

  /* Nav-link jump targets -- the header is position:fixed (64px tall) and
     takes no space in normal flow, so without this an anchor jump would
     land the section's own top edge exactly at the viewport top, hidden
     behind the header. */
  #collection, #range, #technical, #order, #download, #contact{ scroll-margin-top:80px; }

  /* ---------- hero intro: a real "lights coming on" video of the same
     space the site used to fake with a two-photo cross-fade, playing once
     on load and then simply holding on its own last frame (a plain <video>
     with no loop attribute already does this natively -- nothing loops,
     nothing goes blank), with the title/subtitle settling in right after.
     No corner vignette (it would fight the header logo) -- only a soft,
     localised backing behind the text itself, which sits over the video
     rather than the header. */
  .hero{
    position:relative;
    height:calc(100vh - 64px);
    height:calc(100dvh - 64px);
    overflow:hidden;
    background:#0c0c0d;
    display:flex; align-items:flex-start; justify-content:center;
  }
  /* Always-on static layer -- the video's own true final frame, sitting
     underneath the video at all times (see the markup comment in
     template-parts/content-main.php for why this means there's never a
     visible "swap" once the video ends and freezes, and what shows instead
     for screen readers, reduced-motion visitors or a blocked autoplay). */
  .hero-img, .hero-video{
    position:absolute; inset:0; width:100%; height:100%;
    object-fit:cover; display:block;
  }
  .hero-scrim{
    position:absolute; inset:0;
    background:rgba(8,7,6,.08);
  }
  .hero-copy{
    position:relative; z-index:1;
    display:flex; flex-direction:column; align-items:center; text-align:center;
    padding:0 24px;
    /* Moved down from dead-center (the old align-items:center) -- the
       title now sits over the reception stand/tablet on purpose (that's
       fine per direct feedback; the title has its own light text-shadow
       below to stay legible there), and the offset is tuned so the
       subtitle lands specifically on the counter's front face, in the
       clear band between the counter's top edge and the "SCROLL"
       indicator near the bottom of the hero. Mobile needs a taller push
       (see the media query below) since the narrower title stacks taller
       relative to the same photo. */
    margin-top:clamp(150px,23vh,260px);
  }
  .hero-title{
    margin:.5em 0 0; display:flex; flex-direction:column; align-items:center;
    color:#f6f3ee; font-family:"Inter",system-ui,-apple-system,"Segoe UI",sans-serif; font-weight:600;
    letter-spacing:.02em; text-transform:uppercase; line-height:1.06;
    font-size:clamp(2.9rem,7.6vw,5.7rem);
    /* A light shadow, not the old heavy 20px-blur glow -- just enough to
       keep the title readable now that it sits over the tablet/stand,
       restored per direct feedback ("можно кинуть лёгкую тень, так у нас
       было"). */
    text-shadow:0 2px 10px rgba(0,0,0,.4), 0 1px 2px rgba(0,0,0,.3);
  }
  .hero-title .line{
    opacity:0; transform:translateY(18px);
    animation:heroTextIn 1.1s ease-out 2s forwards;
  }
  .hero-title .line:last-child{ animation-delay:2.35s; }
  .hero-title .of{
    font-family:"Inter",system-ui,-apple-system,"Segoe UI",sans-serif; font-style:italic; font-weight:400;
    color:var(--accent); font-size:.56em; line-height:1; margin:.15em 0;
    opacity:0; transform:translateY(14px);
    animation:heroTextIn 1s ease-out 2.2s forwards;
  }
  .hero-sub{
    /* Positioned from the bottom of the hero now, not stacked under the
       title in normal flow (see the markup comment) -- a constant, safe
       gap above .hero-scroll (same bottom:clamp(...) base value as that
       element, plus its own height and a buffer) regardless of how tall
       the title ends up or how short the viewport actually is. This is
       what keeps it specifically on the counter's front face, between the
       counter's top edge and the scroll indicator, per direct feedback.
       Solid 60%-black grey (no alpha) and no text-shadow, per direct
       request once the eyebrow line above it was removed. */
    position:absolute; left:50%; z-index:1;
    bottom:calc(clamp(22px,4vh,40px) + clamp(60px,calc(21.5vh - 47.5px),146px));
    transform:translateX(-50%);
    width:100%; max-width:640px; padding:0 24px;
    text-align:center;
    margin:0; color:#666666; font-weight:400;
    font-size:clamp(1.45rem,2.8vw,1.7rem); letter-spacing:.04em;
    opacity:0;
    animation:heroSubIn 1s ease-out 2.75s forwards;
    /* Explicit "none" kept even though nothing above sets a text-shadow of
       its own -- harmless belt-and-braces against whatever inherits down
       from an ancestor. */
    text-shadow:none;
  }
  /* On phones the title stacks taller (see .hero-copy's comment) -- push
     it down further just on narrow viewports so it clears the ceiling/
     seam area the same way. .hero-sub doesn't need a matching override
     any more; it's positioned from the bottom now, not from the title. */
  @media (max-width:820px){
    .hero-copy{ margin-top:clamp(140px,30vh,340px); }
  }
  .hero-scroll{
    position:absolute; left:50%; bottom:clamp(22px,4vh,40px); transform:translateX(-50%);
    z-index:1;
    display:flex; flex-direction:column; align-items:center; gap:.6em;
    color:rgba(255,255,255,.85);
    opacity:0;
    /* Dedicated keyframe (not the shared heroTextIn) because this element,
       unlike the other hero text, is centered via left:50% +
       translateX(-50%) rather than plain text-align/margin centering.
       heroTextIn's keyframes only declare transform:translateY(...), and a
       CSS animation's transform fully replaces the element's static
       transform for its duration and (with fill-mode:forwards) forever
       after -- so reusing heroTextIn here was silently dropping the
       translateX(-50%) once the fade-in finished, permanently shifting the
       whole "SCROLL" block (text + track/dot together) half its own width
       to the right of true center. heroScrollIn keeps translateX(-50%) in
       every keyframe so centering survives the animation. */
    animation:heroScrollIn 1s ease-out 3.1s forwards;
  }
  .hero-scroll span{
    font-size:.6rem; font-weight:600; letter-spacing:.32em; text-transform:uppercase;
  }
  .hero-scroll-track{
    position:relative; width:1px; height:34px; background:rgba(255,255,255,.3); overflow:hidden;
  }
  .hero-scroll-dot{
    position:absolute; left:0; top:-40%; width:1px; height:40%;
    background:#fff;
    animation:heroScrollDot 1.8s ease-in-out infinite;
  }
  @keyframes heroScrollDot{
    0%{ top:-40%; opacity:0; }
    15%{ opacity:1; }
    85%{ opacity:1; }
    100%{ top:100%; opacity:0; }
  }
  @keyframes heroTextIn{ from{opacity:0; transform:translateY(16px);} to{opacity:1; transform:translateY(0);} }
  @keyframes heroScrollIn{
    from{opacity:0; transform:translateX(-50%) translateY(16px);}
    to{opacity:1; transform:translateX(-50%) translateY(0);}
  }
  /* .hero-sub is centered via left:50% + transform:translateX(-50%) now
     (it's position:absolute, no longer plain centered text) -- same
     reasoning as heroScrollIn just above: reusing plain heroTextIn would
     silently drop that translateX(-50%) once the animation finishes. */
  @keyframes heroSubIn{
    from{opacity:0; transform:translateX(-50%) translateY(14px);}
    to{opacity:1; transform:translateX(-50%) translateY(0);}
  }
  /* Three architect-facing spec highlights (CRI, R9, acoustic class),
     anchored from the TOP of the hero -- a fixed, small offset below the
     header -- rather than from the photo or from the title. That gap is
     comfortably clear of the title's own top edge (185px+) across every
     size in the site's viewport sweep, so unlike a photo-anchored position
     it doesn't shift under object-fit:cover cropping and doesn't need
     retuning per aspect ratio. flex-wrap lets it fold to two rows rather
     than overflow or squeeze illegibly on the narrowest phones. */
  .hero-badges{
    position:absolute; top:clamp(20px,3vh,40px); left:50%; z-index:1;
    transform:translateX(-50%);
    display:flex; flex-wrap:wrap; justify-content:center;
    gap:clamp(10px,2.2vw,16px);
    width:100%; max-width:540px; padding:0 20px; box-sizing:border-box;
    opacity:0;
    /* Fades in alongside the rest of the hero's staggered reveal --
       between the subtitle (2.75s) and the scroll indicator (3.1s) -- so it
       reads as part of the same "everything has settled" moment the client
       asked for, not a late afterthought. */
    animation:heroBadgesIn 1s ease-out 2.9s forwards;
  }
  .hero-badge{
    background:rgba(255,255,255,.92);
    backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
    border-radius:16px;
    padding:12px 17px;
    min-width:92px;
    text-align:center;
    box-shadow:0 6px 22px rgba(0,0,0,.18);
  }
  .hero-badge-value{
    display:block;
    font-family:"Inter",system-ui,-apple-system,"Segoe UI",sans-serif;
    font-weight:700; font-size:clamp(1rem,2.6vw,1.25rem);
    letter-spacing:-.01em; color:#161513; line-height:1.15;
    white-space:nowrap;
  }
  .hero-badge-label{
    display:block;
    font-size:clamp(.6rem,1.35vw,.68rem);
    font-weight:600; letter-spacing:.05em; text-transform:uppercase;
    color:#7a7a76; margin-top:4px;
    white-space:nowrap;
  }
  @keyframes heroBadgesIn{
    from{opacity:0; transform:translateX(-50%) translateY(-10px);}
    to{opacity:1; transform:translateX(-50%) translateY(0);}
  }
  @media (max-width:420px){
    .hero-badges{gap:7px; padding:0 14px;}
    .hero-badge{padding:9px 11px; min-width:82px;}
  }
  @media (prefers-reduced-motion: reduce), (prefers-reduced-data: reduce){
    /* Skip the video entirely -- the always-present .hero-img underneath
       (the video's own last frame) is already the exact static "lights on"
       shot it would have settled on anyway, so hiding it is enough; nothing
       needs to additionally fade or swap in. */
    .hero-video{display:none;}
  }
  @media (prefers-reduced-motion: reduce){
    .hero-title .line, .hero-title .of{animation:none; opacity:1; transform:none;}
    /* .hero-sub and .hero-scroll both rely on transform:translateX(-50%)
       for their resting (centered) position now, since both are
       position:absolute -- the reduced-motion reset has to keep that
       transform instead of clearing it to none like the plain hero text
       above, which doesn't use transform for positioning. */
    .hero-sub{animation:none; opacity:1; transform:translateX(-50%);}
    .hero-scroll{animation:none; opacity:1; transform:translateX(-50%);}
    .hero-scroll-dot{animation:none; opacity:1; top:0;}
    .hero-badges{animation:none; opacity:1; transform:translateX(-50%);}
  }

  /* ---------- Philosophy statement -- a single large, thin, muted-grey
     line of copy sitting between the hero and "About the Collection",
     giving the brand's design philosophy its own quiet moment before the
     more detailed collection copy begins. */
  .philosophy{
    background:var(--bg-deep);
    padding:clamp(64px,11vh,120px) clamp(24px,6vw,64px);
  }
  .philosophy-quote{
    max-width:860px; margin:0 auto; text-align:center;
    font-size:clamp(1.4rem,3vw,2.2rem); font-weight:200; line-height:1.5;
    letter-spacing:-.02em; color:#78766c;
  }

  /* ---------- "About the Collection" intro -- plain scrolling content,
     not part of the lamp-paging mechanic, sitting between the hero and the
     lamp stack. Restructured per the client's sketch: intro copy now
     stacks full-width on its own (no more side-by-side split with a
     features column -- that column used to hold the four feature points,
     which are now their own single-row-of-four bordered boxes below the
     copy instead, see .collection-features-grid further down). The
     product photo that once lived beside/below this copy now lives in the
     scroll-driven .sol-parallax section right after it (see that block's
     own comment below). */
  .section-eyebrow{
    margin:0 0 .9em; font-size:.68rem; font-weight:600; letter-spacing:.22em;
    text-transform:uppercase; color:var(--accent);
  }
  .collection-intro{
    padding:clamp(56px,9vh,110px) clamp(24px,5vw,64px);
    max-width:1280px; margin:0 auto;
  }
  .collection-intro-copy h2{
    margin:0 0 .5em; font-size:clamp(1.7rem,3vw,2.4rem); font-weight:600;
    line-height:1.18; letter-spacing:-.01em; color:var(--ink);
  }
  .collection-intro-copy > p:not(.section-eyebrow){
    margin:0 0 1.1em; font-size:.92rem; line-height:1.7; color:var(--ink-soft);
  }

  .collection-intro-copy{
    margin:0 0 clamp(40px,7vh,64px);
  }
  .collection-features-grid{
    margin:0; display:grid; grid-template-columns:repeat(4,1fr);
    gap:clamp(16px,2vw,24px);
  }
  .collection-feature-box{
    border:1px solid var(--rule);
    padding:clamp(20px,2.4vw,26px);
    opacity:0; transform:translateY(28px);
    transition:opacity .7s cubic-bezier(.16,.8,.24,1), transform .7s cubic-bezier(.16,.8,.24,1);
  }
  .collection-feature-box.is-visible{
    opacity:1; transform:translateY(0);
  }
  /* Small stagger so the four boxes cascade in left-to-right rather than
     popping together -- reads well whether they cross the viewport
     threshold in the same frame (desktop single row, same repeat(4,1fr)/
     860px-breakpoint pattern as .full-range-grid's lamp cards further
     down the page, per the client's explicit "as the lamp cards"
     instruction) or one at a time (mobile single column, where the delay
     is barely noticeable since they're already spaced out by scroll
     position). Revealed the first time each box crosses into the
     viewport -- see the matching IntersectionObserver block in site.js --
     and stays visible afterwards; static (no fade, no delay) under
     prefers-reduced-motion or without IntersectionObserver support, see
     the fallback rule below and the JS guard. */
  .collection-feature-box:nth-child(1){ transition-delay:0s; }
  .collection-feature-box:nth-child(2){ transition-delay:.1s; }
  .collection-feature-box:nth-child(3){ transition-delay:.2s; }
  .collection-feature-box:nth-child(4){ transition-delay:.3s; }
  .collection-feature-box .cf-num{
    display:block; font-size:.72rem; font-weight:600; letter-spacing:.04em;
    color:var(--accent); margin:0 0 .6em;
  }
  .collection-feature-box .cf-title{
    display:block; font-size:.92rem; font-weight:700; color:var(--ink);
    margin:0 0 .5em;
  }
  .collection-feature-box .cf-desc{
    margin:0; font-size:.82rem; line-height:1.6; color:var(--ink-soft);
  }
  @media (max-width:860px){
    .collection-features-grid{ grid-template-columns:repeat(2,1fr); }
  }
  @media (max-width:560px){
    .collection-features-grid{ grid-template-columns:1fr; }
  }
  @media (prefers-reduced-motion: reduce){
    .collection-feature-box{ opacity:1; transform:none; transition:none; }
  }


  /* ---------- Collection showcase: scroll-driven "settling into place"
     lamp, in the same slot right after "About the Collection" that the
     old static .collection-banner used to occupy (same full-bleed
     2170x725 photo, same banner.quote1/quote2 text, same font size --
     copied 1:1 from the old .collection-banner-quote per the client's
     explicit instruction). A big close-up of the fixture shrinks and
     settles into its resting spot over the lobby photo as the visitor
     scrolls, cables fading in once it's landed, a soft warm-white glow
     switching on last. Ported from the approved standalone scroll-depth
     prototype; --solp-* custom properties are set by the matching block
     at the bottom of site.js. Falls back to a single flattened composite
     image (see .sol-parallax-static below) on narrow viewports and under
     prefers-reduced-motion, same threshold (820px) as the lamp-stack's
     own pin mechanic, for the same reasons: on a phone scroll-jacking
     reads as confusing rather than cinematic, and mobile browsers' own
     scroll quirks (address-bar show/hide, momentum) can desync a
     scroll-tied effect. */
  /* Defaults live on :root, not on .sol-parallax itself -- site.js also
     sets the live values on :root (document.documentElement) as the
     visitor scrolls, and a custom property resolves from the NEAREST
     ancestor that declares it. Declaring these defaults on .sol-parallax
     instead would make that nearer declaration permanently win over
     :root's, silently freezing every element at the hardcoded fallback
     no matter what site.js sets -- this bit us once during development,
     worth keeping the two declarations on the same element. */
  :root{
    --solp-photo-scale:1.4;
    --solp-photo-opacity:0;
    --solp-photo-blur:6px;
    --solp-photo-x:0%;
    --solp-photo-y:0%;
    --solp-lamp-w:300px;
    --solp-lamp-x:0px;
    --solp-lamp-y:0px;
    --solp-lamp-brightness:1;
    --solp-lamp-saturate:1;
    --solp-lamp-sepia:0;
    --solp-lamp-hue:0deg;
    --solp-lamp-glow-op:0;
    --solp-dust-op:0;
    --solp-cable-op:0;
    --solp-vignette-op:0;
    --solp-text-op:0;
  }
  .sol-parallax{
    position:relative;
  }
  .sol-parallax-pinwrap{
    position:relative;
    height:300vh; /* ~3 screens of scroll for the whole moment, including the post-settle hold -- see HOLD_VH in site.js. Shorter than the original hero-length prototype (520vh) since this now sits mid-page rather than carrying the opening beat on its own. */
  }
  .sol-parallax-stage{
    position:sticky; top:64px; height:calc(100vh - 64px); height:calc(100dvh - 64px);
    overflow:hidden;
    display:flex; align-items:center; justify-content:center;
    background:var(--bg-deep);
  }
  .sol-parallax-frame{
    position:absolute; left:50%; top:50%;
    width:100%;
    aspect-ratio:2170/725;
    transform:translate(-50%,-50%);
    overflow:hidden;
  }
  .sol-parallax-frame-inner{
    width:100%; height:100%;
    animation:solp-breathe 16s ease-in-out infinite;
  }
  @keyframes solp-breathe{
    0%,100%{ transform:scale(1); }
    50%{ transform:scale(1.045); }
  }
  .sol-parallax-photo{
    width:100%; height:100%; object-fit:cover; display:block;
    opacity:var(--solp-photo-opacity);
    filter:blur(var(--solp-photo-blur));
    transform:translate(var(--solp-photo-x), var(--solp-photo-y)) scale(var(--solp-photo-scale));
  }
  .sol-parallax-vignette{
    position:absolute; inset:0;
    background:radial-gradient(ellipse 75% 70% at 50% 42%, rgba(0,0,0,0) 45%, rgba(0,0,0,var(--solp-vignette-op)) 100%);
    pointer-events:none;
  }
  .sol-parallax-scrim{
    position:absolute; left:0; right:0; bottom:0; height:30%;
    background:linear-gradient(to top, rgba(8,6,4,.62), rgba(8,6,4,0));
    opacity:var(--solp-text-op);
    pointer-events:none;
  }
  /* Same size/position/weight as the old .collection-banner-quote it
     replaces (client's explicit instruction) -- only the opacity drive and
     the fact that it now fades in a beat after the lamp settles (see
     --solp-text-op / holdT in site.js) are new. */
  .sol-parallax-quote{
    position:absolute; z-index:1; left:0; right:0; bottom:clamp(22px,5vh,56px);
    margin:0; padding:0 clamp(24px,6vw,80px); text-align:center;
    font-size:clamp(1.3rem,3vw,2.1rem); font-weight:100; line-height:1.5;
    letter-spacing:-.045em; color:#fff;
    opacity:var(--solp-text-op);
    pointer-events:none;
  }
  .sol-parallax-quote span{display:block;}

  .sol-parallax-lamp{
    position:absolute; left:50%; top:50%;
    width:var(--solp-lamp-w);
    aspect-ratio:2170/725;
    transform:translate(-50%,-50%) translate(var(--solp-lamp-x), var(--solp-lamp-y));
    z-index:5; pointer-events:none;
    will-change:transform, width;
  }
  .sol-parallax-lamp-img{
    position:absolute; left:0; top:0; width:100%; height:auto; display:block;
    filter:
      brightness(var(--solp-lamp-brightness))
      saturate(var(--solp-lamp-saturate))
      sepia(var(--solp-lamp-sepia))
      hue-rotate(var(--solp-lamp-hue));
  }
  /* The lamp PNG itself is a plain unlit product cutout (white diffuser on
     a white background, no glow baked in) -- the earlier wall/lamp glow
     overlay was what sold "the light is on" here, and removing it (client
     found the blur distracting) left the lamp looking switched off once it
     landed. A drop-shadow on .sol-parallax-lamp-img was tried next, but
     that follows the PNG's whole silhouette, so it glowed at the sides too
     -- per feedback, this needs to be strictly a pool of light under the
     fixture. This is its own shape instead, positioned from the lamp
     housing's actual bounding box within the PNG canvas (measured: roughly
     6.5%-93.6% horizontally, bottom edge at ~66% vertically -- not the
     .sol-parallax-lamp box's own edges, which include transparent padding
     the housing doesn't reach), so it only ever appears below it. Same
     --solp-lamp-glow-op timing (site.js) the removed overlay used, kept
     close to white per feedback (the first attempt read too yellow). */
  .sol-parallax-lamp-underglow{
    position:absolute; left:50%; top:64%;
    width:96%; height:34%;
    transform:translateX(-50%);
    /* Previous attempt (a radial-gradient ellipse made wider than its own
       box, to keep it flat/even) never actually reached full transparency
       before hitting the box's rectangular edge -- so that edge showed up
       as a visible hard-edged patch on the wall (per feedback/screenshot).
       Split into two single-purpose pieces instead, which is the only way
       to get both a flat, even width AND a true fade to nothing on all
       sides: a mask shapes the left/right edges (transparent -> opaque
       -> opaque -> transparent, actually reaching 0 well inside the box),
       and the background gradient just fades top-to-bottom -- stretched
       taller per feedback, so the light pools further down beneath the
       fixture instead of stopping right under it. */
    background:linear-gradient(to bottom, rgba(255,250,240,.85) 0%, rgba(255,250,240,.4) 55%, rgba(255,250,240,0) 100%);
    -webkit-mask-image:linear-gradient(to right, transparent 0%, #000 9%, #000 91%, transparent 100%);
    mask-image:linear-gradient(to right, transparent 0%, #000 9%, #000 91%, transparent 100%);
    opacity:var(--solp-lamp-glow-op);
    filter:blur(4px);
    pointer-events:none;
  }
  .sol-parallax-frame-clip{
    position:absolute; left:50%; top:50%;
    width:100%;
    aspect-ratio:2170/725;
    transform:translate(-50%,-50%);
    overflow:hidden;
    z-index:6; pointer-events:none;
  }
  .sol-parallax-lamp-clip{
    position:absolute; left:50%; top:50%;
    width:var(--solp-lamp-w);
    aspect-ratio:2170/725;
    transform:translate(-50%,-50%) translate(var(--solp-lamp-x), var(--solp-lamp-y));
  }
  .sol-parallax-lamp-img-nocable{ top:0; }
  .sol-parallax-lamp-img-cable{ opacity:var(--solp-cable-op); }

  .sol-parallax-dust-wrap{
    position:absolute; left:50%; top:53%;
    width:116%; height:140%;
    transform:translateX(-50%);
    opacity:var(--solp-dust-op);
    mix-blend-mode:screen;
    pointer-events:none;
  }
  .sol-parallax-dust{
    position:absolute;
    width:4px; height:4px;
    border-radius:50%;
    background:radial-gradient(circle, rgba(255,250,238,1) 0%, rgba(255,250,238,.9) 38%, rgba(255,250,238,0) 72%);
    filter:blur(.4px) drop-shadow(0 0 1.5px rgba(255,240,210,.7));
    animation-name:solp-dustfloat;
    animation-timing-function:ease-in-out;
    animation-iteration-count:infinite;
  }
  @keyframes solp-dustfloat{
    0%{ transform:translate(0,0); opacity:0; }
    15%{ opacity:.9; }
    78%{ transform:translate(4px,-64px); opacity:.55; }
    100%{ transform:translate(-5px,-84px); opacity:0; }
  }

  /* Static fallback -- hidden by default, swapped in below 820px and under
     reduced-motion (see the two media queries at the end of this block).
     Same aspect-ratio/quote styling as the old .collection-banner so the
     swap is visually seamless. */
  .sol-parallax-static{ display:none; }
  .sol-parallax-static{
    position:relative; width:100%; overflow:hidden;
    aspect-ratio:2172/724; background:var(--bg-deep);
  }
  .sol-parallax-static-img{
    position:absolute; inset:0; width:100%; height:100%;
    object-fit:cover; display:block;
  }
  .sol-parallax-static::after{
    content:''; position:absolute; inset:0;
    background:linear-gradient(180deg, rgba(0,0,0,0) 40%, rgba(0,0,0,.6) 100%);
    pointer-events:none;
  }
  .sol-parallax-static .sol-parallax-quote{ opacity:1; }
  /* The same dust motes used on the interactive desktop version (reused
     1:1 -- same markup, same solp-dustfloat animation), just not gated
     behind --solp-dust-op (there's no scroll progress to drive it here).
     Kept out entirely under reduced-motion, same as everything else in
     this fallback -- see the JS/CSS split note above .sol-parallax-static
     in the markup. */
  .sol-parallax-dust-wrap-static{ opacity:1; }
  @media (prefers-reduced-motion: reduce){
    .sol-parallax-dust-wrap-static{ display:none; }
  }
  @media (max-width:640px){
    .sol-parallax-static{ aspect-ratio:3/4; }
    .sol-parallax-static .sol-parallax-quote{ font-size:1.05rem; bottom:clamp(18px,4vh,32px); }
  }
  @media (max-width:820px), (prefers-reduced-motion: reduce){
    .sol-parallax-pinwrap{ display:none; }
    .sol-parallax-static{ display:block; }
  }

  /* ---------- "The Full Range" overview grid -- a plain scrolling section
     (like the intro above it, not part of the lamp-paging mechanic) giving
     an at-a-glance view of all 12 models. Clicking a card jumps straight
     into the stack at that lamp, via the same goTo() the wheel/nav-button
     paths use -- see the JS at the bottom of the page. */
  .full-range{
    padding:clamp(48px,8vh,96px) clamp(24px,5vw,64px) clamp(64px,9vh,110px);
    max-width:1280px; margin:0 auto;
  }
  .full-range-head{max-width:none; text-align:left;}
  .full-range-head h2{
    margin:0 0 .5em; font-size:clamp(1.7rem,3vw,2.4rem); font-weight:600;
    line-height:1.18; letter-spacing:-.01em; color:var(--ink);
  }
  /* At several container widths the line break was landing right after
     "of" -- "...one system of" / "light." -- stranding a lone preposition
     on its own line end. Keeping "of light." together as one unbreakable
     unit means a break before it always carries both words to the next
     line instead, so it never separates ("...one system" / "of light."),
     while still wrapping naturally everywhere else. */
  .full-range-head h2 .keep-together{ white-space:nowrap; }
  .range-hint svg{ flex:none; }
  .full-range-head p:not(.section-eyebrow){
    margin:0; font-size:clamp(1.05rem,1.7vw,1.4rem); line-height:1.55; font-weight:300; color:var(--ink-soft); max-width:none;
  }
  .full-range-head p.range-hint.range-hint{ display:flex; align-items:center; gap:.6em; margin:clamp(14px,2vh,22px) 0 0; font-size:.88rem; line-height:1.4; font-weight:500; color:var(--accent); }
  /* ---------- "The Full Range" -- lamp strips. Nine wide bands in a plain column with a
     2px gap, each one the lamp lit in its own room (same scene as the popup).
     Hovering a band makes it taller -- it grows up and down at once -- and lifts it to
     the front, over the bands above and below, while the picture slowly zooms in. */
  body{ overflow-x:clip; }   /* the strips run edge to edge of the window (100vw); clip the scrollbar-width overshoot, no sideways scroll */
  .full-range-grid{
    --ls-h:clamp(110px,12.4vw,260px);        /* resting height of one strip (follows the window width) */
    --ls-gr:1.55;                            /* hovered height = resting height x this */
    --ls-gap:2px;
    --ls-up:calc(var(--ls-h) * (var(--ls-gr) - 1) / 2);   /* how far a hovered strip reaches past its slot */
    margin-top:calc(var(--ls-up) + 12px); margin-bottom:calc(var(--ls-up) + 12px);
    display:block;
    width:100vw; margin-left:calc(50% - 50vw); margin-right:calc(50% - 50vw);   /* full-bleed: out of the 1280px column */
  }
  .full-range-card{
    position:relative; display:block; height:var(--ls-h); color:var(--ink); text-decoration:none;
    z-index:1;
  }
  .full-range-card + .full-range-card{ margin-top:var(--ls-gap); }
  /* the visible band is a box centred on the card's slot, so changing its height grows it both ways */
  .ls-box{
    position:absolute; left:0; right:0; top:50%; height:100%; transform:translateY(-50%);
    overflow:hidden; background:var(--ls-bg,#fff); isolation:isolate;   /* white panel (the earlier dark/grey variant used #535353) */
    transition:height .8s cubic-bezier(.22,.7,.2,1), box-shadow .8s ease;
  }
  .ls-box::after{ content:''; position:absolute; inset:0; z-index:3; pointer-events:none; border:1px solid rgba(0,0,0,.09); }   /* hairline, so white strips stay readable on the white page */
  /* the picture sits on the left and melts into the dark panel on the right;
     it is a little wider than its window, so it can zoom in without showing an edge */
  .ls-view{
    position:absolute; left:0; top:0; bottom:0; width:var(--ls-vw,41%); overflow:hidden;
    -webkit-mask-image:linear-gradient(90deg,#000 var(--ls-ms,80%),transparent 100%);
            mask-image:linear-gradient(90deg,#000 var(--ls-ms,80%),transparent 100%);
  }
  .full-range-card .ls-pic{
    position:absolute; left:var(--ls-pl,-9%); top:-1px; height:calc(100% + 2px); width:var(--ls-pw,118%);
    background-size:cover; background-position:50% var(--ls-y, 10%);
    transform:scale(1); transform-origin:50% 40%;
    transition:transform 1.1s cubic-bezier(.22,.6,.25,1), filter .5s ease;   /* slow "dolly out" when the strip is let go */
  }
  /* resting strips are a touch quieter (strongly muted); the hovered one returns to full colour */
  @media (hover:hover){
    .full-range-card .ls-pic{ filter:saturate(.1); }
    .full-range-card:hover .ls-pic, .full-range-card:focus-visible .ls-pic{ filter:saturate(1); }
  }
  .ls-copy{
    position:absolute; z-index:2; left:var(--ls-cl,44%); right:clamp(20px,4vw,64px); top:50%;
    height:var(--ls-h); transform:translateY(-50%);
    display:flex; flex-direction:column; justify-content:center; gap:.4em;
  }
  .ls-num{ font-size:.66rem; font-weight:700; letter-spacing:.14em; color:var(--accent); }
  .ls-name{
    --b:140%; --g:0;
    margin:0; font-size:clamp(1.6rem,3.1vw,2.7rem); font-weight:200; line-height:1;
    letter-spacing:.04em; text-transform:uppercase; color:#6f6e6a;
    align-self:flex-start;
    /* same "burn-in" as the popup title: a hot front travels along the letters (see JS) */
    background-image:
      linear-gradient(90deg,#6f6e6a 0,#6f6e6a calc(var(--b) - 34%),#a9772f calc(var(--b) - 16%),#d99a4a calc(var(--b) - 8%),rgba(224,160,80,.55) calc(var(--b) - 3%),rgba(224,160,80,0) var(--b)),
      linear-gradient(rgba(111,110,106,.18),rgba(111,110,106,.18));
    -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent;
    filter:drop-shadow(0 0 calc(var(--g) * 5px) rgba(224,150,60,calc(var(--g) * .4)));
  }
  .ls-desc{
    margin:0; font-size:clamp(.7rem,.88vw,.82rem); line-height:1.45; font-weight:400;
    color:#76756f; max-width:38em;
    display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
  }
  .full-range-card:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
  .full-range-card:hover, .full-range-card:focus-visible{ z-index:30; }
  .full-range-card:hover .ls-box, .full-range-card:focus-visible .ls-box{
    height:calc(100% * var(--ls-gr)); box-shadow:0 16px 46px rgba(0,0,0,.28);
  }
  .full-range-card:hover .ls-pic, .full-range-card:focus-visible .ls-pic{ transform:scale(1.13); transition-duration:2.6s, .5s; }
  /* whole-pixel (even) strip heights: no half-pixel edges, so no hairline artefacts while a strip grows or the page scrolls */
  @supports (height:round(1px,1px)){
    .full-range-grid{ --ls-h:round(clamp(110px,12.4vw,260px),2px); }
    .full-range-card:hover .ls-box, .full-range-card:focus-visible .ls-box{ height:round(calc(var(--ls-h) * var(--ls-gr)),2px); }
  }
  @media (max-width:860px){
    .full-range-grid{ --ls-h:clamp(160px,46vw,220px); --ls-gr:1; }   /* touch: plain stack, tap opens the lamp */
    @supports (height:round(1px,1px)){ .full-range-grid{ --ls-h:round(clamp(160px,46vw,220px),2px); } }
    .ls-view{ width:100%; -webkit-mask-image:none; mask-image:none; }
    /* phone: the photo stays clear; only a short white veil rises from the bottom edge, under the text */
    .ls-view::after{ content:''; position:absolute; inset:0; background:linear-gradient(180deg, rgba(255,255,255,0) 58%, rgba(255,255,255,.69) 82%, rgba(255,255,255,.76) 100%); }
    .ls-copy{ left:16px; right:16px; width:auto; top:auto; bottom:0; height:auto; transform:none; justify-content:flex-end; padding-bottom:7px; gap:.3em; }
    .ls-name{ font-size:clamp(1.45rem,6.6vw,2rem); }
    .ls-desc, .ls-num{ display:none; }   /* phone: just the lamp name */
  }
  @media (prefers-reduced-motion: reduce){
    .ls-box, .full-range-card .ls-pic{ transition:none; }
  }

  /* ---------- "Technical Information" -- a plain scrolling section (like
     collection-intro/full-range above, not part of the lamp-paging
     mechanic) placed after the lamp stack, listing the environmental and
     electrical specs shared across the whole collection rather than any
     one lamp. Deliberately reuses the specifications popup's own visual
     language (uppercase small-caps row labels, --rule/--bg-deep from the
     same palette, tabular-nums) instead of introducing a new one, per the
     client's explicit request to keep it "in the same style as the popup
     windows with lamp specifications." */
  .tech-info{
    padding:clamp(56px,9vh,110px) clamp(24px,5vw,64px) clamp(80px,11vh,140px);
    max-width:1280px; margin:0 auto;
    /* Guarantees the content after the pinned lamp stack is always at
       least one viewport tall, so on very tall browser windows there is
       always enough room to scroll fully clear of the last lamp's own
       frame -- otherwise the page could run out of scrollable area a few
       pixels short of that boundary and leave the last lamp's fixed pin
       permanently covering this section. Centers vertically on the
       (rare) tall viewports where this padding alone doesn't reach a
       full viewport height; on ordinary viewports it has no visible
       effect since the content already exceeds 100vh. */
    min-height:100vh; box-sizing:border-box;
    display:flex; flex-direction:column; justify-content:center;
  }
  .tech-info-head{max-width:none; margin:0 0 clamp(20px,3vh,32px);}
  .tech-info-head h2{
    margin:0 0 .4em; font-size:clamp(1.9rem,3.4vw,2.7rem); font-weight:600;
    line-height:1.15; letter-spacing:-.01em; color:var(--ink);
  }
  .tech-info-head h3{
    margin:0; font-size:clamp(1.05rem,1.7vw,1.4rem); line-height:1.55; font-weight:300;
    color:var(--ink-soft);
  }
  .tech-info-table{
    width:100%; border-collapse:collapse; font-variant-numeric:tabular-nums;
  }
  .tech-info-table tr:nth-child(odd){ background:var(--bg-deep); }
  .tech-info-table th, .tech-info-table td{
    padding:clamp(6px,.9vh,9px) clamp(16px,2.4vw,28px);
    text-align:left; vertical-align:baseline; font-weight:400;
    font-size:clamp(.78rem,1.5vw,.9rem); line-height:1.3;
  }
  .tech-info-table th{
    width:38%; font-weight:600; letter-spacing:.02em; text-transform:none;
    font-size:.74rem; color:var(--ink);
  }
  .tech-info-table td{ color:var(--ink-soft); }
  @media (max-width:640px){
    .tech-info-table th, .tech-info-table td{ padding:7px 16px; }
    .tech-info-table th{ width:48%; font-size:.74rem; }
    .tech-info-table td{ font-size:.76rem; }
  }

  /* ---------- "Customization" -- an image+copy block appended to Technical
     Information, illustrating that every luminaire can be built to custom
     sizes, with a custom frame finish (texture imitation, printed
     pattern/logo, or any RAL colour). The client's reference photo is a
     wide panoramic strip (four samples side by side), so this stacks
     image-on-top, full-width copy below -- unlike .collection-intro's
     (former) image-left/copy-right, which assumed a tall/square image and
     would letterbox or crop a strip this wide. Briefly grew a right-hand
     quote column ("Some interiors are remembered...") per an earlier
     client sketch; that quote has since moved to its own full-bleed
     .collection-banner section higher up the page (see that block's
     comment), and this reverted back to its original single-column,
     quote-less layout per the client's explicit instruction. */
  .tech-custom{
    margin-top:clamp(32px,5vh,56px); padding-top:clamp(24px,3.6vh,36px);
    border-top:1px solid var(--rule);
  }
  .tech-custom-copy{
    display:grid; grid-template-columns:minmax(0,5fr) minmax(0,7fr); gap:clamp(20px,4vw,64px);
    align-items:start; margin:0 0 clamp(24px,3.6vh,40px);
  }
  .tech-custom-copy h3{
    margin:0; font-size:clamp(1.5rem,2.6vw,2.1rem); font-weight:600; line-height:1.18;
    letter-spacing:-.01em; color:var(--ink);
  }
  .tech-custom-text p{
    margin:0 0 .9em; font-size:clamp(.92rem,1.2vw,1.02rem); line-height:1.65; font-weight:300; color:var(--ink-soft);
  }
  .tech-custom-text p:last-child{ margin-bottom:0; }
  .tech-custom-grid{
    display:grid; grid-template-columns:repeat(4,1fr); gap:3px;   /* all four in one row (2 x 2 on small screens, below) */
  }
  .tech-custom-tile{ margin:0; }
  .tct-img{ overflow:hidden; background:var(--bg-deep); aspect-ratio:1/1; }
  .tct-img img{
    width:100%; height:100%; object-fit:cover; display:block;
    transition:transform .6s cubic-bezier(.25,.7,.3,1);
  }
  /* tile 04: the RAL badge sits on a close-up of the powder-coated profile (dark, fine-grain finish) */
  .tct-img.is-ral{ position:relative; background:#2b2b2b; }
  .tct-img.is-ral .tct-bg{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
  .tct-img.is-ral .tct-badge{
    position:absolute; left:50%; top:50%; width:60%; height:auto; transform:translate(-50%,-50%);
    filter:drop-shadow(0 6px 14px rgba(0,0,0,.45)); transition:transform .6s cubic-bezier(.25,.7,.3,1);
  }
  .tech-custom-tile:hover .tct-img.is-ral .tct-badge{ transform:translate(-50%,-50%) scale(1.05); }
  .tech-custom-tile:hover .tct-img:not(.is-ral) img{ transform:scale(1.05); }
  .tech-custom-tile figcaption{
    display:flex; align-items:baseline; gap:.7em; padding:.8em 0 0;
    font-size:.86rem; font-weight:600; color:var(--ink);
  }
  .tct-num{ font-size:.64rem; font-weight:600; letter-spacing:.14em; color:var(--accent); }
  @media (max-width:820px){
    .tech-custom-copy{ grid-template-columns:1fr; gap:14px; }
    .tech-custom-grid{ grid-template-columns:repeat(2,1fr); gap:3px 3px; }
    .tech-custom-tile figcaption{ padding-bottom:.9em; }
  }

  /* ---------- "Ordering Information" -- how to build a model code, plain
     scrolling content after Technical Information, same non-pinned
     pattern as .full-range/.tech-info. Reuses the site's own tag/label
     vocabulary (sharp 2px-radius outlined tags, not the reference
     mock-up's fully-rounded pills) so it reads as part of this site
     rather than a pasted-in diagram. */
  .order-info{
    padding:clamp(56px,9vh,110px) clamp(24px,5vw,64px) clamp(64px,9vh,120px);
    max-width:1280px; margin:0 auto; border-top:1px solid var(--rule);
  }
  .order-info-head{max-width:none; margin:0 0 clamp(20px,3vh,32px);}
  .order-info-head h2{
    margin:0 0 .5em; font-size:clamp(1.7rem,3vw,2.4rem); font-weight:600;
    line-height:1.18; letter-spacing:-.01em; color:var(--ink);
  }
  .order-info-head p{
    margin:0; font-size:clamp(1.05rem,1.7vw,1.4rem); line-height:1.55; font-weight:300; color:var(--ink-soft);
  }
  .order-legend{
    display:flex; flex-wrap:wrap; gap:1.8em; margin:0 0 clamp(28px,4.2vh,44px);
    padding-bottom:clamp(20px,3vh,32px); border-bottom:1px solid var(--rule);
  }
  .order-legend-item{display:flex; align-items:center; gap:.65em; font-size:.76rem; color:var(--ink-soft);}
  .order-legend-swatch{width:30px; height:18px; border:1.5px solid var(--ink); flex:none;}
  .order-legend-swatch.is-optional{border-color:var(--accent);}
  .order-columns{
    display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
    gap:clamp(24px,3.4vw,40px) clamp(16px,2.6vw,28px);
  }
  .order-col h4{
    margin:0 0 .8em; padding-bottom:.5em; border-bottom:1px solid var(--rule);
    font-size:.64rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-soft);
  }
  .order-col-tags{display:flex; flex-direction:column; gap:.6em;}
  .order-tag{
    display:flex; flex-direction:column; gap:.2em; align-items:flex-start;
    border:1.5px solid var(--ink); padding:.55em .8em;
    font-size:.74rem; font-weight:600; letter-spacing:.02em; color:var(--ink);
    background:var(--bg);
  }
  .order-tag small{
    font-size:.64rem; font-weight:400; letter-spacing:0; color:var(--ink-soft); line-height:1.35;
  }
  .order-tag.is-optional{ border-color:var(--accent); color:var(--accent); }
  .order-tag.is-optional small{ color:var(--ink-soft); }
  /* Ordering: all eight columns sit in one row on desktop (tighter gaps and tags),
     and fall back to the wrapping grid below 1100px. */
  @media (min-width:1100px){
    .order-columns{ grid-template-columns:repeat(8,minmax(0,1fr)); gap:20px 12px; }
    .order-col h4{ min-height:2.7em; display:flex; align-items:flex-end; font-size:.58rem; letter-spacing:.06em; line-height:1.3; }
    .order-col .order-tag{ padding:.5em .6em; font-size:.7rem; overflow-wrap:anywhere; }
    .order-col .order-tag small{ font-size:.6rem; }
  }
  .order-example{
    margin-top:clamp(36px,5.4vh,56px); padding-top:clamp(24px,3.6vh,36px); border-top:1px solid var(--rule);
  }
  .order-example h4{
    margin:0 0 1em; font-size:.64rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-soft);
  }
  .order-example-chain{
    display:flex; flex-wrap:wrap; align-items:center; gap:.55em; margin:0 0 1.1em;
  }
  .order-example-chain .order-tag{ padding:.45em .7em; font-size:.72rem; }
  .order-example-plus{ color:var(--ink-soft); font-size:.9rem; }
  .order-example-code{
    margin:0; font-size:clamp(.95rem,1.6vw,1.15rem); font-weight:600; letter-spacing:-.01em; color:var(--ink);
  }
  @media (max-width:640px){
    .order-columns{grid-template-columns:repeat(2,1fr);}
    /* The order-example chain (Vega + S + Type 27 + NW + ...) has too many
       tags to fit one mobile-width line at readable size, so flex-wrap was
       breaking it into a ragged two-row block. Keep it a single line by
       disabling wrap and letting it scroll horizontally instead -- tags
       keep their normal size/legibility and the row scrolls edge-to-edge. */
    .order-example-chain{
      flex-wrap:nowrap; overflow-x:auto; -webkit-overflow-scrolling:touch;
      padding-bottom:.5em; margin-bottom:.6em; scrollbar-width:none;
    }
    .order-example-chain::-webkit-scrollbar{ display:none; }
    .order-example-chain .order-tag,
    .order-example-chain .order-example-plus{ flex:0 0 auto; }
  }

  /* ---------- Download -- a single plain section offering the full
     catalogue PDF, between Ordering Information and Contact. Same
     non-pinned scrolling pattern and head styling as .order-info /
     .contact-info; the download control reuses .btn-specs so it reads as
     part of the same button vocabulary as "View Specifications" / "Send
     Message" rather than introducing a new style. */
  .download-info{
    padding:clamp(56px,9vh,110px) clamp(24px,5vw,64px) clamp(64px,9vh,120px);
    max-width:1280px; margin:0 auto; border-top:1px solid var(--rule);
  }
  .download-info-head{max-width:none; margin:0 0 clamp(24px,3.4vh,36px);}
  .download-info-head h2{
    margin:0 0 .5em; font-size:clamp(1.7rem,3vw,2.4rem); font-weight:600;
    line-height:1.18; letter-spacing:-.01em; color:var(--ink);
  }
  .download-btns{ display:flex; flex-wrap:wrap; gap:12px; margin-top:clamp(18px,2.6vh,28px); }
  .download-btns .btn-specs{ align-self:auto; border-radius:16px; }
  .download-info-head p{
    margin:0; font-size:clamp(1.05rem,1.7vw,1.4rem); line-height:1.55; font-weight:300; color:var(--ink-soft); max-width:none;
  }

  /* ---------- Contact -- plain scrolling content after Ordering
     Information, reusing the specs-popup/tech-info visual vocabulary
     (--bg-deep zebra tone, uppercase small-caps labels) for the
     info panel, plus a lightweight contact form that hands off to the
     visitor's own mail client (no backend on a static single-file
     page). */
  .contact-info{
    padding:clamp(56px,9vh,110px) clamp(24px,5vw,64px) clamp(80px,11vh,140px);
    max-width:1280px; margin:0 auto; border-top:1px solid var(--rule);
  }
  .contact-info-head{max-width:none; margin:0 0 clamp(28px,4.2vh,44px);}
  .contact-info-head h2{
    margin:0 0 .5em; font-size:clamp(1.7rem,3vw,2.4rem); font-weight:600;
    line-height:1.18; letter-spacing:-.01em; color:var(--ink);
  }
  .contact-info-head p{
    margin:0 0 .5em; font-size:clamp(1.05rem,1.7vw,1.4rem); line-height:1.55; font-weight:300; color:var(--ink-soft);
  }
  .contact-panel{
    display:grid; grid-template-columns:minmax(0,1.3fr) minmax(0,1fr);
    border:1px solid var(--rule); margin-bottom:clamp(40px,5.6vh,64px);
  }
  .contact-panel-left{ padding:clamp(28px,4vw,44px); }
  .contact-panel-left h4{
    margin:0 0 1em; font-size:.92rem; font-weight:600; color:var(--ink);
  }
  .contact-panel-left ul{ margin:0; padding:0; list-style:none; }
  .contact-panel-left li{
    position:relative; padding:.55em 0 .55em 1.3em; font-size:.86rem; color:var(--ink-soft);
    border-top:1px solid var(--rule); line-height:1.5;
  }
  .contact-panel-left li:first-child{ border-top:none; }
  .contact-panel-left li::before{
    content:""; position:absolute; left:0; top:1.05em;
    width:6px; height:6px; border-radius:50%; background:var(--accent);
  }
  .contact-panel-right{
    background:var(--bg-deep); padding:clamp(28px,4vw,44px);
    display:flex; flex-direction:column; gap:clamp(20px,3vh,32px); justify-content:center;
    border-left:1px solid var(--rule);
  }
  .contact-detail-label{
    display:block; margin:0 0 .5em; font-size:.66rem; font-weight:600;
    letter-spacing:.12em; text-transform:uppercase; color:var(--ink-soft);
  }
  .contact-detail a{
    display:block; font-size:clamp(1.05rem,1.8vw,1.25rem); font-weight:600;
    color:var(--ink); text-decoration:none; letter-spacing:-.01em;
  }
  .contact-detail a + a{ margin-top:.2em; }
  .contact-detail a:hover{ color:var(--accent); }
  @media (max-width:760px){
    .contact-panel{grid-template-columns:1fr;}
    .contact-panel-right{border-left:none; border-top:1px solid var(--rule);}
  }

  .contact-form{ max-width:640px; }
  .contact-form h4{
    margin:0 0 1.2em; font-size:.64rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-soft);
  }
  .contact-form-grid{
    display:grid; grid-template-columns:repeat(2,1fr); gap:clamp(16px,2.4vw,24px);
    margin-bottom:clamp(16px,2.4vw,24px);
  }
  .contact-field{ display:flex; flex-direction:column; gap:.5em; }
  .contact-field.is-wide{ grid-column:1 / -1; }
  .contact-field span{
    font-size:.66rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-soft);
  }
  .contact-field input, .contact-field textarea{
    font-family:inherit; font-size:.88rem; color:var(--ink);
    border:none; border-bottom:1.5px solid var(--rule); background:transparent;
    padding:.5em 0; transition:border-color .15s; resize:vertical;
  }
  .contact-field input:focus, .contact-field textarea:focus{
    outline:none; border-color:var(--accent);
  }
  .contact-form-note{
    margin:1.1em 0 0; font-size:.74rem; color:var(--ink-soft);
  }
  .contact-form-note.is-success{ color:var(--accent); }
  .contact-form-note.is-error{ color:#b3392c; }
  .contact-form-captcha{ margin:0 0 clamp(16px,2.4vw,24px); }
  .contact-form button[disabled]{ opacity:.55; cursor:default; }

  @media (max-width:480px){
    .contact-form-grid{grid-template-columns:1fr;}
    .contact-field.is-wide{grid-column:auto;}
    .contact-form-captcha .g-recaptcha{ transform:scale(0.92); transform-origin:0 0; }
  }

  /* ---------- "back to top" link -- sits between Contact and the footer,
     per the client's request, so a visitor who has scrolled through the
     whole page (lamp stack included) doesn't have to scroll all the way
     back up by hand. Targets #lampStack -- the existing <main> element,
     already the very first normal-flow element right below the fixed
     header -- rather than adding a new anchor id; jumping there lands
     scrollY at 0 same as the top of the page. A plain anchor jump (no
     smooth-scroll), matching every other in-page link on this build --
     see the standing lesson elsewhere in this project against a global
     scroll-behavior:smooth. */
  .back-to-top{
    display:flex; justify-content:center;
    padding:clamp(28px,4vh,40px) 24px 0;
  }
  .back-to-top-link{
    display:inline-flex; flex-direction:column; align-items:center; gap:.55em;
    text-decoration:none; color:var(--ink-soft);
  }
  .back-to-top-arrow{
    display:flex; align-items:center; justify-content:center;
    width:40px; height:40px; border:1px solid var(--rule); border-radius:50%;
    font-size:1rem; line-height:1; color:var(--ink);
    transition:border-color .15s, transform .15s;
  }
  .back-to-top-link:hover .back-to-top-arrow,
  .back-to-top-link:focus-visible .back-to-top-arrow{
    border-color:var(--ink); transform:translateY(-3px);
  }
  .back-to-top-label{
    font-size:.64rem; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
  }
  @media (prefers-reduced-motion:reduce){
    .back-to-top-arrow{transition:none;}
  }

  /* ---------- site footer -- the last thing on the page, after Contact.
     Plain, quiet, normal-flow content (like Order/Download/Contact above
     it) -- not part of the lamp-paging mechanic. Carries the ownership
     line the client asked for: all imagery and text on the site belongs
     to Vecta Design LTD. The year is filled in by a one-line script at
     the bottom of the page (see the stacking-mechanic IIFE) rather than
     hardcoded, so it never needs a rebuild just to stay current. */
  .site-footer{
    padding:clamp(24px,3.6vh,36px) clamp(24px,5vw,64px);
    background:var(--bg-deep);
    border-top:1px solid var(--rule);
  }
  .site-footer-inner{
    max-width:1280px; margin:0 auto;
    display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
    gap:10px 24px;
  }
  .site-footer-brand{
    font-size:.78rem; font-weight:600; letter-spacing:.03em; color:var(--ink);
    white-space:nowrap;
  }
  .site-footer-brand .of{ color:var(--accent); font-weight:400; }
  .site-footer-brand .by{
    font-weight:400; color:var(--ink-soft); margin-left:.6em;
    padding-left:.6em; border-left:1px solid var(--rule);
  }
  .site-footer-copy{
    margin:0; font-size:.7rem; line-height:1.6; color:var(--ink-soft);
    max-width:620px; text-align:right;
  }
  @media (max-width:640px){
    .site-footer-inner{flex-direction:column; align-items:flex-start;}
    .site-footer-copy{text-align:left;}
  }

  /* ---------- the stacking mechanic ----------
     .lamp-frame is exactly one viewport tall and sits in normal flow. A
     script (bottom of page) toggles one class, .is-pinned, onto exactly one
     .lamp-block at a time, giving it position:fixed so it fills the
     viewport below the header; every other block is plain, unpositioned
     flow. Which block is pinned is driven mainly by discrete steps (one
     mouse-wheel notch = one block), with real scroll position (touch,
     trackpad, the scrollbar) kept in sync as a fallback so nothing breaks
     if the visitor scrolls the ordinary way instead. This is deliberately
     NOT position:sticky: some browsers/extensions selectively neutralise
     sticky (position:fixed does not have that problem, and is supported
     everywhere).

     Desktop-only: below the site's mobile breakpoint (max-width:820px,
     matching .main-nav/.nav-toggle) the script disables itself entirely (see
     assets/js/site.js) and the media query right below renders every lamp
     block as a plain, statically-stacked card in normal document flow --
     the visitor just scrolls the page top to bottom, same as any other
     section, with no pin/slide effect and no "card flips on its own". */
  .lamp-frame{
    position:relative;
    /* Keeps a "Full Range" card's #lamp-<name> anchor jump (used on mobile,
       where the pin mechanic -- and its own JS-driven goTo() jump -- is
       switched off, see below) from landing the target right under the
       64px fixed header. */
    scroll-margin-top:64px;
    height:100vh;
    height:100dvh;
  }
  .lamp-block{
    height:calc(100vh - 64px);
    height:calc(100dvh - 64px);
    background:var(--bg);
    box-shadow:var(--shadow);
    display:flex; flex-direction:column;
    overflow:hidden;
  }
  .lamp-block.is-pinned{
    position:fixed;
    top:64px; left:0; right:0;
    z-index:calc(var(--i, 1) * 10);
    transition:transform .55s cubic-bezier(.65,0,.35,1);
  }
  /* A block that isn't pinned yet still sits in normal flow at full
     viewport height (so the document has the right scroll length), which
     means its static position can land almost exactly under the viewport
     right as the scroll threshold is crossed. Left visible, that produces
     a visible "jump" the instant it gets pinned and yanked off-screen to
     start its slide-in -- most noticeable scrolling down, since a
     later block's flow position sits right at the boundary, whereas an
     earlier block (scrolling up) is long since scrolled past and already
     off-screen. Hiding anything not pinned removes that jump entirely. */
  .lamp-block:not(.is-pinned){visibility:hidden;}
  @media (prefers-reduced-motion: reduce), (max-width:820px){
    .lamp-frame{height:auto !important;}
    .lamp-block{position:static !important; height:auto !important; box-shadow:none; visibility:visible !important;}
  }
  .lamp-block.is-placeholder{background:var(--bg-deep);}

  .lamp-inner{
    flex:1; min-height:0;
    display:grid;
    grid-template-columns:minmax(0,44%) minmax(0,1fr) minmax(280px,26%);
    gap:0;
  }
  .lamp-media{
    position:relative; overflow:hidden; background:#0b0b0c;
  }
  .lamp-media img{width:100%; height:100%; object-fit:cover; display:block;}
  .lamp-media .ph-tag{
    position:absolute; left:20px; bottom:20px;
    font-size:.65rem; letter-spacing:.12em; text-transform:uppercase;
    color:#fff; background:rgba(0,0,0,.5); padding:.4em .8em; border-radius:2px;
  }
  /* "Gallery" button, overlaid on the lamp photo -- only present for lamps
     that actually have an interior photo set supplied (see the PROJECTS
     data object in the script below; a lamp with no entry there simply
     never gets this button rendered). Opaque white fill (not a
     transparent/ghost button) plus a dark border and its own drop shadow so
     it reads clearly regardless of how light or dark the underlying photo
     is -- the same problem .ph-tag above solves with a dark backing instead. */
  .lamp-projects-btn{
    position:absolute; left:50%; bottom:26px; transform:translateX(-50%);
    z-index:2;
    background:#fff; border:1.5px solid var(--ink); color:var(--ink);
    padding:.85em 1.5em; font-size:.72rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase;
    cursor:pointer; display:inline-flex; align-items:center; gap:.6em;
    box-shadow:0 14px 34px -8px rgba(0,0,0,.45);
    transition:background .15s, color .15s;
  }
  .lamp-projects-btn:hover, .lamp-projects-btn:focus-visible{background:var(--ink); color:#fff;}
  .ph-visual{
    width:100%; height:100%;
    display:flex; align-items:center; justify-content:center;
    background:
      repeating-linear-gradient(135deg, transparent 0 18px, var(--rule) 18px 19px);
  }
  .ph-visual span{
    font-size:clamp(2.6rem,6vw,4.2rem); font-weight:500; color:var(--ink-soft); opacity:.35;
  }

  .lamp-copy{
    padding:clamp(10px,2.5vh,32px) clamp(20px,3vw,44px);
    display:flex; flex-direction:column; justify-content:safe center; gap:clamp(.3em,1.1vh,.7em);
    min-width:0;
    overflow-y:auto; max-height:100%;
  }
  .lamp-index{
    font-size:.66rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase;
    color:var(--ink-soft);
  }
  .lamp-desc{
    font-size:clamp(.72rem,1.7vh,.85rem); line-height:1.4; color:var(--ink);
    /* No artificial character-count wrap -- lines use the full copy-column
       width, not an arbitrary ch-based cap that would leave the right side
       empty. Left-aligned (ragged-right), not justified: with Inter's
       ExtraLight 200 body weight, justifying stretches inter-word spacing
       to hit the right edge, and on already-thin, low-contrast strokes
       that stretch reads as noticeably large, uneven gaps between words --
       worse than an uneven right edge. Left-align keeps natural, even word
       spacing; hyphens stay off so no word ever breaks mid-line either way. */
    max-width:none;
    text-align:left; hyphens:none;
  }
  .lamp-facts{
    margin:0; max-width:none;
    display:flex; flex-direction:column; gap:clamp(.12em,.5vh,.3em);
  }
  .lamp-fact{
    display:grid; grid-template-columns:76px 1fr; gap:0 .6em;
    font-size:clamp(.64rem,1.5vh,.74rem); line-height:1.35;
  }
  .lamp-fact dt{
    margin:0; padding-top:.1em;
    font-weight:600; letter-spacing:.05em; text-transform:uppercase; font-size:.9em;
    color:var(--ink);
  }
  .lamp-fact dd{
    margin:0; color:var(--ink-soft);
    /* Left-aligned, no hyphen-breaks -- same reasoning as .lamp-desc above:
       at Inter's ExtraLight 200 weight, justifying this already-narrow
       Housing/Mounting/Lamping column stretched word-gaps even more
       noticeably than in the wider description column. Hyphens stay off
       either way, so a word is never broken mid-line. */
    text-align:left; hyphens:none;
  }
  /* The figure grows to fill whatever vertical space the rest of the column
     doesn't use (flex:1 on a flex-column parent), so the drawing is always
     as large as the layout allows -- self-adjusting per lamp and per screen
     height, rather than a fixed size someone has to keep re-tuning. */
  .lamp-drawing{
    flex:1 1 0%; min-height:0; width:100%; margin:0;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    gap:.35em;
  }
  .lamp-drawing img{
    flex:1 1 auto; min-height:0;
    max-width:100%; max-height:100%; width:auto; height:auto;
    object-fit:contain; display:block;
  }
  .lamp-drawing img[data-enlarge]{cursor:zoom-in;}
  .lamp-drawing figcaption{
    flex:none;
    font-size:.58rem; color:var(--ink-soft); letter-spacing:.04em;
    white-space:nowrap;
  }
  .drawing-hint{
    flex:none;
    font-size:.52rem; letter-spacing:.06em; text-transform:uppercase; color:var(--ink-soft); opacity:.65;
  }
  .btn-specs{
    align-self:center;
    border:1px solid var(--ink); background:transparent; color:var(--ink);
    padding:.85em 1.5em; font-size:.72rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase;
    cursor:pointer; display:inline-flex; align-items:center; gap:.6em;
    transition:background .15s, color .15s;
  }
  .btn-specs:hover, .btn-specs:focus-visible{background:var(--ink); color:var(--bg);}

  .lamp-title{
    display:grid; grid-template-rows:1fr auto;
    padding:clamp(20px,3vw,40px) clamp(20px,3vw,40px) clamp(22px,3vw,34px);
    border-left:1px solid var(--rule);
    container-type:inline-size;
  }
  .lamp-title h2{
    align-self:center; justify-self:center;
    margin:0; text-align:center;
    /* Sized against the title column's own width (not the viewport's) so
       it always scales to the space actually available -- a name longer
       than "Circle"/"Accord" (more lamps are coming later) still fits. */
    font-size:clamp(2.9rem,20cqw,5.6rem); line-height:.95;
    font-weight:300; letter-spacing:0; text-transform:uppercase;
    text-wrap:balance;
  }

  /* Per-lamp status navigation, sitting at the bottom of the title column --
     a quiet "01 / 12" readout plus Prev/Next, so the collection's sequence
     and this lamp's place in it are visible without opening the menu. Prev
     on the first lamp and Next on the last are rendered disabled (baked in
     per block, not toggled at runtime) rather than wrapping around. */
  .lamp-nav{
    display:flex; align-items:center; justify-content:center; gap:12px;
  }
  .lamp-nav-btn{
    display:inline-flex; align-items:center; gap:.5em;
    background:none; border:none; padding:0; margin:0; cursor:pointer;
    font-family:inherit; font-size:.66rem; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
    color:var(--ink-soft); transition:color .15s;
  }
  .lamp-nav-btn:hover, .lamp-nav-btn:focus-visible{color:var(--ink);}
  .lamp-nav-btn:disabled{opacity:.3; cursor:default;}
  .lamp-nav-btn:disabled:hover{color:var(--ink-soft);}
  .lamp-nav-arrow{font-size:.85em; line-height:1;}
  .lamp-nav-count{
    font-size:.66rem; font-weight:600; letter-spacing:.1em; color:var(--ink); white-space:nowrap;
  }
  .lamp-nav-track{
    position:relative; width:56px; height:2px; border-radius:2px;
    background:var(--rule); overflow:hidden; flex:none;
  }
  .lamp-nav-fill{
    position:absolute; left:0; top:0; height:100%; background:var(--accent); border-radius:inherit;
  }

  @media (max-width:980px){
    .lamp-inner{grid-template-columns:1fr; overflow-y:auto;}
    /* Hide the scroll container's own scrollbar -- this is purely cosmetic
       (overflow-y:auto stays as a safety net for an unusually short
       viewport or long description), so a stray scrollbar sliver never
       shows down the right edge of a mobile lamp card. */
    .lamp-inner{scrollbar-width:none; -ms-overflow-style:none;}
    .lamp-inner::-webkit-scrollbar{width:0; height:0; display:none;}
    .lamp-copy{overflow-y:visible; max-height:none; scrollbar-width:none; -ms-overflow-style:none;}
    .lamp-copy::-webkit-scrollbar{width:0; height:0; display:none;}
    .lamp-title{border-left:none; border-top:1px solid var(--rule); padding:14px clamp(20px,4vw,32px) 16px;}
    /* The lamp name is what was eating most of the mobile card's height
       (20cqw against a full-viewport-width container, plus two-line names
       like "Crown Moss") -- shrinking it directly reclaims the room
       .lamp-copy needs to fit without its own internal scroll. */
    .lamp-title h2{text-align:center; font-size:clamp(1.4rem,7.5vw,2.1rem); line-height:1.08;}
    .lamp-nav{gap:8px;}
    .lamp-nav-track{width:40px;}
    .lamp-media{height:24vh;}
    /* Every lamp photo is a ceiling shot with the fixture itself sitting in
       the upper part of the frame -- the desktop crop is tall enough that
       object-fit:cover's default center anchor still keeps the fixture in
       view, but squeezed into this short 24vh mobile strip, centering crops
       equally off the top and bottom and cuts the fixture out entirely,
       leaving just room interior. Anchoring to the top keeps the ceiling/
       fixture area in frame and lets the crop lose bottom-of-room content
       (floor, desks, people) instead, which is what actually needs to be
       visible here. */
    .lamp-media img{object-position:center top;}
    /* The 24vh mobile photo strip is short -- shrink the button so it sits
       comfortably inside it rather than dominating the whole strip. */
    .lamp-projects-btn{bottom:10px; padding:.55em 1em; font-size:.62rem; gap:.4em; box-shadow:0 8px 20px -6px rgba(0,0,0,.45);}
    /* The dimension drawing's own image is what a phone-width column has
       the least room for, and per the client's request it's dropped
       entirely on mobile -- only the plain size line (the drawing's own
       figcaption) stays, so a shopper can still see "L / XL" dimensions
       without the cramped drawing + its "click to enlarge" hint (which
       has nothing to click any more) squeezing everything else. */
    .lamp-drawing{flex:none; display:block; width:auto; margin-top:.4em;}
    .lamp-drawing img, .lamp-drawing .drawing-hint{display:none;}
    /* Center the remaining size line under the copy column -- it reads as
       floating/misaligned if left-flush while everything else on this
       card (the name, the specs button) is horizontally centered. */
    .lamp-drawing figcaption{
      display:block; white-space:normal; text-align:center;
      font-size:.66rem; line-height:1.4;
    }
    /* Trim the copy column's own padding/gaps and text size a notch
       further -- the longest product descriptions (Crown, Polo, ...)
       still overflowed a common phone height (~844px) even after the
       drawing/title savings above, so every remaining dimension gets a
       small, still-readable haircut rather than leaning on the (now
       invisible) scrollbar as the everyday fallback. */
    .lamp-copy{padding:10px clamp(16px,4vw,28px) 16px; gap:.4em;}
    .lamp-desc{font-size:.76rem; line-height:1.42;}
    .lamp-fact{font-size:.7rem; line-height:1.32;}
  }

  @media (prefers-reduced-motion:reduce), (max-width:820px){
    /* Prev/Next only make sense while the pin mechanic is driving which
       lamp is on screen -- with it switched off below 820px (see the
       stacking-mechanic comment above), the visitor is just scrolling the
       page normally, so this readout/button pair would be dead weight. */
    .lamp-nav{display:none;}
  }

  .demo-flag{
    position:absolute; top:14px; left:14px; z-index:2;
    font-size:.62rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase;
    background:var(--accent); color:var(--accent-ink); padding:.4em .7em; border-radius:2px;
  }

  /* ---------- drawing lightbox (click a dimension drawing to enlarge) ---------- */
  .drawing-lightbox{
    position:fixed; inset:0; background:rgba(20,18,16,.82);
    display:flex; align-items:center; justify-content:center; padding:5vh 5vw;
    opacity:0; pointer-events:none; transition:opacity .2s ease;
    z-index:9500;
  }
  .drawing-lightbox.open{opacity:1; pointer-events:auto;}
  .drawing-lightbox img{
    max-width:100%; max-height:100%; background:#fff; padding:24px;
    box-shadow:0 40px 100px rgba(0,0,0,.4); cursor:zoom-out;
  }
  .drawing-lightbox-close{
    position:absolute; top:22px; right:26px;
    background:none; border:none; color:#fff; font-size:1.7rem; font-weight:400; cursor:pointer;
    width:44px; height:44px; display:flex; align-items:center; justify-content:center;
    border-radius:50%; transition:background .15s;
  }
  .drawing-lightbox-close:hover, .drawing-lightbox-close:focus-visible{background:rgba(255,255,255,.15);}
  @media (prefers-reduced-motion: reduce){
    .drawing-lightbox{transition:none;}
  }

  /* ---------- gallery lightbox (opened from the "Gallery" button on a
     lamp's photo) -- same dark full-screen backdrop as the drawing
     lightbox above, but the modal itself is a white card
     with a big photo plus a row of thumbnails underneath to switch between
     a lamp's real installation photos. ---------- */
  .projects-overlay{
    position:fixed; inset:0; background:var(--ink);
    opacity:0; pointer-events:none; transition:opacity .2s ease;
    z-index:9500;
  }
  .projects-overlay.open{opacity:1; pointer-events:auto;}
  .projects-lightbox{
    position:fixed; top:50%; left:50%; transform:translate(-50%,-50%) scale(.97);
    width:min(880px,92vw); height:90vh;
    background:#fff; box-shadow:0 40px 100px rgba(0,0,0,.4);
    display:flex; flex-direction:column; overflow:hidden;
    opacity:0; pointer-events:none; transition:opacity .2s ease, transform .2s ease;
    z-index:9501;
  }
  .projects-lightbox.open{opacity:1; pointer-events:auto; transform:translate(-50%,-50%) scale(1);}
  .projects-lightbox-head{
    display:flex; align-items:center; justify-content:space-between; gap:16px;
    padding:16px 20px 16px 26px; border-bottom:1px solid var(--rule); flex:none;
  }
  .projects-lightbox-head h3{margin:0; font-size:1.15rem; font-weight:600; letter-spacing:-.01em;}
  .projects-lightbox-close{
    background:none; border:none; color:var(--ink); font-size:1.4rem; font-weight:400; cursor:pointer;
    width:34px; height:34px; display:flex; align-items:center; justify-content:center;
    border-radius:50%; transition:background .15s;
  }
  .projects-lightbox-close:hover, .projects-lightbox-close:focus-visible{background:var(--bg-deep);}
  /* flex:1 1 0 (not the default "auto") plus min-height:0 forces this area to
     the space actually left over inside the lightbox's own FIXED height,
     instead of sizing itself to the image's intrinsic dimensions -- which is
     what let a tall/vertical photo stretch the whole card past its bounds
     and overlap the header above. Every photo -- horizontal, square or
     vertical -- now scales via object-fit:contain to fit this one fixed-size
     frame, so only the image's rendered WIDTH varies from photo to photo. */
  .projects-lightbox-main{
    position:relative;
    flex:1 1 0; min-height:0; background:var(--bg-deep);
    display:flex; align-items:center; justify-content:center; padding:16px;
  }
  .projects-lightbox-main img{max-width:100%; max-height:100%; object-fit:contain; display:block;}
  /* Square, sharp-cornered buttons -- same white-fill/dark-border/shadow
     language as .lamp-projects-btn (the "Gallery" button on the photo
     itself), for a consistent look across the whole feature rather than a
     generic round gallery-arrow style. */
  .projects-lightbox-arrow{
    position:absolute; top:50%; transform:translateY(-50%);
    width:44px; height:44px; border:1.5px solid var(--ink); cursor:pointer;
    background:#fff; color:var(--ink); padding:0;
    display:flex; align-items:center; justify-content:center;
    box-shadow:0 14px 34px -8px rgba(0,0,0,.45); transition:background .15s, color .15s;
  }
  .projects-lightbox-arrow svg{display:block;}
  .projects-lightbox-arrow:hover, .projects-lightbox-arrow:focus-visible{background:var(--ink); color:#fff;}
  .projects-lightbox-prev{left:14px;}
  .projects-lightbox-next{right:14px;}
  .projects-lightbox-thumbs{
    flex:none; display:flex; gap:10px; padding:14px 26px 20px; overflow-x:auto;
  }
  .projects-lightbox-thumbs button{
    flex:none; width:64px; height:64px; padding:0; border-radius:2px; cursor:pointer;
    border:2px solid transparent; opacity:.6; transition:opacity .15s, border-color .15s;
    background:none;
  }
  .projects-lightbox-thumbs button img{width:100%; height:100%; object-fit:cover; display:block;}
  .projects-lightbox-thumbs button:hover{opacity:.85;}
  .projects-lightbox-thumbs button.active{opacity:1; border-color:var(--ink);}
  @media (max-width:600px){
    .projects-lightbox{width:94vw; height:90vh;}
    .projects-lightbox-head{padding:14px 16px 10px 20px;}
    .projects-lightbox-arrow{width:36px; height:36px;}
    .projects-lightbox-arrow svg{width:17px; height:17px;}
    .projects-lightbox-prev{left:8px;}
    .projects-lightbox-next{right:8px;}
    .projects-lightbox-thumbs{padding:10px 16px 16px; gap:8px;}
    .projects-lightbox-thumbs button{width:52px; height:52px;}
  }
  @media (prefers-reduced-motion: reduce){
    .projects-overlay, .projects-lightbox{transition:none;}
  }

  /* ---------- specifications popup ---------- */
  /* z-index is well above the lamp stack's own range (each pinned block gets
     an ever-increasing inline z-index as the visitor pages through the
     lamps -- see the stacking-mechanic script) so the popup can never end up
     rendered underneath whichever lamp is currently on screen. */
  .popup-overlay{
    position:fixed; inset:0; background:var(--overlay);
    opacity:0; pointer-events:none; transition:opacity .25s ease;
    z-index:9000;
  }
  .popup-overlay.open{opacity:1; pointer-events:auto;}

  .specs-popup{
    position:fixed; top:0; right:0; bottom:0;
    width:min(420px,92vw);
    background:var(--bg);
    border-left:1px solid var(--rule);
    transform:translateX(100%);
    transition:transform .3s ease;
    z-index:9001;
    display:flex; flex-direction:column;
    box-shadow:-30px 0 60px -30px rgba(0,0,0,.25);
  }
  .specs-popup.open{transform:translateX(0);}

  .specs-head{
    display:flex; align-items:center; justify-content:space-between;
    padding:clamp(6px,1.5vh,20px) 22px; border-bottom:1px solid var(--rule);
  }
  .specs-head span{font-size:.68rem; font-weight:600; letter-spacing:.14em; text-transform:uppercase; color:var(--ink-soft);}
  .specs-close{
    background:none; border:none; color:var(--ink); cursor:pointer; font-size:1.1rem;
    width:30px; height:30px; display:flex; align-items:center; justify-content:center;
    border-radius:50%; transition:background .15s;
  }
  .specs-close:hover, .specs-close:focus-visible{background:var(--bg-deep);}

  .specs-title{padding:clamp(4px,1.2vh,20px) 22px 0;}
  .specs-title h3{margin:0 0 .2em; font-size:clamp(1.05rem,2.2vh,1.6rem); font-weight:600; letter-spacing:-.01em;}
  .specs-title p{margin:0; font-size:.72rem; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-soft);}

  .specs-tabs{
    display:flex; gap:22px; padding:clamp(4px,1.1vh,18px) 22px 0; border-bottom:1px solid var(--rule); margin-top:clamp(3px,.9vh,14px);
  }
  .specs-tab{
    background:none; border:none; padding:0 0 clamp(4px,.9vh,12px); cursor:pointer;
    font-size:.72rem; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
    color:var(--ink-soft); border-bottom:2px solid transparent; margin-bottom:-1px;
  }
  .specs-tab.active{color:var(--ink); border-color:var(--accent);}

  .specs-body{padding:clamp(8px,1.4vh,22px) 22px; overflow-y:auto; flex:1; min-height:0;}
  .specs-panel{display:none; flex-direction:column; gap:clamp(6px,1.5vh,26px);}
  .specs-panel.active{display:flex;}

  .specs-block h4{
    margin:0 0 clamp(3px,.6vh,10px); font-size:.66rem; font-weight:600; letter-spacing:.12em; text-transform:uppercase; color:var(--ink-soft);
  }
  table.spec-table{width:100%; border-collapse:collapse; font-size:clamp(.68rem,1.4vh,.82rem); font-variant-numeric:tabular-nums;}
  table.spec-table th{
    text-align:left; font-size:.6rem; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-soft);
    font-weight:600; padding:0 8px clamp(3px,.55vh,8px) 0; border-bottom:1px solid var(--rule);
  }
  table.spec-table td{padding:clamp(3px,.55vh,8px) 8px clamp(3px,.55vh,8px) 0; border-bottom:1px solid var(--rule); white-space:nowrap;}
  table.spec-table tr:last-child td{border-bottom:none;}
  /* The Size column holds a single short label (S / M / L / XL) -- pin it to
     its own content width so it never gets squeezed by the wider columns
     next to it and the letter never wraps onto its own second line. */
  table.spec-table th:first-child, table.spec-table td:first-child{width:1%; white-space:nowrap;}

  .variation-cards{display:grid; grid-template-columns:1fr 1fr; gap:10px;}
  .variation-card{background:var(--bg-deep); border:1px solid var(--rule); padding:clamp(6px,.9vh,12px) 14px;}
  .variation-card .vc-name{font-size:.76rem; font-weight:600; margin-bottom:.25em;}
  .variation-card .vc-flux{font-size:.64rem; color:var(--ink-soft); text-transform:uppercase; letter-spacing:.04em;}
  .variation-card .vc-h{font-size:.64rem; color:var(--ink-soft); margin-top:.35em;}
  .footnote{font-size:.64rem; color:var(--ink-soft); margin:clamp(6px,.9vh,14px) 0 0; padding-top:clamp(4px,.7vh,10px); border-top:1px solid var(--rule);}

  /* Customization note -- sits outside the three .specs-panel divs (not
     itself a panel), so it stays visible under every tab (Dimensions /
     Features / Options) rather than only the one it happens to be typed
     into, and is shared by the one popup markup every lamp reuses. Red
     (var(--accent)) and placed last, per the client's explicit request. */
  .specs-custom-note{
    margin:clamp(10px,1.6vh,18px) 0 clamp(4px,1vh,10px); padding-top:clamp(10px,1.6vh,18px);
    border-top:1px solid var(--rule); font-size:.72rem; line-height:1.6; font-weight:600; color:var(--accent);
  }

  .feature-list{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:clamp(8px,1.8vh,16px);}
  .feature-list li{display:flex; gap:12px; align-items:flex-start;}
  /* The moss-only rows (Decor, Type 22/27 mixing) are toggled per lamp via
     the plain `hidden` attribute in fillSpecs() below -- but `li{display:flex}`
     above is an author-origin rule, and author styles always beat the
     browser's own [hidden]{display:none} default regardless of specificity.
     So a hidden <li class="moss-feature"> was rendering anyway (e.g. Circle
     showing "Decor: Stabilized moss") even though el.hidden was correctly
     true. This explicit override restores the expected hide-when-hidden
     behavior for this list. */
  .feature-list li[hidden]{display:none;}
  .feature-list .dot{
    width:8px; height:8px; margin-top:.4em; border-radius:50%; background:var(--accent); flex:none;
  }
  .feature-list .flabel{font-size:.8rem; font-weight:600;}
  .feature-list .fdetail{font-size:.74rem; color:var(--ink-soft); margin-top:.15em;}

  @media (max-width:520px){
    .specs-popup{width:100vw;}
  }

  @media (prefers-reduced-motion: reduce){
    .popup-overlay, .specs-popup, .btn-specs, .main-nav a{transition:none;}
    .mobile-nav, .mobile-nav-overlay, .nav-toggle span{transition:none;}
  }

/* ---- Lamp popups (iframe host): above header (8000), below specs/gallery/lightbox (9000+) ---- */
html.sol-popup-open, html.sol-popup-open body{overflow:hidden;}
.sol-popup-host{
  position:fixed; inset:0; z-index:8500; background:#0b0b0c;
  opacity:0; transition:opacity .3s ease;
}
.sol-popup-host.is-on{opacity:1;}
.sol-popup-frame{
  position:absolute; inset:0; width:100%; height:100%; border:0; background:transparent;
  opacity:0; transition:opacity .4s ease;
}
.sol-popup-frame.is-ready{opacity:1;}
@media (prefers-reduced-motion: reduce){
  .sol-popup-host, .sol-popup-frame{transition:none;}
}

/* Subtitle paragraphs: balance the lines so a lone word ("PDF.") never dangles on its own row */
.full-range-head p:not(.section-eyebrow), .tech-info-head h3, .order-info-head p, .download-info-head p, .contact-info-head p, .tech-custom-text p{ text-wrap:balance; }

/* dark scrollbar for the host page while a lamp popup is open (the page scrollbar sits next to the dark popup) */
html.sol-popup-open{ color-scheme:dark; scrollbar-color:#3b3b3b #050505; }
html.sol-popup-open::-webkit-scrollbar{ width:12px; background:#050505; }
html.sol-popup-open::-webkit-scrollbar-track{ background:#050505; }
html.sol-popup-open::-webkit-scrollbar-thumb{ background:#3b3b3b; border-radius:6px; border:2px solid #050505; }

/* ---- Order / send-request modal (opens over the lamp popup) ---- */
.order-modal{ position:fixed; inset:0; z-index:9600; display:flex; align-items:center; justify-content:center; padding:16px; opacity:0; pointer-events:none; transition:opacity .25s ease; }
.order-modal.open{ opacity:1; pointer-events:auto; }
.order-backdrop{ position:absolute; inset:0; background:rgba(8,8,10,.62); }
.order-panel{ position:relative; width:min(640px,100%); max-height:calc(100vh - 32px); overflow:auto; background:var(--bg); color:var(--ink); border:1px solid var(--rule); border-radius:6px; padding:clamp(18px,3vw,30px); box-shadow:0 30px 80px -20px rgba(0,0,0,.55); transform:translateY(12px); transition:transform .3s ease; }
.order-modal.open .order-panel{ transform:none; }
.order-head{ display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:clamp(14px,2vw,22px); }
.order-head h3{ margin:0; font-size:1.05rem; font-weight:600; letter-spacing:.02em; }
.order-panel select{ width:100%; box-sizing:border-box; font-family:inherit; font-size:.88rem; color:var(--ink); background:transparent; border:none; border-bottom:1.5px solid var(--rule); border-radius:0; padding:.5em 0; }
.order-panel select:focus{ outline:none; border-color:var(--accent); }
.order-panel select option{ color:#111; }
.order-panel .contact-form-grid{ margin-bottom:clamp(14px,2vw,20px); }
.order-panel .btn-specs{ cursor:pointer; }
.order-panel button[disabled]{ opacity:.55; cursor:default; }
@media (max-width:640px){ .order-panel .contact-form-grid{ grid-template-columns:1fr; } .order-panel .contact-field.is-wide{ grid-column:auto; } .order-panel .g-recaptcha, .order-panel #orderCaptcha{ transform:scale(.92); transform-origin:0 0; } }
@media (prefers-reduced-motion: reduce){ .order-modal, .order-panel{ transition:none; } }
.order-panel [hidden]{ display:none !important; }
.order-checks{ border:0; padding:0; margin:0; min-width:0; }
.order-checks label{ display:flex; align-items:flex-start; gap:.6em; font-size:.88rem; color:var(--ink); padding:.35em 0; cursor:pointer; }
.order-checks input[type=checkbox]{ -webkit-appearance:none; appearance:none; width:16px; height:16px; flex:none; margin:.15em 0 0; padding:0; border:1.5px solid var(--ink-soft); border-radius:2px; background:#fff; position:relative; cursor:pointer; }
.order-checks input[type=checkbox]:checked{ background:var(--ink); border-color:var(--ink); }
.order-checks input[type=checkbox]:checked::after{ content:''; position:absolute; left:4px; top:1px; width:4px; height:8px; border:solid #fff; border-width:0 2px 2px 0; transform:rotate(45deg); }
.order-checks input[type=checkbox]:disabled{ cursor:default; }
.order-checks label.is-off{ opacity:.45; cursor:default; }
.order-code{ margin:0 0 clamp(14px,2vw,20px); padding:.8em 1em; background:var(--bg-deep); border-radius:3px; font-size:.84rem; line-height:1.5; }
.order-code span{ display:block; font-size:.62rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-soft); margin-bottom:.2em; }
.order-code b{ font-weight:600; word-break:break-word; }

/* Security checks are opt-in per form; existing design tokens and buttons stay. */
.sol-captcha-consent [hidden]{display:none!important;}
.sol-captcha-disclosure,.sol-captcha-status{font-size:.74rem;line-height:1.55;margin:.65em 0;color:var(--ink-soft);}
.sol-captcha-disclosure a{color:inherit;text-decoration:underline;text-underline-offset:3px;}
.sol-captcha-activate{margin:.25em 0 .65em;}
.sol-captcha-status:empty{display:none;}

/* Self-hosted Inter, same Google Inter v20 build; EN/ET/DE/IT/ES coverage.
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
*/
@font-face{font-family:"Inter";font-style:normal;font-weight:100 700;font-display:swap;src:url("/wp-content/uploads/fonts/Inter-google-v20-five-languages.woff2") format("woff2");}

/* ---------- unified rounded corners (one radius scale across the site) ----------
   cards 18px, buttons 16px, small cards/tags 10-12px.
   NOT applied to the "Ordering Information" block (.order-info), which stays square. */
.collection-feature-box{ border-radius:18px; }
.tct-img{ border-radius:18px; overflow:hidden; }
.contact-panel{ border-radius:18px; overflow:hidden; }
.btn-specs, .lamp-projects-btn, .sol-captcha-activate{ border-radius:16px; }
.projects-lightbox{ border-radius:18px; }
.projects-lightbox-arrow{ border-radius:14px; }
.projects-lightbox-thumbs button{ border-radius:10px; overflow:hidden; }
.projects-lightbox-thumbs button img{ border-radius:8px; }
.drawing-lightbox img{ border-radius:18px; }
.specs-popup{ border-radius:18px 0 0 18px; overflow:hidden; }
.variation-card{ border-radius:12px; }
.lamp-media .ph-tag{ border-radius:8px; }
.order-panel{ border-radius:18px; }
.order-code{ border-radius:12px; }
.order-checks input[type=checkbox]{ border-radius:5px; }
.hero-badge{ border-radius:16px; }
.tech-info-table tr th:first-child{ border-radius:10px 0 0 10px; }
.tech-info-table tr td:last-child{ border-radius:0 10px 10px 0; }
@media (max-width:520px){ .specs-popup{ border-radius:0; } }

  /* lamp outline right of the name in the Full Range strips (desktop only; the phone list shows just the name) */
  .ls-head{ display:flex; align-items:center; gap:clamp(14px,1.6vw,26px); min-width:0; }
  .ls-shape{ flex:none; display:block; height:clamp(28px,3.2vw,52px); width:auto; overflow:visible; fill:none; stroke:#6f6e6a; stroke-width:1.15px; opacity:.8; transition:stroke .4s ease, opacity .4s ease; }
  .ls-shape *{ vector-effect:non-scaling-stroke; }
  .full-range-card:hover .ls-shape, .full-range-card:focus-visible .ls-shape{ stroke:var(--accent); opacity:1; }
  @media (max-width:860px){ .ls-shape{ display:none; } }
