/* Shared visual layer, loaded after this site's own stylesheet.
   It re-tokenises the palette, hosts the 3D scene canvases and restyles the
   shared surfaces. Every existing component rule keeps working underneath;
   nothing here replaces them. */

/* This block used to declare only the surface and text tokens. styles.css
   carries a full light theme under @media (prefers-color-scheme: light), and
   the tokens left undeclared here kept resolving to it: on a light-mode
   machine the cards went dark from this file while --link, --eyebrow and the
   shadows stayed light. The "See capabilities" links came out at 3.55:1
   against their own card.

   Every theme token is declared here now, so one palette governs the site and
   the light block underneath cannot leak through a gap. The same defect was
   fixed on techworkslab.com and techworkslab.in; it is worth checking for
   whenever a sheet overrides :root after another one. */
:root {
    color-scheme: dark;
    --bg: #0a0817;
    --bg-soft: #100d22;
    --surface: #12102a;
    --surface-2: #181633;
    --field-bg: #0f0d20;
    --line: rgba(160,150,235,.13);
    --accent: #7c6cff;
    --accent-bright: #9d90ff;
    --accent-warm: #3ad6ff;
    --heading: #f2f0ff;
    --text: #eae7ff;
    --text-dim: #a9a2d4;
    --link: #b5aaff;
    --eyebrow: #3ad6ff;
    --tick: #3ad6ff;
    --glow: rgba(124,108,255,.16);
    --shadow: 0 1px 2px rgba(4,2,14,.55), 0 12px 32px rgba(4,2,14,.5);
    --shadow-lg: 0 24px 60px rgba(4,2,14,.62);
    --card-glow: 0 0 0 1px rgba(157,144,255,.35), 0 16px 48px rgba(124,108,255,.2);
    --aero-bg:#0a0817;
    --aero-text:#cbc6f0;
    --aero-signal:#3ad6ff;
    --aero-grid:rgba(150,140,240,.05);
    --scene-ease: cubic-bezier(.16, 1, .3, 1);
    --scene-r: 8px;
}

html { scroll-behavior: smooth; }

body {
    background: var(--aero-bg, #080d16);
    color: var(--aero-text, #cbd5ec);
    -webkit-font-smoothing: antialiased;
}

/* the faint engineering grid that sits under everything */
body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image:
        linear-gradient(var(--aero-grid, rgba(130,160,230,.045)) 1px, transparent 1px),
        linear-gradient(90deg, var(--aero-grid, rgba(130,160,230,.045)) 1px, transparent 1px);
    background-size: 72px 72px;
    -webkit-mask-image: radial-gradient(ellipse 120% 80% at 50% 0%, #000 25%, transparent 75%);
    mask-image: radial-gradient(ellipse 120% 80% at 50% 0%, #000 25%, transparent 75%);
}

h1, h2, h3, h4 { text-wrap: balance; letter-spacing: -0.03em; }

/* ------------------------------------------------------------- scenes --- */

/* Sections that host a canvas. The container must outrank the scrim: a
   container with its own z-index creates a stacking context that a child
   cannot escape, which is what put an earlier scrim on top of its own copy. */
.scene-host { position: relative; overflow: hidden; }
.scene-host > .container,
.scene-host > .container-narrow { position: relative; z-index: 3; }

.scene-host > canvas[data-scene] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
    opacity: 0;
    pointer-events: none;
    transition: opacity 1.5s var(--scene-ease);
    /* each host overrides this to say where its copy sits */
    --scene-mask: radial-gradient(135% 115% at 50% 50%, transparent 0%, rgba(0, 0, 0, .5) 42%, #000 82%);
    -webkit-mask-image: var(--scene-mask);
    mask-image: var(--scene-mask);
}

.scene-host > canvas[data-scene].live { opacity: .62; }

/* alternate which side the geometry occupies down the page, so scrolling
   reads as one continuous object rather than a stack of unrelated panels */
.scene-host:nth-of-type(odd) > canvas[data-scene] {
    --scene-mask: radial-gradient(82% 125% at 88% 50%, #000 8%, rgba(0, 0, 0, .55) 40%, transparent 68%);
}

.scene-host:nth-of-type(even) > canvas[data-scene] {
    --scene-mask: radial-gradient(82% 125% at 12% 50%, #000 8%, rgba(0, 0, 0, .55) 40%, transparent 68%);
}

/* only a whisper of vignette now , the mask does the legibility work */
.scene-host::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: radial-gradient(128% 98% at 50% 45%, transparent 36%, var(--aero-bg, #080d16) 94%);
}

/* the landing hero reads left, so the geometry is cleared off the copy column */
.hero.scene-host > canvas[data-scene].live,
.hero-new.scene-host > canvas[data-scene].live { opacity: .92; }

.hero.scene-host > canvas[data-scene],
.hero-new.scene-host > canvas[data-scene] {
    --scene-mask: radial-gradient(105% 118% at 82% 46%, #000 18%, rgba(0, 0, 0, .62) 48%, transparent 74%);
}

.hero.scene-host::after,
.hero-new.scene-host::after {
    background: linear-gradient(100deg, var(--aero-bg, #080d16) 2%,
        rgba(8, 13, 22, .55) 34%, rgba(8, 13, 22, .12) 62%, transparent 82%);
}

.page-hero.scene-host > canvas[data-scene].live,
.page-header.scene-host > canvas[data-scene].live { opacity: .66; }

.page-hero.scene-host > canvas[data-scene],
.page-header.scene-host > canvas[data-scene] {
    --scene-mask: radial-gradient(95% 130% at 84% 50%, #000 12%, rgba(0, 0, 0, .5) 46%, transparent 72%);
}

/* An article body is a tall column of prose with the scene running its whole
   length, so the mask clears a vertical channel rather than a pocket. The
   geometry survives in the margins and drifts past as the reader scrolls. */
.article.scene-host > canvas[data-scene],
.blog-content.scene-host > canvas[data-scene] {
    --scene-mask: linear-gradient(90deg, #000 0%, rgba(0, 0, 0, .5) 17%, transparent 30%,
                                          transparent 70%, rgba(0, 0, 0, .5) 83%, #000 100%);
}
.article.scene-host > canvas[data-scene].live,
.blog-content.scene-host > canvas[data-scene].live { opacity: .5; }

/* hero copy is the one place type sits over live geometry, so it carries its
   own shadow , cheap insurance against whatever the scene draws behind it */
.hero h1, .hero-new h1, .page-hero h1, .page-header h1,
.hero .hero-lede, .hero-new .hero-lede,
.hero .eyebrow, .hero-new .eyebrow,
.hero-text h1, .hero-text p {
    text-shadow: 0 2px 26px rgba(4, 6, 14, .92), 0 1px 4px rgba(4, 6, 14, .7);
}

/* --------------------------------------------------------- long-form --- */

/* Body copy in the insights pieces is justified to both margins.

   Justification without hyphenation is worse than ragged-right, not better:
   the browser can only stretch the word spaces, so a long word forces a line
   of wide gaps and those gaps line up down the column as rivers. `hyphens:
   auto` lets it break words instead, which is what makes the spacing even, and
   it needs the lang attribute on <html> to pick a dictionary , every page here
   has lang="en".

   Only paragraphs. Headings, list items, pull quotes, captions and anything in
   a table stay ragged: they are short enough that justifying them produces the
   stretched-out lines this is meant to avoid. The pre/code exclusion matters
   more here than on the other sites , these pieces are about evaluation
   harnesses and retrieval, and justifying preformatted text would destroy the
   alignment that is the whole reason for showing it. */
.article-prose p,
.article p {
    text-align: justify;
    -webkit-hyphens: auto;
    hyphens: auto;
    hyphenate-limit-chars: 7 3 3;
}

.article-prose pre,
.article-prose pre p,
.article-prose code,
.article pre,
.article code {
    text-align: left;
    -webkit-hyphens: none;
    hyphens: none;
}

/* A narrow column cannot absorb justification , under about 45 characters
   there are too few word spaces per line to distribute the slack, and the
   result is gappy however good the hyphenation is. Phones get ragged-right. */
@media (max-width: 640px) {
    .article-prose p,
    .article p {
        text-align: left;
        -webkit-hyphens: none;
        hyphens: none;
    }
}

/* ------------------------------------------------------- micro-motion --- */

/* Gated behind .js, set on <html> before anything else can throw. */
.js [data-reveal] {
    opacity: 0;
    transform: translateY(24px);
    transition: opacity .85s var(--scene-ease), transform .85s var(--scene-ease);
}

.js [data-reveal].in { opacity: 1; transform: none; }

:focus-visible {
    outline: 2px solid var(--aero-signal, #3fd6a4);
    outline-offset: 3px;
    border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
    .js [data-reveal] { opacity: 1 !important; transform: none !important; }
    canvas[data-scene] { display: none !important; }
}
