/**
 * /produkt/{handle}/ — product detail page.
 *
 * The page renders inside the theme's header and footer, so the reset, body font and container
 * padding come from the theme; only genuinely page-specific rules live here.
 */

/*
 * No top padding: the breadcrumb bar is the first thing on the page and it is a band, so it has to
 * start on the header's bottom edge. Any gap here leaves a white sliver between two tinted surfaces.
 */
.fts-product-page {
    padding-bottom: 40px;
}

/*
 * The variant selector toggles the purchase controls and the sold-out notice with the
 * `hidden` attribute. Any element given an explicit `display` below would otherwise
 * outrank the user-agent's `[hidden] { display: none }` and stay visible — which showed
 * "Legg i handlekurv" and "Utsolgt" at the same time.
 *
 * **Scoped to this page, because `!important` here also beat the theme's shared chrome.** The
 * cart badge and the topbar marquee both reveal a `hidden` element with an author `display`
 * rule, which is the documented way to outrank the user-agent sheet — but not an `!important`
 * one. Unscoped, this rule hid the badge on every product page and left the marquee one set
 * short.
 */
.fts-product-page [hidden] {
    display: none !important;
}

/* ── Breadcrumb ── */

/*
 * The tinted strip the crumbs sit in (`html-prototype/produkt.html`, `.breadcrumb-bar`). It runs
 * edge to edge, so it is a sibling of the page column rather than a rule inside it — see the
 * template. The hairline is the theme's own `linje` where the prototype writes 0.06 alpha: a
 * hairline under a tint, and the plugin already reads that token for every other one.
 */
.fts-breadcrumb-bar {
    background: var(--wp--preset--color--lys-blaa, #eaf8fb);
    border-block-end: var(--wp--custom--linje, 1px solid rgba(0, 0, 0, 0.08));
    /* The bar is the container the crumbs inside it ask about — an element cannot query its own. */
    container-name: fts-brodsmuler;
    container-type: inline-size;
}

/*
 * `nowrap` + `overflow-x` is the prototype's own, and it is what a breadcrumb needs rather than a
 * nicety: the last crumb is the product title, and «Runde bivoksark med strikk – 3 stk (25 cm)»
 * beside three ancestors wraps to three lines in a phone's width. A scrolled strip keeps the bar one
 * line high at every width; the scrollbar itself is hidden, since the crumbs are also links and the
 * page has its own way back.
 */
.fts-breadcrumb {
    /* 14px: prototypens egen høyde på stripa, ikke et rytmesteg. */
    padding: 14px 0;
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    color: var(--wp--preset--color--tekst-svak, #5a6660);
    white-space: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}

.fts-breadcrumb::-webkit-scrollbar {
    display: none;
}

.fts-breadcrumb a {
    color: var(--wp--preset--color--tekst-svak, #5a6660);
    text-decoration: none;
    transition: color 0.2s;
}

.fts-breadcrumb a:hover {
    color: var(--wp--preset--color--tekst, #1a1a1a);
}

.fts-breadcrumb__sep {
    margin: 0 8px;
    opacity: 0.6;
}

/*
 * The strip on a phone: the prototype's own 14px stays, but four crumbs at 14px with 8px on either
 * side of three separators is a bar taller and wider than the page under it needs — so the type
 * drops a step, the band gets less height and the separators less air.
 */
@container fts-brodsmuler (max-width: 600px) {
    .fts-breadcrumb {
        padding: 8px 0;
        font-size: var(--wp--preset--font-size--kicker-s, 0.75rem);
    }

    .fts-breadcrumb__sep {
        margin: 0 5px;
    }
}

/* ── Layout ── */

.product-hero {
    display: grid;
    /*
     * Prototypens eget forhold og gap (`.product-hero`, produkt.html). Sto på `3fr 2fr` med 60px:
     * målt i den samme 1280px-kolonnen ga det 732 + 488 der designet har 708 + 524, altså et galleri
     * 24px for bredt og en infospalte 36px for smal — nok til at prisen og kjøpsknappen sto trangere
     * enn tegningen.
     *
     * Bare `fr` og ikke `minmax(0, …)`: et grid-elements automatiske minimum er min-content, og det
     * er allerede løst der problemet faktisk bor — `min-inline-size: 0` på `.product-gallery`, se
     * kommentaren der. En `minmax(0, …)` her ville vært den samme vakten skrevet en gang til, ett
     * nivå unna det som forårsaker den.
     */
    grid-template-columns: 1.15fr 0.85fr;
    gap: 48px;
    align-items: start;
    /*
     * 32px: prototypens egen luft mellom brødsmulestripa og galleriet (`.product-hero`,
     * produkt.html). Sto på 20px, som er den avstanden stripa arvet fra da smulene var løs tekst
     * uten stripe rundt seg — mot en tonet flate trenger innholdet under mer luft, ikke mindre.
     */
    padding: 32px 0 60px;
}

/* ── Gallery ── */

/*
 * `min-inline-size: 0` because this is a grid item, and a grid item's automatic minimum size is its
 * *min-content* size — which, once the thumb strip below turns into a horizontal scroller, is every
 * thumb laid end to end, and the whole document scrolls sideways on a phone. `overflow-x: auto` on
 * `.product-gallery__thumbs` does not prevent it: a scroll container clips its own overflow, but its
 * content width still propagates up through this item into the track's automatic minimum.
 */
.product-gallery {
    min-inline-size: 0;
}

/* position: relative because the sale tag sits on top of the main image. */
.product-gallery__main {
    position: relative;
    width: 100%;
    aspect-ratio: 1;
    border-radius: var(--wp--custom--radius--kant, 3px);
    overflow: hidden;
    background: var(--wp--preset--color--lys-blaa, #eaf8fb);
}

.product-gallery__main img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/*
 * The blur-up layer and the picture over it. The preview is the clicked thumb's own 160px file, so
 * it is in the cache already and the frame is never empty while the main-size one decodes; the
 * photograph fades in over it and is opaque, which is what takes the blur away.
 */
.product-gallery__forhandsvisning,
.fts-lysbord__forhandsvisning {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    /* `cover` in both boxes: each is the picture's own shape, so it leaves no gap to feather into. */
    object-fit: cover;
    filter: blur(14px);
    transform: scale(1.06);
    pointer-events: none;
}

.product-gallery__main > img:not(.product-gallery__forhandsvisning),
.product-gallery__main > video,
.fts-lysbord__ramme > img:not(.fts-lysbord__forhandsvisning),
.fts-lysbord__ramme > video {
    position: relative;
    transition: opacity 0.2s;
}

.product-gallery__main[data-laster="true"] > .product-gallery__forhandsvisning:not([hidden]) ~ img,
.fts-lysbord__ramme[data-laster="true"] > .fts-lysbord__forhandsvisning:not([hidden]) ~ img {
    opacity: 0;
}

/*
 * The ring, and it is late on purpose: a cached photograph swaps inside a frame or two, and a
 * spinner that flashed for it would read as the page stuttering. `fts-vis` holds it at nought until
 * the wait is long enough to be worth admitting to.
 */
.product-gallery__main[data-laster="true"]::after,
.fts-lysbord__ramme[data-laster="true"]::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 16px;
    height: 16px;
    margin: -8px 0 0 -8px;
    border: 2px solid var(--wp--preset--color--tekst, #1a1a1a);
    border-right-color: transparent;
    border-radius: 50%;
    opacity: 0;
    pointer-events: none;
    animation: fts-spinn 0.7s linear infinite, fts-vis 0.01s linear 0.3s forwards;
}

@keyframes fts-vis {
    to {
        opacity: 1;
    }
}

.product-gallery__sale-tag {
    position: absolute;
    top: 16px;
    left: 16px;
    z-index: 2;
    padding: 7px 14px;
    border-radius: var(--wp--custom--radius--kant, 3px);
    background: var(--wp--preset--color--gul, #e09900);
    box-shadow: var(--wp--custom--skygge--kort, 0 4px 4px rgba(0, 0, 0, 0.1));
    color: var(--wp--preset--color--hvit, #fff);
    font-size: 1em;
    font-weight: 700;
    letter-spacing: 1px;
    line-height: 1;
    text-transform: uppercase;
}

/*
 * A grid rather than a wrapping row of fixed squares: `IMAGE_PAGE_SIZE` is 10, and a fixed size wraps
 * eight or nine thumbs into a ragged second row. A grid keeps every row full and lets the squares
 * shrink with the column.
 */
.product-gallery__thumbs {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 8px;
    margin-top: 12px;
}

.product-gallery__thumb {
    width: 100%;
    aspect-ratio: 1;
    border-radius: var(--wp--custom--radius--kant, 3px);
    overflow: hidden;
    border: 2px solid transparent;
    cursor: pointer;
    padding: 0;
    background: var(--wp--preset--color--lys-blaa, #eaf8fb);
    transition: border-color 0.2s;
}

.product-gallery__thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Gul, which is the prototype's own marker for the chosen thumb — a near-black ring around a
   photograph reads as a frame rather than as a selection. */
.product-gallery__thumb[aria-current='true'],
.product-gallery__thumb:hover {
    border-color: var(--wp--preset--color--gul, #e09900);
}

.product-gallery__thumb:focus-visible {
    outline: 2px solid var(--wp--preset--color--tekst, #1a1a1a);
    outline-offset: 2px;
}

/*
 * The play badge over a video thumb's poster — the prototype's `.product-gallery__thumb-play`, which
 * is the only place the design says a gallery may hold video at all.
 *
 * `position: relative` goes on the thumb rather than being assumed: the button is a grid item with
 * `overflow: hidden` and nothing else established a containing block, so an absolutely positioned
 * badge would otherwise centre itself on `.product-gallery` and land on the hero image.
 */
.product-gallery__thumb--video {
    position: relative;
}

.product-gallery__thumb-play {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    /*
     * `none`, because the badge covers the whole thumb: a click landing on it would not reach the
     * button underneath, and the badge is the part of the thumb a shopper actually aims at.
     */
    pointer-events: none;
    color: var(--wp--preset--color--hvit, #fff);
}

.product-gallery__thumb-play svg {
    /*
     * 28px of a ~119px cell, which is the prototype's proportion. A `%` here would grow the glyph with
     * the thumb and the strip's cells are already fluid, so the badge would be a different size on
     * every viewport.
     */
    width: 28px;
    height: 28px;
    /*
     * The posters are photographs, so a white glyph needs its own separation from whatever is behind
     * it — the same job the prototype's 0.85 fill opacity does over its own stills.
     */
    filter: drop-shadow(0 1px 3px rgb(0 0 0 / 45%));
}

/*
 * The video in the main slot.
 *
 * `contain`, where the image beside it is `cover`, and the difference is the whole point: every video
 * on this store is portrait 9:16 and the slot is a square. `cover` would crop about 44% of a video's
 * height — the top and bottom of a vertical clip, which is where a cooking video puts the hands and
 * the pan. Letterboxing against the slot's own `--lys-blaa` shows the whole frame, and keeping the
 * square box means switching between a photo and a video does not reflow the page.
 *
 * The `display: block` below is why this element depends on the `[hidden]` rule at the top of this
 * file: it starts `hidden` and the script reveals it, and an explicit `display` outranks the UA
 * sheet's `[hidden]`. Without that rule the video would sit over the photo on every product that has
 * one. Same for the `<img>` beside it, which the script now hides in turn.
 */
.product-gallery__video {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
    background: var(--wp--preset--color--lys-blaa, #eaf8fb);
}

/* ── The slot's own two overlays ── */

/*
 * The way into the large view. It sits on the slot rather than under it, because the thing it opens
 * is the image itself — and it is `hidden` in markup until `single-product.js` has a `<dialog>` it
 * can really open. It takes the top right corner: the sale tag owns the left one and the counter the
 * bottom left.
 *
 * Same borderless glyph as the card's favourite heart — a white stroke over a drop shadow, because
 * the photograph under it is unknown and a second colour would be wrong on half the catalogue.
 */
.product-gallery__zoom {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--wp--preset--color--hvit, #fff);
    cursor: pointer;
    transition: transform var(--wp--custom--overgang--rask, 0.15s);
}

.product-gallery__zoom:hover {
    transform: scale(1.15);
}

.product-gallery__zoom:focus-visible {
    outline: 2px solid var(--wp--preset--color--hvit, #fff);
    outline-offset: 0;
}

.product-gallery__zoom svg {
    width: 22px;
    height: 22px;
    filter: drop-shadow(0 1px 3px rgb(0 0 0 / 45%));
}

/*
 * «3 / 8» over the image, and only at phone width: above it the thumb strip is a five-column grid
 * that already shows how many images there are, while on a phone the strip is a scroller whose
 * second row of thumbs is off screen — so the count is the only thing saying there is more than one.
 * `display: none` here is what the media query below turns on.
 */
.product-gallery__teller {
    display: none;
    position: absolute;
    left: 12px;
    bottom: 12px;
    z-index: 2;
    margin: 0;
    padding: 4px 12px;
    border-radius: var(--wp--custom--radius--pille, 30px);
    background: rgb(0 0 0 / 55%);
    color: var(--wp--preset--color--hvit, #fff);
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    font-variant-numeric: tabular-nums;
    line-height: 1.4;
}

/* ── The large view ── */

/*
 * A native `<dialog>`, so Escape and the focus trap are the platform's. No `display` on the element
 * itself: the user-agent sheet closes it with one, and an author rule here would leave it on the
 * page — the same trap as `[hidden]` at the top of this file.
 *
 * The photograph and the overlay, and nothing else: no sheet, no border, no lift. The overlay is
 * therefore the only thing between the image and the page, so it is nearly opaque — a translucent
 * one would leave the page legible behind the photograph.
 */
.fts-lysbord {
    position: fixed;
    inset: 0;
    width: 100%;
    max-width: none;
    height: 100%;
    max-height: none;
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    overflow: hidden;
}

.fts-lysbord::backdrop {
    background: rgb(255 255 255 / 96%);
}

.fts-lysbord__flate {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    /* Room for the close button above and the counter below, so neither lies on the photograph. */
    padding: 64px 72px;
}

/*
 * The photograph's own box, given a width and a height in px by `single-product.js` and clipping
 * everything inside it. `max-*: 100%` keeps it within the flate's padding before the script has
 * measured anything.
 */
.fts-lysbord__ramme {
    position: relative;
    overflow: hidden;
    max-width: 100%;
    max-height: 100%;
}

/* The `display` here is why both depend on the `[hidden]` rule at the top of this file. */
.fts-lysbord__bilde,
.fts-lysbord__video {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.fts-lysbord__lukk,
.fts-lysbord__pil {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: none;
    border-radius: var(--wp--custom--radius--rund, 50%);
    background: transparent;
    color: var(--wp--preset--color--tekst, #1a1a1a);
    cursor: pointer;
    transition: background var(--wp--custom--overgang--rask, 0.15s);
}

.fts-lysbord__lukk:hover,
.fts-lysbord__pil:hover {
    background: var(--wp--preset--color--lys-blaa, #eaf8fb);
}

.fts-lysbord__lukk svg,
.fts-lysbord__pil svg {
    width: 16px;
    height: 16px;
}

.fts-lysbord__lukk {
    top: 12px;
    right: 12px;
}

.fts-lysbord__nav {
    display: contents;
}

.fts-lysbord__pil--forrige {
    top: 50%;
    left: 12px;
    transform: translateY(-50%);
}

.fts-lysbord__pil--neste {
    top: 50%;
    right: 12px;
    transform: translateY(-50%);
}

.fts-lysbord__teller {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    margin: 0;
    color: var(--wp--preset--color--tekst-svak, #5a6660);
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    font-variant-numeric: tabular-nums;
}

/* ── Info column ── */

.product-info__tag {
    display: inline-flex;
    align-items: center;
    height: 24px;
    padding: 4px 15px;
    border-radius: 16px;
    background: var(--wp--preset--color--lys-blaa, #eaf8fb);
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    color: var(--wp--preset--color--tekst, #1a1a1a);
    margin-bottom: 12px;
}

.product-info__title {
    font-family: var(--wp--preset--font-family--display, "Amatic SC", cursive);
    font-weight: 700;
    font-size: 48px;
    line-height: 1.2;
    color: var(--wp--preset--color--tekst, #1a1a1a);
    margin: 0 0 16px;
}

/*
 * The rating by the title (`.product-info__rating`, produkt.html): its own line above the price,
 * at the prototype's 14px rather than the star row's shared default, which is a card's size.
 */
.product-info__reviews {
    display: flex;
    align-items: center;
    margin-bottom: 16px;
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
}

/* 4px, because the shipping line sits directly under the price and belongs to it. */
.product-info__price-row {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 4px;
}

.product-info__price {
    font-weight: 500;
    font-size: 28px;
    color: var(--wp--preset--color--tekst, #1a1a1a);
}

/*
 * A discounted product states the saving with the pair it already prints: the price it asks for in
 * the palette's orange, the price it does not struck through beside it. The `:not([hidden])` is what
 * makes it follow the variant, since `single-product.js` toggles the attribute and nothing else.
 */
.product-info__price-row:has(.product-info__price-old:not([hidden])) .product-info__price {
    font-weight: 700;
    color: var(--wp--custom--gul-tekst, #c78800);
}

.product-info__price-old {
    font-size: var(--wp--preset--font-size--tittel-m, 1.25rem);
    color: var(--wp--preset--color--tekst-svak, #5a6660);
    text-decoration: line-through;
    text-decoration-thickness: 2px;
}

.product-info__shipping {
    margin: 2px 0 20px;
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    color: var(--wp--preset--color--tekst-svak, #5a6660);
}

.product-info__shipping a {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.product-info__shipping a:hover {
    color: var(--wp--preset--color--tekst, #1a1a1a);
}

/* ── Variant selector ── */

.product-options {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin-bottom: 24px;
}

.product-option__label {
    display: block;
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    font-weight: 600;
    color: var(--wp--preset--color--tekst, #1a1a1a);
    margin-bottom: 8px;
}

.product-option__values {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.product-option__value {
    display: inline-flex;
    align-items: center;
    height: 38px;
    padding: 0 18px;
    border-radius: 30px;
    border: 1px solid rgba(0, 0, 0, 0.12);
    background: var(--wp--preset--color--hvit, #fff);
    font-family: inherit;
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    color: var(--wp--preset--color--tekst, #1a1a1a);
    cursor: pointer;
    transition: border-color 0.2s, background 0.2s;
}

.product-option__value:hover {
    border-color: var(--wp--preset--color--tekst, #1a1a1a);
}

.product-option__value[aria-pressed='true'] {
    background: var(--wp--preset--color--tekst, #1a1a1a);
    border-color: var(--wp--preset--color--tekst, #1a1a1a);
    color: var(--wp--preset--color--hvit, #fff);
}

/* A combination Shopify does not sell — still reachable, but visibly unavailable. */
.product-option__value[data-unavailable='true'] {
    opacity: 0.45;
    text-decoration: line-through;
}

.product-option__value:focus-visible {
    outline: 2px solid var(--wp--preset--color--tekst, #1a1a1a);
    outline-offset: 2px;
}

/* ── Quantity ── */

.qty-row {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 16px;
}

/* A real <label for> rather than an aria-label on the input: the word is on screen in the
   prototype, and a visible label is also what a pointer can click to reach the field. */
.qty-label {
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    font-weight: 500;
    color: var(--wp--preset--color--tekst, #1a1a1a);
}

.qty-selector {
    display: flex;
    align-items: center;
    height: 42px;
    border-radius: 30px;
    background: var(--wp--preset--color--lys-blaa, #eaf8fb);
    overflow: hidden;
}

/*
 * A literal, and it stays one: this `font-size` sizes a **glyph**, not type. The button's whole
 * content is one character, so the value is an icon dimension that happens to be expressed as a
 * font size, and putting it on a text rung would make a future type change resize a control. Same
 * category error as reaching for `kicker` because 14px was close to the design's 12px.
 */
.qty-selector__btn {
    width: 42px;
    height: 42px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: none;
    font-size: 20px;
    cursor: pointer;
    color: var(--wp--preset--color--tekst, #1a1a1a);
    transition: background 0.2s;
}

.qty-selector__btn:hover {
    background: rgba(0, 0, 0, 0.05);
}

/*
 * 16px flat, and never a fluid token: mobile Safari zooms the viewport on focus for any form control
 * under 16px, and every token at this step (`brodtekst`, `tittel-s`) is fluid down to 15px. So the
 * one rung that would read correctly here is the one that breaks it. The prototype makes the same
 * call explicitly — produkter.html bumps `.filter-dropdown` to 16px inside its own 768px query.
 */
.qty-selector__input {
    width: 44px;
    text-align: center;
    border: none;
    background: none;
    font-family: inherit;
    font-size: 16px;
    font-weight: 600;
    color: var(--wp--preset--color--tekst, #1a1a1a);
    font-variant-numeric: tabular-nums;
    -moz-appearance: textfield;
    appearance: textfield;
}

.qty-selector__input::-webkit-outer-spin-button,
.qty-selector__input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* ── CTA ── */

.product-info__cta {
    width: 100%;
    height: 52px;
    border: none;
    border-radius: var(--wp--custom--radius--kant, 3px);
    background: var(--wp--preset--color--gul, #e09900);
    color: var(--wp--preset--color--hvit, #fff);
    font-family: inherit;
    font-size: var(--wp--preset--font-size--brodtekst-l, 1.1rem);
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    cursor: pointer;
    margin-bottom: 12px;
    transition: transform 0.2s, filter 0.2s, background 0.2s;
}

.product-info__cta:hover {
    transform: translateY(-1px);
    filter: brightness(1.08);
}

.product-info__cta:focus-visible {
    outline: 2px solid var(--wp--preset--color--tekst, #1a1a1a);
    outline-offset: 2px;
}

.product-info__cta--added {
    background: var(--wp--preset--color--tekst, #1a1a1a);
}

.product-info__cta[disabled] {
    cursor: default;
    transform: none;
    filter: none;
}

/* Waiting for Shopify. The cart icon steps aside for the ring so the label does not shift. */
.product-info__cta[aria-busy="true"] {
    cursor: progress;
}

.product-info__cta[aria-busy="true"]:hover {
    transform: none;
    filter: none;
}

.product-info__cta[aria-busy="true"] svg {
    display: none;
}

.product-info__cta[aria-busy="true"]::before {
    content: "";
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: fts-spinn 0.7s linear infinite;
}

@keyframes fts-spinn {
    to {
        transform: rotate(360deg);
    }
}

/*
 * Under the CTA, not over it: the shopper has already decided to look: this only tells them not to
 * dawdle. Deliberately unlike .product-info__sold-out, which replaces the CTA — these two are never
 * shown together, and looking alike would suggest they might be.
 */
.product-info__low-stock {
    margin: 12px 0 0;
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    font-weight: 600;
    /* See .prod-card__low-stock: gul fails contrast on white at this size. */
    color: var(--wp--preset--color--tekst, #1a1a1a);
}

.product-info__low-stock[hidden] {
    /* Anything with an explicit display outranks the UA rule for [hidden] — the same trap the
       settings page's [hidden] rows hit. */
    display: none !important;
}

.product-info__sold-out {
    width: 100%;
    height: 52px;
    border-radius: var(--wp--custom--radius--kant, 3px);
    background: var(--wp--preset--color--lys-blaa, #eaf8fb);
    color: var(--wp--preset--color--tekst-svak, #5a6660);
    font-size: var(--wp--preset--font-size--brodtekst-l, 1.1rem);
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* ── Varsle meg ── */

/* Directly under «Utsolgt», which it only ever stands beside. */
.fts-varsle {
    margin: 12px 0 0;
}

.fts-varsle__tekst {
    margin: 0 0 8px;
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    color: var(--wp--preset--color--tekst-svak, #5a6660);
}

.fts-varsle__rad {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* 16px flat for the reason `.qty-selector__input` gives: mobile Safari zooms on focus below it. */
.fts-varsle__felt {
    flex: 1 1 12rem;
    min-width: 0;
    box-sizing: border-box;
    height: 48px;
    padding: 0 14px;
    border: 1px solid rgba(0, 0, 0, 0.18);
    border-radius: var(--wp--custom--radius--kant, 3px);
    background: var(--wp--preset--color--hvit, #fff);
    font-family: inherit;
    font-size: 16px;
    color: var(--wp--preset--color--tekst, #1a1a1a);
}

.fts-varsle__knapp {
    flex: 0 0 auto;
    height: 48px;
    padding: 0 20px;
    border: none;
    border-radius: var(--wp--custom--radius--kant, 3px);
    background: var(--wp--preset--color--tekst, #1a1a1a);
    color: var(--wp--preset--color--hvit, #fff);
    font-family: inherit;
    font-size: var(--wp--preset--font-size--brodtekst, 1rem);
    font-weight: 600;
    cursor: pointer;
}

.fts-varsle__felt:focus-visible,
.fts-varsle__knapp:focus-visible {
    outline: 2px solid var(--wp--preset--color--tekst, #1a1a1a);
    outline-offset: 2px;
}

.fts-varsle__knapp[aria-busy="true"] {
    cursor: progress;
    opacity: 0.7;
}

.fts-varsle__svar {
    margin: 8px 0 0;
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    font-weight: 600;
    color: var(--wp--preset--color--tekst, #1a1a1a);
}

/* Focused by the script in place of the button it replaces; it is not a control. */
.fts-varsle__svar:focus {
    outline: none;
}

.fts-varsle__svar--feil {
    color: var(--wp--preset--color--roed, #c42821);
}

/* ── Betaling ── */

.product-info__more-pay {
    margin: 16px 0 8px;
    text-align: center;
    font-size: var(--wp--preset--font-size--brodtekst-s, 0.875rem);
    color: var(--wp--preset--color--tekst-svak, #5a6660);
}

.payment-logos {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-bottom: 24px;
}

.payment-logo {
    display: flex;
    align-items: center;
    justify-content: center;
}

/*
 * The box, not the file: every mark is drawn at 38×24 whatever its own viewBox says, which is why
 * Apple Pay's 165×106 artwork sits beside five 38×24 ones without a row of mismatched heights.
 */
.payment-logo svg {
    display: block;
    width: 38px;
    height: 24px;
}

/* ── Accordion ── */

.product-accordion {
    margin-top: 24px;
    border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.product-accordion__item {
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

/*
 * The heading exists for document outline only — the button inside it is the whole control — so it
 * carries none of an <h2>'s own typography.
 */
.product-accordion__heading {
    margin: 0;
    font: inherit;
}

.product-accordion__btn {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 0;
    border: none;
    background: none;
    font-family: inherit;
    font-size: var(--wp--preset--font-size--brodtekst, 1rem);
    font-weight: 500;
    color: var(--wp--preset--color--tekst, #1a1a1a);
    text-align: left;
    cursor: pointer;
    transition: color 0.2s;
}

.product-accordion__btn:hover {
    color: var(--wp--preset--color--gul, #e09900);
}

.product-accordion__btn:focus-visible {
    outline: 2px solid var(--wp--preset--color--tekst, #1a1a1a);
    outline-offset: 2px;
}

.product-accordion__chevron {
    width: 12px;
    height: 12px;
    flex-shrink: 0;
    transition: transform 0.25s;
}

.product-accordion__btn[aria-expanded='true'] .product-accordion__chevron {
    transform: rotate(180deg);
}

/*
 * Panels are opened and closed with the `hidden` attribute, not a class — see the [hidden] rule at
 * the top of this file, which is what keeps the attribute winning against the explicit `display`
 * below.
 */
.product-accordion__panel {
    display: block;
    padding: 0 0 20px;
    max-width: 100%;
}

.product-accordion__panel p,
.product-accordion__panel li {
    font-size: 16px;
    line-height: 1.6;
    color: var(--wp--preset--color--tekst, #1a1a1a);
}

.product-accordion__panel p {
    margin: 0 0 12px;
}

/*
 * **A panel's list markers are declared here or they do not exist.** The theme resets
 * `ul, ol { list-style: none }` on the element itself, and one class in the selector is what
 * outweighs a bare type selector whatever the load order. Both types are declared, because the
 * panels are the merchant's own HTML and Shopify's rich text emits numbered lists as readily as
 * bulleted ones. `padding-left` keeps the markers inside the indent, nested lists included.
 */
.product-accordion__panel ul,
.product-accordion__panel ol {
    margin: 4px 0 12px;
    padding-left: 20px;
}

.product-accordion__panel ul {
    list-style: disc;
}

.product-accordion__panel ol {
    list-style: decimal;
}

.product-accordion__panel li {
    line-height: 1.8;
}

.product-accordion__panel li::marker {
    color: var(--wp--preset--color--tekst-svak, #5a6660);
}

/*
 * Nutrition and content tables. The merchant authors these, so the widths are unknown — hence the
 * scroll box: a wide table must take its own overflow rather than push the info column sideways.
 */
.product-accordion__panel table {
    display: block;
    width: 100%;
    max-width: 100%;
    margin: 4px 0 16px;
    overflow-x: auto;
    border-collapse: collapse;
    font-size: 16px;
}

.product-accordion__panel th,
.product-accordion__panel td {
    padding: 8px 12px 8px 0;
    border-bottom: var(--wp--custom--linje, 1px solid rgba(0, 0, 0, 0.08));
    text-align: left;
    vertical-align: top;
    line-height: 1.5;
    color: var(--wp--preset--color--tekst, #1a1a1a);
}

.product-accordion__panel th {
    font-weight: 600;
}

.product-accordion__panel tr:last-child th,
.product-accordion__panel tr:last-child td {
    border-bottom: 0;
}

.product-accordion__panel a {
    color: var(--wp--preset--color--tekst, #1a1a1a);
    text-decoration: underline;
}

.product-accordion__panel a:hover {
    color: var(--wp--preset--color--gul, #e09900);
}

/*
 * h3 is where `fts_rich_text_to_html()` clamps the merchant's headings, and h4–h6 are what a deeper
 * one becomes — so all four get the panel's own heading size rather than the browser's ladder.
 */
.product-accordion__panel h3,
.product-accordion__panel h4,
.product-accordion__panel h5,
.product-accordion__panel h6 {
    margin: 16px 0 8px;
    font-size: var(--wp--preset--font-size--brodtekst, 1rem);
    font-weight: 600;
    color: var(--wp--preset--color--tekst, #1a1a1a);
}

.product-accordion__panel > :first-child {
    margin-top: 0;
}

/* ── Andre kjøpte også ── */

/*
 * The recommendation band. It takes the page's own ground rather than a tint of its own — the
 * prototype separates it from the carousel above by *stopping* that band's tint, which is the same
 * "hvit reviews → lys blå nyhetsbrev → footer" progression its own comment describes further down
 * the page.
 */
.related {
    padding: 48px 0 64px;
    background: var(--wp--preset--color--hvit, #fff);
}

/*
 * The body font, not the theme's `h2`: an unstyled `<h2>` in this theme is Amatic SC, which is right
 * for `.products__title` and wrong here. The family is named because this rule also sets a size and a
 * weight; `.product-accordion__heading` makes the same correction with `font: inherit`.
 *
 * 24px has no rung on the theme's ladder — see docs/tokens.md.
 */
/*
 * The heading and the arrows share a row, so the band reads like the review carousel above it. The
 * auto margin rather than `space-between`: the nav is `hidden` until the track really overflows, and
 * pushed from its own side it sits at the right end either way.
 */
.related__header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 16px;
    margin-bottom: 32px;
}

.related__header .related__title {
    margin-bottom: 0;
}

.related__nav {
    display: flex;
    gap: 8px;
    margin-inline-start: auto;
}

/*
 * The same arrow as `.reviews-mini__arrow` in `product-reviews.css`, private copy rather than a
 * shared class — the two bands are on one page, so this is the one place the duplication shows and
 * the one place to collapse it if a third track appears.
 */
.related__arrow {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1.5px solid var(--wp--preset--color--tekst, #1a1a1a);
    border-radius: 50%;
    background: var(--wp--preset--color--hvit, #fff);
    color: var(--wp--preset--color--tekst, #1a1a1a);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.related__arrow:hover {
    background: var(--wp--preset--color--tekst, #1a1a1a);
    color: var(--wp--preset--color--hvit, #fff);
}

.related__arrow svg {
    width: 12px;
    height: 12px;
}

.related__title {
    margin: 0 0 32px;
    font-family: var(--wp--preset--font-family--brod, Figtree, system-ui, -apple-system, sans-serif);
    font-size: 24px;
    font-weight: 600;
    color: var(--wp--preset--color--tekst, #1a1a1a);
}

/*
 * A track at every width, not the archive's wrapped grid — and **the doubled class is what makes
 * that survive the cascade**. `.products__grid` is `display: grid` in `product-card.css`, so a
 * single `--relatert` class would tie with it at (0,1,0) and be settled by which of the two
 * stylesheets the page happens to print first. That is an enqueue dependency graph deciding a
 * layout, which is the kind of thing that works until somebody adds a `wp_enqueue_style` dependency.
 *
 * `3.5` is the prototype's June 2026 revision stated as a divisor: the fourth card is cut cleanly
 * with at least a quarter of its own width showing and no fade over it, so the row visibly
 * continues. `72px` is the three gutters that sit inside those three and a half cards.
 */
.products__grid.products__grid--relatert {
    display: flex;
    column-gap: 24px;
    /* The card lifts 3px on hover; without the padding the lift is clipped by the scroll box. */
    padding: 4px 0 12px;
    overflow-x: auto;
    /*
     * A swipe past the end must not become a browser back-navigation, which is what it is on iOS and
     * on a trackpad without this — the same rule the product grid's phone track carries.
     */
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    /*
     * Here and not in the arrows' JavaScript: `Element.scrollBy()` honours the computed value, so
     * one declaration covers the buttons and the reduced-motion block below is the only place the
     * preference is read.
     */
    scroll-behavior: smooth;
    scrollbar-width: none;
}

.products__grid--relatert::-webkit-scrollbar {
    display: none;
}

/*
 * No `max-width` on the card: a cap clamps the track's scrollable overflow away, and the cut fourth
 * card is the design statement here. `min-width` is the guard instead — it binds just above the
 * 768px breakpoint, where the ratio would otherwise draw a 191px card.
 */
.products__grid--relatert > .prod-card {
    flex: 0 0 calc((100% - 72px) / 3.5);
    min-width: 220px;
    scroll-snap-align: start;
}

/* ── Responsive ── */

@media (max-width: 1200px) {
    .product-hero {
        grid-template-columns: 1fr;
        gap: 40px;
    }

    .product-info__title {
        font-size: 36px;
    }

    .product-gallery__thumbs {
        grid-template-columns: repeat(4, 1fr);
    }
}

@media (max-width: 768px) {
    .product-info__title {
        font-size: 28px;
    }

    .product-info__price {
        font-size: 24px;
    }

    /*
     * A scroll-snapping row of fixed 64px squares rather than a four-column grid: at phone width a
     * grid cell is about 70px, so ten thumbs become three cramped rows above the fold and push the
     * price and CTA off it. Scrolling keeps the gallery one row tall whatever the image count.
     */
    .product-gallery__thumbs {
        display: flex;
        gap: 8px;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        scrollbar-width: none;
    }

    .product-gallery__thumbs::-webkit-scrollbar {
        display: none;
    }

    .product-gallery__thumb {
        width: 64px;
        flex-shrink: 0;
        scroll-snap-align: start;
    }

    /* The count is the phone's only sign that the gallery holds more than one image. */
    .product-gallery__teller {
        display: block;
    }

    .product-accordion__btn,
    .product-accordion__panel p,
    .product-accordion__panel li {
        font-size: 16px;
    }

    /* Centered, wrapping rather than shrinking the marks. */
    .payment-logos {
        justify-content: center;
        flex-wrap: wrap;
    }

    /*
     * Seventy per cent of the track, so one whole card and a clearly cut second are on screen — the
     * prototype's own number for this width, where a 3.5-card ratio would draw four cards nobody
     * could read.
     */
    .products__grid--relatert > .prod-card {
        flex-basis: 70%;
        min-width: 200px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .product-info__cta,
    .product-option__value,
    .product-gallery__thumb,
    .product-gallery__zoom,
    .product-accordion__btn,
    .product-accordion__chevron,
    .related__arrow,
    .fts-lysbord__lukk,
    .fts-lysbord__pil {
        transition: none;
    }

    .products__grid--relatert {
        scroll-behavior: auto;
    }

    .product-info__cta:hover,
    .product-gallery__zoom:hover {
        transform: none;
    }

    .product-info__cta[aria-busy="true"]::before {
        animation: none;
    }

    /* The ring still appears late, it just does not turn; the photograph arrives without a fade. */
    .product-gallery__main[data-laster="true"]::after,
    .fts-lysbord__ramme[data-laster="true"]::after {
        animation: fts-vis 0.01s linear 0.3s forwards;
    }

    .product-gallery__main > img:not(.product-gallery__forhandsvisning),
    .product-gallery__main > video,
    .fts-lysbord__ramme > img:not(.fts-lysbord__forhandsvisning),
    .fts-lysbord__ramme > video {
        transition: none;
    }
}
