/**
 * Spark 产品落地页专用样式
 *
 * 只放 Tailwind 表达不了的部分：多段渐变、由 JS 逐帧写入的定位、reduced-motion 降级切换。
 * 其余一律用 Tailwind class 写在模板里。
 *
 * @since 1.289
 */

/* ---------- Hero 满屏高度 ---------- */
/* 移动端 Safari 地址栏收缩会让 100vh 变化并导致内容跳动，能用 svh 就用 svh */
.spark-hero-section {
    height: 100vh;
}

@supports (height: 100svh) {
    .spark-hero-section {
        height: 100svh;
    }
}

/* ---------- 购买页 Design & Build 卡片切换器 ---------- */

.spark-purchase-design-build__desktop {
    display: none;
}

.spark-purchase-design-build__mobile {
    display: block;
}

.spark-purchase-design-build__nav-button {
    background-color: #ededed;
    color: #1d1d1d;
    transition: background-color 200ms ease, opacity 200ms ease;
}

@media (hover: hover) and (pointer: fine) {
    .spark-purchase-design-build__nav-button:not(:disabled):hover {
        background-color: #dadada;
    }
}

.spark-purchase-design-build__nav-button:disabled {
    opacity: 0.64;
    cursor: default;
}

.spark-purchase-design-build__nav-arrow {
    display: block;
    flex-shrink: 0;
    background-color: currentColor;
    -webkit-mask-image: url("../svg-icon/spark-chip-arrow.svg");
    mask-image: url("../svg-icon/spark-chip-arrow.svg");
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: contain;
    mask-size: contain;
}

.spark-purchase-design-build__nav-arrow--prev {
    transform: rotate(180deg);
}

@media (min-width: 768px) {
    .spark-purchase-design-build__desktop {
        display: grid;
        grid-template-columns: repeat(12, minmax(0, 1fr));
        column-gap: 8px;
        row-gap: 48px;
        overflow: visible;
        padding: 128px 24px;
    }

    .spark-purchase-design-build__mobile {
        display: none;
    }

    .spark-purchase-design-build__heading {
        grid-column: 2 / span 6;
        align-self: center;
        min-width: 0;
    }

    .spark-purchase-design-build__heading p {
        color: #515151;
        font-size: 16px;
        font-weight: 400;
        line-height: 24px;
    }

    .spark-purchase-design-build__heading h2 {
        margin-top: 8px;
        color: #1d1d1d;
        font-size: 48px;
        font-weight: 500;
        letter-spacing: -0.72px;
        line-height: 48px;
    }

    .spark-purchase-design-build__controls {
        grid-column: 8 / span 4;
        align-self: end;
        justify-self: end;
        display: flex;
        gap: 4px;
    }

    .spark-purchase-design-build__controls button {
        display: flex;
        width: 56px;
        height: 56px;
        align-items: center;
        justify-content: center;
        border-radius: 8px;
    }

    .spark-purchase-design-build__viewport {
        grid-column: 2 / 13;
        min-width: 0;
        overflow: visible;
    }

    .spark-purchase-design-build__track {
        display: flex;
        gap: 8px;
        will-change: transform;
        transition: transform 400ms cubic-bezier(0.4, 0, 0.2, 1);
    }

    .spark-purchase-design-build__card {
        flex: 0 0 calc((100% - 16px) / 3);
        min-width: 0;
    }

    .spark-purchase-design-build__media {
        position: relative;
        overflow: hidden;
        width: 100%;
        aspect-ratio: 1;
        border-radius: 16px;
        background: #ededed;
    }

    .spark-purchase-design-build__media-image {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

    .spark-purchase-design-build__copy {
        display: flex;
        flex-direction: column;
        gap: 8px;
        padding: 24px;
    }

    .spark-purchase-design-build__eyebrow {
        color: #515151;
        font-size: 16px;
        font-weight: 400;
        line-height: 24px;
    }

    .spark-purchase-design-build__copy h3 {
        color: #1d1d1d;
        font-size: 24px;
        font-weight: 500;
        letter-spacing: -0.24px;
        line-height: 32px;
    }

    .spark-purchase-design-build__description {
        color: #858585;
        font-size: 16px;
        font-weight: 400;
        line-height: 24px;
    }
}

/**
 * ImageQuality_FPS 区块高度：两端都是满视口。
 *
 * 移动稿画的是 375×705 = 812 − 107（让出 105 高的吸顶导航），但光把区块做矮
 * 并不能保证标题落在导航下方 —— 没有 scroll-snap，区块顶部贴到视口顶时照样被压住。
 * 所以高度取满视口，让出导航改用顶部内边距（见 section-image-quality-fps.php 的 pt）。
 *
 * 和 Hero 一样优先用 svh：移动端 Safari 地址栏收缩会让 100vh 变化并导致内容跳动。
 */
/* 几个 *_Intro（满屏底图 + 标题）与 FPS 段同理，共用这条规则。
   .spark-iq-intro 是 Image Quality 移动端改版后的形态（Figma 8441:30872，单视频铺满）。 */
.spark-fps-section,
.spark-iq-intro,
.spark-workflow-intro,
.spark-oscontrol-intro,
.spark-more-intro {
    height: 100vh;
}

@supports (height: 100svh) {

    .spark-fps-section,
    .spark-iq-intro,
    .spark-workflow-intro,
    .spark-oscontrol-intro,
    .spark-more-intro {
        height: 100svh;
    }
}

.spark-fps-btn__icon {
    transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1);
}

@media (hover: hover) and (pointer: fine) {
    .spark-fps-section .spark-fps-btn:hover .spark-fps-btn__icon,
    #spark-support-box .spark-fps-btn:hover .spark-fps-btn__icon {
        background-color: #EDEDED;
    }
}

.spark-fps-section .spark-fps-btn:focus-visible .spark-fps-btn__icon,
#spark-support-box .spark-fps-btn:focus-visible .spark-fps-btn__icon {
    background-color: #EDEDED;
}

/* ---------- 悬浮胶囊 header：滚过 Hero 后隐藏 ---------- */
.spark-header--hidden {
    opacity: 0;
    visibility: hidden;
    transform: translateY(-16px);
    pointer-events: none;
}

/* 顶部悬浮胶囊导航的样式已移到 assets/css/pixboom-nav.css（全站加载）。
   上面那条 .spark-header--hidden 留在这里 —— 只有本页需要「滚过 Hero 隐藏」。 */

/* ==================================================================
 * Order Now 选配浮窗（ConfigureDrawer）
 *
 * Figma: 6863:22737 ｜ 组件 6863:23407 ｜ 移动 6898:32256
 * 这里放尺寸算式、三态、进出动效 —— 排版仍在模板的 Tailwind class 里。
 * ================================================================== */

.spark-cfg {
    --cfg-ease: cubic-bezier(0.4, 0, 0.2, 1);
    --cfg-dur: 400ms;
}

/**
 * 面板定位（稿子 6863:23405 的标注）：距页面顶部、底部、右侧均 24，宽度 **4/12 栅格**。
 *
 * 栅格算式：内容宽 C = min(100vw, 1920) − 48（页面左右各 24 内边距），
 * 单列 = (C − 11×8) / 12，4 列区 = 4×单列 + 3×8 = (C − 88) / 3 + 24。
 * 1440 下算出 458.67 —— 组件画的是 400，按标注走栅格（已确认）。
 *
 * 右偏移不是死的 24：超过 1920 时页面内容是居中的，得跟着容器右边缘走，
 * 否则浮窗会贴到视口边、和内容脱开。1920 以下这个式子就等于 24。
 */
.spark-cfg__panel {
    top: 24px;
    bottom: 24px;
    right: 24px;
    width: calc((min(100vw, 1920px) - 48px - 88px) / 3 + 24px);
}

@media (max-width: 767px) {
    .spark-cfg__panel {
        top: 16rem;
        bottom: 16rem;
        left: 16rem;
        right: 16rem;
        width: auto;
    }
}

/**
 * 遮罩层。
 *
 * ⚠️ 稿子里**没有画蒙层** —— 浮窗直接压在页面上、背后不压暗。但浮窗打开时是锁滚动的，
 * 锁了又不压暗，体感上分不清「页面还能不能动」。已确认：**压暗，和站上其它弹窗一致**。
 * 取值按站上的主流写法 rgba(29,29,29,.48) + blur 24（17 处弹窗里 13 处是这一对，
 * 也是设计系统的 system/glass/glass-2-dim）。
 */
.spark-cfg__backdrop {
    background-color: rgba(29, 29, 29, 0.48);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    opacity: 0;
    transition: opacity var(--cfg-dur) var(--cfg-ease);
}

.spark-cfg--open .spark-cfg__backdrop {
    opacity: 1;
}

/* 进出：从右侧滑入 + 淡入。移动端从下往上滑（面板是全宽的，横向滑会很怪） */
.spark-cfg__panel {
    opacity: 0;
    transform: translateX(24px);
    transition: opacity var(--cfg-dur) var(--cfg-ease), transform var(--cfg-dur) var(--cfg-ease);
}

@media (max-width: 767px) {
    .spark-cfg__panel {
        transform: translateY(24rem);
    }
}

.spark-cfg--open .spark-cfg__panel {
    opacity: 1;
    transform: none;
}

/**
 * Header / Footer 压在壳上，内容从下面滚过去。
 * 稿子用的是 blur 12 + 白色渐变（上边缘是「透明 → 白」，下边缘反过来），
 * 这样滚动的内容在贴近边缘时淡出，而不是被一条硬边切掉。
 */
.spark-cfg__edge {
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    pointer-events: none;
}

.spark-cfg__edge>* {
    pointer-events: auto;
}

.spark-cfg__edge--top {
    /* background-image: linear-gradient(to top, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.8) 100%); */
    background-color: rgba(255, 255, 255, 0.8);
}

.spark-cfg__edge--bottom {
    background-image: linear-gradient(to top, #fff 82.398%, rgba(255, 255, 255, 0) 100%);
}

/**
 * Body 要让出上下两条边缘的高度。
 * pt 64 = Header 88（24 + 标题 40 + 24）减掉壳自己的 24 内边距
 * pb 136 = Footer（24 + 按钮 40 + 16 + 发货条 32 + 24）减掉壳的 24 → 112？
 *   不是：稿子的 Body 写的就是 pb-136，Footer 高度也正好 136，
 *   因为 Footer 的 24 底边距和壳的 24 是重叠的（Footer 是 absolute 贴在壳的边框上）。
 * 移动端 Footer 更高（价格与按钮上下排），所以那边单独给值。
 */
.spark-cfg__body {
    padding-top: 64px;
    padding-bottom: 136px;
}

@media (max-width: 767px) {
    .spark-cfg__body {
        padding-top: 56rem;
        padding-bottom: 200rem;
    }
}

/* ---------- 选项按钮（ModelOption）三态 ---------- */

/**
 * Default   1px 实线 #DADADA
 * Selected  2px 实线 #1D1D1D
 * Sold Out  1px **虚线** #DADADA + opacity 64，文字转 #B9B9B9（已确认：禁用不可点）
 *
 * 三态都用 box-shadow 画边而不是 border：border 宽度从 1 变 2 会让按钮尺寸跳一下，
 * inset box-shadow 不占盒模型，切换时位置纹丝不动。
 * 但 Sold Out 是虚线，box-shadow 画不出虚线 —— 那一态才用 border，
 * 并且用 outline 抵掉 1px 的尺寸差（虚线态本来就不可点，位置差一点不影响交互）。
 */
.spark-cfg__option {
    border: 1px solid transparent;
    color: #1D1D1D;
    background-color: transparent;
    box-shadow: inset 0 0 0 1px #DADADA;
    transition: box-shadow 300ms var(--cfg-ease), opacity 300ms var(--cfg-ease),
        color 300ms var(--cfg-ease);
}

@media (hover: hover) and (pointer: fine) {
    .spark-cfg__option:hover[data-state="default"] {
        box-shadow: inset 0 0 0 1px #858585;
    }
}

.spark-cfg__option[data-state="selected"] {
    box-shadow: inset 0 0 0 2px #1D1D1D;
}

.spark-cfg__option[data-state="soldout"] {
    box-shadow: none;
    border: 1px dashed #E4E4E4;
    opacity: 0.64;
    color: #B9B9B9;
    cursor: not-allowed;
}

/* 色板的底色由 JS 从 data-swatch 写上来（动态值不进内联 style，见项目规范） */
.spark-cfg__swatch {
    background-color: #b9b9b9;
}

/* ---------- Addon 行 ---------- */

.spark-cfg__addon {
    border: 1px solid transparent;
    box-shadow: inset 0 0 0 1px #DADADA;
    transition: box-shadow 300ms var(--cfg-ease), opacity 300ms var(--cfg-ease);
}

@media (hover: hover) and (pointer: fine) {
    .spark-cfg__addon:hover[data-state="default"] {
        box-shadow: inset 0 0 0 1px #858585;
    }
}

.spark-cfg__addon[data-state="selected"] {
    box-shadow: inset 0 0 0 2px #1D1D1D;
}

.spark-cfg__addon[data-state="soldout"] {
    box-shadow: none;
    border: 1px dashed #E4E4E4;
    opacity: 0.64;
}

.spark-cfg__addon[data-state="soldout"] .spark-cfg__addon-name,
.spark-cfg__addon[data-state="soldout"] .spark-cfg__addon-price {
    color: #b9b9b9;
}

.spark-cfg__addon-name {
    color: #1d1d1d;
}

/* Checkbox：16 方框 + 2 圆角，选中时填深色 + 白勾 */
.spark-cfg__check-box {
    box-shadow: inset 0 0 0 1px #b9b9b9;
    transition: box-shadow 200ms var(--cfg-ease), background-color 200ms var(--cfg-ease);
}

.spark-cfg__addon[data-state="selected"] .spark-cfg__check-box {
    background-color: #1d1d1d;
    box-shadow: inset 0 0 0 1px #1d1d1d;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6.4 11.2 3.2 8l1.13-1.13L6.4 8.94l5.07-5.07L12.6 5z' fill='%23fff'/%3E%3C/svg%3E");
    background-size: 100% 100%;
}

/* 加减钮（CircleIconButtonGrey）：#EDEDED 底 */
.spark-cfg__qty {
    background-color: #ededed;
    color: #1d1d1d;
    transition: background-color 200ms var(--cfg-ease);
}

.spark-cfg__qty:hover {
    background-color: #dadada;
}

/* ---------- 底部两个按钮 ---------- */

.spark-cfg__btn {
    transition: background-color 300ms var(--cfg-ease), color 300ms var(--cfg-ease),
        border-radius 300ms var(--cfg-ease);
}

.spark-cfg__btn--outline {
    border: 1px solid transparent;
    background-color: transparent;
    color: #1D1D1D;
    box-shadow: inset 0 0 0 1px #858585;
}

@media (hover: hover) and (pointer: fine) {
    .spark-cfg__btn--outline:not(:disabled):hover {
        background: #F6F6F6;
    }
}

.spark-cfg__btn--filled {
    background-color: #1d1d1d;
    color: #ededed;
}

.spark-cfg__btn--filled:hover {
    background-color: #000;
}

.spark-cfg__btn:disabled {
    opacity: 0.48;
    cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {

    .spark-cfg__panel,
    .spark-cfg__backdrop,
    .spark-cfg__option,
    .spark-cfg__addon,
    .spark-cfg__check-box,
    .spark-cfg__qty,
    .spark-cfg__btn {
        transition: none;
    }
}

/* ---------- 二级导航：真 fixed 吸顶 ---------- */
/*
 * 位移完全交给 GSAP：spark-showcase.js 让 translateY 跟着文档流里的 spacer 走，
 * 滚到 0 就停住 —— 观感是「本来在 Hero 后面，滚上来自然吸顶」。
 *
 * 这里刻意不写 transform，也**不能加 transition** —— 逐帧跟随时会拖影、慢半拍。
 * 模板类上的 -translate-y-full 只是 JS 没跑起来时别挡住 Hero 的兜底。
 */

/**
 * 主题随背景自动切换（稿子 6858:21741：「二级导航根据当前背景颜色自动切换主题」）。
 *
 * 所有会变色的属性都收敛到这几个变量，JS 只在 #spark-subnav 上切
 * data-subnav-theme="dark"，判定逻辑见 spark-showcase.js 的 initSubnav()。
 *
 * 取值来源：浅色 `8108:81580` / 深色 `6858:21770` / 移动深色 `7087:15240`。
 * 移动端两版都是**实底、无模糊**（稿子标注「移除背景透明度、移除背景模糊效果」），
 * 桌面两版都是玻璃 + blur 24。
 */
.spark-subnav {
    --subnav-bar: rgba(255, 255, 255, 0.8);
    --subnav-bar-mobile: #ffffff;
    --subnav-fg: #1d1d1d;
    --subnav-divider: rgba(29, 29, 29, 0.16);;
}

.spark-subnav[data-subnav-theme="dark"] {
    --subnav-bar: rgba(29, 29, 29, 0.48);
    --subnav-bar-mobile: #000000;
    --subnav-fg: #ffffff;
    --subnav-divider: rgba(255, 255, 255, 0.24);
}

/* 切主题时整条一起过渡，别硬切。曲线跟胶囊导航同一条 */
.spark-subnav__bar,
.spark-subnav__title,
.spark-subnav__link,
.spark-subnav__icon,
.spark-subnav__divider,
.spark-subnav__arrow {
    transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1),
        color 300ms cubic-bezier(0.4, 0, 0.2, 1),
        opacity 300ms cubic-bezier(0.4, 0, 0.2, 1);
}

.spark-subnav__bar--desktop {
    background-color: var(--subnav-bar);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
}

.spark-subnav__bar--mobile {
    background-color: var(--subnav-bar-mobile);
}

.spark-subnav__title {
    color: var(--subnav-fg);
}

/*
 * 右侧 Profile / Cart 图标：跟标题一样用满色 —— 导航项那 48% 是给文字链接的，
 * 图标是功能按钮，压到一半会看不清。图标 SVG 用 currentColor，所以跟着主题走。
 */
.spark-subnav__icon {
    color: var(--subnav-fg);
    /* opacity: 0.64; */
}

.spark-subnav__icon:hover,
.spark-subnav__icon:focus-visible {
    opacity: 1;
}

.spark-subnav__divider {
    background-color: var(--subnav-divider);
}

/**
 * 文字按钮：两版都是 48% → hover 100%，只有颜色跟着主题变。
 *
 * ⚠️ 稿子的浅色 Hover 变体（`6858:21870`）用的是 **white** —— 在
 * rgba(255,255,255,.8) 的浅色条上会看不见，是把深色变体的颜色带过来了。
 * 已和产品确认按 #1D1D1D（也就是 --subnav-fg）实现。深色版（`6863:21885`）是白色，正确。
 */
.spark-subnav__link {
    color: var(--subnav-fg);
    opacity: 0.48;
}

/*
 * --active 是滚动高亮（见 §5.45）。稿子**没画** active 态：SubNavBar 的五个按钮
 * 在任何一屏、两个主题下都是 opacity 48，组件也没有 active 变体，
 * 所以这里只把透明度提满，不额外加字重或颜色。
 */
.spark-subnav__link:hover,
.spark-subnav__link:focus-visible,
.spark-subnav__link--active {
    opacity: 1;
}

/* 移动端横滑箭头：底下那层渐变遮罩要跟着条的底色走 */
.spark-subnav__arrow {
    color: var(--subnav-fg);
}

.spark-subnav__arrow--prev {
    mask: linear-gradient(to right, var(--subnav-bar-mobile) 50%, transparent);
    background-color: var(--subnav-bar-mobile);
}

.spark-subnav__arrow--next {
    mask: linear-gradient(to left, var(--subnav-bar-mobile) 50%, transparent);
    background-color: var(--subnav-bar-mobile);
}

/**
 * Order Now（DynamicButtonFilledBrand）。两版颜色一样。
 * hover（稿子 `6863:22370`）：#FF4800 → **#C63800** 底、字变 **#FF4800**、圆角变胶囊。
 */
.spark-subnav__order {
    transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1),
        color 300ms cubic-bezier(0.4, 0, 0.2, 1),
        border-radius 300ms cubic-bezier(0.4, 0, 0.2, 1);
}

.spark-subnav__order:hover,
.spark-subnav__order:focus-visible {
    background-color: #c63800;
    color: #ff4800;
    border-radius: 99px;
}

@media (prefers-reduced-motion: reduce) {

    .spark-subnav__bar,
    .spark-subnav__title,
    .spark-subnav__link,
    .spark-subnav__divider,
    .spark-subnav__arrow,
    .spark-subnav__order {
        transition: none;
    }
}

/* ---------- Highlight 卡1 渐变（Figma 7754:29488 的 linear-gradient 原值）---------- */
.spark-card-gradient {
    background-image: linear-gradient(180deg,
            #1D1D1D 0%,
            #C63800 26.289%,
            #FFCCB8 37.461%,
            #FF4800 49.808%,
            #B9B9B9 100%);
}

/* ---------- 序列帧取景框 ---------- */
/* left/top/width/height/border-radius 由 spark-showcase.js 每帧写入；
   这里只给出 JS 未接管时的安全初值（尺寸 0，配合模板上的 invisible 不会露出空框）。 */
.spark-showcase__stage {
    left: 0;
    top: 0;
    width: 0;
    height: 0;
}

/* canvas 的 transform 由 JS 写入，transform-origin 必须是左上角才能和 left/top 计算对齐 */
.spark-showcase__canvas {
    transform-origin: 0 0;
    left: 0;
    top: 0;
}

/* 段落文字：带方向位移的进出动画，提前提示合成层 */
.spark-copy {
    will-change: transform, opacity;
}

/* 卡4 静态图交接：序列帧首帧就绪后淡出，避免两层相机重叠 */
.spark-stage-anchor__fallback {
    transition: opacity 200ms linear;
}

/* ---------- Image Quality 视频网格 ---------- */

/**
 * 行宽。格子宽 = 945.625/1440 = 65.6684% 容器宽（移动端是 50%），行内 flex-1 均分，
 * 所以行宽 = 格数 × 格子宽 + 缝隙总宽，均分回来每格正好是设计稿的宽度。
 * 行比容器宽是设计要的出血，靠父级 flex 的 align-items:center 对称溢出。
 *
 * 为什么不写成 Tailwind 任意值：`w-[calc(131.3368%_+_8px)]` 这类 calc 里带 % 的
 * 值 Tailwind 3 的提取器不认，同文件其他任意值都能编译出来、只有这几个静默丢失。
 */
.spark-iq__row--2 {
    width: calc(100% + 8px);
}

.spark-iq__row--3 {
    width: calc(150% + 16px);
}

@media (min-width: 768px) {
    .spark-iq__row--2 {
        width: calc(131.3368% + 8px);
    }

    .spark-iq__row--3 {
        width: calc(197.0052% + 16px);
    }
}
/* 网格与标题的 transform 由 spark-showcase.js 每帧写入，提前提示合成层 */
.spark-iq__viewport,
.spark-iq__grid,
.spark-iq__copy {
    will-change: transform, opacity;
}

/* 逐字进出：必须 inline-block 才能吃 transform；空格已在 JS 里换成 &nbsp; 所以不会塌 */
.spark-iq__char {
    display: inline-block;
    white-space: pre;
}

/* 静态网格：移动端常规显示；桌面默认隐藏，只在 reduced-motion 时作为降级出现 */
@media (min-width: 768px) {
    .spark-iq-static {
        display: none;
    }
}

/* ---------- reduced-motion 降级 ---------- */
/* 桌面端不 pin、不加载序列帧，改用移动端那套静态箭头切换 + 静态视频网格，信息不丢失。
   类挂在 <body> 上（不是 #spark-showcase），因为静态网格是 #spark-showcase 的兄弟节点。 */
@media (min-width: 768px) {
    .spark-reduced-motion .spark-showcase {
        display: none !important;
    }

    .spark-reduced-motion .spark-showcase-static {
        display: block;
    }

    .spark-reduced-motion .spark-iq-static {
        display: block;
    }
}

/* ---------- ImageQuality_ISO 分段选择器 ---------- */

/**
 * 未选中那颗 chip 的悬停色 #515151（比常态的 #858585 深一档）。
 * 只作用在 aria-checked="false" 上 —— 选中的那颗是白字压在黑药丸上，
 * 换成 #515151 会几乎看不见。用 aria-checked 而不是 Tailwind 的 hover: 变体：
 * hover 变体在输出顺序里排在 text-white 之后，会把选中态也一起改掉。
 *
 * 过渡由模板上的 transition-colors duration-300 提供（缓动就是站点统一的
 * cubic-bezier(0.4, 0, 0.2, 1)，Tailwind 的默认值）。
 * 触屏不用排除：点下去那一刻它就变成 aria-checked="true"，规则自动失配。
 */
.spark-iso__chip[aria-checked="false"]:hover {
    color: #515151;
}

/* ---------- Workflow_SSD 进度条宽度 ---------- */

/**
 * 条宽 = seconds / max(seconds)，数据源是 pixboom_spark_ssd_rows()：420s 与 6s。
 * 6 / 420 = 1.428571%。
 *
 * 为什么写在 CSS 而不是模板里：
 * 1. Tailwind 任意值必须是**字面量**才会被扫到，PHP 拼出来的 w-[1.4286%] 编译不出来；
 * 2. 首屏（JS 未跑、或干脆没有 JS）布局就得是对的 —— JS 只负责 scaleX 0 → 1 的进场，
 *    宽度不归它管（项目规范也要求动 transform 而不是 width）。
 * 改数字时两处要一起改，别只改一处。
 *
 * 桌面 575.333 宽下 1.428571% = 8.2px，与设计稿画的 8px 吻合；
 * 移动 311 宽下是 4.4px，而移动稿画的是 8px（2.6%，没按比例）—— 这里取真比例。
 */
.spark-ssd__fill--pixboom {
    width: 100%;
}

.spark-ssd__fill--ram {
    width: 1.428571%;
}

/* ---------- Workflow_RAW 左右对比 ---------- */

/**
 * 分割位置只有这一个来源：range input 的值写进 --spark-raw-split，
 * 两半的裁切和手柄都读它，所以永远同步。
 *
 * 用 clip-path 而不是「两个改宽度的容器」：两条视频都是整幅铺满同一个框
 * （absolute inset-0 + object-cover），裁切只切显示区域、不影响视频自身的布局，
 * 接缝两侧才是像素级对齐的同一取景。inset() 的 round 让每半边保留 16 圆角，
 * 对应稿子里 RAWMediaLeft / Right 各自的 rounded-[16px]。
 *
 * 8px 接缝：左半裁到 split − 4，右半从 split + 4 开始。
 */
.spark-raw__stage {
    --spark-raw-split: 50%;
}

.spark-raw__pane--left {
    clip-path: inset(0 calc(100% - var(--spark-raw-split) + 4px) 0 0 round 16px);
}

.spark-raw__pane--right {
    clip-path: inset(0 0 0 calc(var(--spark-raw-split) + 4px) round 16px);
}

/**
 * 拖动热区：铺满媒体区的透明 range。
 * 滑块必须做成 1px 宽 —— 浏览器把值映射到 [thumbW/2, W − thumbW/2]，
 * 滑块越宽两端误差越大；1px 时误差 ±0.5px，可以忽略。
 */
.spark-raw__range {
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
    opacity: 0;
    cursor: ew-resize;
    touch-action: pan-y;
}

.spark-raw__range::-webkit-slider-runnable-track {
    height: 100%;
    background: transparent;
}

.spark-raw__range::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 1px;
    height: 100%;
    background: transparent;
}

.spark-raw__range::-moz-range-track {
    height: 100%;
    background: transparent;
}

.spark-raw__range::-moz-range-thumb {
    width: 1px;
    height: 100%;
    border: 0;
    background: transparent;
}

/**
 * 移动端需要足够大的原生 thumb 才能稳定开始触摸拖动。
 * range 向左右各延伸 thumb 宽度的一半，使 48px thumb 的中心仍能精确走到媒体区 0% / 100%，
 * 可见手柄继续按原来的百分比定位，不会出现越靠近两端误差越大的问题。
 */
@media (max-width: 767px) {
    .spark-raw__range {
        left: -24rem;
        right: auto;
        width: calc(100% + 48rem) !important;
    }

    .spark-raw__range::-webkit-slider-thumb {
        width: 48rem;
    }

    .spark-raw__range::-moz-range-thumb {
        width: 48rem;
    }
}

.spark-raw__handle {
    left: var(--spark-raw-split);
    transform: translate(-50%, -50%);
}

/* range 是透明的，焦点环只能画在可见的手柄上 */
.spark-raw__range:focus-visible~.spark-raw__handle {
    outline: 2px solid #FF4800;
    outline-offset: 2px;
}

/* ---------- Workflow_ROI 图表 ---------- */

/* 遮罩高度、三层描边框的宽高、标签的 top 全部由 spark-showcase.js 写入；
   这里只给 JS 未接管时的安全初值（遮罩为 0，框与标签不可见，只剩底图）。 */
.spark-roi__mask {
    height: 0;
}

.spark-roi__rect,
.spark-roi__label {
    visibility: hidden;
    opacity: 0;
}

/* chip 组在移动端放不下 5 颗，横向滚动但不要滚动条 */
.spark-roi__scroller {
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.spark-roi__scroller::-webkit-scrollbar {
    display: none;
}

/* ---------- 手风琴（More_Connectivity 的规格列表） ---------- */

/**
 * 高度由 JS 写入 maxHeight（= scrollHeight），CSS 只负责收起态与过渡。
 * 不用 grid-template-rows: 0fr → 1fr 那套：本项目还要支持较老的 Safari，
 * 而且面板里就一行字，maxHeight 方案的抖动风险可以忽略。
 */
.spark-accordion__panel {
    max-height: 0;
    transition: max-height 320ms ease;
}

/**
 * 加号 → 减号：只把竖线转 90°，转完与横线重合。
 * 稿子（8170:4309 等展开态）里那两条 path 都是水平的，是减号不是 ×。
 * transform-box: fill-box 是必须的 —— SVG 里 transform-origin 默认按 viewBox 原点算，
 * 不改的话竖线会绕着左上角甩出去。
 */
.spark-accordion__bar {
    transform-box: fill-box;
    transform-origin: center;
    transition: transform 320ms ease;
}

.spark-accordion__trigger[aria-expanded="true"] .spark-accordion__bar {
    transform: rotate(90deg);
}

/* ---------- More_Connectivity 的分层媒体 ---------- */

/**
 * 收起态的几何。**这组值与 pixboom_spark_connectivity_base() 里的 scale / y 是同一份**，
 * 写在这里是给 JS 不跑时兜底：没有它，退化后看到的是未缩放的整机图，比稿子小一圈。
 * 运行时由 GSAP 接管（内联 style 覆盖这里），两边顺序都是 translate 外、scale 内。
 *
 * 六层图靠 opacity 切换，同时整个 stage 推近平移 —— 动父元素而不是各层各动，
 * 是因为 sdi/hdmi/io 是透明的局部高亮层，必须与机身严格对齐。
 *
 * 这里**刻意不写 will-change**：这一节只在点手风琴时动，常驻提层是白占 GPU 纹理。
 * 提层改由 initConnectivityMedia() 在补间期间挂、补完摘（同 §5.38）。
 */
.spark-conn__stage {
    transform: translateY(-1.835%) scale(1.3372);
}

/* ---------- 轮播（More_Testimonial 的推荐语） ---------- */

.spark-carousel__viewport {
    width: 100%;
    overflow: visible;
}

.spark-carousel__track {
    transition: transform 400ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* 减少动画：箭头切换改成瞬时跳转 */
@media (prefers-reduced-motion: reduce) {
    .spark-carousel__track {
        transition: none;
    }

    .spark-accordion__panel {
        transition: none;
    }
}

/**
 * 推荐语卡片里的 blockquote 也被站点附加 CSS 加了 `margin: 24px 0`（和 .spark-list-reset
 * 压的是同一个来源，见下面那段说明）。卡片是**固定高 280**，白吃上下共 48px 的后果：
 * 3 行引言正好卡满 280、4 行就溢出 24px，figcaption 被顶到离底边只剩 8px。
 * 稿子里 blockquote 没有任何外边距，清零才是对的。
 */
.spark-carousel__card blockquote {
    margin: 0 !important;
}

/* ---------- 列表重置 ---------- */

/**
 * 🔴 站点在 WordPress「外观 → 自定义 → 附加 CSS」里加了两条**元素级** ul 规则
 * （输出成 <style id="wp-custom-css">，**存在数据库里、仓库怎么 grep 都找不到**）：
 *   ul    { list-style-type: disc; font-size: 16px; padding-left: 1.5em; margin-bottom: 1em }
 *   ul li { margin-bottom: 0.5em; font-size: 16px }
 * 它没带 !important，但排在 tailwind.css 之后、特异度又相同，
 * 所以直接盖掉 preflight 的 `ul { list-style: none; margin: 0; padding: 0 }`。
 *
 * 那两条是给博客正文用的。本页的列表全是**布局用**的（奖项、挂载方式、规格行），
 * 被套上就会多出符号、缩进和间距 —— 实测让 More_Testimonial 凭空高出 24px
 * （ul 的 margin-bottom 16 + 末项 li 的 8）、Rig 列表每项多 8px。
 *
 * 逐个 <ul>/<li> 挂 !mb-0 !pl-0 太脏，统一用这个类抵消。
 * 需要保留项目符号的地方用下面的 .spark-bullet-list，别用这个。
 *
 * ⚠️ 因为是数据库里的配置，换环境（生产/预发/别人的本地）可能根本没有这两条。
 * 所以这两个类都是「无论有没有都能对」的写法，别依赖它存在，也别以为它不存在。
 */
ul.spark-list-reset {
    list-style: none !important;
    margin: 0 !important;
    padding: 0 !important;
    font-size: inherit !important;
}

ul.spark-list-reset>li {
    margin: 0 !important;
    font-size: inherit !important;
}

/* ---------- 项目符号列表 ---------- */

/**
 * 需要**保留**项目符号的列表：规格表的多值行、What's in the box 弹窗的清单。
 * 这两处都不能挂 .spark-list-reset（那个是去符号的），但上面那两条附加 CSS 给的是
 * 缩进 1.5em + margin-bottom 1em + li margin-bottom 0.5em，稿子要的是
 * 缩进 24、行距就是正文行高、项之间没有额外间距，所以自己写一套盖掉。
 *
 * 缩进写死 24px 而不是 1.5em：正文是 16px 时两者等值，但值来自设计稿的 px，
 * 不该跟着字号浮动。
 */
ul.spark-bullet-list {
    list-style: disc !important;
    margin: 0 !important;
    padding-left: 24px !important;
    font-size: inherit !important;
}

ul.spark-bullet-list>li {
    margin: 0 !important;
    font-size: inherit !important;
}

/* ---------- Purchase Controls：双卡片媒体布局 ---------- */

#spark-purchase-controls [data-purchase-controls-media] {
    aspect-ratio: 692 / 415;
}

/* ---------- 规格表 ---------- */

/* 段间距交给模板上的 flex gap —— 上面那条 margin: 0 !important 会压掉这里写的 margin */
.spark-spec-value p {
    margin: 0;
}

/* pin 之后 GSAP 会接管 transform，提前提示合成层 */
.spark-spec-nav {
    will-change: transform;
}

/* ---------- More_RigCompatibility：pin + 下滑切档 ---------- */

/**
 * 被 pin 的是内层 .spark-rig__pin（不是 section）：section 由 ScrollTrigger 的
 * pinSpacing 撑出滚动距离，内层负责占满一屏。
 *
 * 🔴 高度要减掉二级导航的 64：initRigScrub() 用的是 start:'top top+=SUBNAV_HEIGHT'，
 * 也就是这个容器**被 pin 在导航下方**（top = 64），不是视口顶。写满 100svh 的话
 * 底部会正好溢出 64，卡片下沿被切掉 —— 容器自己只占「导航以下」那块。
 * 这里的 64 必须和 spark-showcase.js 的 SUBNAV_HEIGHT 保持一致。
 *
 * 移动端不 pin，所以不给高度 —— 高度由内容决定（稿子的移动 frame 是 707，不是满视口）。
 */
@media (min-width: 768px) {
    .spark-rig__pin {
        height: calc(100vh - 64px);
    }
}

@supports (height: 100svh) {
    @media (min-width: 768px) {
        .spark-rig__pin {
            height: calc(100svh - 64px);
        }
    }
}

/**
 * 只给文字带提合成层。
 *
 * 配图那 5 张**刻意不加 will-change**：它们只在离散切档时交叉淡化，不是逐帧 scrub，
 * 5 张叠图常驻提层纯属浪费显存；实测在被遮挡的窗口里还会出现「层不绘制、媒体框空白」，
 * 去掉之后正常。scrub 的元素才值得提层。
 */
.spark-rig__strip {
    will-change: transform;
}

/**
 * 桌面：文字带绝对定位到卡片的垂直中线，JS 再上移 277 + 56×index，
 * 让 active 项的中心恒好压在中线上（详见 section-more-rig.php 里的说明）。
 *
 * left/right 手写 48 是因为 absolute 之后不再吃父级那层 p-[48px]。
 * 移动端不定位 —— 那边不 pin 也不滑，普通流即可。
 */
@media (min-width: 768px) {
    .spark-rig__strip {
        position: absolute;
        top: 50%;
        left: 48px;
        right: 48px;
    }
}
