/* 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. */

/* These used to be a violet set: background #0e0b1c, text #d6cfee, an amber
   #fbbf24 signal and a purple grid. None of it belonged to this site. EDUSHARK
   is teal and navy — #1a5f7a / #159895 / #57c5b6 over #002b5b — and its own dark
   theme in modern.css paints the body #0d1220. The shared layer loads last and
   won, so the page sat on a purple that agreed with neither, and every section
   on top of it was navy or white. A third colour scheme arriving from a shared
   file is the same class of mistake as a half-overridden palette: what shows
   through the gaps has to be the site's colour, not the layer's. */
:root {
    color-scheme: dark;
    --aero-bg:#0d1220;
    --aero-text:#dde5f0;
    --aero-signal:#3ecfc9;
    --aero-grid:rgba(120,200,210,.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%);
}

/* The vignette here darkened the edges of a section towards --aero-bg. That is
   invisible on the dark hero and footer, and on this site most sections are
   white: a dark ring around a white panel came out as grey fog with a bright
   blown-out centre, which is what made the middle of the page look unfinished.

   The mask on the canvas already keeps the copy clear, so the vignette is only
   applied where the section behind it is genuinely dark. */
.scene-host::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
}

.hero.scene-host::after,
.hero-new.scene-host::after,
.section-dark.scene-host::after,
.blog-header.scene-host::after,
footer .scene-host::after {
    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 --- */

/* Blog body copy justified to both margins.

   Justification without hyphenation is worse than ragged-right: the browser
   can only stretch word spaces, so one long word gives a line of wide gaps and
   those gaps align down the column as rivers. `hyphens: auto` lets it break
   words instead, and it needs lang on <html> to choose a dictionary — every
   page here is lang="en".

   Paragraphs only. Headings, list items, callouts and table cells stay ragged
   because they are too short to distribute slack across. Code blocks are
   excluded outright: justifying preformatted text would wreck the alignment
   that is the whole point of showing it. */
.blog-content p,
.article p {
    text-align: justify;
    -webkit-hyphens: auto;
    hyphens: auto;
    hyphenate-limit-chars: 7 3 3;
}

.blog-content pre,
.blog-content pre p,
.blog-content code,
.blog-content .code-block,
.blog-content .code-block p {
    text-align: left;
    -webkit-hyphens: none;
    hyphens: none;
}

/* Too narrow to justify: under about 45 characters a line has too few word
   spaces to absorb the slack and the result is gappy however it is hyphenated. */
@media (max-width: 640px) {
    .blog-content 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; }
}

/* ---------------------------------------------------------------------------
   Readability corrections for light surfaces.

   aero.css paints body text --aero-text (#dde5f0) so it reads against the dark
   backdrop above. Any component that lays a LIGHT surface over that backdrop
   and does not declare its own text colour inherits the pale value, and pale
   text on a white card is invisible. Measured on courses.html, the "What You
   Get" list was running at 1.27:1 -- the text was there and simply could not
   be seen.

   The fix is to state the ink wherever the surface is stated. These rules sit
   last in the cascade so they win on source order at equal specificity, with
   no !important anywhere.
   --------------------------------------------------------------------------- */
.benefit-item,
.value-item,
.graduate-item,
.deliver-item,
.eligibility-item,
.capstone-category,
.faq-item,
.praxia-use-case {
    color: #333;
}

.benefit-item .benefit-icon {
    color: #0d7b78;          /* 5.10 on white, was 3.53 */
}

/* Type sitting on the brand gradient or on the secondary fill. #159895 gave
   white text 3.28:1; this keeps the hue and clears 4.5. */
:root {
    --secondary-color: #0d7b78;
}

/* A mid-teal tick on the dark navy hero panel read 2.01:1. The panel is dark,
   so the tick has to come from the light end of the palette. */
.highlight-icon {
    color: #8fdfd5;          /* 4.62 on the navy panel, was 2.01 */
}

/* Badges whose text and translucent fill were drawn from the same token, so
   the text sat on a tint of itself. */
.hero-badge {
    color: #d5f5f0;          /* 4.68 on its own composited tint, was 2.58 */
}

.praxia-hero-badge {
    color: #3a1e92;          /* indigo on the pale tint used on the home page */
}

/* The same badge in the Praxia hero sits on deep indigo, so it needs the
   opposite end. Scoping matters here: a single colour cannot serve both
   placements, and the first version of this fix made the hero badge worse. */
.praxia-hero .praxia-hero-badge {
    color: #fbe3a0;          /* light gold on the indigo hero */
}

.praxia-hero-card-title {
    color: #f5c542;          /* gold on the translucent hero card */
}

/* The call-to-action band carries near-white type, so its fill stays dark.
   Indigo rather than the old dark teal, to match the new brand. */
.praxia-cta,
.praxia-download {
    --praxia-cyan: #3b1f8f;
}

/* A navy glyph on the green preview tile: 1.38:1. */
.blog-preview-icon {
    color: #ffffff;
}

/* The active nav item sits on a 10% tint of itself, which landed at 4.49 --
   just under. A shade darker clears it without changing the look. */
.nav-links a.active {
    color: #175668;
}

/* A white wash over the brand teal lightens the panel and drags near-white
   type down with it: .usp-point at rgba(255,255,255,.2) measured 3.29:1. A
   dark wash separates the panel from the section without fighting the text. */
.usp-point {
    background: rgba(0, 0, 0, 0.10);   /* 5.60, was 3.29 */
}

.cta-details {
    background: rgba(0, 0, 0, 0.20);   /* 5.02, was 3.59 */
}

/* --blog-theme-color is only set on blog posts. Elsewhere the header row lost
   its background entirely and kept its pale text: white on white, 1.07:1.
   Stated here as well as in blog.css because this sheet loads last on the
   pages that do not include blog.css at all. */
.comparison-table th {
    background: var(--blog-theme-color, #0d7b78);
    color: #ffffff;
}

/* Phase labels in the lesson sidebar measured 4.38 on their near-white rail --
   close, and still short of 4.5. */
.ls-nav .ph {
    color: #626e79;
}

/* White on the amber series badge measured 3.33. Same hue, deeper. */
.challenge-series-badge {
    background: #9c6d21;
}

/* Mid-accent links on the navy band: 3.40. Take the light end, as the tick
   marks in the hero already do. */
.cta-band a,
.blog-cta a,
.blogs-cta a,
.content-cta .cta-contact a {
    color: #8fdfd5;
}

/* Footer social row. The site previously carried no social links at all, so
   the videos embedded in the lessons were a dead end.

   Colours are stated outright rather than dimmed with opacity. Opacity blends
   text toward whatever is behind it, so the real contrast stops being knowable
   from the stylesheet, and this footer sits on a dark navy where that matters. */
.footer-social{display:flex;align-items:center;gap:.85rem;flex-wrap:wrap;
  padding:0 0 1.15rem;margin:0 0 .35rem}
.footer-social .fs-label{font-size:.78rem;letter-spacing:.09em;
  text-transform:uppercase;font-weight:600;color:#b6c6d8}
.footer-social a{display:inline-flex;align-items:center;gap:.45rem;
  font-size:.9rem;text-decoration:none;color:#e8eef6;
  transition:color .15s ease,transform .15s ease}
.footer-social a:hover,.footer-social a:focus-visible{color:#fff;transform:translateY(-1px)}
.footer-social svg{width:1.05rem;height:1.05rem;fill:currentColor;flex:0 0 auto}
