/* =====================================================================
   ishunlo.studio — upgrade layer (風骨 programme) — shared stylesheet
   All rules live inside the canonical desktop band (>=1024px); pointer-
   dependent rules are additionally gated behind (hover:hover) and
   (pointer:fine). The mobile-first baseline in each page's inline style
   is never altered by this file. Variant dials are scoped to
   html[data-variant="mo | liubai | chi"], stamped by upgrade.js.
   See DEVELOPMENT.md for the programme record.
   ===================================================================== */

@media (min-width: 1024px) {

    /* ---- P0 · Lenis root compatibility --------------------------------
       Lenis stamps .lenis / .lenis-smooth / .lenis-stopped on <html>.
       The inline `html { scroll-behavior: smooth }` must yield while the
       inertia scroller owns the scroll position (Lenis handles anchor
       scrolling itself via `anchors: true`). */
    html.lenis, html.lenis body { height: auto; }
    html.lenis { scroll-behavior: auto !important; }
    html.lenis [data-lenis-prevent] { overscroll-behavior: contain; }
    html.lenis.lenis-stopped { overflow: hidden; }

    /* ---- P0 · 宣紙 grain parity ----------------------------------------
       index.html carries its own inline body::after grain (the stone
       rubbing) at every viewport; that baseline is untouched. The pages
       without grain receive it here, desktop band only. Two recipes:
       ink speckle multiplied into paper for the light pages, and a faint
       light speckle (no blend) for the night page, matching the rubbing
       recipe on the home page. Density is a variant dial from P3 on. */
    html.up-grain body::after,
    html.up-grain-night body::after {
        content: '';
        position: fixed;
        inset: 0;
        background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
        background-size: 128px;
        pointer-events: none;
        z-index: 9998;
    }
    html.up-grain body::after {
        opacity: var(--up-grain, 0.05);
        mix-blend-mode: multiply;
    }
    html.up-grain-night body::after {
        opacity: var(--up-grain, 0.035);
    }

    /* ---- P1 · cinnabar (§3) — desktop band only ------------------------
       The palette lives here, not in the inline styles: mobile keeps the
       launch palette untouched (author's direction, 2026-07-29). Tokens
       sit on body scopes so every var() usage below body resolves to
       them; the gilded page's own deeper oxblood overrides still win at
       their higher specificity, which is the intended art direction.
       The selector rules mirror, byte for byte, the inline declarations
       whose literal blues they replace — same alphas, cinnabar values. */
    body {
        --accent: #A20D0B;
        --accent-light: #B94A48;
        --border: rgba(162, 13, 11, 0.15);
    }
    body.home,
    body.realm-night {
        /* 赤 by eye (2026-07-29, second pass): matched to the author's
           reference button red — deeper than the first two candidates,
           hue ~3°. Large display text and marks at 3.95:1 on #0b0b0b;
           white rides on top at 4.99:1, so the CTAs keep their baseline
           white text, exactly like the reference button. The tint stays
           a close sibling so white holds AA across the CTA gradient. */
        --accent: #D0342C;
        --accent-light: #D63A31;
        --border: rgba(208, 52, 44, 0.15);
    }

    /* shared sub-page components (about + the three case pages).
       Two literal families by GROUND, not by page: the old accent
       washes sit on paper and take the seal red; the old accent-light
       glows all sit on dark grounds (the #0b0b0d philosophy panels,
       the process cards and modal) and take the bright 赤 — a paper
       tint there reads as a murky smudge, not a glow. */
    .about-hero-frame::before { background: linear-gradient(135deg, rgba(162,13,11,0.06) 0%, transparent 55%); }
    #about-philosophy::before { background: radial-gradient(ellipse at 20% 0%, rgba(214, 58, 49, 0.12) 0%, transparent 55%); }
    #about-philosophy .about-build-closer { background: linear-gradient(135deg, rgba(214, 58, 49, 0.12) 0%, transparent 70%); }
    .about-motion-frame::before { background: linear-gradient(135deg, rgba(162,13,11,0.05) 0%, transparent 60%); }

    /* about.html */
    .about-pullquote { background: linear-gradient(135deg, rgba(162,13,11,0.04) 0%, transparent 70%); }
    #about-philosophy .about-build-numeral { color: rgba(214, 58, 49, 0.16); }
    #about-philosophy .about-pipeline .pl-num { color: rgba(214, 58, 49, 0.5); }

    /* conche.html + living-worlds.html */
    .process-card::before { background: radial-gradient(ellipse at 25% 0%, rgba(214, 58, 49, 0.14) 0%, transparent 55%), linear-gradient(180deg, transparent 40%, rgba(0, 0, 0, 0.55) 100%); }
    .process-modal-frame::before { background: radial-gradient(ellipse at 30% 10%, rgba(214, 58, 49, 0.18) 0%, transparent 60%); }

    /* the home page — including the in-situ contrast fixes: ink on the
       vermilion gradient (5.9-7.0:1) where white would drop to 3.3:1 */
    body.home .video-container::before { background: linear-gradient(135deg, rgba(208,52,44,0.05) 0%, transparent 60%); }
    body.home .tech-tag:hover { background: rgba(208, 52, 44, 0.08); }
    body.home .resume-card:focus-visible { box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset, 0 40px 80px -20px rgba(208, 52, 44, 0.25), 0 18px 30px -16px rgba(0, 0, 0, 0.75); }
    body.home .reel-card::before { background: radial-gradient(ellipse at 25% 0%, rgba(214, 58, 49, 0.14) 0%, transparent 55%), linear-gradient(180deg, transparent 40%, rgba(0, 0, 0, 0.75) 100%); }
    /* baseline white text holds AA on this red (4.99:1 / 4.66:1 across
       the gradient), matching the reference button — only the glows need
       recolouring here */
    body.home .cta-primary { box-shadow: 0 12px 30px -12px rgba(208, 52, 44, 0.6); }
    body.home .cta-primary:hover { box-shadow: 0 16px 36px -12px rgba(208, 52, 44, 0.75); }
    /* the card tag sits on the cream card, not the page ground — the seal
       red is its correct ink there (7.4:1; the dark accent reads ~3.5:1) */
    body.home .resume-card-tag { color: #A20D0B; }

    /* ---- Chat surface — the desktop transcript ------------------------
       The baseline chat is a phone UI: iMessage bubbles in a narrow
       column pinned to the top of a 1440px field, with the input bar a
       screen-height below. On desktop it becomes what it actually is —
       a written conversation, set as a transcript. The visitor's line
       is the serif prompt; the author's voice is prose in an ink column
       marked by a seal-red hairline; the bubble metaphor retires, and
       the system blue with it. The space above the transcript is 留白,
       not a void. Mobile keeps the bubbles, untouched.

       One recipe serves all five pages: the chat surface is paper
       everywhere, including over the 墨 home page. */

    /* paper grain on the surface, matching the pages behind it */
    #chat-overlay::after {
        content: '';
        position: absolute;
        inset: 0;
        background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
        background-size: 128px;
        opacity: 0.045;
        mix-blend-mode: multiply;
        pointer-events: none;
        z-index: 0;
    }
    #chat-overlay > .dt-chat-head,
    #chat-overlay > .chat-messages,
    #chat-overlay > .chat-input-bar { position: relative; z-index: 1; }

    /* masthead: the composition gets a head instead of opening on a
       blank field with a floating close button (injected by upgrade.js) */
    .dt-chat-head {
        flex: 0 0 auto;
        width: 100%;
        max-width: 760px;
        margin: 0 auto;
        padding: clamp(2rem, 4vh, 3rem) 2rem 1rem;
        text-align: center;
        border-bottom: 1px solid rgba(0, 0, 0, 0.08);
    }
    .dt-chat-head-label {
        font-family: var(--sans);
        font-size: 0.7rem;
        font-weight: 500;
        letter-spacing: 0.35em;
        text-transform: uppercase;
        color: var(--accent);
    }

    .chat-messages {
        max-width: 760px;
        padding: 2rem 2rem 2.5rem;
        gap: clamp(1.9rem, 3.2vh, 2.8rem);
    }
    /* the transcript accumulates above the input. margin-top:auto rather
       than justify-content:flex-end — the latter clips overflowing
       content out of reach in a scroll container. */
    .chat-messages > *:first-child { margin-top: auto; }

    /* bubbles → transcript */
    .chat-bubble {
        max-width: none;
        padding: 0;
        background: none;
        border-radius: 0;
    }
    .chat-bubble-user {
        align-self: flex-end;
        max-width: 88%;
        text-align: right;
        font-family: var(--serif);
        font-style: italic;
        font-weight: 400;
        font-size: clamp(1.3rem, 1.7vw, 1.7rem);
        line-height: 1.45;
        color: #1d1d1f;
    }
    .chat-bubble-ai {
        align-self: flex-start;
        max-width: 62ch;
        padding-left: clamp(1rem, 1.6vw, 1.5rem);
        border-left: 1px solid rgba(162, 13, 11, 0.3);
        font-size: 1rem;
        line-height: 1.8;
        color: #3a3a3c;
    }

    /* the author's voice is still speaking — same ink column */
    .typing-indicator {
        align-self: flex-start;
        padding: 0.4rem 0 0.4rem clamp(1rem, 1.6vw, 1.5rem);
        border-left: 1px solid rgba(162, 13, 11, 0.3);
    }
    .typing-dot { background: var(--accent); }

    .chat-timestamp {
        font-size: 0.62rem;
        font-weight: 500;
        letter-spacing: 0.28em;
        text-transform: uppercase;
        /* 5.07:1 on the paper surface; the first pass used #a1a1a6 at
           2.57:1, which is unreadable small type */
        color: #6E6E73;
    }

    /* the input echoes the on-page ask field: a ruled line on paper,
       not a pill. The send button stays the vermilion seal-circle. */
    .chat-input-bar {
        border-top: 0;
        background: transparent;
        padding: 0 2rem clamp(2rem, 4vh, 3rem);
    }
    .chat-input-wrap {
        max-width: 760px;
        border: 0;
        border-bottom: 1px solid rgba(0, 0, 0, 0.16);
        border-radius: 0;
        padding: 0.7rem 0;
        gap: 1rem;
        transition: border-color 0.3s var(--ease-out);
    }
    .chat-input-wrap:focus-within { border-color: var(--accent); }
    .chat-input { font-size: 1.05rem; }

    /* availability signal: a pulsing vermilion presence mark in place of
       the system green — the label text carries the meaning, the pulse
       carries the liveness */
    .footer-eyebrow .availability-dot {
        background: var(--accent);
        box-shadow: 0 0 0 0 rgba(208, 52, 44, 0.55);
    }
    @keyframes availability-pulse {
        0% { box-shadow: 0 0 0 0 rgba(208, 52, 44, 0.55); }
        70% { box-shadow: 0 0 0 8px rgba(208, 52, 44, 0); }
        100% { box-shadow: 0 0 0 0 rgba(208, 52, 44, 0); }
    }

    /* ---- P2 · 風 — scrubbed reveals -----------------------------------
       While the engine owns .reveal progress (GSAP inline styles), the
       baseline 0.8s transition must not double-ease every frame. The
       observer's .visible class still lands and remains the framework
       for mobile / reduced-motion, where up-motion is never stamped. */
    html.up-motion .reveal { transition: none; }

    /* ---- P2 · 風 — ink-edge type wipe ---------------------------------
       Driven from upgrade.js via the Web Animations API, NOT a CSS
       animation: several .dt-split hosts (the hero h1 among them) use
       the baseline `animation` property for their own entrance, and a
       CSS wipe on the same shorthand replaces that entrance — the title
       never fades in. WAAPI composes alongside CSS animations, and the
       wipe is cancelled on finish so no mask outlives it. The variant
       dial --up-wipe-ms (§4) supplies the duration. */

    /* Split titles clip each word in an overflow-hidden box (the rise
       mask), so the baseline 20px-blur text-shadow gets cut into hard
       rectangles hugging every word over the video. A tight shadow that
       fits inside the word box keeps the legibility aid without the
       boxes; the hero vignette carries the rest of the contrast. */
    html.up-motion .dt-split { text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); }

    /* ---- P2 · 風 — pinned work stage, scrubbed crossfade --------------
       The inline stepper keeps managing scene classes (video play/pause,
       dots, title reveals); the engine takes over opacity as a continuous
       crossfade, so neighbouring scenes must stay paintable and must not
       run the baseline 0.7s opacity transition against per-frame writes. */
    html.up-motion .dt-stage.is-pinned .conche-feature {
        visibility: visible;
        transition: none;
    }
    html.up-motion .dt-stage.is-pinned .conche-feature:not(.is-active) {
        pointer-events: none;
    }

    /* ---- P3 · variant dials (§4) --------------------------------------
       One language, three inflections — proportion only. 墨 carries the
       strongest grain and heaviest wipe; 留白 the faintest and lightest;
       赤 runs warm between. Mark density needs no dial here: every page
       carries exactly the credit chop and the colophon, nothing more —
       the 留白 minimum. (index keeps its own inline grain baseline.) */
    html[data-variant="mo"] { --up-grain: 0.045; --up-wipe-ms: 1.15s; }
    html[data-variant="liubai"] { --up-grain: 0.038; --up-wipe-ms: 0.95s; }
    html[data-variant="chi"] { --up-grain: 0.05; --up-wipe-ms: 1.05s; }

    /* ---- P3 · the 羅 mark system (§6) ----------------------------------
       Injected by upgrade.js as inline SVG (outlined paths; currentColor
       carries the per-theme cut). 朱文 serves the paper pages; 白文 burns
       on the dark home page with the character in the page ground. */
    .dt-chop { display: inline-flex; color: var(--accent); line-height: 0; }
    .dt-chop svg { display: block; height: 46px; width: auto; }
    .dt-chop--baiwen { --seal-ground: var(--bg-primary, #0b0b0b); }

    /* the chop presses in as the block enters view: settle from 1.15x
       with a slight rotation correction, 120ms, once */
    .dt-chop {
        opacity: 0;
        transform: scale(1.15) rotate(-4deg);
        transition: opacity 0.12s var(--ease-out), transform 0.12s var(--ease-out);
        will-change: transform;
    }
    .dt-chop.is-stamped,
    .is-stamped > .dt-chop { opacity: 1; transform: none; }

    /* credit block: chop beside the credit column */
    .case-spec-row.dt-has-chop {
        display: grid;
        grid-template-columns: auto 1fr;
        align-items: center;
        column-gap: clamp(0.9rem, 1.4vw, 1.3rem);
        row-gap: 0.75rem;
    }
    .dt-has-chop .dt-chop { grid-row: 1 / 3; }
    .dt-has-chop dt { align-self: end; }
    .dt-has-chop dd { align-self: start; }

    /* 落款 colophon: the written form over the chop, above the copyright
       line — every page ends the way a handscroll ends. It stands in for
       the baseline lone-seal footer mark; mobile keeps that mark. */
    html.up-motion .about-footer-mark { display: none; }
    .dt-colophon {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: clamp(1.1rem, 1.6vw, 1.5rem);
        margin: clamp(2.6rem, 4vw, 3.6rem) auto clamp(2.2rem, 3.4vw, 3rem);
    }
    html.up-motion .footer-bottom { flex-wrap: wrap; }
    .footer-bottom .dt-colophon {
        flex-basis: 100%;
        margin: 1.2rem auto clamp(2.4rem, 3.4vw, 3rem);
    }
    .dt-colophon-brush { display: block; line-height: 0; color: #1d1d1f; }
    .dt-colophon-brush svg { display: block; height: clamp(68px, 6.2vw, 90px); width: auto; }
    body.home .dt-colophon-brush { color: #f5f0eb; }
    body.realm-night .dt-colophon-brush { color: var(--ink, #f4ecf6); }

    /* the ink settles first, then the seal presses */
    .dt-colophon .dt-colophon-brush { opacity: 0; transition: opacity 0.65s var(--ease-out); }
    .dt-colophon.is-stamped .dt-colophon-brush { opacity: 1; }
    .dt-colophon .dt-chop { transition-delay: 0.38s; }

    /* ---- P4 · 骨 (§7) — pointer-dependent, sub-200ms, reversible ------ */
    @media (hover: hover) and (pointer: fine) {

        /* Magnetic primary CTAs: upgrade.js drives --mx/--my inside a 6px
           radius; composing the pull through custom properties lets the
           stamp-press :active scale stack on top without a fight. Release
           returns on the house easing. */
        html.up-motion .dt-magnet {
            transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
            transition: transform 0.45s var(--ease-out);
            will-change: transform;
        }
        html.up-motion .dt-magnet.is-magnet { transition: none; }

        /* Stamp-press: the physical grammar of a seal meeting paper —
           compress with a brief vermilion edge, 120ms both ways */
        html.up-motion .cta-btn:active,
        html.up-motion .about-cta:active,
        html.up-motion .menu-resume:active,
        html.up-motion .about-guide-cta:active,
        html.up-motion .chat-send:active,
        html.up-motion .resume-action:active {
            transform: translate3d(var(--mx, 0px), var(--my, 0px), 0) scale(0.972);
            box-shadow: 0 0 0 2px var(--border);
            transition: transform 0.12s var(--ease-out), box-shadow 0.12s var(--ease-out);
        }

        /* Brush-stroke underlines: the nav bars become tapered strokes —
           scaleX from the left with the weight easing out toward the tail,
           an alpha taper standing in for the brush lifting off */
        html.up-motion .nav-desktop a::after,
        html.up-motion .dt-nav-contact::after {
            left: 0; right: 0; height: 2px;
            border-radius: 2px;
            transform: scaleX(0);
            transform-origin: left center;
            transition: none;
            -webkit-mask-image: linear-gradient(90deg, #000 72%, transparent 98%);
                    mask-image: linear-gradient(90deg, #000 72%, transparent 98%);
        }
        /* transitions arm only once the first frames have painted, so the
           mechanic swap (right-collapse → scaleX) can never settle-flash
           on load, in any embedding */
        html.up-ready .nav-desktop a::after,
        html.up-ready .dt-nav-contact::after {
            transition: transform 0.45s var(--ease-out);
        }
        html.up-motion .nav-desktop a:hover::after,
        html.up-motion .dt-nav-contact:hover::after,
        html.up-motion .nav-desktop a.is-current::after { transform: scaleX(1); }

        /* Ink-dot cursor: supplements the system cursor, never replaces
           it; opens into a ring over interactive elements. The wrap is
           positioned by the engine (lerped translate); the dot itself
           only ever scales, so the two transforms never fight. */
        .dt-cursor-wrap {
            position: fixed; top: 0; left: 0;
            pointer-events: none;
            z-index: 10010;
            will-change: transform;
        }
        .dt-cursor {
            width: 8px; height: 8px;
            margin: -4px 0 0 -4px;
            border-radius: 50%;
            background: var(--accent);
            border: 1.5px solid transparent;
            opacity: 0;
            transition: transform 0.18s var(--ease-out),
                        background 0.18s var(--ease-out),
                        border-color 0.18s var(--ease-out),
                        opacity 0.3s ease;
        }
        .dt-cursor.is-live { opacity: 0.85; }
        .dt-cursor.is-ring {
            transform: scale(3.1);
            background: transparent;
            border-color: var(--accent);
        }
        .dt-cursor.is-down { transform: scale(2.2); }

    }

}
