/*
  Product card.

  Borderless by design. The card has no box, no stroke and no drop shadow: the photograph sits on a
  tinted plate and the facts sit under it in plain type. That is how serious product brands present a
  catalog — the piece is the card. A bordered white rectangle is what makes a storefront read as a
  template, and it is exactly what this rebuild removes.

  Availability is a small uppercase corner label on the photograph, not a coloured pill. It has to be
  legible at a glance across a grid without competing with the piece.
*/

/* ---- The shelf -------------------------------------------------------------
   A FIXED column count per breakpoint, not `auto-fill`.

   `auto-fill` with a min track is what made the store read as a mockup: with two products the tracks
   stretched to half the container each, so two pieces floated in an enormous field with a 4rem gutter
   between them. A shelf is a row of equals — the column count is a decision about the page, not a
   consequence of how many products happen to be published this week.

   Measured against real storefronts (see Documents/UxResearchLog.md): premium commerce converges on
   four columns at 1440 and holds four at 1024 by narrowing the card rather than dropping a column.
   Gutters there are small — 4px at Everlane, 10px at Allbirds — because a tight row reads as one
   continuous band of imagery. Ceramigeek takes the same shape with slightly more air, because the
   photographs are dark and need separation that a white flat-lay does not. */
.product-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-lg) var(--space-md);
    align-items: start;
}

@media (min-width: 48rem) {
    .product-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 75rem) {
    .product-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: var(--space-xl) var(--space-md);
    }
}

/* A shelf holding one or two pieces must not stretch them across the whole page. The row stays
   left-aligned at the width a full row would have given each card, so a sparse category looks like a
   shelf with room on it rather than like a layout mistake. */
.product-grid:has(> :last-child:nth-child(-n + 2)) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-inline-size: 50%;
}

@media (max-width: 47.99rem) {
    .product-grid:has(> :last-child:nth-child(-n + 2)) { max-inline-size: none; }
}

.product-card {
    position: relative;
    display: flex;
    flex-direction: column;
    background: none;
    border: 0;
    border-radius: 0;
}

.product-card__link {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    color: inherit;
}

/* ---- Media plate ---------------------------------------------------------- */
.product-card__media {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-lg);
    /* A studio plate: the neutral field the marketplaces ask for, lit from above like a piece on a
       turntable, so a dark mug does not sit on a flat grey rectangle. */
    background:
        radial-gradient(120% 80% at 50% 8%, var(--cg-white) 0%, transparent 62%),
        var(--color-surface-muted);
    aspect-ratio: var(--ratio-piece);
    transition: box-shadow var(--transition-slow), transform var(--transition-slow);
}

.on-ink .product-card__media {
    background:
        radial-gradient(120% 80% at 50% 8%, rgb(255 255 255 / 0.09) 0%, transparent 62%),
        #1c1918;
}

/* Light running over a glaze: a single highlight crosses the plate when the pointer arrives. */
.product-card__media::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(115deg, transparent 35%, rgb(255 255 255 / 0.22) 50%, transparent 65%);
    transform: translateX(-110%);
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
    .product-card__link:hover .product-card__media::after {
        transform: translateX(110%);
        transition: transform 1100ms var(--ease-out);
    }

    .product-card__link:hover .product-card__media {
        transform: translateY(-4px);
        box-shadow: 0 26px 40px -30px rgb(20 14 12 / 0.55);
    }
}

/* Cards on a shelf arrive one after another rather than as one block: the shelf itself stays put
   (it is only the trigger), its cards rise in sequence. */
[data-reveal-armed] .product-grid.reveal { opacity: 1; transform: none; }
[data-reveal-armed] .product-grid.reveal > * {
    opacity: 0;
    transform: translateY(26px);
    transition: opacity 800ms var(--ease-entrance), transform 800ms var(--ease-entrance);
}

[data-reveal-armed] .product-grid.reveal.is-revealed > * { opacity: 1; transform: none; }
[data-reveal-armed] .product-grid.reveal > :nth-child(2) { transition-delay: 90ms; }
[data-reveal-armed] .product-grid.reveal > :nth-child(3) { transition-delay: 180ms; }
[data-reveal-armed] .product-grid.reveal > :nth-child(4) { transition-delay: 270ms; }
[data-reveal-armed] .product-grid.reveal > :nth-child(n + 5) { transition-delay: 360ms; }

/* THE WHOLE PIECE, IN A FRAME THAT NEVER MOVES.
   This was `cover`, which fills the 3:4 frame by cropping — fine for a photograph shot to that exact
   ratio, and a guillotine for anything else. Every marketplace the studio sells alongside states the
   same rule: Mercado Livre requires the item "exibido por inteiro, sem cortes... sem encostar nas
   bordas", Nuvemshop warns against leaving the product "apertado no enquadramento", and Amazon asks
   for the product whole on a neutral field. It is also exactly what the owner asked for on the launch
   carousel, and there is no reason a piece should be shown whole there and sliced here.

   The FRAME keeps its fixed ratio, so the grid stays aligned to the pixel; only the fit changes. The
   muted panel behind is the neutral field those same standards call for. */
.product-card__image {
    inline-size: 100%;
    block-size: 100%;
    object-fit: contain;
    transition: transform var(--transition-slow);
}

/* A restrained scale on hover — the photograph responds, the layout does not move. */
@media (hover: hover) and (pointer: fine) {
    .product-card__link:hover .product-card__image { transform: scale(1.03); }
}

/* ---- Availability label --------------------------------------------------- */
.product-card__badge {
    position: absolute;
    inset-block-start: var(--space-sm);
    inset-inline-start: var(--space-sm);
    padding: 0.28rem 0.55rem;
    border-radius: var(--radius-sm);
    background: var(--cg-paper);
    color: var(--color-ink-strong);
    font-size: var(--text-2xs);
    font-weight: var(--weight-bold);
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    line-height: 1.2;
    max-inline-size: calc(100% - 4.5rem);
}

/* Available and low stock lead with brand red — the one place stock urgency is allowed to shout. */
.product-card__badge--availablenow,
.product-card__badge--availabledigitally {
    background: var(--cg-red);
    color: var(--cg-white);
}

.product-card__badge--lowstock {
    background: var(--cg-red-deep);
    color: var(--cg-white);
}

/* Sold and unavailable recede: they are information, not an offer. */
.product-card__badge--soldout,
.product-card__badge--unavailable,
.product-card__badge--draftorhidden {
    background: rgba(16, 14, 14, 0.82);
    color: var(--cg-paper);
}

.product-card__badge--madetoorder {
    background: var(--cg-black);
    color: var(--cg-paper);
}

/* Reserved: someone is paying for it right now; it may come back, so it reads as a pause, not an end. */
.product-card__badge--reserved {
    background: var(--cg-black);
    color: var(--cg-paper);
}

/* External (Hotmart) course — deliberately not red, so it never reads as native stock. */
.product-card__badge--external {
    background: var(--cg-paper);
    color: var(--color-ink-strong);
    box-shadow: inset 0 0 0 1.5px var(--cg-black);
}

/* Academy affiliation. It can sit alongside an availability badge, because affiliation and availability
   are different facts about the same product — so it is styled to read as a quiet second label rather
   than competing with the first, and the stack below keeps them from overlapping. */
.product-card__badge--academy {
    background: var(--cg-black);
    color: var(--cg-paper);
    letter-spacing: 0.14em;
}

/* Two badges in the same corner stack instead of colliding. */
.product-card__media {
    --cg-badge-gap: 0.4rem;
}

.product-card__badge--academy:not(:first-of-type) {
    transform: translateY(calc(100% + var(--cg-badge-gap)));
}

/* ---- Body -------------------------------------------------------------------
   Fixed rows, so a row of cards lines up.

   Every storefront measured gives the text block a constant height regardless of card width — 92px at
   Everlane, 97px at Allbirds — for one reason: a two-line product name next to a one-line name would
   otherwise push its price and action a line lower, and the row stops reading as a shelf. Here the
   name and the summary each occupy a fixed number of lines whether or not they need them, so names sit
   on one baseline, prices on the next, and actions on the last. */
.product-card__body {
    display: grid;
    gap: var(--space-2xs);
    align-content: start;
}

/* The kind label keeps its line even when it has no text — an external course carries its badge on the
   photograph instead, and without this its name would ride a line higher than the mug beside it.
   The line-height is stated rather than inherited: an empty span collapses to `min-block-size` while a
   span with text uses the inherited line-height, and the 5px between those two is visible as a ragged
   row of names. */
.product-card__kind {
    line-height: 1.2;
    min-block-size: 1.2em;
    display: block;
}

/* One slot for whatever the commercial state is: a price, a price plus a partner note, or neither.
   Fixed height, so a course awaiting an offer does not shorten its card and break the row. */
.product-card__price-slot {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    gap: 0.15rem;
    min-block-size: 3.1rem;
}


.product-card__kind {
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--color-ink-muted);
    font-weight: var(--weight-semibold);
}

.on-ink .product-card__kind { color: var(--color-on-deep-muted); }

/* Two lines, always. "Ceramigeek Academy — Canecas Esculturais de Personagem em Cerâmica" and "Molde
   Base Ceramigeek" have to leave their prices on the same baseline, so the taller of the two decides
   the height and the shorter one keeps the empty line. */
.product-card__name {
    font-size: var(--text-md);
    font-weight: var(--weight-bold);
    font-stretch: var(--stretch-label);
    letter-spacing: var(--tracking-tight);
    line-height: 1.25;
    color: var(--color-ink-strong);
    margin: 0;
    min-block-size: calc(2 * 1.25em);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.on-ink .product-card__name { color: var(--color-on-deep-strong); }

/* One line on a shelf. The summary is orientation, not the sales pitch — the product page carries that
   — and a second line here costs more alignment than it buys comprehension. */
.product-card__desc {
    font-size: var(--text-sm);
    line-height: 1.4;
    color: var(--color-ink-muted);
    margin: 0;
    max-inline-size: 42ch;
    min-block-size: 1.4em;
    display: -webkit-box;
    -webkit-line-clamp: 1;
    line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.on-ink .product-card__desc { color: var(--color-on-deep-muted); }

/* One line. This note is the only variable-length thing in the price slot, so when it wrapped onto a
   second line it pushed its card 5px taller than the rest of the row — enough to see, and exactly the
   kind of drift that stops a row reading as a shelf. */
.product-card__external-note {
    font-size: var(--text-2xs);
    line-height: 1.3;
    color: var(--color-ink-muted);
    margin: 0;
    max-inline-size: none;
    display: -webkit-box;
    -webkit-line-clamp: 1;
    line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.on-ink .product-card__external-note { color: var(--color-on-deep-muted); }

.product-card .price { margin-block-start: var(--space-2xs); }

/* The call to action is a text action with a red rule that appears on hover — no second button in
   a grid of cards, which would turn the catalog into a wall of pills. */
.product-card__cta {
    position: relative;
    display: inline-block;
    align-self: start;
    margin-block-start: var(--space-2xs);
    padding-block-end: 3px;
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--color-ink-strong);
}

.on-ink .product-card__cta { color: var(--color-on-deep-strong); }

.product-card__cta::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    block-size: 2px;
    background: var(--color-accent);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform var(--transition-base);
}

.product-card__link:hover .product-card__cta { color: var(--color-accent); }
.product-card__link:hover .product-card__cta::after { transform: scaleX(1); }
.on-ink .product-card__link:hover .product-card__cta { color: var(--color-accent-on-deep); }
.on-ink .product-card__cta::after { background: var(--color-accent-on-deep); }

/* ---- Favourite toggle ------------------------------------------------------
   Sits over the photograph, top-right. It is a real form POST, so it works without JavaScript;
   ceramic.js only adds the maker's-stamp press animation. */
.favorite-toggle {
    position: absolute;
    inset-block-start: var(--space-sm);
    inset-inline-end: var(--space-sm);
    margin: 0;
}

.favorite-toggle__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 2.5rem;
    block-size: 2.5rem;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--cg-paper) 88%, transparent);
    color: var(--color-ink-strong);
    backdrop-filter: blur(4px);
    transition: background-color var(--transition-fast), color var(--transition-fast);
}

.favorite-toggle__btn:hover {
    background: var(--cg-paper);
    color: var(--color-accent);
}

.favorite-toggle__btn.is-active { color: var(--color-accent); }

.favorite-toggle__btn:focus-visible {
    outline: 3px solid var(--color-focus);
    outline-offset: 2px;
}
