@import 'tailwindcss';

@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php';

@theme {
    /*
     * Warm «cantera» palette (proposal §18, k-means over the real brochure).
     * The neutral scale replaces the former cool-grey `brand-*` VALUES in
     * place: the token NAMES are kept so every existing consumer (public
     * markup, components, `data-tone="dark"`) resolves to the warm scale with
     * zero view edits. `stone`/`slate` namespaces are wiped first so the
     * removed names cannot silently resolve to Tailwind's cold defaults. The
     * Filament admin theme is a separate file and is never touched.
     */
    --color-brand-*: initial;
    --color-stone-*: initial;
    --color-slate-*: initial;

    /*
     * Warm «cantera» neutral scale: paper-cream canvas, warm taupe borders and
     * a deep brown ink. `brand-400` is DECORATIVE ONLY (2.49:1) and
     * `brand-500` is large-secondary only (3.87:1) — neither is valid body
     * text; `brand-600` is the AA secondary-text token.
     */
    --color-brand-50: #FAF7F2;  /* canvas — papel crema                */
    --color-brand-100: #F2ECE3; /* superficie alterna                  */
    --color-brand-200: #E3DACD; /* hairlines / bordes                  */
    --color-brand-300: #CEC3B4; /* bordes fuertes / muted on dark      */
    --color-brand-400: #A99D8D; /* solo decorativo / íconos (2.49:1)   */
    --color-brand-500: #877B6B; /* texto grande secundario (3.87:1)    */
    --color-brand-600: #6B6055; /* texto secundario AA (5.73:1)        */
    --color-brand-700: #524A40; /* superficie oscura                   */
    --color-brand-800: #3E3730; /* superficie profunda                 */
    --color-brand-900: #312825; /* panel oscuro principal              */
    --color-brand-950: #1D1917; /* tinta — texto/superficie más oscura */

    /*
     * Secondary accent «siena» (iron oxides). Re-pointed in place of the former
     * out-of-palette beige `accent-*` scale. Every CTA that consumed
     * `accent-500` changes hue at once — that is the intended palette swap.
     */
    --color-accent-300: #E8CE8C; /* siena claro — énfasis sobre oscuro  */
    --color-accent-400: #D8B45A;
    --color-accent-500: #B8933F; /* CTA sobre claro / enlaces (4.52:1)  */
    --color-accent-600: #9C7A31;
    --color-accent-700: #7E6228; /* siena profundo — texto AA           */

    /*
     * Primary accent «ocre latón» (mineral glints): eyebrows over dark, primary
     * CTA, price emphasis and active filter. Deliberately separate from
     * `accent-*` so ochre and siena never collapse into one scale.
     */
    --color-ochre-300: #E8CE8C;
    --color-ochre-400: #D6B261;
    --color-ochre-500: #BC9440;
    --color-ochre-600: #9A7631;
    --color-ochre-700: #7B5C26;

    /*
     * Informative per-stone tones. Minimal dose: an 8px dot or a top border,
     * never large surfaces.
     */
    --color-piedra-terracota: #A9683F;
    --color-piedra-beige: #C9B08A;
    --color-piedra-gris: #8E8B84;
    --color-piedra-negra: #3A3833;
    --color-piedra-verde: #6E7A5E;

    --color-white: #FFFFFF;
    --color-success-600: #4B7A3D;
    --color-success-700: #3B6130; /* AA status text on light */
    --color-error-600: #B23B3B;
    --color-error-700: #8F2E2E; /* AA status text on light */

    /* Editorial hero palette — re-pointed to the warm «cantera» scale. */
    --color-ink: var(--color-brand-950);
    --color-cream: var(--color-brand-50);
    --color-warm-gray: var(--color-brand-300);

    /* Oversized editorial display type (hero headline). */
    --text-display: clamp(2.25rem, 8.75vw, 7rem);
    --text-display--line-height: 0.85;
    --text-display--letter-spacing: -0.02em;
    --text-display--font-weight: 400;

    /*
     * Editorial-contrast semantic tokens. The public site is LIGHT by default:
     * `surface` is the warm off-white canvas, `on-surface` the ink text and
     * `surface-raised` the white card/field. Sections tagged `data-tone="dark"`
     * override these same custom properties (see the base layer below), so every
     * descendant that consumes a semantic token flips to the dark editorial
     * palette without duplicated markup. Tokens reference the brand/accent scale
     * (not literals) so the runtime `brand-overrides` palette keeps retinting
     * the whole public site without a rebuild.
     */
    --color-surface: var(--color-brand-50);           /* page canvas (light) */
    --color-surface-alt: var(--color-brand-100);      /* alt light band      */
    --color-surface-raised: var(--color-white);       /* cards / form fields */
    --color-surface-hover: var(--color-brand-100);    /* hover surfaces      */
    --color-on-surface: var(--color-brand-950);       /* primary ink         */
    --color-on-surface-muted: var(--color-brand-700); /* secondary text — 8.4:1 AAA (was brand-600, 5.73:1) */
    --color-line: var(--color-brand-200);             /* hairline borders    */

    /* Context-aware accent + status text (WCAG AA on both canvases). */
    --color-accent-text: var(--color-accent-700);   /* siena text on light  */
    --color-ochre-text: var(--color-ochre-700);     /* ochre emphasis on light */
    --color-success-text: var(--color-success-700); /* status text on light */
    --color-error-text: var(--color-error-700);     /* status text on light */
    --color-focus-ring: var(--color-accent-700);    /* focus ring on light  */

    /* On-dark semantic status colors (the 600 tones are too dark on charcoal). */
    --color-success-300: #9CC98B; /* success text on dark */
    --color-error-300: #E39A9A;   /* error text on dark   */

    /*
     * Soft layered shadows: cards and buttons float on a diffused halo instead
     * of relying on hard borders.
     */
    --shadow-soft: 0 1px 2px rgb(0 0 0 / 0.04), 0 10px 30px -12px rgb(0 0 0 / 0.15);
    --shadow-lift: 0 2px 4px rgb(0 0 0 / 0.05), 0 24px 48px -16px rgb(0 0 0 / 0.22);

    /* Wide container (≈100rem / 1600px) for large desktop monitors. */
    --container-wide: 100rem;

    /* Fonts by role */
    --font-serif: 'Fraunces', ui-serif, Georgia, serif;
    --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
    --font-mono: 'JetBrains Mono', ui-monospace, monospace;

    /* Motion tokens — the single source of truth for every transition. */
    --duration-fast: 150ms;
    --duration-base: 250ms;
    --duration-slow: 450ms;
    --duration-slower: 900ms;
    --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
    --ease-in-out-soft: cubic-bezier(0.65, 0, 0.35, 1);
    --default-transition-duration: var(--duration-base);
    --default-transition-timing-function: var(--ease-out-soft);

    /* Fluid type scale (size + line-height + tracking + weight companions). */
    --text-hero: clamp(2.25rem, 4.375vw, 3.5rem);
    --text-hero--line-height: 1.05;
    --text-hero--letter-spacing: -0.02em;
    --text-hero--font-weight: 600;

    /*
     * Editorial page hero. Every secondary page title (catalog, product, projects,
     * blog, nosotros, empresas, contacto…) shares this single size so the heroes
     * stay consistent. The home hero keeps the larger `text-display`.
     */
    --text-page-hero: clamp(2.25rem, 4.6875vw, 3.75rem);
    --text-page-hero--line-height: 0.9;
    --text-page-hero--letter-spacing: -0.02em;
    --text-page-hero--font-weight: 400;
    --text-h1: clamp(1.875rem, 3.4375vw, 2.75rem);
    --text-h1--line-height: 1.1;
    --text-h1--letter-spacing: -0.02em;
    --text-h1--font-weight: 600;
    --text-h2: clamp(1.5rem, 2.5vw, 2rem);
    --text-h2--line-height: 1.2;
    --text-h2--letter-spacing: -0.01em;
    --text-h2--font-weight: 600;
    --text-h3: clamp(1.25rem, 1.875vw, 1.5rem);
    --text-h3--line-height: 1.3;
    --text-h3--letter-spacing: -0.01em;
    --text-h3--font-weight: 500;
    --text-body-lg: 1.125rem;
    --text-body-lg--line-height: 1.7;
    --text-body: 1rem;
    --text-body--line-height: 1.7;
    --text-sm: 0.875rem;
    --text-sm--line-height: 1.55;

    /* Fluid spacing. */
    --spacing-section: clamp(5rem, 8vw, 7rem);
    --spacing-gutter: clamp(1rem, 4vw, 1.5rem);

    /* Radius: sharp, zero-radius surfaces (cards, buttons, badges, inputs). */
    --radius: 0;
    --radius-lg: 0;
}

/* Named duration/easing utilities backed by the motion tokens. */
@utility duration-fast {
    transition-duration: var(--duration-fast);
}

@utility duration-base {
    transition-duration: var(--duration-base);
}

@utility duration-slow {
    transition-duration: var(--duration-slow);
}

@utility duration-slower {
    transition-duration: var(--duration-slower);
}

@utility ease-out-soft {
    transition-timing-function: var(--ease-out-soft);
}

@utility ease-in-out-soft {
    transition-timing-function: var(--ease-in-out-soft);
}

/* Full-viewport helper: svh avoids the mobile URL-bar jump, vh is the fallback.
   The slim utility strip (desktop only) lives in normal flow ABOVE the hero, so
   it must be discounted or the hero overflows the fold by the strip height. */
@utility hero-viewport {
    min-height: calc(100vh - var(--strip-height, 0px));
    min-height: calc(100svh - var(--strip-height, 0px));
}

/* Hero legibility: a soft shadow under the headline/subtitle so lighter photo
   areas never break AA contrast, paired with the lightened hero scrim. */
@utility hero-text-shadow {
    text-shadow: 0 1px 2px rgb(0 0 0 / 0.55), 0 12px 40px rgb(0 0 0 / 0.45);
}

/* Vertical editorial label (hero right edge). */
@utility writing-vertical {
    writing-mode: vertical-rl;
}

/* Scroll affordance: gradient fades that hint at more content above/below
   without the default scrollbar. Coloured with the semantic `surface` token so
   they read correctly on light and dark canvases. */
@utility scroll-fade-top {
    background-image: linear-gradient(to bottom, var(--color-surface), transparent);
}

@utility scroll-fade-bottom {
    background-image: linear-gradient(to top, var(--color-surface), transparent);
}

/*
 * Subtle on-brand scrollbars. The full-screen ficha/format dialogs scroll
 * internally and the default browser scrollbar reads as "raw" on large screens.
 * A thin (8px) muted `brand-400` thumb over a transparent track is discreet and
 * on-palette; Firefox falls back to `scrollbar-width: thin` +
 * `scrollbar-color`. Native wheel/trackpad/touch scrolling is untouched.
 */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--color-brand-400) transparent;
}

*::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

*::-webkit-scrollbar-track {
    background: transparent;
}

*::-webkit-scrollbar-thumb {
    background-color: var(--color-brand-400);
}

*::-webkit-scrollbar-thumb:hover {
    background-color: var(--color-brand-500);
}

/* Horizontal strip scrollbar hidden (gallery section tabs): the strip scrolls
   with touch/swipe and via the ‹ › arrows, so the raw scrollbar is noise. */
.scrollbar-none {
    scrollbar-width: none;
}

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

/* Soft icon weight safety net (Lucide can hard-code stroke-width="2"). */
[data-icon] * {
    stroke-width: var(--icon-stroke, 1.5);
}

/* Reduced motion neutralizes the shared duration tokens globally. */
@media (prefers-reduced-motion: reduce) {
    :root {
        --duration-fast: 0.01ms;
        --duration-base: 0.01ms;
        --duration-slow: 0.01ms;
        --duration-slower: 0.01ms;
    }
}

/*
 * Cross-document page transitions (View Transitions API). CSS-only progressive
 * enhancement: browsers without support ignore the at-rule and navigate
 * normally, and reduced-motion users keep instant navigation because the whole
 * opt-in lives inside the no-preference branch. Persistent chrome (header,
 * footer), the hero media and the single page title get stable names so they
 * morph between documents while the root cross-fades.
 */
@media (prefers-reduced-motion: no-preference) {
    @view-transition {
        navigation: auto;
    }

    [data-site-header] {
        view-transition-name: site-header;
    }

    [data-site-footer] {
        view-transition-name: site-footer;
    }

    [data-hero-bg] {
        view-transition-name: hero-media;
    }

    /* Exactly one <h1> per page (SEO contract) makes this name unique. */
    main h1 {
        view-transition-name: page-title;
    }

    /* Lightbox overlay image — morphs from/to the page when a view transition runs. */
    [data-lightbox-image] {
        view-transition-name: lightbox-image;
    }

    ::view-transition-old(root) {
        animation: page-fade-out var(--duration-fast) var(--ease-out-soft) both;
    }

    ::view-transition-new(root) {
        animation: page-fade-in var(--duration-base) var(--ease-out-soft) both;
    }
}

@keyframes page-fade-out {
    to {
        opacity: 0;
    }
}

@keyframes page-fade-in {
    from {
        opacity: 0;
    }
}

/*
 * Drawer toggle icon morph. Duration tokens collapse to ~0ms under reduced
 * motion, so the icons swap instantly without a separate code path.
 */
.menu-toggle__icon {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: opacity var(--duration-base) var(--ease-out-soft),
        transform var(--duration-base) var(--ease-out-soft);
}

.menu-toggle__icon--close {
    opacity: 0;
    transform: rotate(-90deg) scale(0.8);
}

.menu-toggle.is-open .menu-toggle__icon--menu {
    opacity: 0;
    transform: rotate(90deg) scale(0.8);
}

.menu-toggle.is-open .menu-toggle__icon--close {
    opacity: 1;
    transform: rotate(0deg) scale(1);
}

/* Alpine: hide the drawer until Alpine initializes it. */
[x-cloak] {
    display: none !important;
}

/*
 * Collections accordion — centered label anchor.
 *
 * The collection name is centred on BOTH axes (never rotated) inside its strip,
 * and the label stays OUTSIDE the flexing panels AND outside the row, so its
 * anchor never depends on the animated panel size. Each label is absolutely
 * positioned against the accordion container and sits at the centre of its own
 * strip at all times (closed or open): the `.is-open` state never re-targets it,
 * so the name never drifts while its panel grows. `--label-index` /
 * `--label-count` are set inline per label; `z-index` keeps the label above the
 * lifted panel (z-index 30).
 */
[data-accordion-label] {
    position: absolute;
    /* Sobre el panel levantado (z-index 30) y sus hermanos. */
    z-index: 40;
    left: calc((var(--label-index, 0) + 0.5) * (100% / var(--label-count, 5)));
    top: 50%;
    transform: translate(-50%, -50%);
    /* Constrain each name to its own strip so long collections ("Piedra de
       Colores", "Arquitectónica") wrap inside the column instead of bleeding
       over the neighbouring label on medium screens. */
    width: calc(100% / var(--label-count, 5));
    box-sizing: border-box;
    padding-inline: 0.5rem;
    text-wrap: balance;
    overflow-wrap: break-word;
    transition:
        left var(--duration-base) var(--ease-in-out-soft),
        top var(--duration-base) var(--ease-in-out-soft),
        opacity var(--duration-base) var(--ease-out-soft);
}

/* Mientras hay un panel abierto (o contrayendose), las etiquetas restantes
 * bajan por DEBAJO del panel levantado (z-index 30), de modo que la piedra
 * las tapa a medida que crece. `active` no se libera hasta que la reversa
 * termina (`finish()`), asi que el z-index vuelve solo con el acordeon ya
 * cerrado, sin que los nombres parpadeen a mitad de la contraccion. */
[data-accordion-label].is-behind {
    z-index: 20;
}

@media (max-width: 1023px) {
    [data-accordion-label] {
        left: 50%;
        top: calc((var(--label-index, 0) + 0.5) * (100% / var(--label-count, 5)));
        /* Centered labels: `max-content` stops the absolute box from being
           clipped to the half-width available from `left: 50%` (which wrapped
           long names like "Piedra de Colores"); `max-width` caps very long
           names to 90% so they wrap inside the screen instead of overflowing. */
        width: max-content;
        max-width: 90%;
    }
}

/* En desktop la tira ocupa todo el alto, así que su centro vertical es `top:
 * 50%` (el mismo valor base). En móvil NO debe re-centrarse: cada tira es una
 * banda horizontal y `top: 50%` la arrastraría al centro del contenedor al
 * abrir. Por eso la regla queda confinada a desktop. */
@media (min-width: 1024px) {
    /* El rotulo activo va POR ENCIMA del panel levantado (z-30); los demas quedan debajo y la piedra los tapa al crecer. */
[data-accordion-label].is-open {
    z-index: 40;
}
}

/*
 * Progressive (blur-up) images — VISIBLE BY DEFAULT. The real image is rendered
 * at full opacity so a missing/failed JS bundle, or a Livewire morph that swaps
 * in fresh tile nodes, can never strand a tile blank. The blur-up is an
 * enhancement: only while the motion module marks the wrapper `is-loading` (the
 * asset is still decoding) is the real image hidden behind the decorative LQIP
 * placeholder; once the wrapper is `is-loaded` the real image fades in and the
 * placeholder fades out. Both transient states are added/removed by JS, so
 * content never depends on a callback firing exactly once.
 */
[data-image-real] {
    opacity: 1;
    transition: opacity var(--duration-slow) var(--ease-out-soft);
}

[data-image] [data-image-placeholder] {
    opacity: 0;
    transition: opacity var(--duration-slow) var(--ease-out-soft);
}

[data-image].is-loading [data-image-real] {
    opacity: 0;
}

[data-image].is-loading [data-image-placeholder] {
    opacity: 1;
}

[data-image].is-loaded [data-image-real] {
    opacity: 1;
}

[data-image].is-loaded [data-image-placeholder] {
    opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
    [data-image-real] {
        opacity: 1 !important;
    }

    [data-image-placeholder] {
        opacity: 0 !important;
    }
}

/*
 * Journey fallback. On mobile the sticky stacking is disabled so the panels
 * become a normal vertical stack (photo above copy, natural height) and scroll
 * is never trapped. Under reduced motion the GSAP catalog never builds, so the
 * panels also drop back to static flow with all content visible and no
 * transforms — the enlarged photo layer is re-anchored so nothing is clipped.
 */
@media (max-width: 1023px) {
    [data-journey-sticky] {
        position: relative;
        min-height: auto;
    }
}

/*
 * Fixed-toolbar offsets. The public nav is `position: fixed` (77px on mobile,
 * 89px on desktop); the slim utility strip above it lives in NORMAL FLOW and
 * scrolls away, so it never contributes to the fixed height. `--header-height`
 * therefore describes the FIXED NAV only, and stays accurate in both states
 * (strip visible at the top vs scrolled away). It feeds the pinned "stacked"
 * panels and `html { scroll-padding-top }`, so neither a pinned element nor an
 * anchor jump ever lands under the nav.
 *
 * `--header-height-expanded` is kept as a backward-compatible alias (the
 * catalog/contact pages still reference it for their top padding): it now
 * equals the nav height because the strip pushes content in flow instead of
 * occupying fixed space.
 */
:root {
    --header-height: 77px;
    --header-height-expanded: 77px;
    /* Slim utility strip (desktop only) sits in normal flow above the header;
       the hero subtracts it so it never exceeds the fold. */
    --strip-height: 0px;
}

@media (min-width: 1024px) {
    :root {
        --header-height: 89px;
        --header-height-expanded: 89px;
        /* py-2.5 (20px) + one 1.5rem line (24px) + 1px border. */
        --strip-height: 45px;
    }

    [data-journey-sticky] {
        top: var(--header-height);
        min-height: calc(100svh - var(--header-height));
    }
}

html {
    scroll-padding-top: calc(var(--header-height) + 1rem);
}

/*
 * Gallery sticky section tabs. `--gallery-tabs-height` mirrors the measured
 * height of the pinned section tab bar (44px tab + 8px vertical padding + 2px
 * border), so the section headings can reserve exactly enough `scroll-margin`
 * to land BELOW the tab bar (and below the fixed header via the global
 * `scroll-padding-top`) on a native anchor jump or `scrollIntoView`.
 */
:root {
    --gallery-tabs-height: 3.375rem;
}

[data-use-section] > h2 {
    scroll-margin-top: var(--gallery-tabs-height);
}

/* Instant (non-smooth) anchor jumps under reduced motion. */
@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }
}

@media (prefers-reduced-motion: reduce) {
    [data-journey-sticky] {
        position: relative;
        min-height: auto;
    }

    [data-journey-step-content] {
        opacity: 1 !important;
    }

    [data-journey-photo] {
        position: absolute;
        inset: 0;
    }
}

/*
 * Product comparator: the pressed toggle state and a taller lift for the
 * floating WhatsApp button so the fixed compare bar never overlaps it.
 */
[data-compare-toggle].is-selected {
    border-color: var(--color-accent-500);
    color: var(--color-accent-300);
    background-color: color-mix(in oklab, var(--color-accent-500) 12%, transparent);
}

@media (max-width: 1023px) {
    body:has([data-compare-bar]) [data-whatsapp-button] {
        bottom: calc(env(safe-area-inset-bottom, 0px) + 10.5rem) !important;
    }
}

@layer base {
    html {
        -webkit-text-size-adjust: 100%;
        scroll-behavior: smooth;
    }

    /*
     * Dark editorial sections. Scoping the semantic custom properties on the
     * section element flips every descendant that consumes a token
     * (`bg-surface`, `text-on-surface`, `border-line`, `text-accent-text`,
     * status text, focus ring…) to the on-dark palette with no duplicated
     * markup. Custom properties inherit, so a nested light card can opt back
     * in by tagging itself `data-tone="light"`.
     */
    [data-tone='dark'] {
        --color-surface: var(--color-brand-950);
        --color-surface-alt: var(--color-brand-900);
        --color-surface-raised: var(--color-brand-800);
        --color-surface-hover: var(--color-brand-700);
        --color-on-surface: var(--color-brand-50);
        --color-on-surface-muted: var(--color-brand-300);
        --color-line: var(--color-brand-600);
        --color-accent-text: var(--color-accent-300);
        --color-ochre-text: var(--color-ochre-400);
        --color-success-text: var(--color-success-300);
        --color-error-text: var(--color-error-300);
        --color-focus-ring: var(--color-accent-300);
    }

    body {
        @apply bg-surface font-sans text-on-surface antialiased;
    }

    body[data-image-protection='1'] img {
        user-select: none;
        -webkit-user-drag: none;
    }

    h1,
    h2,
    h3,
    h4,
    h5,
    h6 {
        @apply font-serif;
        text-wrap: balance;
    }

    p {
        text-wrap: pretty;
    }

    ::selection {
        @apply bg-accent-500 text-brand-950;
    }
}

/*
 * Editorial article body. The `prose` class has no effect here (the Tailwind
 * Typography plugin is not installed), so the long-form article content is
 * styled with explicit descendant rules on the `.article-body` wrapper. The
 * block elements (h2/h3/p/ul/ol/blockquote/img) keep generous vertical rhythm
 * and serif, non-bold headings so the article reads like a real editorial
 * piece instead of a default browser stack.
 */
.article-body {
    @apply font-sans text-body-lg leading-[1.75] text-on-surface-muted;
}

.article-body > * + * {
    margin-top: 1.5rem;
}

.article-body h2 {
    @apply mt-10 mb-4 font-serif text-h2 font-normal leading-tight text-on-surface;
}

.article-body h3 {
    @apply mt-8 mb-3 font-serif text-h3 font-normal leading-snug text-on-surface;
}

.article-body a {
    @apply text-accent-text underline decoration-accent-500 underline-offset-4 transition-colors duration-fast ease-out-soft hover:text-on-surface;
}

.article-body ul {
    @apply list-disc space-y-2 pl-6;
}

.article-body ol {
    @apply list-decimal space-y-2 pl-6;
}

.article-body li::marker {
    color: var(--color-accent-600);
}

.article-body blockquote {
    @apply mt-6 border-l-2 border-accent-500 pl-5 font-serif text-h3 font-normal text-on-surface;
}

.article-body img {
    @apply mt-6 w-full;
}

.article-body strong {
    @apply font-semibold text-on-surface;
}

.article-body hr {
    @apply my-10 border-0 border-t border-line;
}

/*
 * Brand logo swap. The header's `data-tone` is driven by Alpine: `dark` shows
 * the light logo + cream ink (dark hero, and always on the solid scrolled bar),
 * `light` shows the dark logo + dark ink (transparent state over the light
 * canvas, e.g. /contacto). The two <img> tags always render; CSS shows one.
 */
[data-site-header][data-tone='dark'] .brand-logo--dark {
    display: none;
}

[data-site-header][data-tone='light'] .brand-logo--light {
    display: none;
}

/*
 * Motion entrance guard.
 *
 * GSAP hides entrance targets AFTER the first paint, so a group was visible for
 * a frame and then "jumped" into its animation (the flash). When `has-js` is
 * present — set synchronously by the inline <head> script — we pre-hide ONLY the
 * groups the on-load reveal owns, so the first paint already matches the
 * animation's start. The class is removed automatically if the motion bundle
 * never boots, so content can never stay hidden.
 */
html.has-js [data-animate-onload] > *,
html.has-js [data-animate-sequence] > * {
    opacity: 0;
}

/* The toolbar enters softly instead of popping in (opacity-only, so it never
 * fights the header's scroll offset). The phone/email strip is NOT animated:
 * fading it in left a transparent gap at the very top on load, which read as
 * a broken/empty bar. It now paints immediately. */
@keyframes header-enter {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

html.has-js [data-site-header] {
    animation: header-enter 0.55s var(--ease-out-soft) 0.05s both;
}
