/*
 * Product card.
 *
 * One component for every place a product appears: the homepage's promotions
 * row and results grid, the product groups on the editable pages, and the
 * accessories page. What makes it one component is the hierarchy - photo,
 * name, what it is, price - not one domain object: callers map theirs onto it
 * (Tts\Ui\ProductCard). Placement is not a variant: the card has no width of
 * its own, and whoever places it decides that, through the grid track or the
 * scroller's flex basis.
 *
 * A listed card is one <a> to the product. Everything inside is therefore part
 * of the link's accessible name, which is why the photo is alt="".
 *
 * The one variant, --detailed, is the product where it is sold rather than a
 * link to it: an <article> with the description in full and a direct order
 * action - the same hierarchy with two parts more. See the end of this file.
 */

.product-card {
    display: flex;
    flex-direction: column;
    /* min-width:0 lets the card shrink inside a grid track; without it a long
       unbroken colour name would push the track wider than its share. */
    min-width: 0;
    overflow: hidden;
    border: 1px solid var(--shop-border);
    border-radius: var(--shop-radius-sm);
    background: var(--shop-surface);
    box-shadow: var(--shop-shadow-card);
    color: inherit;
    text-decoration: none;
    transition:
        transform var(--shop-duration) var(--shop-ease),
        box-shadow var(--shop-duration) var(--shop-ease);
}

.product-card:hover,
.product-card:focus {
    color: inherit;
    text-decoration: none;
}

/* The lift says "this goes somewhere": a linked card, or a detailed card's photo
   or title (its order button is a sibling, so the card itself is not a link). */
.product-card:any-link:hover,
.product-card:has(:is(a.product-card__media, .product-card__title-link):hover) {
    transform: translateY(-2px);
    box-shadow: var(--shop-shadow-card-hover);
}

/* Image ---------------------------------------------------------------------- */

.product-card__media {
    position: relative;
    flex: none;
    aspect-ratio: 1;
    overflow: hidden;
    background: #f5f5f5;
}

.product-card__image {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* A product shot (an accessory) rather than a tile swatch: shown whole on
   white, not cropped to fill the square. */
.product-card--contain .product-card__media {
    background: var(--shop-surface);
}

.product-card--contain .product-card__image {
    padding: var(--shop-space-3);
    object-fit: contain;
}

/* The OP=OP / SALE ribbon, in the accent purple (white on it is 8.98:1). 5px
   and 9px are this ribbon's own proportions, not steps on any scale. A product
   label (below) is the same pill, a size larger. */
.product-card__badge,
.product-card__label {
    padding: 3px var(--shop-space-2);
    border-radius: 5px;
    background: var(--shop-accent);
    color: #fff;
    font-size: 9px;
    font-weight: var(--shop-weight-bold);
    letter-spacing: 1px;
    text-transform: uppercase;
}

.product-card__badge {
    position: absolute;
    inset-block-start: var(--shop-space-2);
    inset-inline-start: var(--shop-space-2);
    z-index: 1;
}

/* Product labels (Tts\Ui\ProductLabel\ProductLabelOverlay) ---------------------
   Set by hand per series or colour. SALE and 100 × 100 cm stack at the top left, SALE
   first; the Blue Angel is its official logo, a stamp at the top right. Both are out
   of the flow, so a card with labels is the same size as one without. The stack stops
   short of the stamp's corner (8px inset, 35/44px stamp, 4px air), so the two never
   meet, however narrow the card. Compact on purpose: the photo is the product. */
.product-card__labels {
    position: absolute;
    inset-block-start: var(--shop-space-2);
    inset-inline-start: var(--shop-space-2);
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--shop-space-1);
    max-inline-size: calc(100% - var(--shop-space-2) * 2 - 35px - var(--shop-space-1));
}

/* A label is the badge (above) about 12% larger, text and padding together, so it
   is noticed first without crowding the photo; SALE swaps only the fill. */
.product-card__label {
    max-inline-size: 100%;
    padding: 3.5px 9px;
    font-size: 10px;
    white-space: nowrap;
}

/* Its own deep red (the token carries the why), and the unit is a unit: "cm",
   not the pill's uppercase "CM". */
.product-card__label--size {
    background: var(--shop-label-size);
    text-transform: none;
}

/* The vivid orange with the badge's white text: the product owner's choice
   (2026-10-06) for a SALE that pops, at 3.56:1 (see the token and "Known
   technical debt"). */
.product-card__label--sale {
    background: var(--shop-brand-orange-vivid);
}

/* The official artwork as supplied: its own white ring and transparent corners, no
   recolouring, shadow or frame added. */
.product-card__stamp {
    position: absolute;
    inset-block-start: var(--shop-space-2);
    inset-inline-end: var(--shop-space-2);
    z-index: 1;
    width: 35px;
    height: 35px;
}

/* Body -----------------------------------------------------------------------
   A column that fills whatever the image leaves. The availability badge takes
   the slack above it, which lands availability and price on the same line
   across a row however many lines the name and colour take. That is why there
   are no fixed heights anywhere in this component. */

.product-card__body {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
    padding: 10px 10px 12px;
}

/* combined.css carries `h3 { margin-bottom: 0.125rem !important }` for the whole
   site, and the product page depends on it, so it cannot be relaxed. An
   !important declaration can only be answered by another one, and among those
   specificity decides - so a single class here outranks the bare `h3`. This is
   the documented legacy boundary; it is needed on h3 alone. */
.product-card__title {
    margin-block-end: 2px !important;
    color: var(--shop-ink);
    font-size: var(--shop-text-md);
    font-weight: var(--shop-weight-bold);
    line-height: var(--shop-leading-tight);
    /* Supplier-supplied names include long unbroken tokens; inside a grid track
       that cannot widen, they would otherwise spill out of the card. */
    overflow-wrap: anywhere;
}

.product-card__meta {
    color: var(--shop-text);
    font-size: var(--shop-text-sm);
    line-height: var(--shop-leading-snug);
    overflow-wrap: anywhere;
}

.product-card__meta + .product-card__meta {
    margin-block-start: 2px;
}

/* Availability ---------------------------------------------------------------
   `auto` on the top margin collects the card's spare height here. align-self
   keeps the pill the width of its content: as a flex item in a column it would
   otherwise stretch and read as a bar. */
/* The padding and gap are a notch tighter on a phone, which is what lets the
   longest real value - "1.193 m² beschikbaar" - fit one line from 360px. At
   320px it has to wrap, so the dot sits against the first line rather than
   floating between two, and the pill still hugs its text. */
.product-card__stock {
    display: inline-flex;
    align-self: flex-start;
    align-items: flex-start;
    gap: 5px;
    margin-block: auto var(--shop-space-2);
    padding: 3px 6px;
    border-radius: 5px;
    background: var(--shop-stock-surface);
    color: var(--shop-stock-text);
    font-size: var(--shop-text-xs);
    line-height: var(--shop-leading-tight);
}

.product-card__stock::before {
    content: "";
    flex: none;
    width: 6px;
    height: 6px;
    /* Centred on the first line of text rather than on the whole pill. */
    margin-block-start: calc((1lh - 6px) / 2);
    border-radius: 50%;
    background: var(--shop-stock-dot);
}

/* A card with no availability line still has to push its price down. :where()
   keeps this at one class, so a variant can set the margin without outranking
   it. */
:where(.product-card__body:not(:has(.product-card__stock))) .product-card__pricing {
    margin-block-start: auto;
}

/* Price ----------------------------------------------------------------------- */

.product-card__pricing {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--shop-space-1);
}

.product-card__price {
    color: var(--shop-price);
    font-size: var(--shop-text-lg);
    font-weight: var(--shop-weight-bold);
}

.product-card__unit {
    color: var(--shop-text-soft);
    font-size: var(--shop-text-xs);
}

@media (min-width: 640px) {
    .product-card__body {
        padding: 12px 14px 14px;
    }

    .product-card__badge {
        font-size: 10px;
    }

    .product-card__label {
        font-size: 11px;
    }

    .product-card__labels {
        max-inline-size: calc(100% - var(--shop-space-2) * 2 - 44px - var(--shop-space-1));
    }

    .product-card__stamp {
        width: 44px;
        height: 44px;
    }

    /* Centred again, as before this pass: it looks the same on one line, but a
       five-digit value still wraps in a desktop card, and there the dot has
       always sat between the two lines. */
    .product-card__stock {
        align-items: center;
        gap: 6px;
        padding: 3px 8px;
    }

    .product-card__stock::before {
        margin-block-start: 0;
    }
}

@media (min-width: 1024px) {
    .product-card__title {
        font-size: var(--shop-text-base);
    }
}

/* Detailed card ----------------------------------------------------------------
   A card with an order button (ProductCard without an href). The image is 4:3:
   product shots are mostly wider than tall. Decision 29 replaced Decision 20's
   side-on cards with descriptions. */

.product-card--detailed {
    /* Contains its visually hidden text, which otherwise widens the page from a rail. */
    position: relative;
}

.product-card--detailed .product-card__media {
    aspect-ratio: 4 / 3;
}

.product-card--detailed .product-card__title {
    font-size: var(--shop-text-base);
}

/* The title link looks like a linked card's title: no underline (the card
   lifts on hover). It meets WCAG 2.5.8 only because the photo is the same link:
   keep it linked. combined.css colours a:hover and a:focus (0,1,1). */
.product-card__title-link,
.product-card__title-link:is(:hover, :focus) {
    color: inherit;
    text-decoration: none;
}

.product-card__title-link:focus-visible {
    outline-offset: 2px;
}

.product-card--detailed .product-card__pricing {
    padding-block-start: var(--shop-space-3);
}

.product-card__order {
    margin-block-start: var(--shop-space-3);
}

/* formHeader() cannot take a class, and combined.css gives every form
   `margin: 0 0 1rem` - the legacy boundary, answered here. */
.product-card__order > form {
    margin: 0;
}

.shop-btn.product-card__order-button {
    width: 100%;
}

/* The in-cart counter: beside the label below 768px ("Hinzufügen 99+" must fit),
   pinned to the button's end from there. */
.product-card__order-count {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    min-width: 1.375rem;
    height: 1.375rem;
    padding-inline: 6px;
    border-radius: 999px;
    background: var(--shop-accent);
    color: #fff;
    font-size: var(--shop-text-xs);
    font-weight: var(--shop-weight-bold);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

@media (min-width: 768px) {
    .shop-btn.product-card__order-button:has(.product-card__order-count) {
        position: relative;
    }

    .product-card__order-count {
        position: absolute;
        inset-block-start: 50%;
        inset-inline-end: var(--shop-space-3);
        transform: translateY(-50%);
    }
}

/* While its add is pending (accessoires.js). */
.shop-btn.product-card__order-button[aria-busy="true"] {
    cursor: progress;
    opacity: 0.7;
}

/* About 115px of button at 320px: less padding, so longer labels fit. */
@media (max-width: 639.98px) {
    .shop-btn.product-card__order-button {
        padding-inline: var(--shop-space-2);
    }

    .shop-btn.product-card__order-button:has(.product-card__order-count) {
        gap: var(--shop-space-1);
    }
}
