        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Helvetica Neue', Arial, sans-serif;
            -webkit-font-smoothing: antialiased;
            -webkit-tap-highlight-color: transparent;
            tap-highlight-color: transparent;
        }

        :root {
            --glass-bg: rgba(255, 255, 255, 0.05);
            --glass-border: rgba(255, 255, 255, 0.1);
            --glass-blur: 20px;
            --accent-color: #ff3b3b;
            --bilibili-pink: #FB7299;
            --text-main: #ffffff;
            --text-sub: #aaaaaa;
        }

        body {
            background-color: #050505;
            color: var(--text-main);
            overflow-y: auto;
            overflow-x: hidden;
        }

        ::-webkit-scrollbar {
            width: 0;
            height: 0;
        }

        #master-container {
            width: 100vw;
        }

        .fullscreen-section {
            height: 100vh;
            height: 100dvh;
            width: 100vw;
            position: relative;
            overflow: hidden;
        }

        #hero {
            display: flex;
            justify-content: center;
            align-items: center;
            background: #000;
        }

        .video-cover-bg {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
            z-index: 0;
            opacity: 1;
            transition: opacity 1s ease-out;
        }

        .video-bg {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
            opacity: 0;
            z-index: 0;
            transition: opacity 1s ease-out;
        }

        .video-bg.loaded {
            opacity: 0.6;
        }

        .video-cover-bg.hidden {
            opacity: 0;
            pointer-events: none;
        }

        .hero-content {
            z-index: 1;
            text-align: center;
            mix-blend-mode: overlay;
        }

        .hero-title {
            font-size: 6rem;
            font-weight: 900;
            letter-spacing: 8px;
            margin-bottom: 1.5rem;
            text-transform: uppercase;
            text-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
            animation: fadeInDown 1.5s ease-out;
            white-space: nowrap;
        }

        .hero-subtitle {
            font-size: 1.8rem;
            color: #ddd;
            letter-spacing: 4px;
            font-weight: 300;
            animation: fadeInUp 1.5s ease-out 0.3s backwards;
        }

        .scroll-hint {
            position: absolute;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 2;
            animation: bounce 2s infinite;
            opacity: 0.7;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 8px;
        }

        .app-scroll-hint {
            position: absolute;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 3;
            animation: bounce 2s infinite;
            opacity: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 8px;
            transition: all 1s cubic-bezier(0.165, 0.84, 0.44, 1);
            transition-delay: 0.4s;
        }

        .product-viewport.animate-active .app-scroll-hint {
            opacity: 0.8;
        }

        .scroll-hint-text {
            font-size: 0.9rem;
            color: rgba(255, 255, 255, 0.7);
            letter-spacing: 1px;
            text-align: center;
        }

        .scroll-hint svg,
        .app-scroll-hint svg {
            width: 30px;
            height: 30px;
            fill: white;
        }

        .hero-footer-right {
            position: absolute;
            bottom: 20px;
            right: 20px;
            z-index: 10;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 8px;
            animation: fadeInRight 1.5s ease-out 0.5s backwards;
        }

        .bilibili-link {
            display: block;
            width: 40px;
            height: 40px;
            transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
        }

        .bilibili-link img {
            width: 100%;
            height: 100%;
            object-fit: contain;
            filter: drop-shadow(0 0 5px rgba(0, 0, 0, 0.5));
            transition: filter 0.3s ease;
            opacity: 0.4;
        }

        .bilibili-link:hover {
            transform: scale(1.15);
        }

        .bilibili-link:hover img {
            filter: drop-shadow(0 0 10px rgba(242, 242, 242, 0.8));
        }

        .copyright-text {
            font-size: 0.7rem;
            color: rgba(255, 255, 255, 0.35);
            letter-spacing: 1.5px;
            font-weight: 300;
            pointer-events: none;
            text-align: center;
            white-space: nowrap;
            opacity: 0.7;
        }

        #shop-interface {
            background: linear-gradient(to bottom, #000 0%, #111 200px);
            position: relative;
            height: auto;
            overflow: visible;
        }

        .glass-nav {
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 1000;
            width: fit-content;
            display: flex;
            gap: 40px;
            padding: 15px 40px;
            border-radius: 50px;
            background: rgba(40, 40, 40, 0.4);
            backdrop-filter: blur(var(--glass-blur));
            -webkit-backdrop-filter: blur(var(--glass-blur));
            border: 1px solid rgba(255, 255, 255, 0.15);
            border-top: 1px solid rgba(255, 255, 255, 0.5);
            border-left: 1px solid rgba(255, 255, 255, 0.1);
            box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05), 0 15px 35px rgba(0, 0, 0, 0.6);
            transition: all 0.4s ease;
            opacity: 0;
            pointer-events: none;
        }

        .glass-nav.visible {
            opacity: 1;
            pointer-events: auto;
        }

        .glass-nav a {
            text-decoration: none;
            color: var(--text-sub);
            font-size: 0.9rem;
            font-weight: 600;
            letter-spacing: 1px;
            cursor: pointer;
            position: relative;
            transition: 0.3s;
        }

        .glass-nav a.active,
        .glass-nav a:hover {
            color: #fff;
            text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
        }

        .glass-nav a.active::after {
            content: '';
            position: absolute;
            bottom: -5px;
            left: 50%;
            transform: translateX(-50%);
            width: 4px;
            height: 4px;
            background: var(--accent-color);
            border-radius: 50%;
        }

        .glass-nav .nav-indicator {
            position: absolute;
            top: 5px;
            bottom: 5px;
            left: 0;
            border-radius: 50px;
            background: rgba(40, 40, 40, 0.4);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.15);
            border-top: 1px solid rgba(255, 255, 255, 0.5);
            border-left: 1px solid rgba(255, 255, 255, 0.1);
            box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05), 0 8px 20px rgba(0, 0, 0, 0.4);
            transition: transform 0.45s cubic-bezier(0.165, 0.84, 0.44, 1), width 0.35s cubic-bezier(0.165, 0.84, 0.44, 1);
            z-index: -1;
        }

        .category-container-x {
            width: 100vw;
        }

        .category-column {
            width: 100vw;
            position: relative;
            display: none;
        }

        .category-column.active {
            display: block;
        }

        .product-viewport {
            height: 100vh;
            width: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
            background: radial-gradient(circle at center, #1a1a1a 0%, #000000 100%);
            overflow: hidden;
        }

        .product-viewport::before {
            content: '';
            position: absolute;
            top: -50%;
            left: -50%;
            width: 200%;
            height: 200%;
            background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0IiBoZWlnaHQ9IjQiPgo8cmVjdCB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9IjAuMDUiLz4KPC9zdmc+');
            opacity: 0.3;
            animation: grain 8s steps(10) infinite;
            pointer-events: none;
        }

        .product-content {
            width: 80%;
            max-width: 1200px;
            height: 70%;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 5%;
            z-index: 2;
        }

        .product-content.reverse {
            flex-direction: row-reverse;
        }

        .p-image-box {
            flex: 1;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            perspective: 1000px;
            opacity: 0;
            filter: blur(15px);
            transform: scale(0.8) translateY(50px);
            transition: all 1s cubic-bezier(0.165, 0.84, 0.44, 1);
        }

        .p-image {
            width: 100%;
            max-height: 100%;
            object-fit: contain;
            filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.6));
            transform: rotateY(-10deg) rotateX(5deg);
            transition: transform 0.6s ease-out;
            border-radius: 20px;
        }

        .p-desc-box {
            flex: 1;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: flex-start;
            padding: 50px 40px;
            background: rgba(40, 40, 40, 0.4);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.15);
            border-top: 1px solid rgba(255, 255, 255, 0.6);
            border-left: 1px solid rgba(255, 255, 255, 0.1);
            border-radius: 30px;
            box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05), 0 15px 35px rgba(0, 0, 0, 0.6);
            opacity: 0;
            transform: translateY(80px);
            transition: all 1s cubic-bezier(0.165, 0.84, 0.44, 1);
            transition-delay: 0.1s;
        }

        .product-viewport.animate-active .p-image-box {
            opacity: 1;
            filter: blur(0);
            transform: scale(1) translateY(0);
        }

        .product-viewport.animate-active .p-desc-box {
            opacity: 1;
            transform: translateY(0);
        }

        .product-viewport:hover .p-image {
            transform: rotateY(0deg) rotateX(0deg) scale(1.05);
        }

        .p-title {
            font-size: 2.5rem;
            font-weight: 700;
            margin-bottom: 1rem;
            background: linear-gradient(45deg, #fff, #888);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }

        .p-text {
            font-size: 1rem;
            color: #ccc;
            line-height: 1.6;
            margin-bottom: 2rem;
        }

        .btn-buy {
            display: inline-block;
            padding: 14px 35px;
            font-size: 1rem;
            font-weight: 700;
            color: #fff;
            text-decoration: none;
            background: rgba(40, 40, 40, 0.4);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.15);
            border-top: 1px solid rgba(255, 255, 255, 0.6);
            border-left: 1px solid rgba(255, 255, 255, 0.1);
            border-radius: 50px;
            box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05), 0 15px 35px rgba(0, 0, 0, 0.6);
            transition: all 0.3s ease;
            cursor: pointer;
        }

        .btn-buy:hover {
            transform: translateY(-3px);
            box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 20px 40px rgba(0, 0, 0, 0.7);
            background: rgba(50, 50, 50, 0.5);
        }

        .app-download-wrapper {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            gap: 30px;
            height: 100%;
            width: 100%;
            z-index: 2;
        }

        .app-icon-box {
            width: 180px;
            height: 180px;
            border-radius: 40px;
            overflow: hidden;
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
            opacity: 0;
            filter: blur(15px);
            transform: scale(0.8) translateY(50px);
            transition: all 1s cubic-bezier(0.165, 0.84, 0.44, 1);
        }

        .app-icon {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        .btn-download {
            display: inline-block;
            padding: 15px 45px;
            font-size: 1.2rem;
            font-weight: 700;
            color: #fff;
            text-decoration: none;
            background: rgba(40, 40, 40, 0.4);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.15);
            border-top: 1px solid rgba(255, 255, 255, 0.6);
            border-left: 1px solid rgba(255, 255, 255, 0.1);
            border-radius: 50px;
            box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05), 0 15px 35px rgba(0, 0, 0, 0.6);
            transition: all 0.3s ease;
            cursor: pointer;
            opacity: 0;
            transform: translateY(80px);
            transition: all 1s cubic-bezier(0.165, 0.84, 0.44, 1);
            transition-delay: 0.2s;
        }

        .btn-download:hover {
            transform: translateY(-5px);
            box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1), 0 20px 40px rgba(0, 0, 0, 0.7);
            background: rgba(50, 50, 50, 0.5);
        }

        .product-viewport.animate-active .app-icon-box {
            opacity: 1;
            filter: blur(0);
            transform: scale(1) translateY(0);
        }

        .product-viewport.animate-active .btn-download {
            opacity: 1;
            transform: translateY(0);
        }

        .launch-wrapper {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100%;
            width: 100%;
            z-index: 5;
            perspective: 1500px;
            padding-top: 120px;
        }

        .tablet-case {
            width: 70vw;
            max-width: 900px;
            aspect-ratio: 16 / 10;
            background: #1c1c1c;
            border-radius: 24px;
            padding: 20px;
            box-shadow: 0 0 0 2px #333, inset 0 0 10px rgba(0, 0, 0, 0.8), 0 30px 60px rgba(0, 0, 0, 0.6);
            position: relative;
            transform: rotateX(5deg);
            transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
            opacity: 0;
            transform: translateY(100px) rotateX(15deg);
        }

        .tablet-case::before {
            content: '';
            position: absolute;
            left: -10px;
            top: 50%;
            width: 4px;
            height: 4px;
            background: #333;
            border-radius: 50%;
            transform: translateY(-50%);
        }

        .tablet-screen {
            width: 100%;
            height: 100%;
            background: #000;
            border-radius: 12px;
            overflow: hidden;
            position: relative;
            border: 1px solid #333;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .video-cover {
            width: 100%;
            height: 100%;
            object-fit: cover;
            opacity: 0.8;
            transition: opacity 0.3s;
        }

        .play-overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.2);
            display: flex;
            align-items: center;
            justify-content: center;
            transition: background 0.3s;
        }

        .play-icon {
            width: 80px;
            height: 80px;
            fill: white;
            filter: drop-shadow(0 0 20px rgba(0, 0, 0, 0.8));
            transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
        }

        .tablet-screen:hover .video-cover {
            opacity: 0.5;
        }

        .tablet-screen:hover .play-overlay {
            background: rgba(0, 0, 0, 0.4);
        }

        .tablet-screen:hover .play-icon {
            transform: scale(1.2);
        }

        .tablet-screen iframe {
            width: 100%;
            height: 100%;
            border: none;
            position: absolute;
            top: 0;
            left: 0;
            z-index: 10;
        }

        .launch-info {
            margin-top: 15px;
            text-align: center;
            opacity: 0;
            transform: translateY(20px);
            transition: all 0.8s ease 0.3s;
        }

        .launch-title {
            font-size: 2rem;
            font-weight: 300;
            letter-spacing: 2px;
            padding: 5px 10px;
            background: rgba(255, 255, 255, 0.05);
            backdrop-filter: blur(10px);
            border-radius: 50px;
            border: 1px solid rgba(255, 255, 255, 0.1);
            display: inline-block;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
        }

        .product-viewport.animate-active .tablet-case {
            opacity: 1;
            transform: translateY(0) rotateX(0deg);
        }

        .product-viewport.animate-active .launch-info {
            opacity: 1;
            transform: translateY(0);
        }

        .tablet-case:hover {
            transform: translateY(-10px) scale(1.01);
            box-shadow: 0 40px 80px rgba(0, 0, 0, 0.7);
        }

        .standard-footer {
            height: 10vh;
            width: 100%;
            scroll-snap-align: end;
            display: flex;
            justify-content: center;
            align-items: center;
            background: rgba(0, 0, 0, 0.3);
            backdrop-filter: blur(20px);
            -webkit-backdrop-filter: blur(20px);
            border-top: 1px solid rgba(255, 255, 255, 0.08);
            gap: 15px;
        }

        .footer-content {
            display: inline-block;
            color: rgba(255, 255, 255, 0.4);
            font-size: 0.8rem;
            letter-spacing: 2px;
            font-weight: 300;
        }

        .footer-content span {
            margin: 0 10px;
            color: var(--accent-color);
            opacity: 0.5;
        }

        .footer-bili-icon {
            display: flex;
            align-items: center;
            justify-content: center;
            width: 24px;
            height: 24px;
            opacity: 0.5;
            transition: all 0.3s ease;
        }

        .footer-bili-icon img {
            width: 100%;
            height: 100%;
            object-fit: contain;
            filter: grayscale(100%);
            transition: all 0.3s ease;
        }

        .footer-bili-icon:hover {
            opacity: 1;
            transform: scale(1.1);
        }

        .footer-bili-icon:hover img {
            filter: grayscale(0%) drop-shadow(0 0 5px rgba(231, 231, 231, 0.8));
        }

        @keyframes fadeInDown {
            from { opacity: 0; transform: translateY(-50px); }
            to { opacity: 1; transform: translateY(0); }
        }

        @keyframes fadeInUp {
            from { opacity: 0; transform: translateY(50px); }
            to { opacity: 1; transform: translateY(0); }
        }

        @keyframes fadeInRight {
            from { opacity: 0; transform: translateX(50px); }
            to { opacity: 1; transform: translateX(0); }
        }

        @keyframes bounce {
            0%, 20%, 50%, 80%, 100% { transform: translateX(-50%) translateY(0); }
            40% { transform: translateX(-50%) translateY(-10px); }
            60% { transform: translateX(-50%) translateY(-5px); }
        }

        @keyframes grain {
            0%, 100% { transform: translate(0, 0) }
            10% { transform: translate(-5%, -10%) }
            50% { transform: translate(12%, 9%) }
            90% { transform: translate(-1%, 7%) }
        }

        @media (max-width: 768px) {
            .hero-title { font-size: 4rem; white-space: normal; }
            .hero-subtitle { font-size: 1rem; }
            .product-content { flex-direction: column !important; width: 90%; }
            .p-image-box { height: 40%; width: 100%; margin-bottom: 20px; }
            .p-desc-box { width: 100%; height: auto; padding: 35px 25px; border-radius: 24px; }
            .glass-nav { width: 95%; gap: 10px; justify-content: space-around; }
            .hero-footer-right { bottom: 20px; right: 20px; }
            .tablet-case { width: 95vw; border-radius: 16px; padding: 10px; }
            .launch-title { font-size: 1.2rem; padding: 10px 20px; }
            .standard-footer { height: 15vh; }
            .app-icon-box {
                width: 120px;
                height: 120px;
                border-radius: 30px;
            }
            .btn-download {
                padding: 12px 35px;
                font-size: 1rem;
            }
            .app-scroll-hint {
                bottom: 20px;
            }
            .scroll-hint-text {
                font-size: 0.7rem;
            }
            .app-scroll-hint svg {
                width: 20px;
                height: 20px;
            }
        }

/* ==========================================================================
   HERO — the wireframe lens
   ==========================================================================
   The hero used to be a 1-screen video background. It is now the procedural CAYE 200mm f3.5,
   driven by caye-hero.js, and it is a SCROLL RUNWAY: this section is taller than the viewport and
   the stage inside it is sticky, so scrolling opens the lens before the shop arrives. The extra
   height is what buys the animation its room; without it a full-screen section would leave the
   explosion about one notch of the wheel to happen in.

   The video rules above (.video-bg, .video-cover-bg, .hero-content) are now unused and are left in
   place rather than deleted, so this change stays additive and is easy to back out.
   ========================================================================== */

#hero {
    /* overrides the flex centring the old #hero rule applied to the video */
    display: block;
    height: 180vh;
    background: #05070a;
    overflow: visible;
}

@media (max-width: 720px) {
    /* A shorter runway on a phone: 80vh of scroll is a long thumb journey on a small screen. */
    #hero { height: 150vh; }
}

.hero-stage {
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100dvh;
    width: 100%;
    overflow: hidden;
}

.hero-canvas {
    position: absolute;
    inset: 0;
    z-index: 0;
}

.hero-canvas canvas {
    display: block;
    width: 100%;
    height: 100%;
}

/* A vignette so the lens sits inside the page rather than on top of it. Transparent to the pointer,
   so dragging the lens still works through it. */
.hero-stage::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: radial-gradient(120% 78% at 50% 46%, rgba(12, 22, 36, 0) 34%, rgba(3, 5, 8, 0.85) 100%);
}

.hero-brand {
    position: absolute;
    top: clamp(20px, 4vh, 42px);
    left: clamp(18px, 4vw, 52px);
    z-index: 2;
    pointer-events: none;
    color: #e9f4ff;
}

.hero-brand span {
    display: block;
    font-size: clamp(12px, 1.1vw, 15px);
    font-weight: 600;
    letter-spacing: 0.24em;
    text-transform: uppercase;
}



/* The site's own hero furniture, lifted above the canvas. */
#hero .scroll-hint { z-index: 2; }
#hero .hero-footer-right { z-index: 3; }

/* ==========================================================================
   PRODUCT ENTRANCE — reimplemented
   ==========================================================================
   The products used to be hidden by default (`opacity: 0`, `filter: blur(15px)`, pushed down) and
   only became visible when an IntersectionObserver added `.animate-active` and a 1s CSS transition
   carried them in. That makes "the content exists on screen" depend on a script-triggered
   transition running to completion: if the observer never fires, or the transition is interrupted,
   or anything earlier on the page throws, the shop is a black screen with nothing in the console.
   The content is present and invisible, which is the worst way for content to fail.

   The order is inverted here. The base state is VISIBLE, and the entrance is an @keyframes
   animation layered on by `.reveal-in` when a viewport first scrolls into view. The fill mode is
   `forwards`, not `both`: `both` would hold the animation's `from` state -- hidden -- for as long as the
   timeline is stalled, which is precisely the failure being removed. `forwards` holds only the END
   state, which is the base state anyway, so a starved, interrupted or missing animation degrades to
   "no animation", never to "no content".

   The per-element stagger the old version got from `transition-delay` is gone. A delay only hides
   the element during the wait if the fill mode holds the `from` state, and holding the `from` state
   is exactly the fragility being removed. Simultaneous is a fair trade for never being blank.
   ========================================================================== */

.p-image-box,
.app-icon-box {
    opacity: 1;
    filter: none;
    transform: none;
}

.p-desc-box,
.launch-info {
    opacity: 1;
    transform: none;
}

.btn-download {
    opacity: 1;
    transform: none;
    transition: all 0.3s ease;
}

.tablet-case {
    opacity: 1;
    transform: rotateX(5deg);
}

.app-scroll-hint {
    opacity: 0.8;
}

@keyframes cayRise {
    from { opacity: 0; filter: blur(15px); transform: translateY(50px) scale(0.94); }
    to   { opacity: 1; filter: none;          transform: none; }
}

@keyframes caySlide {
    from { opacity: 0; transform: translateY(70px); }
    to   { opacity: 1; transform: none; }
}

@keyframes cayTablet {
    from { opacity: 0; transform: translateY(90px) rotateX(15deg); }
    to   { opacity: 1; transform: rotateX(5deg); }
}

.product-viewport.reveal-in .p-image-box,
.product-viewport.reveal-in .app-icon-box {
    animation: cayRise 1s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
}

.product-viewport.reveal-in .p-desc-box,
.product-viewport.reveal-in .launch-info {
    animation: caySlide 1s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
}

.product-viewport.reveal-in .btn-download {
    animation: caySlide 0.9s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
}

.product-viewport.reveal-in .tablet-case {
    animation: cayTablet 0.9s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

.product-viewport.reveal-in .app-scroll-hint {
    animation: caySlide 0.8s ease forwards;
}

@media (prefers-reduced-motion: reduce) {
    .product-viewport.reveal-in * {
        animation: none !important;
    }
}
/* ==========================================================================
   HERO SLOGAN and HEADLINE
   ==========================================================================
   WHERE THE SLOGAN SITS IS MEASURED, NOT CHOSEN. The lens was photographed at three viewport sizes
   and its lit pixels bounded:

       1440x900   lens x  304..1154   y 220..616    the corners are clear
       1024x768   lens x  166.. 872   y 184..516    the corners are clear
        390x844   lens x   38.. 360   y 357..496    full width, and only 139px tall

   On a phone there is no right-hand corner to occupy, so the positions collapse from corners to
   strips above and below the lens, from the same markup.
   ========================================================================== */

.hero-stage {
    /* The typing step lives in caye.js as TYPING_STEP_MS. This stylesheet no longer describes the
       entrance at all. */
}

.hero-slogan {
    position: absolute;
    z-index: 2;
    /* Never intercept a drag: the lens sits behind this and is meant to be rotated through it. */
    pointer-events: none;
    display: flex;
    flex-direction: column;
    font-size: clamp(15px, 1.4vw, 23px);
    font-weight: 300;
    line-height: 1.95;
    letter-spacing: 0.2em;
    color: #7c8b9c;
    /* The lens glows and passes behind this; without a shadow the thin grey type disappears into
       the bright line work. */
    text-shadow: 0 1px 12px rgba(0, 0, 0, 0.95), 0 0 4px rgba(0, 0, 0, 0.85);
}

.hero-slogan--a {
    top: clamp(18px, 3.2vh, 38px);
    right: clamp(16px, 2.6vw, 40px);
    align-items: flex-end;
    text-align: right;
}

.hero-slogan--b {
    bottom: clamp(18px, 3.2vh, 38px);
    left: clamp(16px, 2.6vw, 40px);
    align-items: flex-start;
    text-align: left;
}

.hero-slogan .slogan-sentence {
    display: block;
}

.hero-slogan .slogan-line {
    display: block;
    white-space: nowrap;
}

/* The resting state is VISIBLE. The hidden state exists only where scripting is available, and only
   until the entrance has played -- see the reveal rule and the safety net in caye.js.

   The unit is the CHARACTER, not the line: caye.js wraps every character of every sentence and
   numbers them in one continuous run, so the slogan types itself out. The hidden state has to sit on
   the character to match -- leaving opacity: 0 on .slogan-line while animating its children would
   hold the whole line invisible whatever the children did. */
/* LAYOUT ONLY.
   ---------------------------------------------------------------------------
   The typing lives in caye.js, driven with the Web Animations API rather than from here.

   An entrance written in CSS can be defeated by a stylesheet that is stale, cached behind something
   that ignores query strings, or out of step with the script that builds the characters -- and the
   failure is silent and looks like "the animation is not working", with nothing in the console. In
   the same function that creates the characters, the two cannot disagree.

   What stays here is the resting state: visible. That is what applies if the script never runs. */
.hero-slogan .slogan-char {
    display: inline;
}

/* ---------------------------------------------------------------------------
   The original hero content, positioned.
   ---------------------------------------------------------------------------
   .hero-title and .hero-subtitle keep every original declaration -- 6rem, 8px tracking, the
   fadeInDown / fadeInUp entrances, the 768px step down to 4rem and 1rem. The only thing that had to
   be added is what `#hero { display: flex; justify-content: center; align-items: center }` used to
   provide for .hero-content as a flex item, because it now sits in a sticky stage next to an
   absolutely positioned canvas.

   z-index 2, above the stage's vignette pseudo-element, which shares z-index 1 with .hero-content
   and would otherwise paint over it on DOM order alone.
   --------------------------------------------------------------------------- */
.hero-content {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    will-change: opacity, transform;
}

/* ---------------------------------------------------------------------------
   Narrow: the corners become strips and the type steps up to stay legible.
   ---------------------------------------------------------------------------
   1023px is where the measured lens stops leaving a usable right-hand corner: at 1024 it still has
   152px of clear space to its right, and below that it does not.
   --------------------------------------------------------------------------- */
@media (max-width: 1023px) {
    .hero-slogan {
        font-size: clamp(14px, 4.1vw, 20px);
        letter-spacing: 0.16em;
        line-height: 1.9;
    }

    .hero-slogan--a {
        top: max(18px, env(safe-area-inset-top, 0px) + 14px);
        right: 16px;
    }

    .hero-slogan--b {
        bottom: max(18px, env(safe-area-inset-bottom, 0px) + 14px);
        left: 16px;
    }
}

/* A short landscape window has no clear strip at all; the headline and the scroll hint matter more
   than the slogan does. */
@media (max-height: 460px) {
    .hero-slogan--b {
        display: none;
    }
}

/* The reduced-motion case is handled in caye.js, next to the animation it suppresses, so the two
   cannot drift apart. See revealSlogan(). */

/* ---------------------------------------------------------------------------
   The title must not wrap.
   ---------------------------------------------------------------------------
   The original stepped .hero-title to 4rem below 768px and switched it to `white-space: normal`.
   Six characters at 64px plus 8px of tracking is 432px wide, which does not fit a 390px phone, so
   the title broke into 沧野影像科 / 技 and dropped the orphaned character onto the lens. That is
   what the original did too, on every phone, for as long as it has been live -- restoring the
   styling restored the fault with it.

   `clamp` keeps the original 6rem wherever it fits and scales below that, so the line always fits
   and never breaks. Tracking is trimmed slightly on the narrow end as well, because 8px of it is a
   larger share of a small type size than of a large one.
   --------------------------------------------------------------------------- */
@media (max-width: 768px) {
    .hero-title {
        font-size: clamp(1.75rem, 10.2vw, 4rem);
        letter-spacing: clamp(2px, 0.9vw, 8px);
        white-space: nowrap;
    }
}

/* ---------------------------------------------------------------------------
   Full white for the title.
   ---------------------------------------------------------------------------
   .hero-title was measured at rgb(248,248,248) with a cyan cast where it crossed the lens, despite
   its colour already being #ffffff. `mix-blend-mode: overlay` was the cause: it multiplies wherever
   the backdrop is darker than mid grey, and outside the lens this backdrop is near-black, so the
   blend could only ever take brightness away. That is the opposite of what it did over the original
   video, which was mid-tone and gave it something to work with.

   The blend is not moved onto the subtitles either. That was tried and does nothing: .hero-content
   is absolutely positioned with a z-index, so it forms a stacking context, and a child's
   mix-blend-mode can then only blend against that group's own backdrop, which is empty. Measured:
   the subtitles render as plain #ddd. Rather than leave a rule that reads as if it were doing
   something, the blend is dropped -- against a near-black backdrop `overlay` could only ever have
   darkened them anyway.
   --------------------------------------------------------------------------- */
.hero-content {
    mix-blend-mode: normal;
}

.hero-title {
    color: #ffffff;
}

/* ---------------------------------------------------------------------------
   The canvas must not claim vertical drags.
   ---------------------------------------------------------------------------
   OGL's Orbit registers touchstart and touchmove with { passive: false } and calls
   e.preventDefault() before it checks whether rotation is even enabled, so a live control on a
   full-bleed canvas makes the whole opening screen unscrollable on a phone. The fix is
   `enabled: !touch` where the control is built -- `enabled` is the first thing those handlers test,
   ahead of the preventDefault. This says so to the browser as well, so vertical pans are recognised
   as the page's without waiting for a cancelable event to be resolved.
   --------------------------------------------------------------------------- */
.hero-canvas canvas {
    touch-action: pan-y;
}
