        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        html {
            color-scheme: light;
            background-color: white;
        }

        /* Native <dialog> modals — top-layer rendering, immune to header/viewport issues */
        dialog.cfg-modal {
            position: fixed;
            inset: 0;
            width: 100%;
            height: 100%;
            max-width: none;
            max-height: none;
            margin: 0;
            padding: 0;
            border: none;
            background: rgba(0, 0, 0, 0.18);
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0;
            transition: opacity 0.18s ease;
        }

        dialog.cfg-modal::backdrop {
            background: transparent;
        }

        dialog.cfg-modal .cfg-modal-box {
            background: #fff;
            border: 1px solid #000;
            padding: 28px 36px;
            display: flex;
            flex-direction: column;
            gap: 20px;
            font-family: inherit;
            font-size: 14px;
            letter-spacing: 0.02em;
        }

        dialog.cfg-modal .cfg-modal-btn {
            cursor: pointer;
            border: 1px solid #000;
            padding: 6px 16px;
            display: block;
            background: #fff;
            user-select: none;
            align-self: center;
            line-height: 1;
            font-family: inherit;
            font-size: inherit;
            letter-spacing: inherit;
        }

        /* Remove all focus outlines */
        *:focus {
            outline: none;
        }

        body {
            font-family: 'Inter', sans-serif;
            background-color: white;
            color: black;
            min-height: 100vh;
            -webkit-font-smoothing: antialiased;
            display: flex;
            flex-direction: column;
            color-scheme: light only;
            overflow-anchor: none;
        }

        /* Prevent horizontal scroll without breaking sticky */
        body > * {
            max-width: 100vw;
            overflow-x: clip;
        }

        @media (prefers-color-scheme: dark) {
            body {
                background-color: white !important;
                color: black !important;
            }
        }

        @media (hover: hover) and (pointer: fine) {
            .dropdown-menu a:hover {
                outline: 1px solid #fff;
                outline-offset: -1px;
            }

            .all-materials-link a:not(.page-cta-btn):hover {
                outline: 1px solid #000;
                outline-offset: -1px;
            }

            .footer-links a:hover:not(.insta-hover-btn) {
                outline: 1px solid #000;
                outline-offset: -1px;
            }

            .material-guide-item:hover {
                outline: 1px solid #000;
                outline-offset: -1px;
            }
        }

        ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }

        ::-webkit-scrollbar-track {
            background: transparent;
        }

        ::-webkit-scrollbar-thumb {
            background: black;
            border-radius: 0;
        }

        ::-webkit-scrollbar-thumb:hover {
            background: #333;
        }

        * {
            scrollbar-width: thin;
            scrollbar-color: black transparent;
        }

        /* The page scrollbar itself is never drawn.
           Firefox: any `scrollbar-color` / `scrollbar-width` value other than
           `auto`/`none` opts the element OUT of macOS overlay scrollbars and
           into classic, layout-consuming ones. With the rules above that hit
           the root scroller, Firefox painted a full-height black thumb down
           the right edge (and, because the page overflows a hair sideways,
           one along the bottom) — read as a "border". Opening a modal sets
           body{overflow:hidden}, the scroller disappeared, and the page
           jumped by the scrollbar width. Chrome/Safari kept their overlay
           scrollbars, so neither symptom showed there.
           `scrollbar-width: none` removes the root scrollbar entirely: no
           black edges, no reserved gutter, so no jump when a modal locks
           scrolling. Inner scrollers (galleries, popups, modal bodies) keep
           the thin black bar from the rules above. */
        html,
        body {
            scrollbar-width: none;
            scrollbar-color: auto;
        }

        html::-webkit-scrollbar,
        body::-webkit-scrollbar {
            width: 0;
            height: 0;
            display: none;
        }

        /* VIDEO CONTAINER */
        .video-container {
            position: relative;
            width: 100%;
            height: auto;
            /* Gap matching header height (homepage only).
               header = 0.6rem top + ~24px logo + 0.6rem bottom + 1px border ≈ 44px */
            margin-top: 0;
            display: flex;
            flex-direction: row;
            overflow: hidden;
            z-index: 1;
            /* Safari WebKit fix: do NOT add transform:translateZ(0)/will-change:transform here.
               When a parent has its own GPU compositing layer AND child .gallery-slide elements
               also have will-change:transform layers, Safari's compositor sometimes fails to
               re-composite the children after the parent's opacity transitions from 0→1,
               leaving slides invisible. Keeping only will-change:opacity avoids this. */
            will-change: opacity;
            background-color: transparent;
            opacity: 1;
            transition: opacity 0.4s ease;
        }

        .video-container.fading {
            opacity: 0;
        }

        /* Slideshow fade-in on homepage load. Containers start invisible and
           fade in once body has .slideshows-ready (set after window.load,
           which fires after slideshow images/videos have downloaded).
           Both .video-container and .video-bottom already have an opacity
           transition defined further below — this just gates the initial
           opacity until content is actually loaded. */
        body:not(.slideshows-ready) .video-container,
        body:not(.slideshows-ready) .video-bottom {
            opacity: 0;
        }

        .video-wrapper {
            width: 50%;
            aspect-ratio: 1 / 1;
            position: relative;
            overflow: hidden;
            transform: translateZ(0);
            -webkit-transform: translateZ(0);
            border-bottom: 1px solid #000;
        }

        .video-wrapper:first-child {
            border-right: 1px solid #000;
        }

        .video-poster {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
            background-color: white;
            z-index: 1;
        }

        .video-wrapper > video {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
            transform: translateZ(0);
            -webkit-transform: translateZ(0);
            pointer-events: none;
            opacity: 0;
            z-index: 2;
            transition: opacity 0.3s ease;
            will-change: opacity;
        }

        .video-wrapper > video.playing {
            opacity: 1;
        }

        /* Hide all video controls */
        video::-webkit-media-controls,
        video::-webkit-media-controls-enclosure,
        video::-webkit-media-controls-panel,
        video::-webkit-media-controls-play-button,
        video::-webkit-media-controls-start-playback-button,
        video::-webkit-media-controls-overlay-enclosure,
        video::-webkit-media-controls-overlay-play-button {
            display: none !important;
            -webkit-appearance: none !important;
            opacity: 0 !important;
        }

        /* Additional webkit control hiding for all elements */
        *::-webkit-media-controls-panel {
            display: none !important;
            -webkit-appearance: none !important;
        }

        *::-webkit-media-controls-start-playback-button {
            display: none !important;
            -webkit-appearance: none !important;
        }

        *::-webkit-media-controls-overlay-play-button {
            display: none !important;
            -webkit-appearance: none !important;
        }

        /* Prevent interaction with video controls */
        video {
            pointer-events: none;
        }


        .video-bottom {
            display: none;
            opacity: 1;
            transition: opacity 0.4s ease;
            will-change: opacity;
        }

        .video-bottom.fading {
            opacity: 0;
        }

        /* Mobile portrait only */
        @media (max-width: 1000px) and (orientation: portrait) {
            .video-container {
                height: auto;
                border-top: none;
            }

            .video-wrapper {
                width: 100%;
                aspect-ratio: 1 / 1;
            }

            .video-container .video-wrapper:nth-child(2) {
                display: none;
            }

            .video-wrapper:first-child {
                border-right: none;
            }

            .video-bottom {
                display: block;
                width: 100%;
                aspect-ratio: 1 / 1;
                position: relative;
                z-index: 1;
                overflow: hidden;
                transform: translateZ(0);
                -webkit-transform: translateZ(0);
                background-color: transparent;
                border-top: 1px solid #000;
            }

            .video-bottom .video-poster {
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                object-fit: cover;
                display: block;
                z-index: 1;
            }

            .video-bottom > video {
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                object-fit: cover;
                display: block;
                transform: translateZ(0);
                -webkit-transform: translateZ(0);
                pointer-events: none;
                opacity: 0;
                z-index: 2;
                transition: opacity 0.3s ease;
                will-change: opacity;
            }

            .video-bottom > video.playing {
                opacity: 1;
            }
        }
        
        /* GALLERY SLIDESHOW */
                @media (max-width: 1000px) and (orientation: portrait) {
                    /* Add thin black line to the bottom of the slides2 container in portrait mode */
                    .gallery-slideshow-2 {
                        border-bottom: 1px solid #000;
                    }
                }
        .gallery-slideshow,
        .gallery-slideshow-2 {
            position: absolute;
            inset: 0;
            z-index: 3;
            overflow: hidden;
            outline: none;
            cursor: default;
        }

        .gallery-track {
            position: relative;
            width: 100%;
            height: 100%;
            transform: translateZ(0); /* own compositing layer — both slides share the same rasterisation pass, preventing subpixel gap */
        }

        .gallery-slide {
            position: absolute;
            inset: 0;
            overflow: hidden;
            backface-visibility: hidden;
            -webkit-backface-visibility: hidden;
        }

        .gallery-slide img,
        .gallery-slide video {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
            user-select: none;
            -webkit-user-drag: none;
            pointer-events: none;
        }

        /* Chevron nav buttons */
        .gallery-btn {
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            z-index: 10;
            width: 48px;
            height: 48px;
            background: transparent;
            border: none;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 0;
            opacity: 0;
            transition: opacity 0.25s ease;
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation; /* prevents double-tap zoom on mobile */
            user-select: none;
        }

        .gallery-btn svg {
            width: 30px;
            height: 30px;
            flex-shrink: 0;
        }

        .gallery-prev { left: 26px; }
        .gallery-next { right: 26px; }

        /* Dot indicators */
        .gallery-dots {
            position: absolute;
            bottom: 22px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 0;
            z-index: 10;
            opacity: 0;
            transition: opacity 0.25s ease;
        }

        /* Square hit area — dots touch edge to edge; flex centers the visual child */
        .gallery-dot {
            width: 22px;
            height: 22px;
            border: none;
            background: transparent;
            cursor: pointer;
            padding: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            -webkit-tap-highlight-color: transparent;
        }

        /* Visual circle — inset box-shadow instead of border so the ring starts at the
           element's outer edge and overlaps the antialiased clip boundary, closing the
           hairline gap that a regular border leaves outside overflow:hidden */
        .gallery-dot-visual {
            position: relative;
            flex-shrink: 0;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            box-shadow: inset 0 0 0 1px white;
            overflow: hidden;
            pointer-events: none;
        }

        /* Fill child — wipes in/out via clip-path transitions set by JS */
        .gallery-dot-fill {
            display: block;
            position: absolute;
            inset: 0;
            background: white;
            clip-path: inset(0 100% 0 0);
        }

        /* Reveal on hover (desktop) */
        .gallery-slideshow:hover .gallery-btn,
        .gallery-slideshow-2:hover .gallery-btn {
            opacity: 1;
        }

        /* Dots visible while actively switching */
        .gallery-dots.is-visible {
            opacity: 1;
        }

        /* Always visible on touch devices (no hover capability) */
        @media (hover: none) {
            .gallery-btn {
                opacity: 1 !important;
            }
        }


        /* GALLERY TAP ZONES (left = prev, right = next) */
        .gallery-tap-zone {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 40%;
            background: transparent;
            border: none;
            padding: 0;
            cursor: pointer;
            z-index: 5;
            touch-action: manipulation;
            -webkit-tap-highlight-color: transparent;
        }
        .gallery-tap-left  { left: 0; }
        .gallery-tap-right { right: 0; }

        /* Download dialog items — hover only on real pointer devices */
        @media (hover: hover) and (pointer: fine) {
            .cfg-dl-item:hover { border-bottom-color: #000 !important; }
        }

        /* GALLERY NEXT TEXT BUTTON (bottom-right) */
        .gallery-next-text {
            position: absolute;
            bottom: 14px;
            right: 18px;
            z-index: 10;
            color: white;
            mix-blend-mode: difference;
            font-family: 'Inter', sans-serif;
            font-size: 13px;
            letter-spacing: 0.02em;
            font-style: normal;
            background: none;
            border: none;
            cursor: pointer;
            padding: 0;
            user-select: none;
            touch-action: manipulation;
        }

        /* HEADER */
        .header {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            z-index: 50;
            background-color: transparent;
            padding: calc(0.75rem + env(safe-area-inset-top)) 2rem 0.5rem;
            /* Clicks pass through the header's empty areas; the interactive
               controls below opt back in via pointer-events: auto. */
            pointer-events: none;
            transition: transform 0.3s ease;
        }

        .header .nav,
        .header .nav-right,
        .header .nav-center {
            pointer-events: none;
        }

        .header #headerGridLogo,
        .header .lang-switch-container,
        .header .hamburger {
            pointer-events: auto;
        }


        /* Hidden state. The travel is deliberately more than the header's own
           height, which is what a plain translateY(-100%) gives you.

           -100% lands the bottom edge exactly on y=0 — correct to the pixel,
           and with zero margin for error. iOS Safari does not give us that
           margin. With viewport-fit=cover the page runs edge to edge under the
           Dynamic Island, and a position:fixed element is pinned to the *layout*
           viewport while what you actually see is the *visual* viewport. Those
           two disagree by tens of px while the toolbar collapses and during
           rubber-band overscroll, and Safari only reconciles fixed elements at
           the end of a scroll gesture. A header parked at exactly y=-100% then
           gets pushed back down into view, and what is left on screen is the
           strip beside the front camera. Overshooting past the safe-area inset
           absorbs the whole discrepancy. Off-screen is off-screen, so the extra
           distance is free — the transition covers it in the same 0.3s.

           Deliberately NOT also visibility:hidden: `html.fonts-ready
           .font-gated { visibility: visible }` sits on the logo *inside* this
           header, and visibility is overridable by descendants — the logo would
           stay painted while everything around it slid away. */
        .header.header-hidden {
            transform: translate3d(0, calc(-100% - env(safe-area-inset-top, 0px) - 16px), 0);
        }

        /* Nav content — black text/borders on the white header background.
           (mix-blend-mode:difference removed; colours inverted to match.) */
        .header .nav {
            position: relative;
            z-index: 1;
        }

        .header .nav, .header .nav *:not(.hamburger):not(.menu-icon):not(.menu-line) {
            color: #000000 !important;
            border-color: #000000 !important;
            text-decoration-color: #000000 !important;
        }

        .nav {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .nav-right {
            display: flex;
            align-items: center;
            gap: 2.5rem;
            pointer-events: auto;
        }
        
        .home-button {
            font-family: 'Inter', sans-serif;
            font-size: 0.875rem;
            font-weight: 300;
            color: black;
            text-decoration: none;
            cursor: pointer;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.3s ease;
            transform: translateZ(0);
            -webkit-transform: translateZ(0);
            -webkit-font-smoothing: antialiased;
            will-change: opacity;
            position: relative;
            display: inline-block;
            line-height: 1;
            padding: 2px 4px;
        }

        .home-button.visible {
            opacity: 1;
            pointer-events: auto;
        }

        @media (hover: hover) and (pointer: fine) {
            .home-button.visible:hover {
                outline: 1px solid #000;
                outline-offset: -1px;
            }
        }

        .lang-switch-container {
            position: relative;
            display: inline-block;
            cursor: pointer;
            font-family: 'Inter', sans-serif;
            letter-spacing: 0.15em;
            font-size: 0.875rem;
            font-weight: 300;
            color: black;
        }

        /* Extended touch/click area — invisible, does not affect layout */
        .lang-switch-container::after {
            content: '';
            position: absolute;
            inset: -10px;
        }

        /* Clip wrapper: carries the overflow:hidden so the slide animation
           is unaffected but the container itself is free of overflow:hidden,
           allowing the ::after touch-area extension above to work */
        .lang-clip {
            overflow: hidden;
            width: 2em;
            height: 1em;
        }

        .lang-wrapper {
            position: relative;
            transition: transform 0.3s ease;
        }
        
        .lang-active, .lang-inactive {
            display: block;
            height: 1em;
            line-height: 1em;
        }
        
        .logo-container {
            position: relative;
            display: flex;
            flex-direction: column;
            align-items: center;
            z-index: 70;
            pointer-events: auto;
            padding: 0 !important;
            margin: 0 !important;
        }
        
        .logo-studio {
            font-size: 0.75rem;
            font-weight: 400;
            letter-spacing: 0.1em;
            position: relative;
            display: block;
            line-height: 1.2;
            margin-bottom: 4px;
            color: black;
            text-decoration: line-through;
            text-decoration-color: black;
        }

        .logo {
            text-decoration: none;
            color: black;
            display: flex;
            line-height: 1;
            transition: opacity 0.3s ease, color 0.3s ease;
            padding: 0 !important;
            margin: 0 !important;
        }
        
        @media (hover: hover) and (pointer: fine) {
            .logo:hover {
                color: #555;
            }
        }
        
        .logo-main {
            font-size: 24px;
            font-weight: 400;
            display: flex;
            letter-spacing: 0.15em;
            font-family: 'Anton', sans-serif;
            line-height: 1;
            padding: 0 !important;
            margin: 0 !important;
        }
        
        .logo-letter {
            display: inline-block;
        }
        
        .logo-wrapper {
            position: relative;
            display: inline-flex;
            flex-direction: column;
            align-items: flex-start;
            width: fit-content;
            height: fit-content;
            z-index: 70;
            pointer-events: auto;
            cursor: pointer;
            padding: 0 !important;
            margin: 0 !important;
        }

        html.fonts-loading .font-gated {
            visibility: hidden;
        }

        html.fonts-loading.fonts-fallback .font-gated {
            visibility: visible;
        }

        html.fonts-ready .font-gated {
            visibility: visible;
        }

        .font-gated-host {
            position: relative;
        }

        .font-loading-spinner {
            position: absolute;
            left: 50%;
            top: 50%;
            width: 16px;
            height: 16px;
            border: 1px solid #000;
            border-right-color: transparent;
            border-bottom-color: transparent;
            border-radius: 50%;
            transform: translate(-50%, -50%);
            transform-origin: center;
            pointer-events: none;
            display: none;
            z-index: 2;
            animation: font-loader-spin 0.45s linear infinite;
            /* KNOWN REMAINING RISK — the last looping animation on a page that
               also runs WebGL. The regenerate and hero spinners were both
               removed outright because an unpromoted infinite animation repaints
               its layer every frame and cost the swap its framerate on iPhone;
               will-change did NOT fix that, so the hint below is not a proven
               fix, just the cheap half of one.
               Kept as an animation for now because this one is different in kind:
               it is a placeholder standing in for text while a webfont loads, so
               replacing it with a solid block (the fix used on the two buttons)
               would be a visible design change on every page, not a no-op. It
               also runs during page load rather than during a swap — which is
               exactly when the hero does its first geometry build and fade-in, so
               if the mobile hero fade-in ever needs another suspect, start here. */
            will-change: transform;
        }

        @supports (background: conic-gradient(#000 0deg, transparent 360deg)) {
            .font-loading-spinner {
                border: none;
                background: conic-gradient(from 0deg, #000 0deg 300deg, transparent 300deg 360deg);
                -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 1.4px), #000 calc(100% - 1px));
                mask: radial-gradient(farthest-side, transparent calc(100% - 1.4px), #000 calc(100% - 1px));
            }
        }

        html.fonts-loading .font-gated-host .font-loading-spinner {
            display: block;
        }

        html.fonts-ready .font-gated-host .font-loading-spinner {
            display: none;
        }

        @keyframes font-loader-spin {
            from { transform: translate(-50%, -50%) rotate(0deg)   translateZ(0); }
            to   { transform: translate(-50%, -50%) rotate(360deg) translateZ(0); }
        }

        /* Grid Logo Styles */
        :root {
            /* Allow `height: 0 → height: auto` (and similar intrinsic-size
               keywords) to animate. Used by the configurator settings popup
               so the browser handles dynamic height transitions natively
               without JS scrollHeight measurement / re-sync jumps. */
            interpolate-size: allow-keywords;
            --logo-grid-size-max: 210;
            --logo-grid-size: clamp(12px, calc(2.5vw * var(--logo-scale)), calc(var(--logo-grid-size-max) * var(--logo-scale) * 1px));
            --logo-grid-gap: 0px;
            --logo-cell-gap: 0px;
            --logo-plane-skew-y: -16deg;
            --logo-plane-scale-y: 1.12;
            --logo-scale: 0.68;
            --logo-relative-offset-y: 3px;
            --logo-glyph-size-max: 48;
            --logo-glyph-size: clamp(10px, calc(2.1vw * var(--logo-scale)), calc(var(--logo-glyph-size-max) * var(--logo-scale) * 1px));
            --logo-glyph-scale-x: 0.60;
            --logo-glyph-scale-y: 0.60;
            --logo-font-family: "Rubik Mono One", "League Spartan", "Arial Black", sans-serif;

            /* ── Type scale ──
               Four tiers for everything that is not a display size. The
               contrast on this site now comes from the BOTTOM of the scale,
               the way Leibal and Deoron build it: their headings top out
               around 19px, and the range is opened up by running copy and
               metadata at 10-13px instead. Nothing here got bigger; the
               1.6rem gallery caption reads as large because the paragraph
               under it no longer competes with it.

               Tracking is what buys the legibility back — the site's 0.15em
               to 0.2em is already in the range both references use for small
               text (Leibal sets 2px on a 15px root, ~0.13em) — and so does
               the leading, hence the separate 1.85 for prose.

               One scale at every width: these do not shrink again at a
               breakpoint, because 12px is already the small end and a phone
               is read closer than a laptop. */
            --type-label: 0.8125rem;  /* 13px - product / card names */
            --type-read: 0.8125rem;   /* 13px - long-form page copy */
            --type-prose: 0.75rem;    /* 12px - card + drawer copy */
            --type-meta: 0.6875rem;   /* 11px - specs, prices, subtitles */
            --type-micro: 0.625rem;   /* 10px - notes, status, placeholders */
            --type-prose-lh: 1.85;
        }

        .logo-planes {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: var(--logo-grid-gap);
            width: fit-content;
        }

        .logo-grid-plane {
            width: var(--logo-grid-size);
            aspect-ratio: 1;
            transform-origin: center;
            transform:
                skewY(var(--logo-plane-skew-y))
                scaleY(var(--logo-plane-scale-y));
        }

        .logo-grid-plane.single-plane {
            width: calc((var(--logo-grid-size) * 2) + var(--logo-grid-gap));
            aspect-ratio: 4 / 3;
        }

        /* Override grid-plane styles when it hosts the blob SVG logo:
           kill the skew/scale (designed for the letter grid) and use a square aspect. */
        .logo-grid-plane.single-plane:has(> .header-blob-logo) {
            aspect-ratio: 1 / 1;
            width: 52px;
            height: 52px;
            max-height: none;
            transform: none;
            padding: 0 !important;
            margin: 0 !important;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .logo-grid {
            width: 100%;
            height: 100%;
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            grid-template-rows: repeat(3, 1fr);
            gap: var(--logo-cell-gap);
            background: transparent;
            border: none;
        }

        .logo-grid .cell {
            display: flex;
            align-items: center;
            justify-content: center;
            background: transparent;
            color: #000;
            overflow: hidden;
        }

        .logo-grid .cell.letter span {
            display: block;
            font-family: var(--logo-font-family);
            font-size: var(--logo-glyph-size);
            line-height: 0.7;
            transform: scale(var(--logo-glyph-scale-x), var(--logo-glyph-scale-y));
            transform-origin: center;
            font-weight: 900;
            text-rendering: geometricPrecision;
            color: #fff !important;
            mix-blend-mode: difference;
        }

        .header-grid-logo {
            display: grid;
            grid-template-columns: repeat(8, 1fr);
            gap: 0;
            font-family: 'Anton', sans-serif;
            font-size: 24px;
            font-weight: 400;
            line-height: 1;
            letter-spacing: normal;
            align-content: start;
        }

        .header-grid-cell {
            display: flex;
            align-items: flex-end;
            justify-content: center;
            font-size: 24px;
            height: 24px;
            overflow: visible;
            will-change: opacity, height;
            transform: translateZ(0);
            backface-visibility: hidden;
        }

        .header-grid-cell-flip {
            transform: scaleX(-1) translateZ(0);
            padding-inline: 3px;
        }

        .header-tagline {
            position: absolute;
            left: 100%;
            top: 50%;
            transform: translateY(-50%);
            margin-left: 1rem;
            font-family: 'Inter', sans-serif;
            font-weight: 400;
            font-size: 0.6rem;
            letter-spacing: 0.22em;
            color: #000;
            white-space: nowrap;
            line-height: 1;
            -webkit-font-smoothing: antialiased;
            pointer-events: none;
        }


        /* The mark is deliberately smaller than the button. These three custom
           properties drive everything drawn — bar length, bar weight, the gap
           between the two bars, and the hover travel derived from them — while
           width/height below stay the hitbox and do not move. To resize the
           mark, scale --menu-bar-w and --menu-gap together (they are 24px/8px
           at 100%; the values here are 83%) and leave the rest alone. */
        .hamburger {
            --menu-bar-w: 20px;
            --menu-bar-h: 2px;
            --menu-gap: 6px;
            background: transparent;
            border: none;
            cursor: pointer;
            border-radius: 0;
            padding: 0;
            z-index: 9990;
            position: relative;
            /* Hitbox — unchanged by the mark's size; .menu-icon is centred in
               it, and .hamburger::after bleeds it out another 10px all round. */
            width: 24px;
            height: 18px;
            display: inline-flex;
            align-items: center;
            justify-content: center;
        }

        .hamburger.white {
            background: transparent;
        }

        .menu-icon {
            display: inline-flex;
            flex-direction: column;
            justify-content: space-between;
            width: var(--menu-bar-w);
            height: calc(var(--menu-gap) + 2 * var(--menu-bar-h));
        }

        .menu-line {
            display: block;
            width: var(--menu-bar-w);
            height: var(--menu-bar-h);
            background-color: #000;
            transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
        }

        /* Hover: both bars slide onto the icon's centre line, the top one
           standing up 90deg, and the two meet as a plus — the same mark the
           corner crosses on .page-cta-frame use. Neither bar is scaled, so the
           plus keeps the resting bars' exact 24px length and 2px weight and
           comes out square. 5px is the travel from each bar's resting centre
           to the middle of .menu-icon, which is half the distance between the
           two bar centres: (gap + bar) / 2. Derived rather than hardcoded so
           resizing the mark cannot leave the plus off-centre.

           The upright arm is a full bar length in a much shorter .menu-icon,
           so it overhangs top and bottom. Nothing clips it and it is
           transform-only, so no layout shifts; the nav row is taller than the
           plus either way. */
        .hamburger:hover .menu-line:nth-child(1) {
            transform: translateY(calc((var(--menu-gap) + var(--menu-bar-h)) / 2)) rotate(90deg);
        }

        .hamburger:hover .menu-line:nth-child(2) {
            transform: translateY(calc((var(--menu-gap) + var(--menu-bar-h)) / -2));
        }


        .hamburger.white .menu-line {
            background-color: #fff;
        }

        .hamburger.active {
            opacity: 0;
            pointer-events: none;
        }

        /* Extended touch/click area for the MENU button */
        .hamburger::after {
            content: '';
            position: absolute;
            inset: -10px;
        }
        
        .dropdown-menu {
            position: fixed;
            top: 0;
            right: 9rem;
            background-color: black;
            padding: 3rem 3rem 2rem 3rem;
            transform: translateY(-100%);
            transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94), visibility 0s 0.4s;
            z-index: 75;
            visibility: hidden;
            pointer-events: auto;
            max-height: 100vh;
            overflow-y: auto;
            will-change: transform;
            backface-visibility: hidden;
        }

        @media (orientation: landscape) {
            body:not(.touch-device) .dropdown-menu {
                top: 0;
                right: 0;
                width: 33.333vw;
                min-width: 360px;
                height: 100dvh;
                max-height: 100dvh;
                padding: 5rem 3rem 2rem 3rem;
                transform: translateX(100%);
                transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94), visibility 0s 0.4s;
            }

            body:not(.touch-device) .dropdown-menu.active {
                transform: translateX(0);
                visibility: visible;
                transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94), visibility 0s 0s;
            }

            body:not(.touch-device) .dropdown-close {
                display: block;
                width: 44px;
                height: 44px;
                top: calc(0.6rem + 11px);
                right: calc(1rem - 2px);
            }
        }

        .dropdown-menu.active {
            transform: translateY(0);
            visibility: visible;
            transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94), visibility 0s 0s;
        }

        .dropdown-menu ul {
            list-style: none;
            display: flex;
            flex-direction: column;
            gap: 1.5rem;
        }

        /* Lang switch in dropdown for all orientations */
        .header-lang-switch {
            display: none;
        }
        .dropdown-menu .lang-switch-container {
            color: white;
            display: inline-block;
        }

        /* "Projects:" heading over objects.config. Plain text, not a link, so
           it repeats the link typography without the hover/underline states. */
        .dropdown-group-label {
            color: white;
            font-size: var(--type-label);
            font-weight: 700;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            line-height: 1;
            padding: 2px 4px 2px calc(4px + 0.3em);
            cursor: default;
            user-select: none;
        }

        /* One indent step under "Projects:", with the label's row kept close
           but not touching. The series sit one step deeper again, so the
           nesting reads projects > objects.config > series. */
        .dropdown-project-item {
            margin-top: -0.5rem;
            padding-left: 0.9rem;
        }

        .dropdown-series-item {
            margin-top: -0.9rem;
            padding-left: 1.8rem;
        }

        .dropdown-menu a {
            color: white;
            text-decoration: none;
            font-size: var(--type-label);
            font-weight: 700;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            position: relative;
            display: inline-block;
            white-space: nowrap;
            transform: translateZ(0);
            backface-visibility: hidden;
            line-height: 1;
            padding: 2px 4px 2px calc(4px + 0.3em);
        }
        
        .dropdown-menu a.strikethrough::after {
            content: '';
            position: absolute;
            left: -0.2em;
            right: -0.2em;
            top: 50%;
            height: 1px;
            background-color: white;
            transform: translateY(-50%);
        }

        .dropdown-close {
            display: none;
            position: absolute;
            top: 2rem;
            right: 2rem;
            background: none;
            border: none;
            width: 22px;
            height: 22px;
            cursor: pointer;
            transition: opacity 0.3s ease;
            z-index: 100;
            padding: 0;
        }

        @media (hover: hover) and (pointer: fine) {
            .dropdown-close:hover {
                opacity: 0.6;
            }
        }

        .dropdown-close::before,
        .dropdown-close::after {
            content: '';
            position: absolute;
            top: 50%;
            left: 50%;
            width: 16px;
            height: 1px;
            background-color: white;
        }

        .dropdown-close::before {
            transform: translate(-50%, -50%) rotate(45deg);
        }

        .dropdown-close::after {
            transform: translate(-50%, -50%) rotate(-45deg);
        }

        /* CONTENT */
        .content {
            flex: 1;
            padding: calc(54px + 4rem) 2rem 100px;
            display: flex;
            align-items: flex-start;
            justify-content: center;
            transition: opacity 0.4s ease;
            overflow: visible; /* Ensure overflow doesn't block sticky */
        }

        .content .font-loading-spinner.gallery-loading-spinner {
            width: 20px;
            height: 20px;
            top: 42%;
        }

        .content.fading {
            opacity: 0;
            pointer-events: none;
        }

        /* ── PAGE-TRANSITION INERT STATE ─────────────────────────────────
           Every control that starts a page transition must go dead the
           instant it is clicked: no second click, and — because an element
           with pointer-events:none drops out of hit-testing — :hover stops
           matching, so the sliding difference fill retracts on its own.

           That is exactly what the objects.config bottom CTA already got for
           free from `.content.fading` above; these two classes extend it to
           the header buttons, the burger/dropdown, the logo and the footer,
           which all live outside .content.

             body.nav-transitioning — the outgoing half. Added by
               fadeOutAndNavigate() the moment a click is accepted, and by the
               `pageswap` catch-all so plain <a href> links get it too.
             html.nav-entering      — the first moments of the NEW document,
               while the cross-document View Transition is still fading the
               old page out. Without it a button sitting under the pointer
               re-hovers the instant the new page paints.

           `*` + !important is deliberate: individual controls (dropdown
           items, canvas overlays, mindmap labels) set pointer-events:auto on
           themselves, and a `none` on an ancestor does not stop those — the
           property has to be forced on the element itself. */
        html.nav-entering body *,
        body.nav-transitioning * {
            pointer-events: none !important;
        }

        /* Higher-specificity holdout that would otherwise beat the rule above
           (it is the one `pointer-events: auto !important` in this file). */
        html.nav-entering .blob-configurator-container.cfg-zoomed > .cfg-zoom-btn,
        body.nav-transitioning .blob-configurator-container.cfg-zoomed > .cfg-zoom-btn {
            pointer-events: none !important;
        }

        /* Reserve viewport height on series pages so the footer doesn't pop up
           into the empty content area before JS finishes building the view. */
        .content.series-top {
            min-height: calc(100vh - var(--header-h, 54px));
            padding-top: calc(54px + 1rem);
            padding-bottom: 0;
        }

        /* Cross-document fade-OUT between pages.
           Browser-native View Transitions API. The fade-IN is handled below
           by JS (via .content.series-top.ready) because cross-document VT
           captures the new page before JS finishes building its content,
           which would fade in an empty <div>. */
        @view-transition {
            navigation: auto;
        }
        ::view-transition-old(root) {
            animation: 400ms ease both vt-fade-out;
        }
        ::view-transition-new(root) {
            /* Skip VT fade-in; the new page appears, then .content fades in
               itself once buildSeriesPage() has populated the DOM. */
            animation: none;
        }
        @keyframes vt-fade-out { to { opacity: 0; } }

        /* Page-load content fade-in is driven from JS via inline styles
           (see fadeInSeriesContent in app.js) — CSS class-based fades on
           freshly-inserted elements proved unreliable across browsers. */

        /* Cross-page header persistence — keep the header static during
           navigation so the logo doesn't briefly flash. The named view
           transition pulls .header out of the root fade group; with
           animation:none, the header is captured but neither old nor new
           is animated, so the visible result is "header stays in place". */
        .header {
            view-transition-name: header;
        }
        ::view-transition-old(header),
        ::view-transition-new(header) {
            animation: none;
        }

        .content.all-materials {
            padding-top: 80px;
        }

        .content.compact-top {
            padding-top: 55px;
        }


        .gallery-grid {
            display: flex;
            flex-direction: column;
            gap: 2rem;
            width: 100%;
            max-width: 1600px;
        }

        .gallery-row {
            display: grid;
            grid-template-columns: 1.2fr 1fr;
            gap: 3rem;
            width: 100%;
            align-items: start; /* Critical for sticky to work in grid */
        }
        
        .gallery-main-item {
            display: flex;
            flex-direction: column;
            width: 100%;
            position: -webkit-sticky;
            position: sticky;
            top: 50px;
            align-self: start;
        }

        /*  GRID LAYOUT */
        .gallery-image-row {
            display: grid;
            grid-template-columns: repeat(6, 1fr);
            gap: 1rem;
            width: 100%;
        }

        .gallery-square {
            grid-column: 1 / 6;
            width: 100%;
            aspect-ratio: 1;
            cursor: pointer;
            position: relative;
            background-color: white;
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
            overflow: hidden;
        }

        .gallery-thumbnails {
            grid-column: 6 / 7;
            position: relative;
            width: 100%;
            height: 100%;
            overflow-y: scroll;
            overflow-x: hidden;
            display: flex;
            flex-direction: column;
            gap: 1rem;
        }
        
        /* Hide scrollbar */
        .gallery-thumbnails::-webkit-scrollbar {
            display: none;
        }
        
        .gallery-thumbnails {
            -ms-overflow-style: none;
            scrollbar-width: none;
        }

        .gallery-thumbnail {
            aspect-ratio: 1;
            width: 100%;
            cursor: pointer;
            background-color: white;
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
            opacity: 0;
            transition: opacity 0.3s ease;
            position: relative;
            overflow: hidden;
            -webkit-tap-highlight-color: transparent;
        }

        .gallery-thumbnail.loaded {
            opacity: 1;
        }

        .gallery-thumbnail::before {
            content: '';
            position: absolute;
            inset: -1px;
            background-color: #fff;
            /* background-color: #8e4300; */
            mix-blend-mode: difference;
            opacity: 0;
            transition: opacity 0.3s ease;
            pointer-events: none;
            transform: translate3d(0, 0, 0);
            backface-visibility: hidden;
        }

        @media (hover: hover) and (pointer: fine) {
            .gallery-thumbnail:hover::before {
                opacity: 1;
            }
        }

        /* 3D Canvas thumbnails */
        .gallery-thumbnail-3d {
            aspect-ratio: 1;
            width: 100%;
            cursor: pointer;
            background-color: white;
            position: relative;
            overflow: hidden;
            opacity: 0;
            transition: opacity 0.3s ease;
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation;
        }

        .gallery-thumbnail-3d.loaded {
            opacity: 1;
        }

        .gallery-thumbnail-3d canvas,
        .gallery-thumbnail-3d img {
            display: block;
            width: 100% !important;
            height: 100% !important;
            position: relative;
            z-index: 1;
        }

        .gallery-thumbnail-3d::before {
            content: '';
            position: absolute;
            inset: -1px;
            background-color: #fff;
            mix-blend-mode: difference;
            opacity: 1;
            pointer-events: none;
            transform: translate3d(-100%, 0, 0);
            backface-visibility: hidden;
            z-index: 2;
        }

        .gallery-thumbnail-3d.overlay-animating::before {
            animation: overlaySlideIn 0.4s ease forwards;
        }

        .gallery-thumbnail-3d.overlay-visible::before {
            transform: translate3d(0, 0, 0);
        }

        .gallery-thumbnail-3d.overlay-animating-out::before {
            animation: overlaySlideOut 0.4s ease forwards;
        }

        @keyframes overlaySlideIn {
            from { transform: translate3d(-100%, 0, 0); }
            to { transform: translate3d(0, 0, 0); }
        }

        @keyframes overlaySlideOut {
            from { transform: translate3d(0, 0, 0); }
            to { transform: translate3d(100%, 0, 0); }
        }

        /* CSS glitch burst — triggers once when overlay reaches end position */
        .gallery-thumbnail-3d.glitch-burst canvas,
        .gallery-thumbnail-3d.glitch-burst img {
            animation: thumbGlitch 0.15s steps(2, end) 1;
        }

        @keyframes thumbGlitch {
            0%   { clip-path: inset(0 0 65% 0);   transform: translate3d(-4px, 0, 0); }
            20%  { clip-path: inset(30% 0 20% 0);  transform: translate3d(3px, 0, 0);  }
            40%  { clip-path: inset(60% 0 5% 0);   transform: translate3d(-2px, 0, 0); }
            60%  { clip-path: inset(10% 0 50% 0);  transform: translate3d(5px, 0, 0);  }
            80%  { clip-path: inset(45% 0 10% 0);  transform: translate3d(-3px, 0, 0); }
            100% { clip-path: none;                 transform: none; }
        }


        /* Main 3D canvas on product page */
        .gallery-square-3d {
            grid-column: 1 / 6;
            width: 100%;
            aspect-ratio: 1;
            cursor: grab;
            position: relative;
            background-color: white;
            overflow: hidden;
            opacity: 0;
            transition: opacity 0.3s ease;
        }

        .gallery-square-3d.loaded {
            opacity: 1;
        }

        .gallery-square-3d canvas {
            display: block;
            width: 100% !important;
            height: 100% !important;
        }

        /* ── Blob Table Configure Button (reworked) ── */
        .blob-config-btn-wrapper {
            display: inline-flex;
            align-items: stretch;
            margin-top: 1rem;
            transition: opacity 0.2s ease, margin-top 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
            will-change: opacity;
            /* Positioned parent for the KUBIK preset dropdown that sits
               absolutely above the apply-changes button when open. */
            position: relative;
            /* `will-change: opacity` above makes this wrapper its own stacking
               context, so the dropdown's z-index:12 only sorts INSIDE it — the
               whole subtree then paints at the wrapper's own level. Without a
               z-index here that level is `auto`, and `.gallery-caption`
               (position:relative; z-index:1) paints over the open preset menu.
               A z-index on the wrapper lifts the entire strip above it. */
            z-index: 30;
        }
        /* Push the whole button strip down when the configurator is open so
           the preset dropdown lands above the apply button with the same
           visual gap the specs have below (matches the 1.5rem .gallery-details
           flex gap). Value = trigger height (~26px) + trigger→button offset
           (16px), so the trigger clears the description by exactly that gap.
           Scoped to `.has-preset-dropdown` so non-KUBIK configurators (which
           don't inject a preset trigger) keep their default spacing. */
        .blob-config-btn-wrapper.has-preset-dropdown.cfg-is-open {
            margin-top: calc(26px + 16px);
        }

        .blob-config-btn-wrapper.is-fading {
            opacity: 0;
        }

        .blob-config-label-btn {
            position: relative;
            overflow: hidden;
            padding: 6px 14px;
            border: 1px solid #000;
            background: #fff;
            color: #000;
            /* Match the KUBIK preset trigger's type styling so the button
               row reads as one system when both are on screen. */
            font-family: 'Inter', sans-serif;
            font-weight: 700;
            font-size: 0.72rem;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            cursor: pointer;
            white-space: nowrap;
            line-height: 1;
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation;
        }

        /* Sliding overlay — identical technique to .gallery-thumbnail-3d::before */
        .blob-config-label-btn::before {
            content: '';
            position: absolute;
            inset: -1px;
            background-color: #fff;
            mix-blend-mode: difference;
            opacity: 1;
            pointer-events: none;
            transform: translate3d(-100%, 0, 0);
            backface-visibility: hidden;
            z-index: 1;
        }

        /* Reuse same keyframe names — CSS scoping prevents conflicts */
        .blob-config-label-btn.overlay-animating::before {
            animation: overlaySlideIn 0.4s ease forwards;
        }
        .blob-config-label-btn.overlay-visible::before {
            transform: translate3d(0, 0, 0);
        }
        .blob-config-label-btn.overlay-animating-out::before {
            animation: overlaySlideOut 0.4s ease forwards;
        }


        /* Icon squares — always visible */
        .blob-config-icon-sq {
            width: 30px;
            border: 1px solid #000;
            margin-left: -1px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            color: #000;
            background: none;
            flex-shrink: 0;
            transition: background 0.15s, color 0.15s;
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation;
            box-sizing: border-box;
        }

        @media (hover: hover) {
            .blob-config-icon-sq:hover {
                background: #000;
                color: #fff;
            }
        }

        /* The icon squares and "request" belong to the closed state, next to
           "edit parameters". Once the configurator is open the row is about
           applying changes, so they step aside — same as the squares. */
        .blob-config-btn-wrapper.cfg-is-open .blob-config-icon-sq,
        .blob-config-btn-wrapper.cfg-is-open .blob-config-request-btn {
            display: none;
        }

        /* ── KUBIK preset dropdown ──
           Sits above the "apply changes" button when the configurator is
           open. Matches the site's global dropdown (black surface, uppercase
           letter-spaced white type). Slides open with an ease-out cubic so it
           feels like the header menu, not a plain toggle. The menu itself
           carries no border — the trigger's border is enough to define the
           surface, and a border on a max-height:0 element would show as a
           hairline seam when collapsed. */
        .kubik-preset-dropdown {
            position: absolute;
            left: 0;
            bottom: calc(100% + 16px);
            display: none;
            z-index: 12;
            font-family: 'Inter', sans-serif;
        }
        .blob-config-btn-wrapper.cfg-is-open .kubik-preset-dropdown {
            display: inline-block;
        }
        .kubik-preset-btn {
            display: inline-flex;
            align-items: center;
            gap: 14px;
            background: #000;
            color: #fff;
            border: 1px solid #000;
            padding: 6px 14px;
            font-family: inherit;
            font-weight: 700;
            font-size: 0.72rem;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            cursor: pointer;
            white-space: nowrap;
            line-height: 1;
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation;
        }
        .kubik-preset-btn-label {
            display: inline-block;
        }
        .kubik-preset-btn-arrow {
            display: inline-block;
            font-size: 0.7rem;
            transition: transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
            line-height: 1;
            transform-origin: 50% 50%;
        }
        .kubik-preset-dropdown.open .kubik-preset-btn-arrow {
            transform: rotate(180deg);
        }
        .kubik-preset-menu {
            /* Menu grows UP from the trigger so the apply-changes button
               below stays uncovered. No border here — the trigger's black
               surface already defines the edge, and a bordered max-height:0
               element would render its own top/bottom borders together as a
               hairline stripe when collapsed. */
            position: absolute;
            bottom: 100%;
            left: 0;
            min-width: 100%;
            background: #000;
            overflow: hidden;
            max-height: 0;
            transition: max-height 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
            pointer-events: none;
        }
        .kubik-preset-dropdown.open .kubik-preset-menu {
            /* Comfortably above any realistic preset count without needing JS. */
            max-height: 480px;
            pointer-events: auto;
        }
        .kubik-preset-menu-inner {
            padding: 16px 14px 14px;
            display: flex;
            flex-direction: column;
            /* Flex column defaults to align-items:stretch, which would blow
               every item out to the full menu width — so the hover/active
               outline would run edge-to-edge instead of hugging each word.
               `flex-start` keeps items intrinsic-sized. */
            align-items: flex-start;
            gap: 16px;
            opacity: 0;
            transform: translateY(8px);
            transition: opacity 0.28s ease, transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
        }
        .kubik-preset-dropdown.open .kubik-preset-menu-inner {
            opacity: 1;
            transform: translateY(0);
        }
        /* Picking a preset collapses the menu in 0.3s instead of the usual 0.4s.
           The collapse animates max-height, which is layout-driven and so runs
           on the main thread — and a preset pick starts a regenerate swap whose
           synchronous geometry rebuild seizes that thread once the exit slide
           ends: regen-swap.js EXIT_DURATION_S (0.35s) plus a frame of slack,
           so ~0.38s. A 0.4s collapse got frozen mid-flight; 0.3s clears it with
           ~80ms to spare. The margin only widens under load — the slide is a
           dt-accumulating rAF tween with dt clamped to 0.1s, so jank makes it
           finish LATER in wall-clock terms, while the CSS transition tracks
           wall-clock regardless.

           *** Coupled to EXIT_DURATION_S. Lower that and lower this too. ***

           Dismissing the menu (outside click / trigger) keeps the full 0.4s:
           nothing rebuilds then. Removed again by openMenu(), so opening is
           always full-speed. */
        .kubik-preset-dropdown.preset-menu-quick .kubik-preset-menu {
            transition: max-height 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
        }
        .kubik-preset-dropdown.preset-menu-quick .kubik-preset-menu-inner {
            transition: opacity 0.2s ease, transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
        }
        .kubik-preset-item {
            display: block;
            background: transparent;
            color: #fff;
            border: none;
            /* Same technique the site's .dropdown-menu a uses so the outline
               that appears on hover/active hugs the letters tightly instead
               of sitting far off them. Padding is em-based so it scales with
               the preset's smaller 0.72rem type instead of inheriting the
               big-dropdown's 2px/4px absolute values (which read as a fat
               box at this size). The extra 0.3em on the left offsets the
               trailing space that letter-spacing:0.3em adds after every
               glyph so the outline reads optically centred. */
            padding: 0.125em 0.25em 0.125em calc(0.25em + 0.3em);
            margin: 0;
            font-family: inherit;
            font-weight: 700;
            font-size: 0.72rem;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            text-align: left;
            cursor: pointer;
            white-space: nowrap;
            line-height: 1;
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation;
        }
        /* Hover AND active share the 1px inset white outline — same rule the
           header dropdown uses (`.dropdown-menu a:hover`). Sitting outside
           the box model means marking or unmarking never reflows the menu.
           Hover is gated behind (hover:hover) so it doesn't stick on touch. */
        @media (hover: hover) and (pointer: fine) {
            .kubik-preset-item:hover {
                outline: 1px solid #fff;
                outline-offset: -1px;
            }
        }
        .kubik-preset-item.active {
            outline: 1px solid #fff;
            outline-offset: -1px;
        }

        /* ── Active-preset toast ──
           Fades in over the middle of the configurator canvas when the regen
           button re-rolls an active themed preset (see preset-toast.js).
           Overlay only: pointer-events:none so it never eats a drag, and it
           sits below the help button (z-index 21) in the corner stack. */
        .cfg-preset-toast {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            z-index: 12;
            pointer-events: none;
            max-width: calc(100% - 80px);
            text-align: center;
            font-family: 'Inter', sans-serif;
            font-weight: 700;
            font-size: 0.55rem;
            letter-spacing: 0.22em;
            text-transform: uppercase;
            line-height: 1.2;
            color: #999;
            opacity: 0;
            /* Own compositing layer: the fade runs on the compositor instead
               of competing with the geometry rebuild the regen click kicks
               off on the main thread (which was collapsing the fade-in into a
               single frame — a hard pop). */
            will-change: opacity;
            backface-visibility: hidden;
            -webkit-backface-visibility: hidden;
            /* Fade OUT — slow and unhurried, nothing else is happening then. */
            transition: opacity 0.5s ease;
        }
        .cfg-preset-toast.visible {
            opacity: 1;
            /* Fade IN — eased so it starts almost imperceptibly and settles,
               rather than ramping linearly into view. */
            transition: opacity 0.55s cubic-bezier(0.25, 0.46, 0.45, 0.94);
        }

        /* ── Blob Table Configurator ── */
        .blob-configurator-wrapper {
            display: flex;
            flex-direction: column;
            width: 100%;
            height: 100%;
            position: relative;
            /* Force own compositing layer so opacity transitions correctly
               affect children with mix-blend-mode / transform / z-index
               (mobile Safari compositing bug workaround) */
            transform: translateZ(0);
        }

        /* ── Across-series: kill long-press callouts and text marking ──
           Applies to the whole configurator (canvas, sliders, controls row
           AND the settings popup). No text inside the configurator is meant
           to be selectable, and a long-press must never pop the iOS
           callout / Android context menu. (The contextmenu event is also
           suppressed on the wrapper in JS — see openConfigurator.) */
        .blob-configurator-wrapper,
        .blob-configurator-wrapper * {
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
            -webkit-touch-callout: none;
        }

        /* ── Configurator Help Overlay ── */
        .blob-cfg-help-overlay {
            position: absolute;
            inset: 0;
            background: rgba(0, 0, 0, 1);
            z-index: 50;
            cursor: pointer;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            gap: 12px;
            padding: 32px;
            box-sizing: border-box;
            opacity: 0;
            transition: opacity 0.3s ease;
        }

        .blob-cfg-help-overlay.visible {
            opacity: 1;
        }

        .blob-cfg-help-title {
            color: #fff;
            font-family: 'Inter', sans-serif;
            font-weight: 700;
            font-size: var(--type-label);
            letter-spacing: 0.3em;
            text-transform: uppercase;
            text-align: center;
            pointer-events: none;
        }

        .blob-cfg-help-canvas {
            width: min(180px, 55%);
            aspect-ratio: 1 / 1;
            display: block;
            pointer-events: none;
        }

        .blob-cfg-help-content {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 12px;
            width: 100%;
            opacity: 1;
            transition: opacity 0.3s ease;
        }

        .blob-cfg-help-overlay p {
            color: #fff;
            font-family: 'Inter', sans-serif;
            font-weight: 300;
            font-size: 0.875rem;
            line-height: 1.6;
            letter-spacing: 0.2em;
            text-align: center;
            max-width: 400px;
            pointer-events: none;
            /* Reserve space for 6 lines — the leg description can be longer
               than the blob description, especially in German. Fixed height
               prevents layout shift when text swaps between phases. */
            min-height: calc(6 * 0.875rem * 1.6);
            /* Pre-existing transition primes Safari's transition machinery.
               The animation overrides the duration via inline style per phase.
               GPU hints keep opacity on the compositor thread so mobile Safari
               doesn't batch transition + opacity into a single snap.
               (Same fix pattern as .landing-body-stack — see summary.txt.) */
            transition: opacity 0.35s ease;
            will-change: opacity;
            transform: translateZ(0);
            backface-visibility: hidden;
        }


        .blob-cfg-help-btn {
            position: absolute;
            top: 6px;
            right: 6px;
            background: none;
            border: none;
            padding: 6px;
            color: #000;
            cursor: pointer;
            z-index: 21;
            display: flex;
            align-items: center;
            justify-content: center;
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation;
            transform: translateZ(0);
            -webkit-transform: translateZ(0);
        }

        .blob-cfg-help-close {
            position: absolute;
            top: 8px;
            right: 8px;
            background: none;
            border: none;
            padding: 6px;
            color: #fff;
            cursor: pointer;
            z-index: 51;
            display: flex;
            align-items: center;
            justify-content: center;
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation;
            width: 28px;
            height: 28px;
        }

        .blob-cfg-help-close svg {
            width: 14px;
            height: 14px;
        }

        @keyframes blob-help-ping {
            0%   { transform: scale(0.7); opacity: 0.75; }
            100% { transform: scale(2.4); opacity: 0; }
        }

        .blob-cfg-help-btn.attention::before {
            content: '';
            position: absolute;
            inset: 0;
            border-radius: 50%;
            box-shadow: 0 0 0 1.5px currentColor;
            animation: blob-help-ping 0.85s ease-out forwards;
            pointer-events: none;
        }

        /* ── Rectangular box ping (edit parameters / apply changes buttons) ── */
        @keyframes btn-box-ping {
            0%   { box-shadow: 0 0 0 0px rgba(0,0,0,0.4); }
            100% { box-shadow: 0 0 0 8px rgba(0,0,0,0); }
        }

        .btn-ping-overlay {
            position: absolute;
            pointer-events: none;
            z-index: 9999;
        }

        .btn-ping-overlay.attention {
            animation: btn-box-ping 0.85s ease-out forwards;
        }

        @media (hover: hover) {
            .blob-cfg-help-btn:hover { color: #666; }
        }

        .blob-configurator-controls-row {
            display: flex;
            justify-content: center;
            padding: 8px 0;
            position: relative;
            z-index: 20; /* stacking context here (not on .config-controls) so
                            the settings popup renders above the description text
                            without creating a compositing layer that breaks
                            mobile Safari opacity transitions on the buttons */
        }

        .blob-configurator-container {
            position: relative;
            background: #fff;
            width: 100%;
            flex: 1;
            min-height: 0;
            /* `hidden` first as the fallback; `clip` + clip-margin then lets
               painting bleed CLIP_BLEED past the box so the corner brackets
               below can sit outside the canvas. Still clips everything beyond
               that, which is what the grid-edit ± paddles and dashed lines
               relied on (.kubik-grid-edit-overlay also clips itself now). */
            overflow: hidden;
            overflow: clip;
            overflow-clip-margin: 12px;
        }
        /* Was clipped by the container's overflow:hidden; now that the
           container bleeds, clip it here so grid handles still can't escape. */
        .kubik-grid-edit-overlay {
            overflow: hidden;
        }
        /* ── Corner crosses (replaces the full border) ──
           Four thin crosses sitting entirely OUTSIDE the canvas, one off each
           corner, instead of a box drawn around the whole thing. Same 1px
           black hairline as the old outline.

           Geometry: the ::after box is outset by exactly ARM (8px), and each
           cross is centred ARM in from ITS corner — which lands the centre
           precisely ON the canvas corner point, arms running 8px each way, half
           outside and half inside. Classic crop mark.

           The outset box is required: a background never paints outside its own
           element, so the box has to already extend past the corner for the
           outer half of the cross to exist at all. Outsetting MORE than ARM is
           what pushed the crosses away from the corners; keep inset == ARM.

           Drawn as eight background bars (two per corner) on one ::after
           rather than four extra DOM nodes, so nothing new lands inside the
           container — every :not(canvas) child there is load-bearing for the
           touch-action and .cfg-zoomed fade rules.

           The container's overflow-clip-margin (above) is what allows painting
           out here at all; keep it >= this inset or the crosses get cut off.
           Not faded in zoom mode, matching the old outline.

           To resize, change ARM in all three places together: inset (-ARM),
           the 16px background-size (2*ARM), and the 8px position offset (ARM). */
        .blob-configurator-container::after {
            content: '';
            position: absolute;
            inset: -8px;
            pointer-events: none;
            z-index: 40;
            background-repeat: no-repeat;
            background-image:
                linear-gradient(#000, #000), linear-gradient(#000, #000),
                linear-gradient(#000, #000), linear-gradient(#000, #000),
                linear-gradient(#000, #000), linear-gradient(#000, #000),
                linear-gradient(#000, #000), linear-gradient(#000, #000);
            /* Per corner: horizontal arm (15x1) + vertical arm (1x15).
               15 and not 16, offset 1px and not 0: the 1px stroke lands on the
               INSIDE of the corner (the vertical arm occupies the canvas's
               first column), so the arms are symmetric about the corner but not
               about the stroke's own centre line — the outward half measured
               from the crossing pixel came out one pixel longer than the
               inward half. Dropping a pixel and nudging 1px inward makes both
               halves 7px from the stroke centre. */
            background-size:
                15px 1px, 1px 15px,
                15px 1px, 1px 15px,
                15px 1px, 1px 15px,
                15px 1px, 1px 15px;
            background-position:
                left 1px top 8px,     left 8px top 1px,       /* top-left     */
                right 1px top 8px,    right 8px top 1px,      /* top-right    */
                left 1px bottom 8px,  left 8px bottom 1px,    /* bottom-left  */
                right 1px bottom 8px, right 8px bottom 1px;   /* bottom-right */
        }

        /* ── Configurator: page scroll vs. object drag ──────────────────────
           The container used to be touch-action:none (written inline by every
           viewer's installInteraction) AND overflow:hidden + overscroll-
           behavior:contain. Together those made it a scroll container that
           swallowed everything: you could not scroll the page past an open
           configurator, by touch OR by mouse wheel.

           Now the canvas is relaxed to pan-y and installDragScrollGuard
           (drag-rotate.js) cancels the scroll per gesture, but only when the
           finger actually landed on the object — so the object still rotates
           in both axes while the empty white fill around it scrolls the page.
           !important because the viewers still write touch-action inline for
           their inline/gallery role, and inline beats a plain rule. */
        .blob-configurator-container,
        .blob-configurator-container > canvas {
            touch-action: pan-y !important;
        }
        /* EVERY non-canvas child keeps owning its own touches: sliders,
           buttons, dropdowns, the grid-edit overlay, the drag coach overlay.
           This is the rule that matters most. `.custom-slider` (the div) has
           no touch-action of its own — only its <svg> does — and the slider
           handle is drawn overflowing that svg, so a grab on the handle lands
           on the bare div. Without this, relaxing the container above makes
           slider drags scroll the page instead of moving the slider. */
        .blob-configurator-container > :not(canvas) {
            touch-action: none !important;
        }
        /* Zoom mode: the viewer owns every touch again (pinch + two-finger
           pan), and scroll chaining is contained so trackpad panning doesn't
           flash the page scrollbar on macOS/iPad — the only reason
           overscroll-behavior was ever set here. */
        .blob-configurator-container.cfg-zoomed,
        .blob-configurator-container.cfg-zoomed > canvas {
            touch-action: none !important;
            overscroll-behavior: contain;
        }
        .blob-configurator-wrapper:has(.cfg-zoomed) {
            overscroll-behavior: contain;
        }
        /* Mobile: the zoom button shows the icon only (screens are narrow;
           the label was competing with the layout/edit buttons on the other
           side of the row). Desktop keeps the "zoom" text. */
        @media (hover: none) and (pointer: coarse) {
            .cfg-zoom-btn-label { display: none !important; }
        }

        /* Three.js canvas inside configurator fills the square */
        .blob-configurator-container > canvas {
            display: block;
            width: 100% !important;
            height: 100% !important;
            position: absolute;
            top: 0;
            left: 0;
        }

        /* ── Two-finger zoom mode ──
           While the user pinch-zooms the 3D view, everything inside the thin
           border except the WebGL <canvas> fades away (sliders, buttons,
           measurement labels, tooltip, help/selection overlays). The base
           opacity transition lives on all overlay children so they fade back
           in smoothly when zoom mode ends; sliders keep their own (more
           specific) 0.4s transition. The `.cfg-zoomed` class is toggled by
           the viewer (KubikSystemSystem) — only the interactive configurator
           instance ever sets it. */
        .blob-configurator-container > :not(canvas) {
            transition: opacity 0.45s ease;
        }
        .blob-configurator-container.cfg-zoomed > :not(canvas) {
            opacity: 0 !important;
            pointer-events: none !important;
        }
        /* Measurement labels (the CSS2D layer) stay on while zoomed so you can
           read / toggle dimensions in zoom mode; it's already pointer-events:none. */
        .blob-configurator-container.cfg-zoomed > .cfg-keep-in-zoom {
            opacity: 1 !important;
        }
        /* The zoom button stays visible + clickable in zoom mode on EVERY
           device. On mouse/trackpad it's the only way back out (no pinch); on
           touch, keeping it up gives a one-tap "reset the view" even though
           pinch-out / idle-return also leave zoom mode. It used to fade with
           the rest of the overlay on touch, which stranded users who couldn't
           easily un-zoom. */
        .blob-configurator-container.cfg-zoomed > .cfg-zoom-btn {
            opacity: 1 !important;
            pointer-events: auto !important;
        }

        .blob-configurator-container .custom-slider {
            position: absolute;
            z-index: 20;
            cursor: pointer;
            user-select: none;
            -webkit-user-select: none;
            mix-blend-mode: difference;
            transition: opacity 0.4s ease;
        }
        .blob-configurator-container .custom-slider.slider-idle {
            opacity: 0.2;
        }

        /* ── Edit modes on phones: hide the canvas edge sliders ──
           In grid-edit (KUBIK system / system-min) and wave-edit (CURRENT
           table / shelf) the overlay drops its own handles, ± paddles and
           edge switches exactly where the edge sliders sit. On a phone there
           isn't room for both, so the sliders fade out while editing and come
           back when the mode is applied/left. Wider screens keep them — there
           the two never collide. The `.cfg-editing` class is toggled by the
           configurator's enter/exit-edit handlers. */
        @media (max-width: 700px) {
            .blob-configurator-container.cfg-editing .custom-slider {
                opacity: 0 !important;
                pointer-events: none !important;
            }
            /* The always-on bottom-centre "main" slider (CURRENT table/shelf
               density) sits at bottom:8 by default, right on top of the corner
               button row (edit-wave / zoom at bottom:16). On phones there isn't
               room for both side by side, so lift the slider clear above the
               row. Wider screens keep it at the bottom edge — there the centred
               slider and the corner buttons never collide. */
            .blob-configurator-container .cfg-bottom-slider {
                bottom: 48px !important;
            }
        }

        .blob-configurator-container .sl-waves {
            left: 8px;
            top: 50%;
            transform: translateY(-50%);
            width: 40px;
            height: 260px;
        }

        .blob-configurator-container .sl-randomness {
            bottom: 8px;
            left: 50%;
            transform: translateX(-50%);
            width: 220px;
            height: 44px;
        }

        .blob-configurator-container .sl-height {
            right: 8px;
            top: 50%;
            transform: translateY(-50%);
            width: 40px;
            height: 220px;
        }

        .blob-configurator-container .sl-diameter {
            top: 8px;
            left: 50%;
            transform: translateX(-50%);
            width: 220px;
            height: 40px;
        }

        .blob-configurator-container .sl-major {
            top: 8px;
            left: 50%;
            transform: translateX(-50%);
            width: 220px;
            height: 40px;
        }

        .blob-configurator-container .sl-minor {
            top: 50px;
            left: 50%;
            transform: translateX(-50%);
            width: 220px;
            height: 40px;
        }

        .blob-configurator-container .sl-radius {
            bottom: 8px;
            left: 50%;
            transform: translateX(-50%);
            width: 220px;
            height: 44px;
        }

        .blob-configurator-container .custom-slider svg {
            display: block;
            width: 100%;
            height: 100%;
            overflow: visible;
            touch-action: none;
        }

        .config-tooltip {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            font-family: Arial, sans-serif;
            font-size: 12px;
            font-weight: bold;
            color: #fff;
            background: #000;
            padding: 5px 10px;
            pointer-events: none;
            z-index: 30;
            white-space: nowrap;
            opacity: 0;
            transition: opacity 0.15s;
        }
        .config-tooltip.visible { opacity: 1; }

        /* ── 3D drag overlay (touch devices only) ── */
        .drag-overlay {
            display: none;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: black;
            padding: 1.1rem 1.4rem 1rem 1.4rem;
            z-index: 30;
            pointer-events: auto;
            width: 195px;
            opacity: 0;
            transition: opacity 0.35s ease;
        }
        @media (hover: none) {
            .drag-overlay.visible {
                display: block;
                opacity: 1;
            }
            .drag-overlay.dismissing {
                display: block;
                opacity: 0;
            }
        }
        .drag-overlay-close {
            position: absolute;
            top: 0.5rem;
            right: 0.5rem;
            background: none;
            border: none;
            width: 20px;
            height: 20px;
            cursor: pointer;
            padding: 0;
        }
        .drag-overlay-close::before,
        .drag-overlay-close::after {
            content: '';
            position: absolute;
            top: 50%;
            left: 50%;
            width: 12px;
            height: 1px;
            background-color: white;
        }
        .drag-overlay-close::before { transform: translate(-50%,-50%) rotate(45deg); }
        .drag-overlay-close::after  { transform: translate(-50%,-50%) rotate(-45deg); }
        .drag-overlay h4 {
            color: white;
            font-size: 0.65rem;
            font-weight: 700;
            letter-spacing: 0.25em;
            text-transform: uppercase;
            margin: 0 0 0.35rem 0;
            padding-right: 1.2rem;
        }
        .drag-overlay p {
            color: #aaa;
            font-size: 0.55rem;
            letter-spacing: 0.12em;
            line-height: 1.45;
            margin: 0;
        }

        .config-controls {
            position: relative;
            display: flex;
            gap: 8px;
            align-items: center;
            justify-content: center;
            /* z-index removed — position:relative + z-index creates a stacking
               context that mobile Safari promotes to its own compositing layer,
               preventing it from fading with the parent's opacity transition.
               The settings popup still stacks correctly via its own z-index. */
        }

        .config-icon-btn {
            background: none;
            border: none;
            padding: 6px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #000;
            transition: background 0.15s, color 0.15s;
            line-height: 0;
            flex-shrink: 0;
            width: 30px;
            height: 30px;
            box-sizing: border-box;
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation; /* prevents double-tap zoom on mobile */
        }
        @media (hover: hover) {
            .config-icon-btn:hover { color: #666; }
            .config-icon-btn.active:hover { background: #333; }
        }
        .config-icon-btn.active {
            background: #000;
            color: #fff;
        }
        .config-icon-btn svg { display: block; }

        /* "create random object" busy state — while a regenerate swap runs the
           button is covered by a solid block and made non-interactive.

           This used to be a rotating spinner, on the assumption that a CSS
           transform animation is free because it runs on the compositor. That
           assumption is wrong in an important way: animating transform makes an
           animation *eligible* for compositing, it does not make the browser
           promote the element. Unpromoted, the looping rotation repainted the
           configurator UI layer — which sits directly over the WebGL canvas —
           on every single frame for the whole swap. On an iPhone that cost the
           entire swap animation: a preset pick (same geometry, same assets, no
           spinner) was smooth while the regenerate button was not.

           Forcing promotion with will-change did not fix it either, so the
           indicator is now animation-free by construction. There is nothing
           per-frame left for a browser to get wrong: the cover paints once when
           the class lands and once when it leaves, and the swap's own slide-out
           is what tells the user the tap registered. Do not reintroduce a
           looping animation on any element layered over a live canvas. */
        .config-icon-btn.is-regenerating,
        .config-seg-btn.is-regenerating {
            pointer-events: none;
            cursor: default;
        }

        /* Stepped progress strip along the bottom of the box the object lives
           in — the configurator's canvas container, or the homepage hero's. See
           swap-progress.js for why it steps instead of animating: it replaced a
           rotating spinner whose looping animation repainted this layer, which
           sits over a live WebGL canvas, on every frame.

           There is deliberately NO transition and NO animation here. The fill is
           moved by discrete transform writes at real phase boundaries, so paint
           happens a handful of times per swap rather than sixty times a second.
           Adding a transition to "smooth it out" would reintroduce exactly the
           per-frame cost this exists to avoid. */
        .swap-progress {
            position: absolute;
            left: 0;
            right: 0;
            bottom: 0;
            height: 3px;
            /* No track — the filled part is the only thing that shows. */
            background: transparent;
            /* It lies over the canvas, so it must never eat a drag or a pinch. */
            pointer-events: none;
            z-index: 3;
        }
        .swap-progress-fill {
            width: 100%;
            height: 100%;
            background: #000;
            /* scaleX from the left edge: paint/composite only, never layout. */
            transform-origin: left center;
            transform: scaleX(0);
        }
        .config-ctrl-sep {
            width: 1px;
            height: 20px;
            background: #ddd;
            flex-shrink: 0;
        }

        /* Settings popup overlays the canvas in its bottom-left corner and
           grows upward. Only top + right borders are drawn — the canvas's
           own outline serves as the popup's bottom and left edge, so the
           popup looks docked to the canvas frame. */
        /* Clipping shell anchored above the controls row. The popup itself
           lives inside this clipper and slides up into view (open) or down
           out of it (closed). `overflow: hidden` on the clipper keeps the
           popup's borders / padding / backdrop from ever peeking when it's
           translated below the clipper's bottom edge. The clipper has no
           background and pointer-events: none so the canvas above remains
           interactive when the popup is closed. */
        .config-settings-popup-clipper {
            position: absolute;
            bottom: 100%;
            left: 0;
            width: 240px;
            z-index: 25;
            pointer-events: none;
            /* Deliberately clips NOTHING — no overflow:hidden, no clip-path.
               The popup now collapses to a point (scale 0) instead of sliding
               down behind the button, so there is nothing left to hide, and
               the corner crosses can straddle the edges freely.

               Do NOT reintroduce clip-path here: an ancestor with clip-path
               becomes a "backdrop root", and backdrop-filter can only sample
               down to the nearest backdrop root — which silently kills the
               popup's frosted blur. Same trap applies to putting opacity,
               filter or mix-blend-mode on this element.
               https://drafts.csswg.org/filter-effects-2/#BackdropRoot */
            --cross-out: 8px;   /* how far the mark layer extends past the popup */

            /* Everything — panel and all three corner crosses — grows out of
               the ONE bottom-left point, the one the canvas's own bottom-left
               cross already marks. Nothing travels in from elsewhere.

               Asymmetric: height snaps up first, then the width keeps
               unfolding to the right. Both axes
               live on the POPUP, as two SEPARATE css properties — `transform`
               carries scaleX and `scale` carries the Y — because a single
               property can only hold one timing. They compose (both are scales
               about the same bottom-left origin), so it still reads as one
               point opening.

               This is why the split is no longer across two elements: putting
               scaleY on the clipper also scaled its ::after corner marks, and
               a 1px stroke scaled below 1px renders semi-transparent, so the
               crosses visibly faded in. The clipper must stay untransformed;
               the marks grow their BOX instead, on the same two timings.

               Contents are NOT painted during this (see --pop-reveal): a
               scaling box stretches whatever is inside it. */
            --pop-h: 0.28s;   /* height — quick, goes first  */
            --pop-w: 0.54s;   /* width  — unhurried, continues */
            /* The marks fade only at the corner end of their travel. Collapsed,
               all three sit on the bottom-left point where the canvas already
               draws its own cross, one line width apart — two parallel 1px
               hairlines read as a single fat one. Fading just that moment kills
               the artifact while leaving them fully solid and crisp for the
               rest of the motion (a fade across the whole travel is what made
               them look weak before). Short on purpose. */
            --pop-mark-fade: 0.16s;
            /* Closing is a true mirror of opening, not just the same two
               durations run backwards. Opening, both axes start together and
               the quick one finishes first. Reversed, the quick axis must
               therefore START last so that BOTH land at zero on the same
               frame — otherwise one axis collapses early and the panel sits as
               a sliver instead of converging on the corner point. The quick
               axis is the HEIGHT, so the extra delay goes there; swap this if
               the durations above are ever swapped back. */
            --pop-h-out: calc(var(--pop-out-delay) + var(--pop-w) - var(--pop-h));
            /* Contents are revealed by a wipe, not animated individually — an
               invisible edge sweeps across and uncovers whatever happens to be
               there, so nothing depends on the number of rows.

               The wipe deliberately runs AFTER the box has finished building,
               and must keep doing so. Locking it to the panel's horizontal
               extension was tried and reverted: the rows live inside a box
               being scaled on two axes at different rates, so anything painted
               while that is happening is stretched. Clipping only chooses what
               is visible — it cannot undo an ancestor transform. Revealing
               into a panel that has stopped moving is what keeps the text
               undistorted. */
            --pop-reveal: 0.34s;    /* wipe on, after the box is built */
            --pop-wipe-out: 0.18s;  /* wipe off, before the box folds */
            --pop-out-delay: 0.18s; /* == --pop-wipe-out, so the fold follows it */
            --pop-ease: cubic-bezier(0.4, 0, 0.2, 1);
            /* No transform here, ever: this element is the marks' parent, and
               scaling it would scale their 1px strokes into translucency. */
        }
        .config-settings-popup {
            box-sizing: border-box;
            width: 100%;
            background: rgba(255, 255, 255, 0.8);
            backdrop-filter: blur(2px);
            -webkit-backdrop-filter: blur(2px);
            padding: 10px 12px;
            display: flex;
            flex-direction: column;
            gap: 8px;
            overflow-x: hidden;
            overflow-y: auto;
            /* Hide scrollbar across browsers (popup still scrolls if content
               exceeds the 2/3-canvas max-height cap set inline by JS). */
            scrollbar-width: none;        /* Firefox */
            -ms-overflow-style: none;     /* IE / legacy Edge */
            -webkit-overflow-scrolling: touch;
            overscroll-behavior: contain;
            /* Step 2: unfolds to the right off the drawn rule. JS still sets
               `max-height` inline on open so the panel can't exceed 2/3 of the
               canvas height. scaleX only — vertical growth is the rule above,
               which is precisely what stopped the two-axis stretch. */
            transform-origin: bottom left;
            transform: scaleX(0);
            scale: 1 0;
            pointer-events: none;
            transition: transform var(--pop-w) var(--pop-ease) var(--pop-out-delay),
                        scale var(--pop-h) var(--pop-ease) var(--pop-h-out);
        }
        /* Corner crosses instead of the old top+right hairlines, drawn exactly
           like the canvas ones: centred ON the popup's corners, half in half
           out. Three of them — top-left, top-right, bottom-right; the popup's
           bottom-left already sits under the canvas's own bottom-left cross.

           On the CLIPPER, not the popup: the popup is a scroll container
           (overflow-y:auto), so a ::after there would be clipped by its
           scrollport and would scroll away with the content.

           Straddling requires a box that already extends past the popup, since
           a background never paints outside its own element — hence
           inset: -(--cross-out), with each cross centred --cross-out in from
           this layer's corner, which lands it exactly on the popup's corner.
           Arms are 15px offset 1px so both halves measure 7px from the stroke
           centre (a 16px arm puts the crossing pixel off-centre by a line
           width — that is what made the canvas marks look lopsided).

           It collapses with the popup, about the SAME point. transform-origin
           can't just be `bottom left` here: this layer is inset by
           --cross-out, so its own bottom-left sits --cross-out outside the
           popup's. Offsetting the origin in by --cross-out on both axes lands
           it exactly on the popup's bottom-left corner, so panel and marks
           converge on the one point instead of drifting apart mid-animation. */
        .config-settings-popup-clipper::after {
            content: '';
            position: absolute;
            /* Anchored to the bottom-left corner and GROWN, not scaled — the
               strokes stay 1px and crisp the whole way. Closed size is
               2*--cross-out, so all three crosses start stacked on that one
               corner point and separate as the box opens. The +2*--cross-out
               keeps the box exactly --cross-out beyond the panel on every
               side at all times, which is what makes the crosses straddle. */
            left: calc(-1 * var(--cross-out));
            bottom: calc(-1 * var(--cross-out));
            width: calc(2 * var(--cross-out));
            height: calc(2 * var(--cross-out));
            z-index: 1;
            pointer-events: none;
            opacity: 0;
            /* Closing: the fade lands at the END of the collapse, so they are
               already gone by the time they reach the canvas's cross. */
            transition: width var(--pop-w) var(--pop-ease) var(--pop-out-delay),
                        height var(--pop-h) var(--pop-ease) var(--pop-h-out),
                        opacity var(--pop-mark-fade) var(--pop-ease)
                                calc(var(--pop-out-delay) + var(--pop-w) - var(--pop-mark-fade));
            background-repeat: no-repeat;
            background-image:
                linear-gradient(#000, #000), linear-gradient(#000, #000),
                linear-gradient(#000, #000), linear-gradient(#000, #000),
                linear-gradient(#000, #000), linear-gradient(#000, #000);
            background-size:
                15px 1px, 1px 15px,
                15px 1px, 1px 15px,
                15px 1px, 1px 15px;
            /* All three live here, on ::after, and NOT on ::before: a ::before
               paints underneath the element's children, so the frosted panel
               covered the inner arms of any cross drawn there. ::after paints
               above, and carries z-index:1 on top of that. */
            background-position:
                left 1px top 8px,     left 8px top 1px,      /* top-left     */
                right 1px top 8px,    right 8px top 1px,     /* top-right    */
                right 1px bottom 8px, right 8px bottom 1px;  /* bottom-right */
        }
        .config-settings-popup-clipper:has(.config-settings-popup.open)::after {
            width: calc(100% + 2 * var(--cross-out));
            height: calc(100% + 2 * var(--cross-out));
            opacity: 1;
            /* Opening: solid almost immediately, so they are only translucent
               in the first moments while still stacked on the corner. */
            transition: width var(--pop-w) var(--pop-ease) 0s,
                        height var(--pop-h) var(--pop-ease) 0s,
                        opacity var(--pop-mark-fade) var(--pop-ease) 0s;
        }

        .config-settings-popup.open {
            transform: scaleX(1);
            scale: 1 1;
            pointer-events: auto;
            transition: transform var(--pop-w) var(--pop-ease) 0s,
                        scale var(--pop-h) var(--pop-ease) 0s;
        }
        /* The reveal: every row carries the SAME clip-path inset, animated in
           lockstep, so the exposed edge lands at the same x across all of them
           and reads as one straight wipe travelling over the panel — an
           invisible cloak being pulled off, rather than N elements animating.
           Nothing here depends on how many rows there are or what they contain.

           This works because the popup is a stretch flex column, so every row
           is the same width and a percentage inset resolves to the same place
           in each. A vertical wipe would NOT line up — rows differ in height
           and offset — so keep it horizontal.

           Clipping the rows rather than the popup is deliberate: the panel's
           frosted surface must stay fully visible while the box builds, and
           clip-path on the popup itself would also make it a backdrop root and
           kill its own blur. */
        /* :not(.open) rather than a bare `> *`: the row-collapse rule further
           down (.config-settings-row, which drives .is-hidden) has the same
           specificity and is declared later, so a bare selector loses the
           transition shorthand entirely and the wipe-out silently never ran —
           while the .open rule, being one class heavier, still worked. That
           asymmetry is what made closing snap while opening animated.

           Both rules therefore also RE-DECLARE the collapse transitions:
           `transition` is a shorthand, so listing only clip-path here would
           drop max-height/opacity/margin and make .is-hidden rows snap. */
        .config-settings-popup:not(.open) > * {
            clip-path: inset(0 100% 0 0);
            transition:
                clip-path var(--pop-wipe-out) var(--pop-ease),
                max-height 0.25s ease,
                opacity 0.2s ease,
                margin-top 0.25s ease,
                margin-bottom 0.25s ease;
        }
        .config-settings-popup.open > * {
            clip-path: inset(0 0 0 0);
            /* Waits for the slow axis, so nothing is painted until the panel
               has stopped moving (see the distortion note on the vars). */
            transition:
                clip-path var(--pop-reveal) var(--pop-ease) var(--pop-w),
                max-height 0.25s ease,
                opacity 0.2s ease,
                margin-top 0.25s ease,
                margin-bottom 0.25s ease;
        }
        .config-settings-popup::-webkit-scrollbar { display: none; }

        /* Conditional rows + their headlines animate height/opacity when
           toggled by .is-hidden, so the popup's overall layout reflows
           smoothly instead of snapping.
           NOTE: flex-shrink:0 is critical — without it the popup's flex
           layout squeezes these items to 0 height while max-height is
           transitioning, which makes scrollHeight read 0 and the popup
           never opens. */
        .config-settings-row,
        .config-settings-popup > .config-settings-label {
            flex-shrink: 0;
            overflow: hidden;
            max-height: 200px;
            transition:
                max-height 0.25s ease,
                opacity 0.2s ease,
                margin-top 0.25s ease,
                margin-bottom 0.25s ease;
        }
        .config-settings-row.is-hidden,
        .config-settings-popup > .config-settings-label.is-hidden {
            max-height: 0 !important;
            opacity: 0;
            margin-top: 0 !important;
            margin-bottom: 0 !important;
            pointer-events: none;
        }

        .config-settings-row {
            display: flex;
            flex-direction: column;
            align-items: stretch;
            gap: 3px;
        }

        .config-settings-label {
            font-size: 10px;
            color: #999;
        }

        .config-settings-divider {
            height: 1px;
            background: #ddd;
            margin: 2px 0;
        }

        .config-seg-group {
            display: flex;
        }

        .config-seg-btn {
            flex: 1;
            padding: 5px 4px;
            border: 1px solid #ccc;
            background: #fff;
            color: #999;
            font-size: 11px;
            font-family: Arial, sans-serif;
            cursor: pointer;
            transition: background 0.12s, color 0.12s, border-color 0.12s;
            margin-left: -1px;
            -webkit-tap-highlight-color: transparent;
            position: relative;
            text-align: center;
            min-width: 0;
        }
        /* Standalone action button (e.g., re-roll) — solid black border on
           all sides, no overlap-margin since it's not part of a seg-group. */
        .config-seg-btn.action {
            border-color: #000;
            color: #000;
            margin-left: 0;
        }
        @media (hover: hover) {
            .config-seg-btn.action:hover { background: #000; color: #fff; }
        }
        .config-seg-btn:first-child { margin-left: 0; }
        .config-seg-btn.active {
            background: #000;
            color: #fff;
            border-color: #000;
            z-index: 1;
        }
        .config-seg-btn.unavailable {
            color: #ddd;
            border-color: #e8e8e8;
            cursor: default;
            pointer-events: none;
            position: relative;
        }
        .config-seg-btn.unavailable::after {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: linear-gradient(
                to bottom right,
                transparent calc(50% - 0.5px),
                #ccc calc(50% - 0.5px),
                #ccc calc(50% + 0.5px),
                transparent calc(50% + 0.5px)
            );
        }

        .config-legpos-wrap {
            display: flex;
            align-items: center;
            gap: 6px;
            flex: 1;
            height: 30px;
            min-width: 140px;
        }
        .config-legpos-value {
            font-size: 11px;
            color: #999;
            min-width: 18px;
            text-align: right;
            flex-shrink: 0;
        }

        .gallery-caption {
            display: block;
            font-size: 1.6rem;
            font-weight: 300;
            margin-top: 0.5rem;
        }

        .caption-subtype {
            display: block;
            font-size: var(--type-prose);
            font-weight: 300;
            letter-spacing: 0.15em;
            margin-top: 0.15rem;
        }

        /* Series intermediate page */
        .series-page-headline {
            font-family: 'Inter', sans-serif;
            font-size: 1.8rem;
            font-style: italic;
            font-weight: 300;
            letter-spacing: 0.2em;
            text-align: center;
            margin-bottom: 2rem;
        }

        /* objects.config is the project name rather than a series title. Caps
           come from CSS so the stored name keeps its real lowercase spelling. */
        .series-page-headline.project-title {
            text-transform: uppercase;
        }

        /* When a series blurb follows, the name gives up most of its gap so
           the two read as one block. */
        .series-page-headline.has-blurb {
            margin-bottom: 0.6rem;
        }

        .series-page-blurb {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-prose);
            font-style: italic;
            font-weight: 300;
            letter-spacing: 0.12em;
            line-height: 1.6;
            text-align: center;
            max-width: 34rem;
            margin: 0 auto 2rem;
            color: #555;
        }

        @media (max-width: 1000px) {
            .series-page-blurb {
                max-width: 26rem;
                padding: 0 1rem;
            }
        }

        .series-product-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 2rem;
            width: 100%;
        }

        .series-product-card {
            display: flex;
            flex-direction: column;
            align-items: center;
            cursor: pointer;
        }

        .series-product-canvas {
            width: 100%;
            aspect-ratio: 1;
            position: relative;
            overflow: hidden;
            background-color: white;
            opacity: 0;
            transition: opacity 0.5s ease;
        }

        .series-product-canvas canvas {
            display: block;
            width: 100% !important;
            height: 100% !important;
        }

        .series-product-canvas.loaded {
            opacity: 1;
        }

        .series-product-label {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-label);
            font-weight: 300;
            letter-spacing: 0.15em;
            margin-top: 0.5rem;
            text-align: center;
        }

        @media (hover: hover) and (pointer: fine) {
            .gallery-thumbnail:hover {
                opacity: 0.7 !important;
            }
        }

        .gallery-nav {
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            width: 40px;
            height: 40px;
            cursor: pointer;
            z-index: 10;
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0;
            transition: opacity 0.3s ease;
            mix-blend-mode: difference;
        }
        
        @media (hover: hover) and (pointer: fine) {
            .gallery-square:hover .gallery-nav {
                opacity: 1;
            }
        }
        
        .gallery-nav-left {
            left: 2%;
        }
        
        .gallery-nav-right {
            right: 2%;
        }
        
        .gallery-nav svg {
            width: 100%;
            height: 100%;
            stroke: #ffffff;
            stroke-width: 1;
            fill: none;
        }
        
        .gallery-image-container {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: white;
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
            opacity: 0;
            transition: opacity 0.3s ease;
        }

        .gallery-image-container.loaded {
            opacity: 1;
        }
        
        .gallery-image-container.slide-out-left {
            animation: slideOutLeft 0.4s ease forwards;
        }
        
        .gallery-image-container.slide-out-right {
            animation: slideOutRight 0.4s ease forwards;
        }
        
        .gallery-image-container.slide-in-left {
            animation: slideInLeft 0.4s ease forwards;
        }
        
        .gallery-image-container.slide-in-right {
            animation: slideInRight 0.4s ease forwards;
        }
        
        @keyframes slideOutLeft {
            from {
                transform: translateX(0);
            }
            to {
                transform: translateX(-100%);
            }
        }
        
        @keyframes slideOutRight {
            from {
                transform: translateX(0);
            }
            to {
                transform: translateX(100%);
            }
        }
        
        @keyframes slideInLeft {
            from {
                transform: translateX(-100%);
            }
            to {
                transform: translateX(0);
            }
        }
        
        @keyframes slideInRight {
            from {
                transform: translateX(100%);
            }
            to {
                transform: translateX(0);
            }
        }
        
        .gallery-caption {
            font-family: 'Inter', sans-serif;
            font-size: 1.6rem;
            font-style: italic;
            font-weight: 400;
            letter-spacing: 0.2em;
            position: relative;
            z-index: 1;
            transition: opacity 0.3s ease;
        }
        
        .gallery-details {
            display: flex;
            flex-direction: column;
            gap: 1.5rem;
            flex: 1;
            min-width: 0;
        }
        
        .gallery-description {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-read);
            font-weight: 300;
            line-height: var(--type-prose-lh);
            color: black;
            letter-spacing: 0.2em;
            transition: opacity 0.3s ease;
        }

        /* The commission line is its own element, fed from content.json.
           .gallery-details is a flex column with gap: 1.5rem, which already
           matches the ~1.4rem blank line that <br><br> leaves between the
           paragraphs inside a description. No extra margin here, or the gap
           above the commission line ends up double the others. */
        
        .gallery-specs {
            display: grid;
            grid-template-columns: auto 1fr;
            gap: 0.5rem 1rem;
            font-family: 'Inter', sans-serif;
            font-size: var(--type-meta);
            font-weight: 300;
            color: black;
            letter-spacing: 0.2em;
            margin-top: 0;
        }
        
        .gallery-spec-label {
            text-align: left;
            transition: opacity 0.3s ease;
        }
        
        .gallery-spec-value {
            text-align: left;
            transition: opacity 0.3s ease;
        }

        .gallery-specs-note {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-prose);
            font-weight: 300;
            font-style: italic;
            color: black;
            letter-spacing: 0.2em;
            margin-top: 0.75rem;
        }

        /* MATERIAL DESCRIPTION */
        .material-description {
            grid-column: 1 / -1;
            text-align: center;
            font-family: 'Inter', sans-serif;
            font-size: 1rem;
            font-weight: 300;
            letter-spacing: 0.5em;
            text-transform: uppercase;
            color: black;
            transition: opacity 0.3s ease;
            margin: 0 0 3rem 0;
            padding: 0;
        }

        .material-description-empty {
            font-weight: 300;
            text-transform: none;
            letter-spacing: 0.2em;
            margin-bottom: 15rem;
            margin-top: 15rem;
        }

        /* GPU-ACCELERATED ANIMATION CLASSES FOR MOBILE PERFORMANCE */
        /* Note: .gallery-description intentionally excluded — translateZ(0) on it
           creates a compositing layer that paints over the configurator settings
           popup on mobile Safari, and the description doesn't animate anyway. */
        .gallery-spec-value,
        .gallery-spec-label,
        .material-description,
        .about-content,
        .series-description,
        .footer-links a,
        [data-en]:not(.lang-active):not(.lang-inactive):not(.cfg-label-span),
        [data-de]:not(.lang-active):not(.lang-inactive):not(.cfg-label-span) {
            will-change: opacity;
            transform: translateZ(0);
            backface-visibility: hidden;
        }

        /* Fade animation utility classes - no !important for smooth transitions */
        .fade-out {
            opacity: 0;
            transition: opacity 0.3s ease;
        }

        .fade-in {
            opacity: 1;
            transition: opacity 0.3s ease;
        }

        /* ABOUT PAGE */
        .about-content {
            grid-column: 1 / -1;
            text-align: center;
            font-family: 'Inter', sans-serif;
            font-size: var(--type-read);
            font-weight: 300;
            line-height: var(--type-prose-lh);
            letter-spacing: 0.2em;
            margin-bottom: 3rem;
            margin-top: 4rem;
            /* Smaller type needs a shorter line, not the same one. The 10rem
               side padding left ~150 characters per line at 13px on a wide
               screen; a max-width caps the measure the way .shop-intro
               already does, and does it at every width instead of only where
               the padding happens to bite. Auto margins do the centring — a
               max-width block in a grid track otherwise sits hard against the
               left. The padding drops to 1rem because box-sizing is
               border-box site-wide: left at 10rem it would be subtracted from
               the 70ch rather than added outside it. */
            max-width: 70ch;
            margin-left: auto;
            margin-right: auto;
            padding: 0 1rem;
            color: black;
            transition: opacity 0.3s ease;
            opacity: 1;
        }
        
        .about-content h2 {
            font-family: 'Inter', sans-serif;
            font-size: 1rem;
            font-weight: 700;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            color: black;
            text-align: center;
            margin-bottom: 2rem;
        }
        
        .about-content p {
            margin-bottom: 1.5rem;
            font-weight: 300;
        }
        
        .about-content p:last-child {
            margin-bottom: 0;
        }
        
        /* PAGE-BOTTOM CTA BUTTON

           The one "go up a level" control every page carries above its footer.
           Visual language is lifted verbatim from the homepage's KUBIK SERIES
           hero button (.kubik-hero .hero-cta-btn in index.html): white plate,
           corner crop marks instead of a border, uppercase Inter 500 at
           0.2em, and a white mix-blend-mode:difference panel that slides in
           from the left on hover to invert the button.

           Duplicated rather than shared with the hero because .hero-cta-btn
           is scoped inside .kubik-hero in index.html's own <style> block —
           same reason .home-wave-buttons .wave-cta-btn duplicates it there.
           If you retune one, retune all three. */
        .page-cta-frame {
            position: relative;
            display: inline-flex;
        }

        /* Corner crosses. The crosses live on this wrapper, not on the <a>:
           the button keeps overflow:hidden to clip its sliding hover fill, and
           an element's overflow clips its own ::after too.

           Geometry is exact and copied from the hero: the ::after box is
           outset by exactly ARM (5px) and each cross is centred ARM in from
           ITS OWN corner, which lands the crossing point precisely on the
           button's corner. Arms are 2*ARM - 1 with a 1px offset so the 1px
           stroke sits symmetric about the corner. To resize, change ARM in
           all three places at once: inset (-ARM), background-size (2*ARM - 1)
           and the position offset (ARM). */
        .page-cta-frame::after {
            content: '';
            position: absolute;
            inset: -5px;
            pointer-events: none;
            background-repeat: no-repeat;
            background-image:
                linear-gradient(#000, #000), linear-gradient(#000, #000),
                linear-gradient(#000, #000), linear-gradient(#000, #000),
                linear-gradient(#000, #000), linear-gradient(#000, #000),
                linear-gradient(#000, #000), linear-gradient(#000, #000);
            background-size:
                9px 1px, 1px 9px,
                9px 1px, 1px 9px,
                9px 1px, 1px 9px,
                9px 1px, 1px 9px;
            background-position:
                left 1px top 5px,     left 5px top 1px,       /* top-left     */
                right 1px top 5px,    right 5px top 1px,      /* top-right    */
                left 1px bottom 5px,  left 5px bottom 1px,    /* bottom-left  */
                right 1px bottom 5px, right 5px bottom 1px;   /* bottom-right */
        }

        .page-cta-btn {
            position: relative;
            display: inline-block;
            overflow: hidden;
            padding: 9px 21px;
            border: none;
            background: #fff;
            color: #000;
            font-family: 'Inter', sans-serif;
            font-weight: 500;
            font-size: 0.875rem;
            letter-spacing: 0.2em;
            text-decoration: none;
            text-transform: uppercase;
            cursor: pointer;
            white-space: nowrap;
            line-height: 1;
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation;
        }

        /* Hover fill. inset:0 — there is no border to cover. */
        .page-cta-btn::before {
            content: '';
            position: absolute;
            inset: 0;
            background-color: #fff;
            mix-blend-mode: difference;
            opacity: 1;
            pointer-events: none;
            /* -101%, not -100%. Safari lays the overlay out on fractional
               device pixels and rounds the parked position up, leaving a 1px
               sliver of the fill visible down the button's left edge — the
               thin black line. One extra percent parks it clear of the seam;
               it is off-screen either way, so nothing else changes. */
            transform: translate3d(-101%, 0, 0);
            backface-visibility: hidden;
            z-index: 1;
        }

        /* Driven by the setupCtaHover() state machine in app-core.js, not a
           :hover transition — same technique as .blob-config-label-btn. The
           fill sweeps IN from the left, holds, then leaves to the RIGHT
           (overlaySlideOut), and each animation runs to completion before the
           next can start, so a flicked pointer can't retrigger a half-played
           sweep. Header CTAs never reach this: their ::before is display:none. */
        .page-cta-btn.overlay-animating::before {
            animation: overlaySlideIn 0.4s ease forwards;
        }
        .page-cta-btn.overlay-visible::before {
            transform: translate3d(0, 0, 0);
        }
        .page-cta-btn.overlay-animating-out::before {
            animation: overlaySlideOut 0.4s ease forwards;
        }

        /* Keep that fill from inverting the corner crosses.

           ::before blends against everything painted below it *in the button's
           enclosing stacking context*, and .page-cta-frame::after (the crosses)
           sits in that same context at z-index auto — below z-index: 1. So
           wherever a cross overlaps the button, the sweep flips its inner half:
           black crosses go white on the light buttons, white crosses go black
           in the shop drawer.

           Most of these buttons are saved from it by accident. The
           "GPU-ACCELERATED ANIMATION CLASSES" rule above matches [data-en] and
           hands out transform: translateZ(0), and createCtaButton() in
           app-core.js puts data-en straight on the <a> — that transform makes
           the button its own stacking context and traps the blend inside it.

           The buttons that miss out are the ones whose label isn't a plain
           data-en string on the button itself: the header Newsletter link
           (data-en is on the inner .cta-long / .cta-short spans, because the
           label swaps per breakpoint) and the drawer buttons built by
           price-request.js and pages/shop.js (labels are set as textContent,
           so there is no data-en at all).

           isolation: isolate gives those the same stacking context on purpose.
           Scoped with :not([data-en]) so it only lands where one is missing —
           on everything else it would be a no-op anyway. */
        .page-cta-btn:not([data-en]) {
            isolation: isolate;
            /* And promoted, which is the other half of what the [data-en] rule
               hands out. isolation alone gives the stacking context but leaves
               the element unpromoted, so Safari composites the difference blend
               against a fractional pixel grid — that is the shifting and the
               1px seam on the drawer's "enquire" / "send request" buttons.
               translateZ(0) makes it round the same way the labelled buttons
               already do. */
            transform: translateZ(0);
        }

        /* The hover fill is now driven by setupCtaHover() (app-core.js) through
           the .overlay-* classes above, which only wire on hover-capable
           pointers — so mobile taps never leave the button stuck inverted. */

        /* Breathing room above the footer. The row itself is .all-materials-link,
           which already centres and spans the grid — this only adds the space the
           plain text link never needed. */
        .all-materials-link.page-cta {
            margin-top: 2.5rem;
            margin-bottom: 3.5rem;
            letter-spacing: normal;
        }

        /* On the 3D scene pages (series overviews, objects.config) the button is
           injected by app-core.js below the stage and fades in with the footer,
           so it starts invisible — same contract as .footer. */
        .all-materials-link.page-cta-auto {
            opacity: 0;
            transition: opacity 0.4s ease;
        }

        .all-materials-link.page-cta-auto.page-cta-revealed {
            opacity: 1;
        }

        /* After .page-cta-revealed (equal specificity) so an in-flight fade-out
           beats a stale reveal — same ordering rule as .footer.fading. */
        .all-materials-link.page-cta-auto.fading {
            opacity: 0;
        }

        /* ALL MATERIALS LINK */
        .all-materials-link {
            grid-column: 1 / -1;
            text-align: center;
            margin-top: 0;
            font-family: 'Inter', sans-serif;
            font-size: 0.875rem;
            letter-spacing: 0.2em;
        }
        
        .all-materials-link a:not(.page-cta-btn) {
            color: black;
            text-decoration: none;
            position: relative;
            display: inline-block;
            line-height: 1;
            padding: 2px 4px 2px calc(4px + 0.2em);
        }

        /* Links pointing at the objects.config project are set in caps. Scoped
           to its own class so the sibling links in the same row ("material
           guide", the other material categories) keep their normal casing. */
        .objects-config-link {
            text-transform: uppercase;
        }


        /* METALS SERIES GRID */
        .metals-series-grid {
            display: flex;
            flex-wrap: wrap;
            gap: 2rem;
            justify-content: center;
            width: 100%;
        }

        .metals-series-card {
            width: calc(33.333% - 1.334rem);
            flex-shrink: 0;
            cursor: pointer;
        }

        .metals-series-card.coming-soon {
            cursor: default;
        }

        .metals-series-frame {
            position: relative;
            width: 100%;
            aspect-ratio: 1 / 1;
            overflow: hidden;
        }

        .metals-series-frame img {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
            transition: opacity 0.5s ease;
        }

        .metals-series-img-hover {
            opacity: 0;
        }

        .metals-series-card.is-hovered .metals-series-img-hover {
            opacity: 1;
        }

        .metals-series-label {
            position: absolute;
            bottom: 1rem;
            left: 1rem;
            font-family: 'Inter', sans-serif;
            font-size: clamp(0.75rem, 1vw, 1rem);
            font-weight: 400;
            font-style: italic;
            letter-spacing: 0.05em;
            text-transform: uppercase;
            color: white;
            mix-blend-mode: difference;
            pointer-events: none;
        }

        .metals-series-coming-soon {
            position: absolute;
            inset: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: 'Oswald', sans-serif;
            font-size: clamp(1.5rem, 2.5vw, 3rem);
            font-weight: 700;
            letter-spacing: 0.15em;
            text-transform: uppercase;
            color: white;
            mix-blend-mode: difference;
            pointer-events: none;
            text-align: center;
            padding: 0 1rem;
        }

        /* OBJECTS PAGE — PLASMA COLLECTIBLES */
        .objects-page-wrapper {
            width: 100%;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 1.5rem;
            min-height: clamp(320px, 58vh, 620px);
        }

        .objects-series-header {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            gap: 0;
        }

        .objects-series-headline {
            font-family: 'Inter', sans-serif;
            font-size: 1.8rem;
            font-style: italic;
            font-weight: 300;
            letter-spacing: 0.2em;
            cursor: pointer;
            position: relative;
            display: inline-block;
            line-height: 1;
            padding: 2px 4px;
            transition: opacity 0.3s ease;
        }

        .objects-series-random-area {
            width: fit-content;
            max-width: 100%;
            display: flex;
            flex-direction: column;
            align-items: stretch;
            gap: 2.5rem;
            padding-top: 1.5rem;
        }

        .objects-series-link {
            text-decoration: none;
            color: black;
            white-space: nowrap;
            display: flex;
            align-items: center;
            gap: 1rem;
            width: 100%;
            justify-content: flex-start;
        }

        .objects-series-icon {
            width: 48px;
            height: 48px;
            object-fit: contain;
            flex-shrink: 0;
        }

        @media (hover: hover) and (pointer: fine) {
            .objects-series-headline:hover {
                outline: 1px solid #000;
                outline-offset: -1px;
            }
        }

        .objects-series-subtitle {
            font-family: 'Inter', sans-serif;
            font-size: 0.8rem;
            font-weight: 400;
            letter-spacing: 0.18em;
            text-transform: uppercase;
            color: black;
            margin-top: 0.5rem;
            text-align: center;
        }

        .objects-collectibles-grid {
            display: grid;
            grid-template-columns: repeat(3, minmax(0, 1fr));
            gap: 3rem 3rem;
            width: 100%;
            align-items: start;
        }

        .collectible-card {
            width: 100%;
            cursor: default;
        }

        .collectible-card.collectible-reveal {
            opacity: 0;
        }

        .collectible-card.collectible-reveal.is-visible {
            opacity: 1;
            transition: opacity 0.7s ease;
        }

        @media (orientation: landscape) {
            .objects-collectibles-grid {
                grid-template-columns: repeat(2, minmax(0, 1fr));
            }
        }

        .collectible-frame {
            position: relative;
            width: 100%;
            overflow: hidden;
            background-color: #000;
        }

        .collectible-frame img {
            position: absolute;
            inset: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
            transition: opacity 0.5s ease;
        }

        .collectible-img-hover {
            opacity: 0;
        }

        .collectible-card.is-hovered .collectible-img-hover {
            opacity: 1;
        }

        .collectible-coming-soon {
            position: absolute;
            inset: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: 'Inter', sans-serif;
            font-size: clamp(0.65rem, 1vw, 0.85rem);
            font-weight: 400;
            letter-spacing: 0.2em;
            text-transform: uppercase;
            color: #fff;
            pointer-events: none;
        }

        .collectible-meta {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            padding-top: 0.6rem;
        }

        .collectible-number {
            font-family: 'Inter', sans-serif;
            font-size: 0.8rem;
            font-weight: 400;
            letter-spacing: 0.08em;
            color: black;
        }

        .collectible-price {
            font-family: 'Inter', sans-serif;
            font-size: 0.8rem;
            font-weight: 400;
            letter-spacing: 0.05em;
            color: black;
        }

        /* Portrait: single column, edge-to-edge images */
        @media (max-width: 1000px) and (orientation: portrait) {
            .objects-collectibles-grid {
                grid-template-columns: 1fr;
                gap: 2rem 0;
                margin-left: -1rem;
                margin-right: -1rem;
                width: calc(100% + 2rem);
            }
            .collectible-meta {
                padding-left: 1rem;
                padding-right: 1rem;
            }

            .objects-page-wrapper {
                min-height: auto;
            }

        }

        /* FOOTER */
        .footer {
            background-color: transparent;
            padding: 3rem 4rem 10rem;
            font-size: var(--type-prose);
            font-family: 'Inter', sans-serif;
            letter-spacing: normal;
            color: black;
            /* Hidden by default — same reveal-on-ready pattern as .font-gated.
               The footer is in the static HTML, so on a fresh cross-document
               load it would otherwise paint at full opacity before the JS-built
               content settles, causing a flash + layout shift. It is revealed
               (.footer-revealed) only once the new page has settled, and the
               MutationObserver in app-core strips that class whenever .fading is
               added, so every navigation starts from hidden again. */
            opacity: 0;
            transition: opacity 0.4s ease;
        }

        .footer.footer-revealed {
            opacity: 1;
        }

        /* Must come AFTER .footer-revealed (equal specificity) so an in-flight
           fade-out wins over a stale reveal. */
        .footer.fading {
            opacity: 0;
        }

        .footer-content {
            position: relative;
            z-index: 1;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 2rem;
        }
        
        .footer-akuratna {
            font-weight: 700;
            color: black;
        }
        
        .footer-links {
            display: flex;
            gap: 2rem;
            flex-wrap: nowrap;
            align-items: center;
        }
        
        .footer-links a {
            color: black;
            text-decoration: none;
            cursor: pointer;
            position: relative;
            display: inline-block;
            line-height: 1;
            padding: 2px 4px;
        }

        .instagram-icon {
            width: 22px;
            height: 22px;
            color: black;
            transition: transform 0.6s ease;
        }

        .insta-hover-btn {
            position: relative;
        }

        .insta-hover-btn::after {
            content: attr(data-tooltip);
            position: absolute;
            bottom: calc(100% + 6px);
            left: 50%;
            transform: translateX(-50%);
            font-family: Arial, sans-serif;
            font-size: 12px;
            font-weight: bold;
            color: #fff;
            background: #000;
            padding: 5px 10px;
            border-radius: 0;
            white-space: nowrap;
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.15s;
        }

        @media (hover: hover) and (pointer: fine) {
            .insta-hover-btn:hover .instagram-icon {
                transform: rotate(270deg);
            }
            .insta-hover-btn:hover::after {
                opacity: 1;
            }
        }
        
        .footer-links a {
            transition: opacity 0.3s ease;
        }
        
        .lang-switch-container.footer-lang {
            font-family: 'Inter', sans-serif;
            letter-spacing: 0.15em;
            font-size: 0.875rem;
        }
        
        /* About page: bring content above the fixed background canvas (z-index:1) */
        body.about-active .content,
        body.about-active .footer {
            position: relative;
            z-index: 2;
        }

        /* About background uses a position:fixed canvas with shapes that are
           manually scroll-synced via `worldGroup.position.y = innerHeight +
           2 * scrollY` each frame (about-background.js). During rubber-band
           overscroll, the page bounces visually but window.scrollY clips at
           0 / max, so the canvas freezes while the DOM content slides —
           shapes appear "stuck" until the bounce settles. Disabling the
           rubber-band only while the about background is active removes
           that desync without affecting any other page. */
        html:has(body.about-active),
        body.about-active {
            overscroll-behavior-y: contain;
        }

        /* FULL PAGE OVERLAYS */
        .modal-overlay {
            position: fixed;
            inset: 0;
            background-color: white;
            z-index: 100;
            opacity: 0;
            visibility: hidden;
            transition: opacity 0.3s ease, visibility 0.3s ease;
            overflow-y: auto;
        }

        .modal-overlay.active {
            opacity: 1;
            visibility: visible;
        }

        .modal-content {
            background-color: white;
            padding: 5rem 3rem 3rem;
            max-width: 900px;
            margin: 0 auto;
            min-height: 100vh;
        }

        .modal-close {
            position: fixed;
            top: 2rem;
            right: 2rem;
            background: none;
            border: none;
            width: 24px;
            height: 24px;
            cursor: pointer;
            transition: opacity 0.3s ease;
            z-index: 101;
            padding: 0;
        }

        @media (hover: hover) and (pointer: fine) {
            .modal-close:hover {
                opacity: 0.6;
            }
        }

        .modal-close::before,
        .modal-close::after {
            content: '';
            position: absolute;
            top: 50%;
            left: 50%;
            width: 100%;
            height: 1px;
            background-color: black;
        }

        .modal-close::before {
            transform: translate(-50%, -50%) rotate(45deg);
        }

        .modal-close::after {
            transform: translate(-50%, -50%) rotate(-45deg);
        }

        .modal-content h1 {
            font-size: 2rem;
            margin-bottom: 2rem;
            font-weight: 700;
        }

        .modal-content h2 {
            font-size: 1.5rem;
            margin-top: 2rem;
            margin-bottom: 1.5rem;
            font-weight: 700;
        }

        .modal-content h3 {
            font-size: 1.1rem;
            margin-top: 1.5rem;
            margin-bottom: 0.75rem;
            font-weight: 700;
        }

        .modal-content p {
            margin-bottom: 1rem;
            line-height: 1.6;
        }

        .modal-content ul {
            margin-bottom: 1rem;
            margin-left: 1.5rem;
            line-height: 1.6;
        }

        .lang-content {
            transition: opacity 0.3s ease;
        }

        .lang-content.fading {
            opacity: 0;
        }
        
        /* DESKTOP ADJUSTMENTS */
        @media (min-width: 769px) {
            .gallery-small-images {
                grid-template-columns: 0.8fr 0.8fr 0.8fr;
                gap: 2rem;
                justify-content: start;
            }
            
            .gallery-small-square {
                width: 100%;
            }
        }
        
        /* IMAGE LIGHTBOX */
        .image-lightbox {
            position: fixed;
            inset: 0;
            background-color: rgba(0, 0, 0, 0.9);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 200;
            opacity: 0;
            visibility: hidden;
            transition: opacity 0.3s ease, visibility 0.3s ease;
            padding: 2rem;
        }
        
        .image-lightbox.active {
            opacity: 1;
            visibility: visible;
        }
        
        .image-lightbox-content {
            max-width: 90vw;
            max-height: 90vh;
            position: relative;
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        
        .image-lightbox-content img {
            max-width: 100%;
            max-height: 80vh;
            object-fit: contain;
        }
        
        .image-lightbox-caption {
            color: white;
            text-align: center;
            font-family: 'Inter', sans-serif;
            font-size: var(--type-prose);
            font-style: italic;
            letter-spacing: 0.15em;
            margin-top: 1rem;
            max-width: 600px;
        }
        
        .image-lightbox-close {
            position: absolute;
            top: 1rem;
            right: 1rem;
            background: none;
            border: none;
            width: 32px;
            height: 32px;
            cursor: pointer;
            transition: opacity 0.3s ease;
            z-index: 201;
            padding: 0;
        }

        @media (hover: hover) and (pointer: fine) {
            .image-lightbox-close:hover {
                opacity: 0.6;
            }
        }

        .image-lightbox-close::before,
        .image-lightbox-close::after {
            content: '';
            position: absolute;
            top: 50%;
            left: 50%;
            width: 100%;
            height: 1px;
            background-color: white;
        }

        .image-lightbox-close::before {
            transform: translate(-50%, -50%) rotate(45deg);
        }

        .image-lightbox-close::after {
            transform: translate(-50%, -50%) rotate(-45deg);
        }
        
        /* LANDSCAPE LAYOUT */
        @media (orientation: landscape) {
            .content {
                padding-top: calc(54px + 2rem);
            }
            .content.all-materials {
                padding-top: calc(54px + 3rem);
            }
            .content.compact-top {
                padding-top: calc(54px + 0.5rem);
            }
        }

        /* MOBILE PORTRAIT LAYOUT */
        @media (max-width: 1000px) and (orientation: portrait) {
            :root {
                --logo-scale: 1.00;
            }

            .header {
                padding: calc(0.75rem + env(safe-area-inset-top)) 1rem 0.5rem;
            }

            .header .nav {
                min-height: 34px;
            }

            .logo-studio {
                font-size: 0.9rem;
                margin-bottom: 6px;
            }

            .logo-main {
                font-size: 24px;
                letter-spacing: 0.15em;
            }

            .nav-right {
                gap: 1.2rem;
            }

            .header-tagline {
                left: 100%;
                top: 50%;
                transform: translateY(-50%);
                margin-left: 0.7rem;
                margin-top: 0;
                font-size: 0.48rem;
                letter-spacing: 0.17em;
            }

            /* Hitbox shrinks here; the mark scales with it via the two custom
               properties (22px/6px at 100%). Everything else is derived. */
            .hamburger {
                --menu-bar-w: 18px;
                --menu-gap: 5px;
                width: 22px;
                height: 16px;
            }

            .content {
                padding: calc(55px + 2rem) 1rem 80px;
            }

            .content.all-materials {
                padding-top: 90px;
            }

            .content.compact-top {
                padding-top: 55px;
            }

            .gallery-grid {
                gap: 2rem;
            }

            .gallery-row {
                display: flex;
                flex-direction: column;
                gap: 1rem;
            }

            .gallery-main-item {
                display: grid;
                grid-template-columns: 1fr;
                grid-template-rows: auto auto auto;
                width: 100%;
                gap: 0.5rem;
                position: static; /* Disable sticky on mobile portrait */
            }

            .gallery-image-row {
                display: contents;
            }

            .gallery-square {
                width: 100%;
                max-width: 100%;
                grid-row: 1;
                grid-column: auto;
                scroll-margin-top: 60px; /* Account for header when scrolling */
            }

            .gallery-caption {
                grid-row: 3;
                font-size: 1.6rem;
                margin-top: 0;
            }

            .series-product-grid {
                grid-template-columns: 1fr;
                gap: 1.5rem;
            }

            .gallery-thumbnails {
                display: flex !important;
                flex-direction: row;
                width: 100%;
                justify-content: flex-start;
                gap: 0.5rem;
                height: auto;
                grid-row: 2;
                aspect-ratio: auto !important;
                overflow-x: auto;
                overflow-y: hidden;
                grid-column: auto;
            }

            .gallery-thumbnail {
                flex-shrink: 0;
                flex: 0 0 auto;
                width: calc((100vw - 2rem) / 5 - 0.4rem);
                height: calc((100vw - 2rem) / 5 - 0.4rem);
                max-height: none;
            }

            .gallery-thumbnail-3d {
                flex-shrink: 0;
                flex: 0 0 auto;
                width: calc((100vw - 2rem) / 5 - 0.4rem);
                height: calc((100vw - 2rem) / 5 - 0.4rem);
                max-height: none;
            }

            /* Portrait: lift the config button strip ("edit parameters" /
               "apply changes" + preset dropdown) above the description so it
               sits directly under the gallery main item, in thumb reach.
               .gallery-details is a flex column, so `order` is enough — the
               DOM order (description → button → specs) stays untouched, and
               the 1rem margin-top is left exactly as it is on desktop. */
            .gallery-details > .blob-config-btn-wrapper {
                order: -1;
                align-self: flex-start;
            }
            
            .gallery-specs {
                gap: 0.4rem 0.8rem;
                margin-top: 0;
            }
            
            .material-description {
                font-size: 0.875rem;
                margin-top: 1rem;
                margin-bottom: 1rem;
            }

            .material-description-empty {
                margin-bottom: 15rem;
                margin-top: 15rem;
            }
            
            .all-materials-link {
                font-size: 0.8rem;
                margin-top: 0.5rem;
            }

            .about-content {
                margin-bottom: 2rem;
                margin-top: 2rem;
                padding: 0;
            }
            
            .about-content h2 {
                font-size: 1rem;
                margin-bottom: 1.5rem;
            }
            
            .dropdown-menu {
                right: 0;
                left: 0;
                width: 100%;
                padding: 2rem 2rem 2rem;
                max-height: 100vh;
                overflow-y: auto;
            }
            
            .dropdown-menu ul {
                gap: 1rem;
            }

            .dropdown-close {
                display: block;
                width: 44px;
                height: 44px;
                top: calc(env(safe-area-inset-top) + 0.5rem + 11px);
                right: calc(1rem - 2px);
            }
            
            .footer {
                padding: 2rem 1rem 8rem;
            }

            .footer-content {
                flex-direction: column;
                align-items: flex-start;
                gap: 1.5rem;
            }
            
            .footer-links {
                gap: 1.5rem;
            }
            
            .modal-content {
                padding: 4rem 1.5rem 2rem;
                font-size: 0.85rem;
            }

            .modal-close {
                top: 1rem;
                right: 1rem;
                font-size: 2rem;
            }

            .modal-content h2 {
                font-size: 1.1rem;
                margin-bottom: 1rem;
            }

            .modal-content h3 {
                font-size: 0.95rem;
                margin-top: 1rem;
            }

            .modal-content p,
            .modal-content ul {
                font-size: 0.8rem;
            }
            
            .dropdown-menu a.strikethrough::after {
                height: 1px;
            }
        }

        /* Narrow phones: allow series labels to wrap instead of overflowing */
        @media (max-width: 430px) and (orientation: portrait) {
            .objects-series-link {
                white-space: normal;
                max-width: 100%;
            }

            .objects-series-link [data-lang] {
                display: block;
                overflow-wrap: anywhere;
                line-height: 1.2;
            }
        }

        /* TOUCH DEVICES */


        .touch-device .logo-studio {
            font-size: 0.75rem;
            margin-bottom: 4px;
        }

        .touch-device .logo-main {
            font-size: 1.5rem;
            letter-spacing: 0.2em;
        }

        .touch-device .logo-wrapper.hidden {
            opacity: 0;
            visibility: hidden;
            pointer-events: none;
        }

        .touch-device .hamburger {
            width: 22px;
            height: 16px;
            background: transparent;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            padding: 0;
        }

        .touch-device .hamburger::before {
            display: none;
        }

        /* Disable hover-driven menu-line animation on touch devices */
        .touch-device .hamburger:hover .menu-line:nth-child(1),
        .touch-device .hamburger:hover .menu-line:nth-child(2) {
            transform: none;
        }

        .touch-device .menu-line {
            transition: none;
        }

        .touch-device .lang-switch-header {
            font-size: 0.75rem;
        }

        /* Mobile dropdown for all touch devices (portrait AND landscape) */
        .touch-device .dropdown-menu {
            right: 0;
            left: 0;
            width: 100%;
            padding: 2rem 2rem 2rem;
            max-height: 100vh;
            overflow-y: auto;
        }

        /* Touch only, and about the finger rather than the type: with
           `line-height: 1` and 2px of padding the link box IS the font size,
           so at 13px a row would be ~17px tall. The padding takes it to 29px.
           It is not the 44px ideal — 44 across 15 rows overflows the menu and
           makes it scroll, which is worse — but it is up from the 20px these
           rows have always had. */
        .touch-device .dropdown-menu a {
            padding-top: 8px;
            padding-bottom: 8px;
        }

        /* Tracking is a width problem, not a pointer or orientation one:
           0.3em pushes the longest labels ("WAVEFORM SERIES") past a narrow
           screen. Keyed off width alone so a narrow desktop window and a
           phone agree. */
        @media (max-width: 600px) {
            .dropdown-menu a,
            .dropdown-group-label {
                letter-spacing: 0.2em;
            }
        }

        .touch-device .dropdown-menu ul {
            gap: 1rem;
        }

        .touch-device .dropdown-menu a.strikethrough::after {
            height: 1px;
        }

        .touch-device .dropdown-close {
            display: block;
            width: 44px;
            height: 44px;
            top: calc(env(safe-area-inset-top) + 0.5rem + 11px);
            right: calc(1rem - 2px);
        }


        /* Larger touch targets for header interactive elements */
        .touch-device .home-button {
            padding: 12px 8px;
        }

        /* Larger touch targets for footer links */
        .touch-device .footer-links a {
            min-height: 44px;
            display: inline-flex;
            align-items: center;
            padding: 0 8px;
        }

        /* WAVE TEXT OVER WIREFRAME */
        .wave-text-overlay {
            position: absolute;
            top: 50%;
            left: 0;
            width: 100vw;
            transform: translateY(-50%);
            pointer-events: none;
            /* z-index removed - creates stacking context that breaks mix-blend-mode */
            /* opacity is kept - won't break blend mode since it's animated from 0 to 1 */
            opacity: 0;
            transition: opacity 0.6s ease;
        }

        .wave-text-overlay.visible {
            opacity: 1;
        }

        .wave-text-overlay svg {
            width: 100%;
            height: auto;
            /* isolation removed - prevents blending with background */
        }

        /* SERIES GRID LAYOUT (HOMEPAGE) */
        .series-section {
            display: flex;
            flex-direction: column;
            gap: 0;
            width: 100%;
            margin-bottom: 3rem;
            overflow: visible;
            position: relative;
        }

        .series-header {
            text-align: center;
            position: relative;
            z-index: 5;
            transform: translateY(50%);
        }

        .series-name {
            font-family: 'Inter', sans-serif;
            font-size: 1.8rem;
            font-style: italic;
            font-weight: 300;
            letter-spacing: 0.2em;
            margin-bottom: 0;
            cursor: pointer;
            position: relative;
            display: inline-block;
            line-height: 1;
            padding: 2px 4px;
            transition: opacity 0.3s ease;
        }

        @media (hover: hover) and (pointer: fine) {
            .series-name:hover {
                outline: 1px solid #000;
                outline-offset: -1px;
            }
        }

        .series-description {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-read);
            font-weight: 300;
            letter-spacing: 0.15em;
            line-height: var(--type-prose-lh);
            transition: opacity 0.3s ease;
        }

        .series-objects-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 3rem;
            width: 100%;
            justify-items: center;
        }

        /* Single centered canvas for 3D series */
        .series-3d-canvas-container {
            width: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            position: relative;
            overflow: visible !important;
        }

        .series-3d-canvas {
            width: 100%;
            max-width: 600px;
            aspect-ratio: 1;
            cursor: pointer;
            background-color: white;
            opacity: 0;
            transition: opacity 0.3s ease;
            position: relative;
            overflow: visible !important;
        }

        .series-3d-canvas.loaded {
            opacity: 1;
        }

        .series-3d-canvas canvas {
            display: block;
            width: 100% !important;
            height: 100% !important;
        }

        .series-object-item {
            display: flex;
            flex-direction: column;
            width: 100%;
            max-width: 400px;
        }

        .series-object-image {
            width: 100%;
            aspect-ratio: 1;
            position: relative;
            overflow: hidden;
            background-color: white;
            cursor: pointer;
            transform: translateZ(0);
            -webkit-transform: translateZ(0);
            backface-visibility: hidden;
            -webkit-backface-visibility: hidden;
        }

        .series-object-image img {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
            transition: opacity 0.3s ease;
            -webkit-transition: opacity 0.3s ease;
            will-change: opacity;
        }

        .series-object-image .image-first {
            opacity: 1;
            z-index: 1;
        }

        .series-object-image .image-second {
            opacity: 0;
            z-index: 2;
        }

        @media (hover: hover) and (pointer: fine) {
            .series-object-image:hover .image-first {
                opacity: 0;
            }

            .series-object-image:hover .image-second {
                opacity: 1;
            }
        }

        .series-object-name {
            font-family: 'Inter', sans-serif;
            font-size: 1.4rem;
            font-style: italic;
            font-weight: 300;
            letter-spacing: 0.15em;
            transition: opacity 0.3s ease;
            text-align: left;
            margin-top: 0.75rem;
        }

        /* Mobile adjustments for metals series grid */
        @media (max-width: 1000px) and (orientation: portrait) {
            .metals-series-card {
                width: 100%;
            }

            .metals-series-coming-soon {
                font-size: clamp(2rem, 8vw, 4rem);
            }
        }

        /* Mobile adjustments for series grid */
        @media (max-width: 1000px) and (orientation: portrait) {
            .series-section {
                margin-bottom: 1rem;
            }

            .series-objects-grid {
                grid-template-columns: 1fr;
                gap: 2.5rem;
            }

            .series-object-item {
                max-width: none;
            }

            .series-name {
                font-size: 1.5rem;
            }

            .series-header {
                transform: none; /* Prevent PLASMA title from overlapping wave text on small screens */
            }

            .series-object-name {
                font-size: 1.2rem;
            }
        }

        /* MATERIAL GUIDE PAGE */
        .material-guide-container {
            display: flex;
            width: 100%;
            max-width: 1200px;
            height: calc(100vh - 100px); /* Full viewport height minus header */
            position: relative;
            padding: 0;
            align-items: center;
            justify-content: center;
        }

        .material-guide-list {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            gap: 1.5rem;
            transition: all 0.5s ease;
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            z-index: 2;
        }

        .material-guide-list.shifted {
            left: 20%;
            opacity: 0.3;
        }

        .material-guide-item {
            font-family: 'Inter', sans-serif;
            font-size: 1rem;
            font-weight: 700;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            color: black;
            cursor: pointer;
            position: relative;
            display: inline-block;
            line-height: 1;
            padding: 2px 4px;
        }

        .material-guide-item.strikethrough::after {
            content: '';
            position: absolute;
            left: -0.2em;
            right: -0.2em;
            top: 50%;
            height: 1px;
            background-color: black;
            transform: translateY(-50%);
        }

        .material-guide-detail {
            position: absolute;
            right: 5%;
            top: 0;
            bottom: 0;
            width: 55%;
            height: 100%;
            overflow-y: auto;
            opacity: 0;
            visibility: hidden;
            transition: opacity 0.5s ease, visibility 0.5s ease;
            z-index: 1;
            display: flex;
            align-items: center;
        }

        .material-guide-detail.active {
            opacity: 1;
            visibility: visible;
        }

        .material-guide-detail-inner {
            border: 1px solid black;
            padding: 2.5rem;
            background-color: white;
            position: relative;
            width: 100%;
            max-height: 90%;
            overflow-y: auto;
        }

        .material-guide-close {
            position: absolute;
            top: 1rem;
            right: 1rem;
            width: 24px;
            height: 24px;
            cursor: pointer;
            transition: opacity 0.3s ease;
        }

        @media (hover: hover) and (pointer: fine) {
            .material-guide-close:hover {
                opacity: 0.6;
            }
        }

        .material-guide-close::before,
        .material-guide-close::after {
            content: '';
            position: absolute;
            top: 50%;
            left: 50%;
            width: 100%;
            height: 1px;
            background-color: black;
        }

        .material-guide-close::before {
            transform: translate(-50%, -50%) rotate(45deg);
        }

        .material-guide-close::after {
            transform: translate(-50%, -50%) rotate(-45deg);
        }

        .material-guide-title {
            font-family: 'Inter', sans-serif;
            font-size: 1.3rem;
            font-weight: 700;
            letter-spacing: 0.2em;
            text-transform: uppercase;
            margin-bottom: 1.5rem;
        }

        .material-guide-text {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-read);
            font-weight: 300;
            letter-spacing: 0.15em;
            line-height: var(--type-prose-lh);
            margin-bottom: 2rem;
        }

        .material-guide-section {
            border-top: 1px solid black;
            padding-top: 1.5rem;
            margin-bottom: 2rem;
        }

        .material-guide-section:last-child {
            margin-bottom: 0;
        }

        .material-guide-section-title {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-meta);
            font-weight: 700;
            letter-spacing: 0.2em;
            text-transform: uppercase;
            margin-bottom: 0.75rem;
        }

        .material-guide-section-content {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-read);
            font-weight: 300;
            letter-spacing: 0.15em;
            line-height: var(--type-prose-lh);
        }

        /* Subcategory buttons */
        .material-guide-subcategories {
            display: flex;
            gap: 1rem;
            margin-bottom: 2rem;
            flex-wrap: wrap;
        }

        .subcategory-button {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-meta);
            font-weight: 400;
            letter-spacing: 0.15em;
            text-transform: uppercase;
            padding: 0.6rem 1.2rem;
            cursor: pointer;
            transition: all 0.3s ease;
            border: 1px solid black;
            background-color: transparent;
            color: black;
        }

        /* Only apply hover effect on devices with hover capability (non-touch) */
        @media (hover: hover) {
            .subcategory-button:hover {
                opacity: 0.7;
            }
        }

        .subcategory-button.active {
            background-color: black;
            color: white;
            border-color: black;
        }

        .subcategory-button.strikethrough {
            text-decoration: line-through;
        }

        /* Mobile adjustments for material guide */
        @media (max-width: 1000px) and (orientation: portrait) {
            .material-guide-container {
                flex-direction: column;
                padding: 0;
                height: calc(100vh - 100px); /* Full viewport height minus header */
                justify-content: center;
                align-items: center;
            }

            .material-guide-list {
                position: absolute;
                left: 50%;
                transform: translateX(-50%);
                transition: all 0.5s ease;
                margin: 0;
                display: flex;
                flex-direction: column;
                align-items: center;
            }

            .material-guide-list.shifted {
                transform: translateX(-100%);
                opacity: 0;
                pointer-events: none;
            }

            .material-guide-detail {
                position: absolute;
                top: 0;
                bottom: 0;
                left: 0;
                right: 0;
                width: 100%;
                height: 100%;
                display: flex;
                align-items: center;
                justify-content: center;
                padding: 0 1rem;
            }

            .material-guide-detail-inner {
                padding: 2rem 1.5rem;
                max-height: 90%;
                overflow-y: auto;
            }

            .material-guide-title {
                font-size: 1.1rem;
            }

            .subcategory-button {
                padding: 0.5rem 1rem;
            }
        }

        /* COMMISSIONS PAGE */
        .commissions-page {
            display: flex;
            gap: 4rem;
            width: 100%;
            max-width: 1600px;
            min-height: calc(100vh - 200px);
            align-items: flex-start;
        }

        .commissions-column {
            flex: 1;
            display: flex;
            flex-direction: column;
            gap: 4rem;
        }

        .commissions-container {
            display: flex;
            flex-direction: column;
            gap: 2rem;
            width: 100%;
        }

        .commissions-canvas-container {
            width: 100%;
            height: 600px;
            position: relative;
            opacity: 0;
            transition: opacity 0.4s ease;
        }

        .commissions-canvas-container.loaded {
            opacity: 1;
        }

        .commissions-canvas-placeholder {
            background-color: transparent;
        }

        #commissions-shelf-canvas {
            width: 100%;
            height: 100%;
            background-color: white;
        }

        #commissions-shelf-canvas canvas {
            display: block;
            width: 100% !important;
            height: 100% !important;
        }

        #commissions-table-canvas {
            width: 100%;
            height: 100%;
            background-color: white;
        }

        #commissions-table-canvas canvas {
            display: block;
            width: 100% !important;
            height: 100% !important;
        }

        #commissions-wavy-tube-canvas {
            width: 100%;
            height: 100%;
            background-color: white;
        }

        #commissions-wavy-tube-canvas canvas {
            display: block;
            width: 100% !important;
            height: 100% !important;
        }

        .commissions-text-container {
            display: flex;
            flex-direction: column;
            gap: 2rem;
            padding: 2rem 0;
            align-self: stretch;
        }

        .commissions-title {
            font-family: 'Inter', sans-serif;
            font-size: 2rem;
            font-weight: 300;
            letter-spacing: 0.2em;
            margin-bottom: 1rem;
        }

        .commissions-content {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-read);
            font-weight: 300;
            letter-spacing: 0.15em;
            line-height: var(--type-prose-lh);
            transition: opacity 0.3s ease;
        }

        .commissions-content p {
            margin-bottom: 1.5rem;
        }

        .commissions-content p:last-child {
            margin-bottom: 0;
        }

        /* Mobile adjustments for commissions page */
        @media (max-width: 1000px) and (orientation: portrait) {
            .commissions-page {
                flex-direction: column;
                gap: 2rem;
                padding: 0 1rem;
            }

            /* Merge columns and use order for alternating layout */
            .commissions-column {
                gap: 2rem;
                width: 100%;
            }

            .commissions-column:first-child,
            .commissions-column:last-child {
                display: contents;
            }

            .commissions-canvas-container {
                height: 50vh;
                min-height: 300px;
            }

            .commissions-text-container {
                padding: 1rem 0;
            }

            .commissions-title {
                font-size: 1.5rem;
            }
        }

        /* DEBUG CONTROLS */
        .debug-controls-panel {
            background: white;
            border: 1px solid black;
            padding: 1.5rem;
            margin-top: 1rem;
            font-family: 'Inter', sans-serif;
        }

        .debug-controls-panel h4 {
            font-size: 0.875rem;
            font-weight: 700;
            letter-spacing: 0.2em;
            text-transform: uppercase;
            margin-bottom: 1.5rem;
            border-bottom: 1px solid black;
            padding-bottom: 0.5rem;
        }

        .debug-control-group {
            margin-bottom: 1.5rem;
        }

        .debug-control-group:last-child {
            margin-bottom: 0;
        }

        .debug-control-group label {
            display: block;
            font-weight: 600;
            margin-bottom: 0.5rem;
            font-size: 0.75rem;
            letter-spacing: 0.1em;
            text-transform: uppercase;
        }

        .debug-control-group input[type="range"] {
            width: 100%;
            margin-bottom: 0.5rem;
            height: 2px;
            background: black;
            outline: none;
            -webkit-appearance: none;
            appearance: none;
        }

        .debug-control-group input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 12px;
            height: 12px;
            background: black;
            cursor: pointer;
            border-radius: 50%;
        }

        .debug-control-group input[type="range"]::-moz-range-thumb {
            width: 12px;
            height: 12px;
            background: black;
            cursor: pointer;
            border-radius: 50%;
            border: none;
        }

        .debug-value-display {
            display: flex;
            justify-content: space-between;
            font-size: 0.7rem;
            color: #666;
            letter-spacing: 0.05em;
        }

        .debug-value-display input[type="number"] {
            width: 60px;
            padding: 0.2rem 0.3rem;
            border: 1px solid #ccc;
            background: white;
            color: black;
            font-family: 'Inter', sans-serif;
            font-size: 0.7rem;
            text-align: center;
        }

        .debug-button-group {
            display: flex;
            gap: 0.5rem;
            margin-top: 1.5rem;
        }

        .debug-button {
            flex: 1;
            padding: 0.6rem;
            background: black;
            color: white;
            border: 1px solid black;
            cursor: pointer;
            font-family: 'Inter', sans-serif;
            font-weight: 600;
            font-size: 0.7rem;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            transition: all 0.3s ease;
        }

        @media (hover: hover) and (pointer: fine) {
            .debug-button:hover {
                background: white;
                color: black;
            }
        }

        .debug-info {
            background: #f9f9f9;
            padding: 0.75rem;
            margin-top: 1rem;
            font-size: 0.7rem;
            letter-spacing: 0.05em;
            line-height: 1.6;
        }

        .debug-info strong {
            display: block;
            margin-bottom: 0.5rem;
            font-weight: 700;
            text-transform: uppercase;
        }

        @media (max-width: 1000px) and (orientation: portrait) {
            .debug-controls-panel {
                padding: 1rem;
            }

            .debug-controls-panel h4 {
                font-size: 0.75rem;
            }

            .debug-control-group label {
                font-size: 0.65rem;
            }

            .debug-button {
                font-size: 0.65rem;
                padding: 0.5rem;
            }
        }

        /* ── Blob Configurator Mobile Overrides ── */
        @media (max-width: 1000px) and (orientation: portrait) {
            .blob-configurator-container .sl-waves {
                left: 4px;
            }
            .blob-configurator-container .sl-height {
                right: 4px;
            }
            .blob-configurator-container .sl-diameter {
                top: 4px;
            }
            .blob-configurator-container .sl-major {
                top: 4px;
            }
            .blob-configurator-container .sl-minor {
                top: 48px;
            }
            .blob-configurator-container .sl-randomness {
                bottom: 4px;
            }
            .blob-configurator-container .sl-radius {
                bottom: 4px;
            }
        }

        /* ── Landing welcome dialog ── */
        dialog.landing-modal {
            position: fixed;
            inset: 0;
            width: 100%;
            height: 100%;
            max-width: none;
            max-height: none;
            margin: 0;
            padding: 0;
            border: none;
            background: rgba(0, 0, 0, 0.18);
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0;
            transition: opacity 0.18s ease;
        }

        dialog.landing-modal::backdrop {
            background: transparent;
        }

        dialog.landing-modal .landing-modal-box {
            background: #000;
            border: none;
            border-radius: 22px;
            padding: 48px 52px 44px;
            display: flex;
            flex-direction: column;
            gap: 28px;
            font-family: inherit;
            max-width: 90vw;
            width: 820px;
            position: relative;
            /* isolate: keeps the difference-blended drawing blending against the
               box's own black, not against the page behind the dialog.
               overflow: the drawing is clipped by the rounded corners. */
            isolation: isolate;
            overflow: hidden;
        }

        /* Line drawing hopping around behind/over the copy. The source SVG has
           black strokes on transparent, so invert() makes them white; difference
           then leaves them white on the black box and punches them through the
           white text as black. */
        dialog.landing-modal .landing-drawing {
            position: absolute;
            /* Anything that lands past an edge is clipped by the box's
               overflow:hidden / rounded corners. The floor keeps the objects at a
               readable size on phones instead of shrinking with the box. */
            width: max(62%, 22rem);
            height: auto;
            pointer-events: none;
            filter: invert(1);
            mix-blend-mode: difference;
            opacity: 0;
        }

        dialog.landing-modal .landing-drawing.is-visible {
            opacity: 1;
        }

        dialog.landing-modal .landing-modal-close {
            position: absolute;
            top: 18px;
            right: 20px;
            width: 22px;
            height: 22px;
            cursor: pointer;
            background: none;
            border: none;
            padding: 0;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        dialog.landing-modal .landing-modal-close::before,
        dialog.landing-modal .landing-modal-close::after {
            content: '';
            position: absolute;
            width: 16px;
            height: 1px;
            background: #fff;
        }

        dialog.landing-modal .landing-modal-close::before {
            transform: rotate(45deg);
        }

        dialog.landing-modal .landing-modal-close::after {
            transform: rotate(-45deg);
        }

        @media (hover: hover) and (pointer: fine) {
            dialog.landing-modal .landing-modal-close:hover {
                opacity: 0.5;
            }
        }

        dialog.landing-modal .landing-stay-tuned {
            font-family: 'Inter', sans-serif;
            font-size: clamp(2.2rem, 7vw, 4.8rem);
            font-weight: 900;
            letter-spacing: 0.38em;
            text-transform: uppercase;
            line-height: 1;
            min-height: clamp(2.2rem, 7vw, 4.8rem);
            margin: 0;
            color: #fff;
        }

        dialog.landing-modal .landing-stay-tuned .landing-stay-tuned-text {
            display: inline-block;
        }

        dialog.landing-modal .landing-dots {
            display: inline-flex;
            align-items: flex-end;
            gap: 0.32em;
            letter-spacing: 0;
            margin-left: 0.05em;
            vertical-align: baseline;
        }

        dialog.landing-modal .landing-dot {
            display: inline-block;
            width: 0.26em;
            height: 0.26em;
            background: #fff;
            flex-shrink: 0;
        }

        /* sequence: 0 → 1 → 2 → 3 → 2 → 1 → 0 → repeat (6 phases × ~0.67s = 4s) */
        dialog.landing-modal .landing-dot-1 {
            animation: landing-dot1 4s step-end infinite;
        }

        dialog.landing-modal .landing-dot-2 {
            animation: landing-dot2 4s step-end infinite;
        }

        dialog.landing-modal .landing-dot-3 {
            animation: landing-dot3 4s step-end infinite;
        }

        @keyframes landing-dot1 {
            0%      { opacity: 0; animation-timing-function: step-end; }
            16.67%  { opacity: 1; animation-timing-function: step-end; }
            100%    { opacity: 1; }
        }

        @keyframes landing-dot2 {
            0%      { opacity: 0; animation-timing-function: step-end; }
            33.33%  { opacity: 1; animation-timing-function: step-end; }
            83.33%  { opacity: 0; animation-timing-function: step-end; }
            100%    { opacity: 0; }
        }

        @keyframes landing-dot3 {
            0%      { opacity: 0; animation-timing-function: step-end; }
            50%     { opacity: 1; animation-timing-function: step-end; }
            66.67%  { opacity: 0; animation-timing-function: step-end; }
            100%    { opacity: 0; }
        }

        dialog.landing-modal .landing-body {
            font-family: inherit;
            font-size: 14px;
            letter-spacing: 0.02em;
            line-height: 1.65;
            color: #fff;
            max-width: 560px;
        }

        dialog.landing-modal .landing-body a {
            color: #fff;
            text-decoration: underline;
            text-underline-offset: 2px;
        }

        @media (hover: hover) and (pointer: fine) {
            dialog.landing-modal .landing-body a:hover {
                opacity: 0.55;
            }
        }

        dialog.landing-modal .landing-lang-switch {
            position: absolute;
            bottom: 20px;
            right: 20px;
        }

        dialog.landing-modal .landing-lang-switch.font-gated-host {
            position: absolute;
        }

        dialog.landing-modal .landing-lang-switch,
        dialog.landing-modal .landing-lang-switch .lang-active,
        dialog.landing-modal .landing-lang-switch .lang-inactive {
            color: #fff;
        }

        dialog.landing-modal .landing-lang-switch .font-loading-spinner {
            width: 10px;
            height: 10px;
        }

        /* Spinners sit on the black box now — invert them too */
        dialog.landing-modal .font-loading-spinner {
            border-color: #fff;
            border-right-color: transparent;
            border-bottom-color: transparent;
        }

        @supports (background: conic-gradient(#000 0deg, transparent 360deg)) {
            dialog.landing-modal .font-loading-spinner {
                border: none;
                background: conic-gradient(from 0deg, #fff 0deg 300deg, transparent 300deg 360deg);
            }
        }

        /* Grid stack: both paragraphs occupy the same cell so the container
           always reserves height for the tallest version → no layout shift */
        dialog.landing-modal .landing-body-stack {
            display: grid;
            /* Smooth language-switch fade on mobile: transition must exist
               BEFORE the fade-out class is added, otherwise some mobile
               browsers skip the animation entirely.  GPU-layer hints
               (translateZ / backface-visibility) keep the opacity change
               on the compositor thread for jank-free rendering. */
            transition: opacity 0.3s ease;
            will-change: opacity;
            transform: translateZ(0);
            backface-visibility: hidden;
        }

        dialog.landing-modal .landing-body-stack.font-gated-host {
            position: relative;
        }

        /* The drawing paints over everything, which would blend straight through
           this small copy. Lift the stack above it (z-index) and lay a soft black
           pad underneath the text so it always reads against solid black. */
        dialog.landing-modal .landing-body-stack {
            position: relative;
            z-index: 2;
        }

        dialog.landing-modal .landing-body-stack::before {
            content: '';
            position: absolute;
            inset: -2px -8px;
            /* Not solid: the drawing still shows through, just dimmed enough for
               the copy to read. Blur softens the edge without spreading far. */
            background: rgba(0, 0, 0, 0.5);
            filter: blur(12px);
            z-index: -1;
        }

        dialog.landing-modal .landing-body-stack .font-loading-spinner {
            width: 12px;
            height: 12px;
        }

        dialog.landing-modal .landing-body-stack .landing-body {
            grid-column: 1;
            grid-row: 1;
        }

        @media (max-width: 600px) {
            dialog.landing-modal .landing-modal-box {
                padding: 40px 24px 32px;
                width: 92vw;
                border-radius: 18px;
            }

            /* SVG scales naturally — no override needed */
        }

        /* ==================== SHOP ==================== */
        /* /shop/ and /newsletter/. Typography follows the rest of the site:
           Inter 300 at 0.95rem with 0.15em letter-spacing — the same voice as
           .series-description and .material-guide-text. The drawer inverts to
           black so it reads as a panel laid over the page rather than more
           page; everything inside it flips with it, buttons included. */

        .content.shop-page {
            display: block;
            width: 100%;
            max-width: 1400px;
            /* #main-content is a plain block, so `align-self` here was inert
               and the capped column sat hard against the left — which is why
               the page read as off-centre with a gap on the right. Auto
               margins are what actually centre a max-width block. */
            margin-left: auto;
            margin-right: auto;
        }

        /* Heading and intro copy follow .about-content / .material-guide-title —
           centred, 1rem, 0.2em tracking, the 700-weight 0.3em heading. That is
           the page voice everywhere else on the site. */
        .shop-inner {
            margin-top: 4rem;
            padding: 0 10rem;
            text-align: center;
            font-family: 'Inter', sans-serif;
            font-size: var(--type-prose);
            font-weight: 300;
            letter-spacing: 0.2em;
            color: #000;
        }

        .shop-title {
            font-family: 'Inter', sans-serif;
            font-size: 1rem;
            font-weight: 700;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            text-align: center;
            margin: 0 0 2rem;
        }

        .shop-intro,
        .shop-empty {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-prose);
            font-weight: 300;
            letter-spacing: 0.2em;
            line-height: var(--type-prose-lh);
            margin: 0 auto 4rem;
            max-width: 70ch;
        }

        .shop-order-ok {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-prose);
            font-weight: 300;
            letter-spacing: 0.15em;
            line-height: var(--type-prose-lh);
            max-width: 46ch;
            margin: -2rem 0 3rem;
            padding-left: 1rem;
            border-left: 1px solid #000;
        }

        .shop-grid {
            text-align: left;
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
            gap: 4rem 3rem;
        }

        .shop-card {
            cursor: pointer;
            outline-offset: 6px;
            transition: opacity 0.3s ease;
        }

        .shop-card.is-sold { opacity: 0.45; }

        @media (hover: hover) and (pointer: fine) {
            .shop-card:hover { opacity: 0.7; }
        }

        .shop-card-caption {
            margin-top: 1rem;
            font-family: 'Inter', sans-serif;
            font-weight: 300;
            letter-spacing: 0.15em;
            line-height: 1.7;
        }

        .shop-card-title {
            font-size: var(--type-label);
            text-transform: uppercase;
        }

        .shop-card-label { font-weight: 400; }

        .shop-card-price { font-size: var(--type-meta); }

        .shop-card-price { margin-top: 0.35rem; }

        /* ---- gallery ---- */

        .shop-gallery-viewport {
            position: relative;
            width: 100%;
            aspect-ratio: 4 / 5;
            overflow: hidden;
            background: #f4f4f4;
        }

        .shop-gallery-viewport img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
        }

        /* Left and right thirds step the gallery. Invisible on purpose — the
           image is the interface; a chrome of arrows would be louder than the
           object. */
        .shop-gallery-nav {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 33%;
            border: 0;
            padding: 0;
            background: transparent;
            cursor: pointer;
            -webkit-tap-highlight-color: transparent;
        }

        .shop-gallery-prev { left: 0; }
        .shop-gallery-next { right: 0; }

        .shop-gallery-dots {
            display: flex;
            gap: 0.5rem;
            margin-top: 0.75rem;
        }

        .shop-gallery-dot {
            width: 5px;
            height: 5px;
            padding: 0;
            border: 0;
            border-radius: 50%;
            background: #c8c8c8;
            cursor: pointer;
            transition: background 0.2s ease;
        }

        .shop-gallery-dot.is-active { background: #000; }

        /* Items whose photos haven't been shot yet. A flat block beats a
           broken image icon, and it keeps the grid rhythm intact. */
        .shop-gallery.is-empty {
            aspect-ratio: 4 / 5;
            background: #f4f4f4;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .shop-gallery-placeholder {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-micro);
            font-weight: 300;
            letter-spacing: 0.2em;
            text-transform: uppercase;
            color: #aaa;
        }

        /* ---- drawer ---- */
        /* The panel is white like the page it slides over, with no dividing
           line down its left edge. The backdrop does that job instead: it
           dims the page behind, so the panel reads as the lit surface and the
           separation is a change in brightness rather than a drawn border. */

        .shop-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.5);
            opacity: 0;
            transition: opacity 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
            z-index: 110;
        }

        .shop-backdrop.is-open { opacity: 1; }

        .shop-drawer {
            position: fixed;
            inset: 0 0 0 auto;
            width: min(460px, 100%);
            background: #fff;
            color: #000;
            transform: translateX(100%);
            /* Same duration and easing as the nav dropdown, so the two
               sliding panels on the site move at one speed. */
            transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
            z-index: 120;
            display: flex;
            flex-direction: column;
        }

        .shop-drawer.is-open { transform: translateX(0); }

        body.shop-drawer-open { overflow: hidden; }

        .shop-drawer-scroll {
            text-align: left;
            flex: 1;
            overflow-y: auto;
            -webkit-overflow-scrolling: touch;
            padding: 4rem 2rem calc(2rem + env(safe-area-inset-bottom));
        }

        .shop-drawer-close {
            position: absolute;
            top: 1.25rem;
            right: 1.25rem;
            width: 24px;
            height: 24px;
            border: 0;
            padding: 0;
            background: transparent;
            cursor: pointer;
            z-index: 1;
        }

        .shop-drawer-close::before,
        .shop-drawer-close::after {
            content: '';
            position: absolute;
            left: 50%;
            top: 50%;
            width: 18px;
            height: 1px;
            background: #000;
        }

        .shop-drawer-close::before { transform: translate(-50%, -50%) rotate(45deg); }
        .shop-drawer-close::after  { transform: translate(-50%, -50%) rotate(-45deg); }

        /* The drawer now shares the page's palette, so the gallery needs no
           overrides of its own — it keeps the light placeholder and dark dots
           it uses in the grid. */
        .shop-drawer .shop-gallery.is-empty,
        .shop-drawer .shop-gallery-viewport { background: #f4f4f4; }

        .shop-drawer-head {
            margin-top: 1.5rem;
            font-family: 'Inter', sans-serif;
            font-weight: 300;
            letter-spacing: 0.15em;
            line-height: 1.7;
        }

        .shop-drawer-title {
            font-size: var(--type-label);
            font-weight: 300;
            letter-spacing: 0.15em;
            text-transform: uppercase;
            margin: 0;
        }

        /* Only the shop's piece name goes bold — the site's bold-label voice,
           700 at 0.3em, matching the configurator's dropdown and label buttons
           and the nav dropdown's links. Scoped to .shop-drawer-head, which
           only pages/shop.js builds: price-request.js puts its "Request a
           price" heading straight into the scroll body with the same class,
           and that one stays as it was. */
        .shop-drawer-head .shop-drawer-title {
            font-weight: 700;
            letter-spacing: 0.3em;
        }

        .shop-drawer-sub,
        .shop-drawer-price { font-size: var(--type-meta); }

        /* The object being enquired about, under the panel title. It sits
           outside .shop-drawer-head, so it would otherwise fall back to the
           document's letter-spacing and read visibly tighter than everything
           around it. Bold, because it is the one line that says which object
           this message is about. */
        .shop-drawer-object {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-label);
            font-weight: 700;
            letter-spacing: 0.15em;
            line-height: 1.7;
            margin-top: 0.5rem;
            text-transform: uppercase;
        }

        .shop-drawer-price { margin-top: 0.35rem; }

        .shop-drawer-text {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-prose);
            font-weight: 300;
            letter-spacing: 0.15em;
            line-height: var(--type-prose-lh);
            margin: 2rem 0 0;
        }

        .shop-specs {
            margin: 1.5rem 0 0;
            font-size: var(--type-meta);
            letter-spacing: 0.15em;
            /* Tight rows: this is a spec block, not prose. The label column
               sizes to content so the values line up in one column. */
            row-gap: 0.1rem;
            column-gap: 1.25rem;
            line-height: 1.5;
            color: #999;
        }

        .shop-drawer .shop-specs .gallery-spec-label,
        .shop-drawer .shop-specs .gallery-spec-value {
            color: #999;
        }

        .shop-note,
        .shop-sold-note,
        .shop-enquiry-alt,
        .shop-form-status,
        .shop-form-sent {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-micro);
            font-weight: 300;
            letter-spacing: 0.15em;
            line-height: 1.8;
            color: #999;
            margin: 0.75rem 0 0;
        }

        .shop-drawer .shop-sold-note,
        .shop-drawer .shop-form-sent { color: #000; }

        .shop-sold-note {
            margin-top: 2.5rem;
            text-transform: uppercase;
        }

        .shop-form-sent { margin-top: 2.5rem; }

        /* ---- buttons ---- */
        /* Black outline on white, filling to black with white text on hover.
           The drawer is the same white surface as the page, so it needs no
           inverted variant. */

        .shop-btn {
            display: block;
            width: 100%;
            margin-top: 2.5rem;
            padding: 0.9rem 1rem;
            border: 1px solid #000;
            background: transparent;
            color: #000;
            font-family: 'Inter', sans-serif;
            font-size: var(--type-meta);
            font-weight: 400;
            letter-spacing: 0.2em;
            text-transform: uppercase;
            text-align: center;
            text-decoration: none;
            cursor: pointer;
            transition: background 0.25s ease, color 0.25s ease;
        }

        .shop-btn:disabled { opacity: 0.4; cursor: default; }

        @media (hover: hover) and (pointer: fine) {
            .shop-btn:not(:disabled):hover {
                background: #000;
                color: #fff;
            }
        }

        /* ---- form fields ---- */

        .shop-field { display: block; margin-top: 1.25rem; }

        .shop-field-label {
            display: block;
            font-family: 'Inter', sans-serif;
            font-size: var(--type-micro);
            font-weight: 300;
            letter-spacing: 0.2em;
            text-transform: uppercase;
            margin-bottom: 0.5rem;
        }

        .shop-field input,
        .shop-field textarea {
            width: 100%;
            padding: 0.6rem 0.7rem;
            border: 1px solid #ccc;
            border-radius: 0;
            background: transparent;
            color: #000;
            font-family: 'Inter', sans-serif;
            font-size: 0.9rem;
            font-weight: 300;
            letter-spacing: 0.15em;
        }

        .shop-field textarea { min-height: 6rem; resize: vertical; }

        .shop-field input:focus,
        .shop-field textarea:focus {
            outline: none;
            border-color: #000;
        }

        .shop-consent {
            display: flex;
            gap: 0.6rem;
            align-items: flex-start;
            margin-top: 1.25rem;
            font-family: 'Inter', sans-serif;
            font-size: var(--type-micro);
            font-weight: 300;
            letter-spacing: 0.15em;
            line-height: 1.7;
            color: #666;
        }

        .shop-consent input {
            margin-top: 0.3rem;
            flex: none;
            accent-color: #000;
        }

        .shop-honeypot {
            position: absolute;
            left: -9999px;
            opacity: 0;
            pointer-events: none;
        }

        /* Links inside shop/newsletter copy follow the site convention:
           inherit the text colour, underline to mark themselves. The browser
           default blue belongs to no part of this design. */
        .shop-page a,
        .shop-enquiry-alt a,
        .shop-consent a {
            color: inherit;
            text-decoration: underline;
        }

        /* "Enquire via email" — the mailto route, kept as a small text link at
           the very bottom of the enquiry drawer. Type matches the field labels
           (micro, uppercase, wide-tracked). No border at rest; on hover a thin
           line frame draws in, the way the site's buttons do. The border is
           transparent rather than absent so nothing shifts when it appears. */
        .pr-email-alt {
            margin: 2rem 0 0;
            text-align: center;
        }

        .pr-email-link {
            display: inline-block;
            padding: 0.5rem 0.9rem;
            border: 1px solid transparent;
            color: #999;
            font-family: 'Inter', sans-serif;
            font-size: var(--type-micro);
            font-weight: 300;
            letter-spacing: 0.2em;
            line-height: 1.6;
            text-transform: uppercase;
            text-decoration: none;
            transition: border-color 0.25s ease, color 0.25s ease;
        }

        @media (hover: hover) and (pointer: fine) {
            .pr-email-link:hover {
                border-color: #000;
                color: #000;
            }
        }

        /* ---- newsletter ---- */
        /* The whole page is a title, three lines saying what arrives, and one
           row to sign up. Nothing explains anything. The fields are
           text-length, not page-length. */

        /* .shop-inner centres its text, but a max-width block still needs its
           own auto margins or it sits hard against the left of the padding
           box — which is why this column read as off-centre. */
        .newsletter-inner .newsletter-form,
        .newsletter-inner .shop-intro,
        .newsletter-inner > p {
            max-width: 34rem;
            margin-left: auto;
            margin-right: auto;
        }

        /* The page is short by nature — a title, a band and one line. Without
           real air around them it reads as a fragment dropped at the top of an
           empty page rather than as a page. */
        .shop-inner.newsletter-inner { margin-top: 8rem; }
        .newsletter-inner .shop-title { margin-bottom: 3.5rem; }

        /* The shop's version is a left-ruled block hung off a left-aligned
           column. This page is centred, so it drops the rule and centres with
           the rest. */
        .newsletter-inner .shop-order-ok {
            max-width: 34rem;
            margin: 3rem auto 0;
            padding-left: 0;
            border-left: 0;
        }
        .newsletter-inner .newsletter-form { margin: 5rem auto 8rem; }

        @media (max-width: 1000px) {
            .shop-inner.newsletter-inner { margin-top: 4rem; }
            .newsletter-inner .shop-title { margin-bottom: 2.5rem; }
            .newsletter-inner .newsletter-form { margin: 3rem auto 4rem; }
        }

        /* The three points as one line, slashes between them. Wraps between
           items on a narrow screen, never mid-phrase. */
        .newsletter-points {
            margin: 0 auto;
            max-width: 40rem;
            font-family: 'Inter', sans-serif;
            font-size: 0.8rem;
            font-weight: 700;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            line-height: 2.1;
        }

        .newsletter-points-item { white-space: nowrap; }

        /* Lighter than the words it divides, so the line reads as three things
           and not as one long string. */
        .newsletter-points-sep {
            font-weight: 300;
            padding: 0 0.75rem;
        }

        @media (max-width: 1000px) {
            .newsletter-points {
                font-size: 0.7rem;
                letter-spacing: 0.25em;
            }

            .newsletter-points-sep { padding: 0 0.4rem; }
        }

        /* No boxes: one rule under the whole line, the address typed on it and
           the send button sitting at its right end. Both controls are
           transparent and borderless — the single 1px line is the only drawn
           element, which is the same hairline the corner crosses are made of. */
        .newsletter-row {
            display: flex;
            align-items: center;
            gap: 1rem;
            max-width: 34rem;
            margin: 0 auto;
            border-bottom: 1px solid #000;
        }

        .newsletter-row input {
            flex: 1;
            min-width: 0;
            padding: 0.6rem 0;
            border: 0;
            border-radius: 0;
            background: transparent;
            color: #000;
            text-align: left;
            font-family: 'Inter', sans-serif;
            font-size: 0.85rem;
            font-weight: 300;
            letter-spacing: 0.15em;
        }

        .newsletter-row input:focus { outline: none; }

        .newsletter-row input::placeholder {
            color: #999;
            text-transform: uppercase;
            letter-spacing: 0.2em;
            font-size: 0.75rem;
        }

        .newsletter-submit {
            flex: none;
            padding: 0.6rem 0;
            border: 0;
            background: transparent;
            color: #000;
            font-family: 'Inter', sans-serif;
            font-size: 0.75rem;
            font-weight: 700;
            letter-spacing: 0.2em;
            text-transform: uppercase;
            cursor: pointer;
            transition: opacity 0.25s ease;
        }

        @media (hover: hover) and (pointer: fine) {
            .newsletter-submit:hover { opacity: 0.45; }
        }

        /* One short line, centred under the rule with the box beside it rather
           than hanging off the left edge of a text block. */
        .newsletter-form .shop-consent {
            margin-top: 1.5rem;
            justify-content: center;
            align-items: center;
        }

        .newsletter-form .shop-consent input { margin-top: 0; }

        /* ---- responsive / motion ---- */

        @media (max-width: 700px) {
            .shop-grid {
                grid-template-columns: 1fr;
                gap: 3rem;
            }

            .shop-drawer {
                width: 100%;
                height: 100dvh;
            }
        }

        @media (prefers-reduced-motion: reduce) {
            .shop-drawer {
                transition: opacity 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
                transform: none;
                opacity: 0;
            }

            .shop-drawer.is-open { opacity: 1; }
        }

        /* ---- header shop / newsletter buttons ---- */
        /* Same corner-cross frame and sliding-difference hover as .page-cta,
           scaled down to sit next to the language switch. */

        /* One frame per link, so each gets its own set of crosses.
           `.header .nav-right` sets pointer-events: none so clicks fall through
           the header's empty areas — every interactive control inside has to
           opt back in, or it takes no clicks and shows no hover. */
        .header-cta {
            display: inline-flex;
            align-items: stretch;
            margin: 3px 0;
            pointer-events: auto;
        }

        .header .header-cta,
        .header .header-cta .page-cta-btn {
            pointer-events: auto;
        }

        /* Plain type in the header — no white plate, no corner crosses, no
           sliding difference fill. The header sits over page content and every
           one of those is a drawn box competing with it. What is left is a
           label that outlines itself on hover, exactly like the dropdown's
           links do, in black instead of white. */
        /* padding-right is short by exactly one letter-space: letter-spacing
           is applied *after* the last glyph too, so that trailing gap is part
           of the label's box and pushes the hover outline off-centre to the
           right. Subtracting it re-centres the type in its box. Retune the
           0.3em here whenever letter-spacing changes (see the two
           breakpoints below, which carry their own values). */
        .header-cta .page-cta-btn {
            padding: 6px calc(14px - 0.3em) 6px 14px;
            font-size: 0.72rem;
            font-weight: 700;
            letter-spacing: 0.3em;
            background: transparent;
        }

        /* The crosses live on the frame's ::after, the sweep on the button's
           ::before. Both go. Note .header-cta *is* the frame — the two classes
           sit on one element — so this is a compound selector, not a
           descendant one. */
        .header-cta.page-cta-frame::after,
        .header-cta .page-cta-btn::before {
            display: none;
        }

        @media (hover: hover) and (pointer: fine) {
            .header-cta .page-cta-btn:hover {
                outline: 1px solid #000;
                outline-offset: -1px;
            }
        }

        @media (max-width: 900px) {
            .header-cta { display: none; }
        }

        /* ---- centred header CTAs ---- */
        /* Shop and Newsletter sit on the header's centreline — but it is the
           GAP between them that is centred, not the pair.

           The difference matters because the two labels are not the same
           width. Centring the pair as a block (a flex child with margin:auto,
           or left:50% + translateX(-50%)) puts the midpoint of
           Shop + gap + Newsletter on the line, so the longer word drags the gap
           off to one side and the pair reads as lopsided against the logo.

           So: the wrapper is a zero-width anchor pinned at left:50%, sitting
           exactly on the centreline, and each button is absolutely placed half
           a gap out from it — Shop's right edge at centre - gap/2, Newsletter's
           left edge at centre + gap/2. One word left of the line, one right,
           the gap straddling it, whatever the labels say in either language.

           Out of flow, so .nav's space-between still pins the logo left and the
           lang switch + hamburger right. .nav is already position:relative. */
        .nav {
            --nav-center-gap: 4rem;
        }

        .nav-center {
            position: absolute;
            left: 50%;
            top: 0;
            bottom: 0;
            width: 0;
            /* Matches .nav-right: the empty half of the header must not eat
               clicks meant for the page under it. The buttons opt back in. */
            pointer-events: none;
            z-index: 2;
        }

        .nav-center .header-cta {
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            /* The 3px vertical margin was flex-layout breathing room in
               .nav-right; centring is done by top/transform now. */
            margin: 0;
            white-space: nowrap;
            pointer-events: auto;
        }

        .nav-center .header-cta:first-child {
            right: calc(var(--nav-center-gap) / 2);
        }

        .nav-center .header-cta:last-child {
            left: calc(var(--nav-center-gap) / 2);
        }

        /* When the shop is gated the build removes its button, leaving Newsletter
           as the only child — which would otherwise match BOTH rules above and
           get pinned left AND right inside the zero-width anchor, collapsing to a
           sliver (just an "N"). A lone button sits dead-centre instead. */
        .nav-center .header-cta:only-child {
            left: 50%;
            right: auto;
            transform: translate(-50%, -50%);
        }

        /* ---- configurator "request" button ---- */
        /* Shares .blob-config-label-btn, so it inherits the sliding overlay
           hover from setupCfgLabelHover. Only the left border is dropped, so it
           butts against the icon squares as one control strip. */

        .blob-config-request-btn {
            border-left: none;
        }

        /* ---- shop drawer: cross-framed buttons ---- */
        /* Opaque white rather than transparent: the hover overlay inverts what
           is behind it with mix-blend-mode: difference, and over a transparent
           button that means white-on-white — the label vanishes mid-sweep. */

        .shop-drawer .page-cta-btn {
            background: #fff;
            color: #000;
        }

        .shop-cta {
            margin-top: 2.5rem;
            display: flex;
        }

        .shop-cta .page-cta-frame { width: 100%; }
        .shop-cta .page-cta-btn { width: 100%; text-align: center; }

        /* ---- footer: wrap the legal links onto a second row in portrait ---- */
        /* With Right of Withdrawal and Delivery added, one row squeezes the
           links too small to read on a phone. */

        @media (max-width: 1000px) and (orientation: portrait) {
            .footer-content {
                flex-wrap: wrap;
                row-gap: 0.75rem;
            }

            .footer-links {
                flex-wrap: wrap;
                justify-content: flex-start;
                row-gap: 0.5rem;
                column-gap: 1.25rem;
            }
        }

        /* The 10rem side padding is a desktop measure — .about-content drops it
           on narrow screens for exactly this reason, and the shop follows. */
        @media (max-width: 1000px) {
            .shop-inner {
                margin-top: 2rem;
                padding: 0;
            }

            .shop-title {
                font-size: 1rem;
                margin-bottom: 1.5rem;
            }

            .shop-intro,
            .shop-empty {
                letter-spacing: 0.15em;
                margin-bottom: 2.5rem;
            }
        }

        @media (max-width: 1300px) and (min-width: 1001px) {
            .shop-inner { padding: 0 4rem; }
        }


        /* ---- responsive header label ---- */
        /* "Newsletter" is too wide for a phone header, so a short label takes
           over. Both are in the markup because CSS can hide text but not
           rewrite it, and both carry data-lang so the switcher keeps working. */

        .cta-short { display: none; }

        @media (max-width: 900px) {
            /* The buttons stay on phones — they are the whole point of putting
               the shop in the header — but tighten right down to fit. */
            .header-cta { display: inline-flex; }

            .cta-long { display: none; }
            .cta-short { display: inline; }

            .header-cta .page-cta-btn {
                padding: 5px calc(9px - 0.18em) 5px 9px;
                font-size: 0.62rem;
                letter-spacing: 0.18em;
            }

            /* The centred pair has the logo on one side and the lang switch +
               hamburger on the other, and none of them are in the same flow —
               the gap is the only thing keeping them apart. Tighten it in step
               with the labels so the pair stays clear of both. */
            .nav { --nav-center-gap: 2rem; }
        }

        @media (max-width: 420px) {
            .header-cta .page-cta-btn {
                padding: 4px calc(7px - 0.12em) 4px 7px;
                font-size: 0.58rem;
                letter-spacing: 0.12em;
            }

            .nav { --nav-center-gap: 1.1rem; }
        }

        /* ---- shop / newsletter page-load fade ---- */
        /* The wrapper is in the static HTML, so the browser paints its zero
           state before the page script fills it. That is what makes this
           reliable where fading freshly-inserted nodes is not: there is no
           frame in which the content is visible before the fade begins.
           Duration matches the 400ms every other page transition uses. */

        .shop-inner,
        .newsletter-inner {
            opacity: 0;
        }

        /* No JS, no fade — never leave the page blank. */
        @media (scripting: none) {
            .shop-inner,
            .newsletter-inner { opacity: 1; }
        }

        /* The shop's terms are a row of their own, below the whole footer
           content block rather than inside the link list — mixing them in
           there pushed the Instagram icon around and squeezed everything. */
        .footer-shop-row {
            position: relative;
            z-index: 1;
            display: flex;
            flex-wrap: wrap;
            justify-content: flex-end;
            gap: 0.75rem 2rem;
            margin-top: 1.25rem;
        }

        @media (max-width: 1000px) and (orientation: portrait) {
            .footer-shop-row { justify-content: flex-start; }
        }

        /* The header button for the page you are already on: no hover sweep,
           no pointer, no navigation. */
        .header-cta .page-cta-btn.is-current {
            cursor: default;
            opacity: 0.45;
        }

        .header-cta .page-cta-btn.is-current::before {
            display: none;
        }

        /* The shop terms are footer links and must behave like the rest of the
           row — same colour, padding and the outline-on-hover, which they
           missed by living outside .footer-links. */
        .footer-shop-row a {
            color: black;
            text-decoration: none;
            cursor: pointer;
            position: relative;
            display: inline-block;
            line-height: 1;
            padding: 2px 4px;
            transition: opacity 0.3s ease;
        }

        .footer-shop-row a:hover {
            outline: 1px solid #000;
            outline-offset: -1px;
        }

        /* ---- enquiry panel: snapshot of the configuration ---- */
        /* A large drawing of the object the enquiry is about, sitting behind
           the copy. The capture is a PNG cleared to opaque white and the panel
           is white, so the tile has no visible edge: it reads as a drawing on
           the page rather than a pasted-in image.

           It is decoration only. Nothing about it responds to the pointer, it
           cannot be selected with the text around it, and it cannot be dragged
           out as an image.

           Layering sits inside .shop-drawer's own stacking context (fixed,
           z-index 120), so the negative z-index cannot escape behind the
           panel: -2 the drawing, 0 the copy on top of it. */

        .pr-head {
            position: relative;
        }

        .pr-thumb {
            position: absolute;
            /* Cancels .shop-drawer-scroll's 4rem top padding, so the drawing
               runs right up to the panel's top edge. */
            top: -4rem;
            right: -2rem;
            width: min(420px, 96%);
            aspect-ratio: 4 / 5;
            z-index: -2;
            opacity: 0;
            pointer-events: none;
            user-select: none;
            -webkit-user-select: none;
            transition: opacity 350ms ease;
        }

        /* Washed out on purpose. The capture is white-backed, so dropping the
           opacity leaves the ground white and fades the black lines to a light
           grey: present enough to show what the enquiry is about, faint enough
           that the copy sits over it without a fight. */
        .pr-thumb.is-loaded {
            opacity: 0.32;
        }

        .pr-thumb img {
            display: block;
            width: 100%;
            height: 100%;
            object-fit: contain;
            pointer-events: none;
            user-select: none;
            -webkit-user-select: none;
            /* Stops the browser's native image drag, which pointer-events
               alone does not prevent in every engine. */
            -webkit-user-drag: none;
        }

        @media (max-width: 700px) {
            .pr-thumb {
                width: min(300px, 88%);
                top: -4rem;
                right: -1.25rem;
            }

        }

        /* iOS zooms the page in whenever a focused field's text is smaller than
           16px, and there is no way to opt out short of disabling zoom for
           everyone. 16px on touch layouts keeps the tap from moving the page.
           The visual difference at this size is slight; the zoom is not. */
        @media (max-width: 900px) {
            .shop-field input,
            .shop-field textarea,
            .newsletter-form input {
                font-size: 16px;
            }
        }

        /* The footnote under the request button, keyed to the star in the
           intro. Italic and small: advice, not part of the flow. It matters
           because a mailto cannot be relied on to carry anything but the
           address; several iOS mail apps, Zoho among them, drop the subject
           and body outright, so the configuration has to be saved by hand. */
        .pr-save-note {
            font-family: 'Inter', sans-serif;
            font-size: var(--type-fine, 0.7rem);
            font-style: italic;
            font-weight: 300;
            letter-spacing: 0.12em;
            line-height: 1.7;
            color: #666;
            margin: 1rem 0 0;
        }
