/*
  Page scaffolding: containers, ground sections, header shell, branded footer, skip link.
  Mobile-first. Every value comes from tokens.css.
*/

/* Sticky-footer shell. */
body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100svh;
}

/* Scroll lock for overlays — a class, never an inline style (CSP forbids inline styles). */
body.is-scroll-locked { overflow: hidden; }

.visually-hidden {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ---- Containers ----------------------------------------------------------- */
.container {
    width: 100%;
    max-width: var(--container-max);
    margin-inline: auto;
    padding-inline: var(--container-pad);
}

.container--wide { max-width: var(--container-wide); }
.container--narrow { max-width: var(--container-narrow); }

/* ---- Ground sections -------------------------------------------------------
   The whole site is built from alternating PAPER and INK grounds. A section declares its ground and
   inherits the right text colours; `.on-ink` (typography.css) handles the type register. */
.section {
    padding-block: var(--section-gap);
    position: relative;
}

.section--tight { padding-block: var(--section-gap-tight); }
.section--flush-top { padding-block-start: 0; }
.section--flush-bottom { padding-block-end: 0; }

.section--paper { background: var(--color-bg); color: var(--color-ink); }
.section--surface { background: var(--color-surface); color: var(--color-ink); }
.section--muted { background: var(--color-surface-muted); color: var(--color-ink); }

.section--ink {
    background: var(--cg-black);
    color: var(--color-on-deep);
}

/* THE INSIDE OF THE KILN.
   Dark bands get two layers of light that sit over the content and never take a click:
     ::before — a fine grain, so the black reads as a fired matte surface rather than as a flat screen
                colour. A 7 KB pre-rendered tile, NOT an SVG turbulence filter: that one had to be
                re-rasterised and blended over whole bands and stalled the page's painting.
     ::after  — an ember glow that follows the pointer (interactions.js sets --gx / --gy and
                data-glow). A plain translucent gradient: no blend mode, which would force the
                whole band to be recomposited on every frame.
   Both are decoration: invisible to assistive technology, absent under reduced motion for the glow. */
.section--ink::before,
.site-footer::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: var(--grain-opacity);
    background-image: url("/images/brand/grain.png");
    background-size: 96px 96px;
}

.site-footer { position: relative; }

.section--ink::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(32rem circle at var(--gx, 50%) var(--gy, 50%), var(--kiln-glow), transparent 68%);
    opacity: 0;
    transition: opacity 600ms var(--ease-out);
}

.section--ink[data-glow="on"]::after { opacity: 0.4; }

/* TWO NEIGHBOURS MUST NEVER SHARE A GROUND.
   Each section declares its own ground in its own partial, which is fine until you remember the owner
   can REORDER and HIDE sections from the panel. The moment she does, two sections that were never
   meant to touch end up adjacent on the same colour and the boundary between them disappears — which
   is what she reported on Academy, where "PARA QUEM É" and "PRONTO PARA COMEÇAR?" both landed on
   paper and read as one very long section.

   Fixing this in the partials would only work for the order they happen to be declared in. The ground
   is not a property of a section, it is a property of where that section ENDS UP, so the rule is
   positional: when a ground repeats, the second one shifts. That holds for every arrangement she can
   produce from the panel, including ones nobody has tried yet.

   Light grounds swap between paper and muted. Two ink sections in a row keep their colour — flipping
   one to light would break the `on-ink` text riding on it — and take a hairline instead, which is
   enough to show the seam on a dark ground. */
.section--paper + .section--paper { background: var(--color-surface-muted); }
.section--muted + .section--muted { background: var(--color-bg); }
.section--surface + .section--surface { background: var(--color-surface-muted); }
.section--ink + .section--ink { border-block-start: 1px solid var(--color-on-deep-border); }

/* Section opener: eyebrow + headline + lead, optionally with a trailing link.
   The inner column is itself a grid: display headlines have deep descenders and the reset zeroes all
   margins, so relying on margin collapsing here makes the lead collide with the headline. */
.section__head {
    display: grid;
    gap: var(--space-md);
    margin-block-end: var(--space-xl);
}

.section__head > div {
    display: grid;
    gap: var(--space-md);
    justify-items: start;
    align-content: start;
}

.section__head .eyebrow { margin: 0; }

.section__head--split {
    grid-template-columns: 1fr;
    align-items: end;
}

.section__head-aside {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    align-items: center;
}

@media (min-width: 56rem) {
    .section__head--split {
        grid-template-columns: minmax(0, 1fr) auto;
        gap: var(--space-xl);
    }
}

/* A hairline rule that carries the brand red at its start — used to separate editorial blocks. */
.rule {
    border: 0;
    border-block-start: 1px solid var(--color-border);
    position: relative;
    margin: 0;
}

.rule::before {
    content: "";
    position: absolute;
    inset-block-start: -2px;
    inset-inline-start: 0;
    inline-size: 3.5rem;
    block-size: 3px;
    background: var(--color-accent);
}

.on-ink .rule { border-block-start-color: var(--color-on-deep-border); }
.on-ink .rule::before { background: var(--color-accent-on-deep); }

/* ---- Honest empty state ----------------------------------------------------
   Deliberately NOT a dashed grey box: an editorial statement with a real photograph beside it, so an
   empty catalog still looks like the brand rather than like broken software. */
.empty-state {
    display: grid;
    gap: var(--space-lg);
    align-items: center;
}

.empty-state__media {
    margin: 0;
    overflow: hidden;
    border-radius: var(--radius-lg);
    background: var(--color-surface-muted);
}

.empty-state__media img {
    inline-size: 100%;
    block-size: 100%;
    aspect-ratio: var(--ratio-editorial);
    object-fit: cover;
}

.empty-state__title {
    font-size: var(--text-2xl);
    letter-spacing: var(--tracking-display);
}

.empty-state__description {
    color: var(--color-ink-muted);
    max-width: var(--measure-narrow);
}

.empty-state__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-md) var(--space-xl);
    margin-block-start: var(--space-lg);
}

.empty-state__title { margin-block-end: var(--space-sm); }

.on-ink .empty-state__description { color: var(--color-on-deep-muted); }

@media (min-width: 56rem) {
    .empty-state--with-media {
        grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
        gap: var(--space-2xl);
    }
}

/* ---- Branded media placeholder ---------------------------------------------
   Used where a product has no approved photograph yet. It is deliberately on-brand (porcelain plate,
   mascot mark, red rule) and deliberately honest — it says the photograph is pending rather than
   dressing the gap up as a product shot. Replaces the old off-brand pastel placeholder SVGs. */
.media-pending {
    display: grid;
    place-items: center;
    gap: var(--space-md);
    inline-size: 100%;
    block-size: 100%;
    min-block-size: 16rem;
    padding: var(--space-xl);
    background: var(--color-surface-muted);
    border-radius: var(--radius-lg);
    text-align: center;
}

.media-pending__mark {
    inline-size: clamp(3.5rem, 12vw, 6rem);
    block-size: auto;
    opacity: 0.28;
}

.media-pending__label {
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    font-weight: var(--weight-semibold);
    color: var(--color-ink-muted);
    max-inline-size: 24ch;
}

.media-pending__rule {
    inline-size: 2.5rem;
    block-size: 3px;
    background: var(--color-accent);
}

.on-ink .media-pending { background: #1c1918; }
.on-ink .media-pending__label { color: var(--color-on-deep-muted); }

/* ---- Skip link ------------------------------------------------------------ */
.skip-link {
    position: absolute;
    left: var(--space-sm);
    top: -100%;
    z-index: var(--z-toast);
    padding: var(--space-xs) var(--space-md);
    background: var(--cg-black);
    color: var(--cg-white);
    border-radius: var(--radius-sm);
    transition: top var(--transition-fast);
}

.skip-link:focus { top: var(--space-sm); }

/* ---- Header --------------------------------------------------------------- */
.site-header {
    position: sticky;
    top: 0;
    z-index: var(--z-header);
    background: var(--glass-paper);
    backdrop-filter: saturate(1.4) blur(14px);
    -webkit-backdrop-filter: saturate(1.4) blur(14px);
    border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
    transition: box-shadow var(--transition-base), background-color var(--transition-base);
}

/* Raised once the page has scrolled — set by header.js, not by a scroll-linked animation. */
.site-header[data-scrolled="true"] {
    box-shadow: 0 10px 30px -18px rgb(20 14 12 / 0.35);
    background: color-mix(in srgb, var(--color-bg) 92%, transparent);
}

/* THE HEAT LINE.
   The page is a firing: as the reader goes down it, a line under the header heats from the brand red
   to ember to the white-hot core. header.js publishes the scroll ratio as --heat (0..1); the line is a
   scaled bar, so it costs one composited transform per frame and nothing when the page is still.
   Decorative: aria-hidden, and invisible until the page has actually moved. */
.site-header__heat {
    position: absolute;
    inset-inline: 0;
    inset-block-end: -1px;
    block-size: 2px;
    background: var(--heat-gradient);
    transform-origin: left center;
    transform: scaleX(var(--heat, 0));
    opacity: var(--heat-visible, 0);
    transition: opacity var(--transition-base);
    pointer-events: none;
}

.site-header__heat::after {
    content: "";
    position: absolute;
    inset-block: -3px;
    inset-inline-end: 0;
    inline-size: 3rem;
    background: radial-gradient(closest-side, var(--kiln-core), transparent);
    filter: blur(2px);
    opacity: 0.9;
}

.site-header__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    min-height: var(--header-height);
}

.brand {
    display: inline-flex;
    align-items: center;
    flex: none;
}

/* The Ceramigeek mark is a LOCKUP (mascot + drawn wordmark), not a single glyph, so it needs more
   height than a typical header logo before the wordmark becomes readable. */
.brand__logo {
    display: block;
    height: 3.1rem;
    width: auto;
    transition: transform var(--transition-fast);
}

/* The mascot lifts a couple of pixels on hover — one small, cheap moment of personality on a
   control the visitor uses constantly. */
.brand:hover .brand__logo { transform: translateY(-2px) scale(1.03); }

@media (min-width: 48rem) {
    .brand__logo { height: 3.7rem; }
}

/* ---- Main ----------------------------------------------------------------- */
.site-main {
    display: block;
    flex: 1 0 auto;
}

.site-main:focus { outline: none; }

/* ---- Footer ----------------------------------------------------------------
   The transition into the footer is a maker's mark, not a graphic: a thin Ceramigeek-red rule broken
   by the small mascot. The large black/red ornament that used to sit here was rejected by the owner
   and has been removed from the site entirely (the source artwork is untouched in the Design folder).
   The primary logo then sits on a porcelain plate inside the footer — the logo artwork is black + red
   on transparent, so it needs a light plate to read on the ink ground. */
.footer-transition {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    width: 100%;
    max-width: var(--container-max);
    margin-inline: auto;
    padding: var(--space-2xl) var(--container-pad) var(--space-xl);
    background: var(--color-bg);
}

.footer-transition__rule {
    flex: 1 1 auto;
    block-size: 2px;
    background: linear-gradient(to right, transparent, var(--cg-red));
}

.footer-transition__rule:last-child {
    background: linear-gradient(to left, transparent, var(--cg-red));
}

.footer-transition__stamp {
    flex: none;
    inline-size: 2.5rem;
    block-size: auto;
    opacity: 0.9;
}

.site-footer {
    background: var(--cg-black);
    color: var(--color-on-deep);
    border-top: 3px solid var(--cg-red);
    padding-block: var(--space-2xl) var(--space-lg);
}

.site-footer__inner {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-xl);
    padding-block-end: var(--space-xl);
    border-block-end: 1px solid var(--color-on-deep-border);
}

.site-footer__brand {
    display: grid;
    gap: var(--space-md);
    /* Centred on its column, tagline included: the owner's square mascot on its plate read as
       misplaced when it hung from the left edge above a wider line of text (September 2026). */
    justify-items: center;
    text-align: center;
}

/* A SQUARE plate with the logo dead centre (owner, 02/10/2026: "centralize a logomarca ali no
   quadradinho certinho"). It used to take its size from the image plus uneven padding (more on the
   sides than top and bottom), so the mark never sat in the middle of what reads as a square. */
.site-footer__plate {
    display: grid;
    place-items: center;
    inline-size: 6.75rem;
    aspect-ratio: 1;
    padding: var(--space-sm);
    background: var(--cg-paper);
    border-radius: var(--radius-md);
}

.site-footer__logo {
    display: block;
    inline-size: 100%;
    block-size: 100%;
    object-fit: contain;
    object-position: center;
}

.site-footer__tagline {
    max-width: 34ch;
    color: var(--color-on-deep-muted);
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
}

.site-footer__heading {
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: var(--tracking-eyebrow);
    color: var(--color-on-deep-muted);
    margin-block-end: var(--space-md);
    font-weight: var(--weight-semibold);
    color: var(--color-on-deep-muted);
}

.site-footer__col ul {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    margin: 0;
    padding: 0;
}

.site-footer__col a {
    color: var(--color-on-deep);
    font-size: var(--text-sm);
    transition: color var(--transition-fast);
    display: inline-block;
    padding-block: 2px;
}

.site-footer__col a:hover { color: var(--color-accent-on-deep); }

.site-footer__bar {
    padding-block-start: var(--space-lg);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md);
    align-items: center;
    justify-content: space-between;
}

.site-footer__copy {
    font-size: var(--text-xs);
    color: var(--color-on-deep-muted);
    max-width: none;
}

.site-footer__social {
    display: flex;
    gap: var(--space-md);
    list-style: none;
    margin: 0;
    padding: 0;
}

.site-footer__social a {
    font-size: var(--text-xs);
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--color-on-deep-muted);
    transition: color var(--transition-fast);
}

.site-footer__social a:hover { color: var(--color-accent-on-deep); }

/* THE SIGNATURE. The brand's own two words — the same two slots the opening ident reads, so the owner
   renames it in one place — set across the whole width in outline, with GEEK taking the red. It sits
   at the very bottom and is cut by the page edge, like a maker's stamp pressed into the base of a
   piece. Decorative only (aria-hidden): the footer's links carry everything a visitor needs. */
.site-footer { overflow: hidden; }

.site-footer__wordmark {
    display: flex;
    justify-content: center;
    max-inline-size: none;
    margin: var(--space-xl) 0 calc(var(--text-giant) * -0.2);
    font-family: var(--font-display);
    font-size: var(--text-giant);
    font-weight: 900;
    font-stretch: 125%;
    line-height: 0.8;
    letter-spacing: -0.045em;
    text-transform: uppercase;
    white-space: nowrap;
    color: transparent;
    -webkit-text-stroke: 1.5px color-mix(in srgb, var(--color-on-deep) 26%, transparent);
    user-select: none;
    pointer-events: none;
}

.site-footer__wordmark-stamp {
    -webkit-text-stroke-color: color-mix(in srgb, var(--cg-red-on-dark) 70%, transparent);
    background: linear-gradient(180deg, color-mix(in srgb, var(--cg-red) 38%, transparent), transparent 78%);
    -webkit-background-clip: text;
    background-clip: text;
}


@media (min-width: 48rem) {
    .site-footer__inner {
        grid-template-columns: 1.6fr 1fr 1fr 1fr;
        gap: var(--space-2xl);
    }
}

/* ---- Reveal-on-scroll ------------------------------------------------------
   Content is fully visible by default. reveal.js adds `data-reveal-armed` to <html> only when
   IntersectionObserver is available AND reduced motion is not requested, so no-JS and reduced-motion
   visitors never see a hidden element. */
[data-reveal-armed] .reveal {
    opacity: 0;
    transform: translateY(28px);
    transition: opacity var(--duration-reveal) var(--ease-entrance),
        transform var(--duration-reveal) var(--ease-entrance);
    transition-delay: var(--reveal-delay, 0ms);
}

[data-reveal-armed] .reveal.is-revealed {
    opacity: 1;
    transform: none;
}

[data-reveal-armed] .reveal--delay-1 { --reveal-delay: 90ms; }
[data-reveal-armed] .reveal--delay-2 { --reveal-delay: 180ms; }
[data-reveal-armed] .reveal--delay-3 { --reveal-delay: 270ms; }

/* THE TWO CERAMIC REVEALS.
   Headlines are THROWN: they rise out of a slot, the way a wall of clay is pulled up from the wheel —
   clipped from below and lifted into place. Photographs are FIRED: they arrive pale, as if still
   unfired porcelain, over the plate behind them, and take their full colour the way a glaze does.
   Only opacity and transform move — both composited. An earlier version used a desaturate + blur
   `filter`, which on full-size photographs stalled the browser's painting outright.
   Both key off the same armed/revealed pair as every other reveal, so nothing can stay hidden when
   the script does not run. */
[data-reveal-armed] .reveal :is(h1, h2, .display, .feature__title, .release__title) {
    clip-path: inset(0 -0.2em 100% -0.2em);
    transform: translateY(0.45em);
    transition: clip-path 1100ms var(--ease-entrance), transform 1100ms var(--ease-entrance);
    transition-delay: calc(var(--reveal-delay, 0ms) + 80ms);
}

[data-reveal-armed] .reveal.is-revealed :is(h1, h2, .display, .feature__title, .release__title) {
    clip-path: inset(-0.3em -0.2em -0.3em -0.2em);
    transform: none;
}

[data-reveal-armed] :is(figure.reveal, .reveal figure) > img {
    opacity: 0.18;
    transform: scale(1.05);
    transition: opacity 1500ms var(--ease-out), transform 1600ms var(--ease-out);
    transition-delay: calc(var(--reveal-delay, 0ms) + 120ms);
}

[data-reveal-armed] figure.reveal.is-revealed > img,
[data-reveal-armed] .reveal.is-revealed figure > img {
    opacity: 1;
    transform: none;
}

/* Product photographs fire too, but only through opacity: their transform belongs to the
   hand-turn tilt (experience.css) and must not be taken over here. */
[data-reveal-armed] .reveal .product-card__media img {
    opacity: 0.18;
    transition: opacity 1400ms var(--ease-out), transform var(--transition-base);
    transition-delay: calc(var(--reveal-delay, 0ms) + 120ms), 0ms;
}

[data-reveal-armed] .reveal.is-revealed .product-card__media img { opacity: 1; }
[data-reveal-armed] .reveal .product-card__media.is-turning img { transition: opacity 0ms, transform 60ms linear; }

/* ---- Error reference ------------------------------------------------------ */
.error__reference {
    margin-top: var(--space-md);
    font-size: var(--text-sm);
    color: var(--color-ink-muted);
}

.error__reference-code {
    font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
    background: var(--color-surface-muted);
    padding: 0.1em 0.4em;
    border-radius: var(--radius-sm);
    word-break: break-all;
}
