/**
 * Hero slider.
 *
 * The track is a scroll-snap row, so swiping and slide-to-slide movement work
 * with no JavaScript at all. hero.js only adds autoplay, the dots and keyboard
 * control. If the script fails the hero is still a usable, swipeable banner.
 */

.halo-hero {
    position: relative;
    /* Break out of the theme's 1222px content container so the hero runs edge to
       edge. The negative side margins are the standard full-bleed trick; the
       theme already keeps horizontal overflow off the body, which is what makes
       the vw unit safe here despite the scrollbar. */
    margin-right: calc(50% - 50vw);
    margin-bottom: 30px;
    margin-left: calc(50% - 50vw);
    overflow: hidden;
    background: var(--halo-soft, #f7f7f7);
}

.halo-hero-track {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    /* Firefox / IE-era scrollbar hiding; WebKit handled below. */
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.halo-hero-track::-webkit-scrollbar {
    display: none;
}

.halo-hero-slide {
    position: relative;
    flex: 0 0 100%;
    scroll-snap-align: center;
    /* Fixed ratio keeps the reserved height stable, so adding the hero does not
       shift the rest of the page while the image loads. */
    aspect-ratio: 1920 / 620;
    min-height: 320px;
}

.halo-hero-bg {
    position: absolute;
    inset: 0;
}

.halo-hero-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
}

/* Readability shading. Text over product photography is the one thing that
   reliably goes wrong, so every slide gets at least a soft fade by default. */
.halo-hero-slide::after {
    position: absolute;
    inset: 0;
    content: "";
    pointer-events: none;
}

.scrim-none::after   { display: none; }
.scrim-soft::after   { background: linear-gradient(90deg, rgba(255, 255, 255, .78) 0%, rgba(255, 255, 255, .45) 45%, rgba(255, 255, 255, 0) 75%); }
.scrim-strong::after { background: linear-gradient(90deg, rgba(255, 255, 255, .92) 0%, rgba(255, 255, 255, .7) 50%, rgba(255, 255, 255, .1) 85%); }

.align-right.scrim-soft::after   { background: linear-gradient(270deg, rgba(255, 255, 255, .78) 0%, rgba(255, 255, 255, .45) 45%, rgba(255, 255, 255, 0) 75%); }
.align-right.scrim-strong::after { background: linear-gradient(270deg, rgba(255, 255, 255, .92) 0%, rgba(255, 255, 255, .7) 50%, rgba(255, 255, 255, .1) 85%); }

.align-center.scrim-soft::after   { background: radial-gradient(60% 80% at 50% 50%, rgba(255, 255, 255, .8) 0%, rgba(255, 255, 255, 0) 100%); }
.align-center.scrim-strong::after { background: radial-gradient(70% 90% at 50% 50%, rgba(255, 255, 255, .93) 0%, rgba(255, 255, 255, .15) 100%); }

/* Light-text slides need the fade to darken instead. */
.theme-light.scrim-soft::after   { background: linear-gradient(90deg, rgba(0, 0, 0, .62) 0%, rgba(0, 0, 0, .3) 45%, rgba(0, 0, 0, 0) 75%); }
.theme-light.scrim-strong::after { background: linear-gradient(90deg, rgba(0, 0, 0, .8) 0%, rgba(0, 0, 0, .55) 50%, rgba(0, 0, 0, .1) 85%); }
.theme-light.align-right.scrim-soft::after   { background: linear-gradient(270deg, rgba(0, 0, 0, .62) 0%, rgba(0, 0, 0, .3) 45%, rgba(0, 0, 0, 0) 75%); }
.theme-light.align-right.scrim-strong::after { background: linear-gradient(270deg, rgba(0, 0, 0, .8) 0%, rgba(0, 0, 0, .55) 50%, rgba(0, 0, 0, .1) 85%); }
.theme-light.align-center.scrim-soft::after   { background: radial-gradient(60% 80% at 50% 50%, rgba(0, 0, 0, .6) 0%, rgba(0, 0, 0, 0) 100%); }
.theme-light.align-center.scrim-strong::after { background: radial-gradient(70% 90% at 50% 50%, rgba(0, 0, 0, .8) 0%, rgba(0, 0, 0, .15) 100%); }

.halo-hero-inner {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    width: 100%;
    max-width: 1222px;
    height: 100%;
    margin: 0 auto;
    padding: 24px 30px;
}

.halo-hero-copy   { max-width: 620px; }
.align-center .halo-hero-inner { justify-content: center; }
.align-center .halo-hero-copy  { text-align: center; }
.align-right .halo-hero-inner  { justify-content: flex-end; }
.align-right .halo-hero-copy   { text-align: right; }

.halo-hero-eyebrow {
    margin: 0 0 10px;
    color: var(--halo-gold-ink, #8a6d1f);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .18em;
    text-transform: uppercase;
}

.halo-hero-title {
    margin: 0;
    color: var(--halo-ink, #1c1f27);
    /* Scales with the viewport so long headlines never need a manual font size
       per slide, which is what the old builder slides did. */
    font-size: clamp(26px, 4vw, 54px);
    font-weight: 700;
    line-height: 1.12;
    letter-spacing: -.01em;
}

.halo-hero-sub {
    margin: 14px 0 0;
    color: var(--halo-copy, #555);
    font-size: clamp(15px, 1.4vw, 20px);
    line-height: 1.5;
}

.theme-light .halo-hero-title,
.theme-light .halo-hero-sub { color: #fff; }
.theme-light .halo-hero-eyebrow { color: var(--halo-gold, #d4a11e); }

.halo-hero-btn {
    display: inline-block;
    margin-top: 22px;
    padding: 14px 32px;
    border-radius: 2px;
    background: var(--halo-ink, #1c1f27);
    color: #fff !important;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    transition: background .2s ease, color .2s ease;
}

.halo-hero-btn:hover {
    background: var(--halo-gold, #d4a11e);
    color: var(--halo-ink, #1c1f27) !important;
}

.theme-light .halo-hero-btn {
    background: #fff;
    color: var(--halo-ink, #1c1f27) !important;
}

/* Dots */
.halo-hero-dots {
    position: absolute;
    bottom: 16px;
    left: 50%;
    display: flex;
    gap: 10px;
    transform: translateX(-50%);
    z-index: 2;
}

/* Woodmart puts min-height:42px and display:flex on every <button>, which beat a
   plain height declaration and rendered these as 30x42 blocks. Hence the
   !important and the explicit min/max height reset. Slim gold bars match the
   active-tab underline used elsewhere on the page. */
.halo-hero-dot {
    display: block !important;
    width: 28px !important;
    min-width: 0 !important;
    height: 3px !important;
    min-height: 0 !important;
    max-height: 3px !important;
    padding: 0 !important;
    border: 0 !important;
    border-radius: 0 !important;
    background: rgba(28, 31, 39, .25);
    cursor: pointer;
    transition: background .2s ease;
}

.halo-hero-dot:hover { background: rgba(28, 31, 39, .5) !important; }
.halo-hero-dot[aria-selected="true"] { background: var(--halo-gold, #d4a11e) !important; }

.halo-hero-dot:focus-visible {
    outline: 3px solid var(--halo-gold-ink, #8a6d1f);
    outline-offset: 3px;
}

/* Tablet and phone: taller frame so the headline has room once it wraps. */
@media (max-width: 1024px) {
    .halo-hero-slide { aspect-ratio: 1024 / 520; }

    /* 但 bare 幻灯片例外：它没有文案层，「给标题换行留高度」这个理由不成立，
     * 而把 3.1:1 的成品设计稿塞进 1.969 的框，cover 会裁掉左右。
     * 2026-09-13 实测 768px 只剩 64% 宽、1024px 只剩 63% —— 图上的字左右各少一截。
     * 所以这里让 bare 用回和桌面一样的 1920/620。
     * 手机（≤767px）不受影响：下面有更具体的规则按竖图比例定框。
     */
    /* min-height 必须一起解掉：顶部那条 min-height:320px 会反过来把宽度算成
     * 320×3.097 = 991px，在 768 的视口上直接横向溢出（改完当场量到过）。
     * bare 是整张图，本来也不需要「最矮多高」这个保底。
     */
    .halo-hero-slide.layout-bare {
        aspect-ratio: 1920 / 620;
        min-height: 0;
    }
    .halo-hero-inner { padding: 20px 22px; }
}

@media (max-width: 767px) {
    .halo-hero-slide {
        aspect-ratio: auto;
        min-height: 420px;
    }

    .halo-hero-inner {
        align-items: flex-end;
        padding: 20px 18px 42px;
    }

    /* On a narrow screen there is no empty space to align into, so every slide
       reads better as a single bottom-aligned block. */
    .halo-hero-copy,
    .align-center .halo-hero-copy,
    .align-right .halo-hero-copy {
        max-width: none;
        text-align: left;
    }

    .align-center .halo-hero-inner,
    .align-right .halo-hero-inner { justify-content: flex-start; }

    .halo-hero-slide::after {
        background: linear-gradient(0deg, rgba(255, 255, 255, .93) 0%, rgba(255, 255, 255, .6) 42%, rgba(255, 255, 255, 0) 72%);
    }

    .theme-light .halo-hero-slide::after,
    .halo-hero-slide.theme-light::after {
        background: linear-gradient(0deg, rgba(0, 0, 0, .82) 0%, rgba(0, 0, 0, .45) 42%, rgba(0, 0, 0, 0) 72%);
    }

    .halo-hero-btn { margin-top: 16px; padding: 12px 24px; }
}

/* Respect the OS setting: no auto movement, and no smooth scroll jumps. */
@media (prefers-reduced-motion: reduce) {
    .halo-hero-track { scroll-behavior: auto; }
}

/* -------------------------------------------------------------------------
 * Split layout
 *
 * Text sits beside the artwork instead of on top of it. This is the safe option
 * for stock or supplier photography: no scrim, no contrast guesswork, and the
 * image is not cropped to a 3:1 letterbox. The copy column lines up with the
 * site's 1222px container even though the section itself is full bleed.
 * ---------------------------------------------------------------------- */
.halo-hero-slide.layout-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: stretch;
    aspect-ratio: auto;
    min-height: 400px;
    background: #fff;
}

.layout-split::after {
    display: none;                 /* nothing to shade, the text is on white */
}

.layout-split .halo-hero-bg {
    position: relative;
    inset: auto;
}

.layout-split .halo-hero-inner {
    max-width: none;
    height: auto;
    margin: 0;
    padding-block: 40px;
    padding-inline: max(24px, calc((100vw - 1222px) / 2)) clamp(24px, 3vw, 56px);
}

.layout-split .halo-hero-copy {
    max-width: 520px;
    text-align: left;
}

/* Alignment only means something when the text is over the image. */
.layout-split .halo-hero-inner { justify-content: flex-start; }

@media (max-width: 767px) {
    /* Side by side leaves neither half usable on a phone, so stack: image on
       top, copy underneath on white. */
    .halo-hero-slide.layout-split {
        grid-template-columns: 1fr;
        min-height: 0;
    }

    .layout-split .halo-hero-bg {
        aspect-ratio: 16 / 10;
    }

    .layout-split .halo-hero-inner {
        align-items: flex-start;
        padding: 22px 18px 46px;
    }

    .layout-split .halo-hero-copy { max-width: none; }
}

/* -------------------------------------------------------------------------
 * Image treatment
 *
 * The supplier hero artwork is a dark green gradient (hue ~140, lightness ~22%),
 * which fights the gold and ink palette used everywhere else. Desaturating it
 * lets the gold eyebrow and the button carry the brand colour instead.
 * ---------------------------------------------------------------------- */
.treat-neutral .halo-hero-img {
    filter: grayscale(1) contrast(1.04) brightness(1.03);
}

.treat-warm .halo-hero-img {
    filter: saturate(.3) sepia(.16) contrast(1.03);
}

/* -------------------------------------------------------------------------
 * Product shots
 *
 * A stack of product photos beside the copy that cross-fades on a loop, so one
 * slide can show several products without adding more slides. Used instead of
 * baking the products into the background artwork: the photos can then be
 * swapped from the media library without re-compositing anything.
 * ---------------------------------------------------------------------- */
.halo-hero-shots {
    position: absolute;
    top: 50%;
    /* Proportional split, not a fixed anchor. The previous version used
       right: max(8px, calc((100vw - 1420px) / 2)), which collapses to a flat 8px
       on any viewport narrower than 1420px — so raising that number moved
       nothing on a normal laptop and the left card kept landing on the headline.
       Pinning the left edge to a percentage keeps the gap at every width. */
    left: 50%;
    right: max(8px, calc((100vw - 1440px) / 2));
    height: 82%;
    /* Sits above the background but never overlaps the copy geometrically, so
       the text stays selectable and readable. */
    z-index: 1;
    transform: translateY(-50%);
    pointer-events: none;
}

/* Coverflow: the middle card is scaled up and in front, the two beside it sit
   smaller and behind, partially tucked under it. Each tick shifts which card is
   in the middle, so the group appears to rotate rather than cross-fade.
   Position is driven by data-pos, written by hero.js relative to the active
   index (-1 = left, 0 = middle, 1 = right, anything else parked out of view). */
.halo-hero-shot {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 52%;
    height: 92%;
    /* contain, not cover: these product photos run 1.00 / 0.69 / 0.91 / 1.43 /
       2.08, and cropping to a common box cuts the product itself. */
    object-fit: contain;
    object-position: center;
    border-radius: 14px;
    /* Matches the drop shadow on the reference artwork's cards. */
    box-shadow: 0 18px 40px rgba(0, 0, 0, .45);
    opacity: 0;
    transform: translate(-50%, -50%) scale(.7);
    transition: transform .6s cubic-bezier(.22, .61, .36, 1), opacity .6s ease;
    will-change: transform, opacity;
}

/* Offsets are wide on purpose: at -92% / -8% the middle card covered almost all
   of its neighbours, so the group read as one card with slivers behind it. Each
   side card now shows roughly half of itself, like the reference artwork. */
.halo-hero-shot[data-pos="0"] {
    z-index: 3;
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.04);
}

/* -104% instead of -108%: the left card sticks out past the box's left edge by
   (offset - 50%) of its own width, so a bigger offset eats into the gap that the
   50% split just bought. */
.halo-hero-shot[data-pos="-1"] {
    z-index: 2;
    opacity: .95;
    transform: translate(-104%, -47%) scale(.86);
}

.halo-hero-shot[data-pos="1"] {
    z-index: 2;
    opacity: .95;
    transform: translate(4%, -53%) scale(.86);
}

/* Before hero.js runs, the first shot is the only one marked active, so the
   hero still shows one product if the script never loads. */
.halo-hero-shot.is-active:not([data-pos]) {
    z-index: 3;
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.04);
}

/* Only two shots: keep each card in a fixed left/right slot and just swap which
   one is scaled up and in front. Driving them off data-pos like the 3-card case
   makes the pair slide sideways every tick (positions alternate between 0/1 and
   -1/0), which reads as the whole group shuffling rather than one card stepping
   forward. */
.halo-hero-shots[data-count="2"] .halo-hero-shot {
    z-index: 2;
    opacity: .9;
}

.halo-hero-shots[data-count="2"] .halo-hero-shot:nth-of-type(1) {
    transform: translate(-74%, -50%) scale(.84);
}

.halo-hero-shots[data-count="2"] .halo-hero-shot:nth-of-type(2) {
    transform: translate(-26%, -50%) scale(.84);
}

.halo-hero-shots[data-count="2"] .halo-hero-shot.is-active {
    z-index: 3;
    opacity: 1;
}

.halo-hero-shots[data-count="2"] .halo-hero-shot.is-active:nth-of-type(1) {
    transform: translate(-66%, -50%) scale(1.04);
}

.halo-hero-shots[data-count="2"] .halo-hero-shot.is-active:nth-of-type(2) {
    transform: translate(-34%, -50%) scale(1.04);
}

/* Narrower than the shots box is wide, so the headline wraps before it reaches
   the cards instead of being overlapped by the left one. */
.has-shots .halo-hero-copy {
    max-width: min(40%, 470px);
}

/* Slides with product shots drop the background photo, so paint the brand dark
   here. Without this a light-text slide would sit on the light --halo-soft. */
.halo-hero-slide.no-bg {
    background: #14161c;
    background-image:
        radial-gradient(120% 90% at 78% 50%, rgba(212, 161, 30, .16) 0%, rgba(0, 0, 0, 0) 60%),
        linear-gradient(100deg, #14161c 0%, #1c1f27 46%, #23262f 100%);
}

.halo-hero-slide.no-bg::after {
    display: none;              /* nothing to shade, the panel is already dark */
}

@media (max-width: 1024px) {
    .halo-hero-shots {
        left: 48%;
        right: 12px;
        height: 76%;
    }

    .has-shots .halo-hero-copy {
        max-width: 42%;
    }
}

@media (max-width: 767px) {
    /* Side by side leaves neither half usable on a phone: stack the cards above
       the copy. The coverflow is kept, just tighter. */
    .halo-hero-slide.has-shots {
        min-height: 540px;
    }

    /* Stacked on a phone, so the box goes back to being centred full width
       rather than a right-hand column. */
    .halo-hero-shots {
        top: 16px;
        left: 4%;
        right: 4%;
        height: 230px;
        transform: none;
    }

    .halo-hero-shot {
        width: 58%;
        border-radius: 10px;
        box-shadow: 0 10px 24px rgba(0, 0, 0, .4);
    }

    .halo-hero-shot[data-pos="-1"] { transform: translate(-86%, -48%) scale(.74); }
    .halo-hero-shot[data-pos="1"]  { transform: translate(-14%, -52%) scale(.74); }

    .has-shots .halo-hero-inner {
        align-items: flex-end;
        padding-bottom: 46px;
    }

    .has-shots .halo-hero-copy {
        max-width: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .halo-hero-shot {
        transition: none;
    }
}

/* -------------------------------------------------------------------------
 * layout=bare：整张成品设计稿
 *
 * 标题、卖点、按钮、产品图都已经画在图里了，所以这种幻灯片不输出任何文案层，
 * 只有背景图加一个铺满的链接。
 *
 * 它和其他幻灯片共用同一个 track，因此**同样受 aspect-ratio: 1920/620
 * 和 object-fit: cover 约束**。出图必须是 1920×620（或同比例的 2560×827），
 * 比例不对会被裁掉上下 —— 活动图上的字一裁就没了，而这种图全靠那些字。
 *
 * 不给它单独放宽比例是故意的：track 是等高的横向容器，混进一张比例不同的图
 * 会把整个 hero 的高度撑走，旁边那几张分类幻灯片的构图跟着一起毁。
 * ---------------------------------------------------------------------- */
.halo-hero-slide.layout-bare .halo-hero-inner {
    display: none;
}

/* bare 不需要压暗层。那层是为了让白字在照片上读得清，
   这里没有字，压暗只会让设计稿发灰。 */
.halo-hero-slide.layout-bare::after {
    display: none;
}

/* 成品稿不做去色处理，treatment 选 none 之外的值也一律忽略 */
.halo-hero-slide.layout-bare .halo-hero-img {
    filter: none;
}

/* 整张图可点 */
.halo-hero-bare-link {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: block;
}

.halo-hero-bare-link:focus-visible {
    outline: 3px solid var(--halo-gold-ink, #8a6a12);
    outline-offset: -6px;
}

/* -------------------------------------------------------------------------
 * bare 幻灯片的手机端处理
 *
 * 手机上这个框是**竖的**：@media (max-width:767px) 里幻灯片是
 * min-height 420px，而带产品图的分类幻灯片是 540px，轮播 track 又是等高的，
 * 于是所有幻灯片都被撑成大约 390×540（比例 0.72）。
 *
 * 往 0.72 的框里放 3.1:1 的宽图，object-fit: cover 会裁掉左右约四分之三
 * —— 实测只剩中间 23%，活动图上的字基本全没了。这不是 bug，是比例本身冲突，
 * 靠改 CSS 变不出内容来。
 *
 * 所以分两种情况：
 *
 *   has-mobile-art  后台填了手机版竖图（1080×1440）。<picture> 已经把图换掉了，
 *                   继续用 cover 铺满，效果和桌面端一样。
 *
 *   no-mobile-art   没有竖图，只能把宽图缩进去（contain）。上下留品牌深色，
 *                   像信箱框。图会变小，主标题还看得清，细节小字看不清了 ——
 *                   但至少是完整的，能让人知道有活动并点进去。
 *                   比裁到只剩中间 23% 强。
 * ---------------------------------------------------------------------- */
@media (max-width: 767px) {
    .halo-hero-slide.layout-bare.no-mobile-art {
        /* 图缩小后周围露出来的底色。用品牌墨色，别露白 */
        background: #18181c;
    }

    .halo-hero-slide.layout-bare.no-mobile-art .halo-hero-img {
        object-fit: contain;
    }

    /* 框必须和竖图同比例（3:4），否则 cover 一样会裁。
     *
     * 2026-09-13 踩过：把三张带产品图的分类幻灯片下线之后，手机端 banner 上下被裁掉
     * 约 19%（实测框 390×420 = 0.929，竖图 1080×1440 = 0.75，只剩 81% 的高度）。
     * 原因是**框高原来是被别的幻灯片撑出来的** —— `has-shots` 那几张的
     * min-height 是 540px，轮播 track 等高，所以框曾经是 390×540（0.72），
     * 凑巧和竖图的 0.75 接近，看不出问题。它们一下线，只剩下面那条
     * min-height: 420px，比例立刻错到 0.93。
     *
     * 教训：**别让 banner 的显示比例依赖同一轮播里其它幻灯片的高度。**
     * 这里把比例写死成竖图自己的比例，390 宽时框是 520 高，一个像素都不裁。
     * 注意 min-height 要一起解掉，否则 420px 会把 aspect-ratio 顶掉。
     *
     * 如果以后同一轮播里混进 has-shots 那种更高的幻灯片，等高 track 会重新把这些
     * 拉高、裁切也会回来 —— 那时候要么给那类也定比例，要么别混着放。
     */
    .halo-hero-slide.layout-bare.has-mobile-art {
        aspect-ratio: 1080 / 1440;
        min-height: 0;
    }

    /* 竖图铺满，和桌面端一致 */
    .halo-hero-slide.layout-bare.has-mobile-art .halo-hero-img {
        object-fit: cover;
    }

    /* picture 只是包装层，别让它影响图片的定位 */
    .halo-hero-bg picture {
        display: block;
        width: 100%;
        height: 100%;
    }
}
