diff --git a/src/components/marketing/LensHero.astro b/src/components/marketing/LensHero.astro index fc1b1a8..393b4ea 100644 --- a/src/components/marketing/LensHero.astro +++ b/src/components/marketing/LensHero.astro @@ -1,9 +1,10 @@ --- /** - * The `lens-hero` block (src/blocks.ts): the brand lens as a living scene beside the headline. - * The lens follows the pointer, breathes, narrows as the hero scrolls away and takes the colour of - * the activity log's current line (lens.ts). Every word comes from the block's props. Without - * JavaScript, at motion "none" or under reduced motion it is a still lens with the whole log shown. + * The `lens-hero` block (src/blocks.ts): the brand lens as a page's opening scene. The lens sits + * between the headline and the actions and comes out of the dark: it lights up once, then its + * light slowly swells and fades, follows the pointer a little and dims as the page scrolls on + * (lens.ts). Under it, one line of the activity log at a time. Every word comes from the props. + * Without JavaScript, at motion "none" or under reduced motion: a still, lit lens and the whole log. */ import { external, href } from "./href.ts"; @@ -34,89 +35,79 @@ const parts = at >= 0 && props.highlight ? [props.heading.slice(0, at), props.highlight, props.heading.slice(at + props.highlight.length)] : [props.heading, "", ""]; - -// The instrument ring: 72 ticks, every sixth one long. -const ticks = Array.from({ length: 72 }, (_, i) => ({ angle: i * 5, long: i % 6 === 0 })); -// Fine radial fibres of the iris. -const fibres = Array.from({ length: 60 }, (_, i) => i * 6); const first = props.log[0]?.state ?? "watching"; ---
-
-
+
{props.eyebrow &&

{props.eyebrow}

}

{parts[0]}{parts[1] && {parts[1]}}{parts[2]}

- {props.body &&

{props.body}

} -

- {primary && {props.primaryLabel}} - {props.secondaryLabel && secondary && {props.secondaryLabel}} -

-
-

{props.logLabel}

-
    - {props.log.map((item, i) => ( -
  1. {item.text}
  2. - ))} -
-
+
+ {props.body &&

{props.body}

} +

+ {primary && {props.primaryLabel}} + {props.secondaryLabel && secondary && {props.secondaryLabel}} +

+
+

{props.logLabel}

+
    + {props.log.map((item, i) => ( +
  1. {item.text}
  2. + ))} +
+ +
diff --git a/src/components/marketing/lens.ts b/src/components/marketing/lens.ts index b2ac852..55e7fdb 100644 --- a/src/components/marketing/lens.ts +++ b/src/components/marketing/lens.ts @@ -1,19 +1,19 @@ /** - * The lens hero's motion (LensHero.astro): the lens follows the pointer through critically damped - * springs, one per depth layer, drifts on its own when the pointer is still or coarse, narrows as - * the hero scrolls away, and steps through the activity log, taking each line's state. One rAF - * loop per hero, stopped offscreen, on a hidden tab and once everything has settled. Nothing moves - * at motion "none", under reduced motion or after the reader pauses it. + * The lens hero's motion (LensHero.astro). The lens lights up once (CSS), then follows the pointer + * a little through critically damped springs, one per depth layer, drifts on its own when the + * pointer is still or coarse, and dims as the hero scrolls away (`--lens-exit`). Under it the + * activity log shows one line at a time. One rAF loop per hero, stopped offscreen, on a hidden tab + * and once settled. Nothing moves at motion "none", under reduced motion or after a pause. */ type Vec = { x: number; y: number }; /** A depth layer: how far it travels (viewBox units at full reach) and its time constant (ms). */ -type Layer = { el: SVGGraphicsElement | HTMLElement; reach: number; tau: number; pos: Vec }; +type Layer = { el: SVGGElement; reach: number; tau: number; pos: Vec }; const IDLE_AFTER = 2800; // ms without pointer movement before the lens drifts on its own -const STEP_EVERY = 2800; // ms per activity-log line -const HOLD_LAST = 4200; // ms the last line stays before the log starts again -const TAU_SCROLL = 160; +const STEP_EVERY = 3200; // ms per activity-log line +const HOLD_LAST = 4800; // ms the last line stays before the log starts again +const TAU_SCROLL = 180; function still(): boolean { const level = document.documentElement.dataset.chMotion; @@ -28,25 +28,22 @@ function mount(root: HTMLElement): void { if (still()) return; const q = (s: string) => root.querySelector(s); const svg = q(".mk-lens__svg"); - const eye = q(".mk-lens__eye"); + const body = q(".mk-lens__body"); const iris = q(".mk-lens__iris"); const pupil = q(".mk-lens__pupil"); - const stage = q(".mk-lens__stage"); const toggle = q("[data-lens-toggle]"); - const items = [...root.querySelectorAll(".mk-lens__log li")]; - if (!svg || !eye || !iris || !pupil || !stage) return; + const items = [...root.querySelectorAll(".mk-lens__status li")]; + if (!svg || !body || !iris || !pupil) return; const layers: Layer[] = [ - { el: svg, reach: 4, tau: 300, pos: { x: 0, y: 0 } }, - { el: eye, reach: 10, tau: 200, pos: { x: 0, y: 0 } }, - { el: iris, reach: 22, tau: 120, pos: { x: 0, y: 0 } }, - { el: pupil, reach: 12, tau: 110, pos: { x: 0, y: 0 } }, + { el: body, reach: 5, tau: 320, pos: { x: 0, y: 0 } }, + { el: iris, reach: 24, tau: 140, pos: { x: 0, y: 0 } }, + { el: pupil, reach: 10, tau: 110, pos: { x: 0, y: 0 } }, ]; const fine = matchMedia("(pointer: fine)").matches; let target: Vec = { x: 0, y: 0 }; let lastMove = -Infinity; let scroll = 0; - let pupilScale = 1; let visible = true; let paused = false; let raf = 0; @@ -69,17 +66,12 @@ function mount(root: HTMLElement): void { wake(); }; - /** Where the gaze goes when nobody is steering it: a slow Lissajous drift at 30% reach. */ + /** Where the gaze goes when nobody steers it: a slow Lissajous drift at 30% reach. */ const drift = (t: number): Vec => ({ x: 0.3 * Math.sin((t / 11000) * Math.PI * 2), y: 0.3 * Math.sin((t / 17000) * Math.PI * 2 + 1.3), }); - const stateScale = () => { - const s = root.dataset.state; - return s === "waiting" ? 1.16 : s === "working" ? 0.86 : 1; - }; - const frame = (now: number) => { raf = 0; const dt = Math.min(64, now - (last || now)); @@ -92,20 +84,12 @@ function mount(root: HTMLElement): void { l.pos.x = approach(l.pos.x, goal.x, dt, l.tau); l.pos.y = approach(l.pos.y, goal.y, dt, l.tau); if (Math.abs(l.pos.x - goal.x) + Math.abs(l.pos.y - goal.y) > 0.002) moving = true; + l.el.style.transform = `translate(${l.pos.x * l.reach}px, ${l.pos.y * l.reach}px)`; } const rect = root.getBoundingClientRect(); - const exit = Math.min(1, Math.max(0, -rect.top / (rect.height * 0.85))); + const exit = Math.min(1, Math.max(0, -rect.top / (rect.height * 0.8))); scroll = approach(scroll, exit, dt, TAU_SCROLL); if (Math.abs(scroll - exit) > 0.001) moving = true; - const wantPupil = stateScale() * (1 - 0.3 * scroll); - pupilScale = approach(pupilScale, wantPupil, dt, 220); - if (Math.abs(pupilScale - wantPupil) > 0.002) moving = true; - - const [housing, ring, irisL, pupilL] = layers as [Layer, Layer, Layer, Layer]; - housing.el.style.transform = `translate(${housing.pos.x * housing.reach}px, ${housing.pos.y * housing.reach}px)`; - ring.el.style.transform = `translate(${ring.pos.x * ring.reach}px, ${ring.pos.y * ring.reach}px)`; - irisL.el.style.transform = `translate(${irisL.pos.x * irisL.reach}px, ${irisL.pos.y * irisL.reach}px)`; - pupilL.el.style.transform = `translate(${pupilL.pos.x * pupilL.reach}px, ${pupilL.pos.y * pupilL.reach}px) scale(${pupilScale})`; root.style.setProperty("--lens-exit", scroll.toFixed(3)); if (moving && visible && !document.hidden) wake(); @@ -115,13 +99,9 @@ function mount(root: HTMLElement): void { if (!raf && visible && !document.hidden) raf = requestAnimationFrame(frame); } - /** Light the log up to `step`, give the lens that line's state, and schedule the next line. */ + /** Show log line `step`, give the lens its state, and schedule the next line. */ const show = () => { - items.forEach((li, i) => { - li.classList.toggle("is-done", i < step); - li.classList.toggle("is-on", i === step); - }); - root.style.setProperty("--lens-progress", ((step + 1) / items.length).toFixed(3)); + items.forEach((li, i) => li.classList.toggle("is-on", i === step)); const state = items[step]?.dataset.state; if (state) root.dataset.state = state; if (state === "live") { diff --git a/src/pages/[...path].astro b/src/pages/[...path].astro index 53216e2..4348507 100644 --- a/src/pages/[...path].astro +++ b/src/pages/[...path].astro @@ -234,7 +234,23 @@ const footer = footerMenu ? markActive(footerMenu, activeItem(footerMenu, activeRoute, ancestors)) : undefined; // RFC-0025: the page's header and footer, and the menus they name beyond primary and footer. -const chrome = resolveChrome(site, route.kind === "page" ? route.page : undefined); +const resolvedChrome = resolveChrome(site, route.kind === "page" ? route.page : undefined); +// cloudhal-marketing: a page that opens with the lens hero (a dark scene in every colour mode) gets +// the transparent header over it, as over a loud band (RFC-0025 overHero); lens-hero.css styles +// data-over="lens". +const first = route.kind === "page" ? route.page.sections?.[0] : undefined; +const opensWithLens = first?.kind === "component" && (first as { type?: string }).type === "lens-hero"; +const chrome = + opensWithLens && !resolvedChrome.header.hidden + ? { + ...resolvedChrome, + header: { + ...resolvedChrome.header, + transparent: true, + over: "lens" as unknown as typeof resolvedChrome.header.over, + }, + } + : resolvedChrome; const menus: Record = { primary: nav, footer }; const extraMenus = await Promise.all( chrome.menus diff --git a/src/styles/features/lens-hero.css b/src/styles/features/lens-hero.css index 83260ad..54998d8 100644 --- a/src/styles/features/lens-hero.css +++ b/src/styles/features/lens-hero.css @@ -1,131 +1,90 @@ /* - * The lens hero (src/components/marketing/LensHero.astro): a dark scene in every colour mode, so - * the lens glows the same way for every reader. Its own colours are fixed and checked against its - * own background; the brand role still drives the button. The iris stays the brand's orange and - * takes only a hint of each state; the arc, the glow and the log's dots carry the state colour. lens.ts writes the layer transforms, - * --lens-exit (0 → 1 as the hero scrolls away) and --lens-progress (the activity log's share). - * Two things loop (the breathing glow, the slow instrument ring); both stop when the reader - * pauses, at motion "none" and under reduced motion. + * The lens hero (src/components/marketing/LensHero.astro): a dark scene in every colour mode, so the + * lens glows the same way for every reader. The lens sits between the headline and the actions; + * its light and a field of faint rings spread over the whole hero, and its edge dissolves into the + * dark, so it seems to come out of the background. It lights up once, then the light swells and + * fades on a slow breath (two CSS loops: the breath and the halo). lens.ts writes the layer + * transforms and --lens-exit (0 → 1 as the hero scrolls away). Its own colours are fixed and + * checked against its own background; the brand role still drives the primary button. + * + * A page that opens with it gets the transparent header over it ([...path].astro, data-over + * "lens"), and its closing call to action and footer join the same dark, so the page starts and + * ends in one scene. */ .mk-lens { - --lens-bg: #0b0807; + --lens-bg: #070504; --lens-text: #f7efe9; --lens-muted: #c9bdb4; --lens-subtle: #a3968d; - --lens-line: rgb(246 195 173 / 0.16); - --lens-tint: #f6821f; - --lens-tint-alpha: 0; + --lens-line: rgb(246 195 173 / 0.14); --lens-exit: 0; - --lens-progress: 1; + --lens-size: min(72vw, 36vh, 420px); position: relative; isolation: isolate; overflow: clip; margin-inline: calc(50% - 50vw); - padding-block: clamp(2.5rem, 7vw, 6rem) clamp(3rem, 7vw, 6rem); + padding-block: calc(var(--ch-header-h, 4rem) + clamp(1rem, 3vh, 2.5rem)) clamp(2.5rem, 6vh, 4.5rem); + min-height: max(40rem, 100svh); + display: grid; + align-items: center; background: var(--lens-bg); color: var(--lens-text); } -.mk-lens[data-state="working"] { - --lens-tint: #5dbcc5; - --lens-tint-alpha: 0.18; -} -.mk-lens[data-state="waiting"] { - --lens-tint: #f2c14e; - --lens-tint-alpha: 0.16; -} -.mk-lens[data-state="live"] { - --lens-tint: #4cc38a; - --lens-tint-alpha: 0.18; -} - -/* The sky: warm haze and a faint instrument grid, moving with the scroll only. */ -.mk-lens__sky { - position: absolute; - inset: 0; - z-index: -1; - pointer-events: none; -} -.mk-lens__cloud { - position: absolute; - border-radius: 50%; - filter: blur(48px); - transform: translateY(calc(var(--lens-exit) * var(--depth, 0px))); -} -.mk-lens__cloud--a { - --depth: 80px; - inset: -20% 35% 45% -15%; - background: radial-gradient(closest-side, rgb(180 68 31 / 0.38), transparent); -} -.mk-lens__cloud--b { - --depth: 140px; - inset: 10% -10% -10% 45%; - background: radial-gradient(closest-side, rgb(246 130 31 / 0.22), transparent); -} -.mk-lens__cloud--c { - --depth: 40px; - inset: 55% 20% -40% 5%; - background: radial-gradient(closest-side, rgb(93 188 197 / 0.1), transparent); -} -.mk-lens__grid { - position: absolute; - inset: 0; - background: - repeating-linear-gradient(90deg, var(--lens-line) 0 1px, transparent 1px 56px), - repeating-linear-gradient(0deg, var(--lens-line) 0 1px, transparent 1px 56px); - opacity: 0.35; - mask-image: radial-gradient(60% 70% at 70% 45%, #000, transparent); -} .mk-lens__inner { - display: grid; - gap: clamp(1.5rem, 4vw, 3rem); + position: relative; + display: flex; + flex-direction: column; align-items: center; - max-width: 76rem; + max-width: 60rem; margin-inline: auto; padding-inline: 1rem; -} -@media (min-width: 900px) { - .mk-lens__inner { - grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); - min-height: min(78vh, 720px); - } + text-align: center; } +.mk-lens__top, +.mk-lens__bottom { + position: relative; + z-index: 1; +} .mk-lens__eyebrow { margin: 0 0 1rem; font-size: 0.8rem; font-weight: 600; - letter-spacing: 0.14em; + letter-spacing: 0.16em; text-transform: uppercase; color: #ffb98f; } .mk-lens__title { - margin: 0; - max-width: 15ch; + margin: 0 auto; + max-width: 20ch; font-family: var(--ch-font-display, inherit); - font-size: clamp(2.4rem, 5.4vw, 4.6rem); + font-size: clamp(2.3rem, 5.2vw, 4.4rem); font-weight: 700; - line-height: 1.02; + line-height: 1.03; letter-spacing: -0.035em; color: var(--lens-text); + text-wrap: balance; } .mk-lens__highlight { background: linear-gradient(95deg, #ffd8bd, #ff9a5c 45%, #f6821f); background-clip: text; color: transparent; } -.mk-lens__body { - margin: 1.25rem 0 0; - max-width: 34rem; - font-size: clamp(1.05rem, 1.6vw, 1.2rem); +.mk-lens__body-text { + margin: 0 auto; + max-width: 38rem; + font-size: clamp(1.02rem, 1.5vw, 1.18rem); line-height: 1.6; color: var(--lens-muted); + text-wrap: pretty; } .mk-lens__actions { display: flex; flex-wrap: wrap; + justify-content: center; gap: 0.75rem; - margin: 2rem 0 0; + margin: 1.5rem 0 0; } .mk-lens .mk-lens__ghost { border-color: rgb(247 239 233 / 0.35); @@ -135,91 +94,24 @@ border-color: var(--lens-text); } -/* The activity log: every line readable without JavaScript; lens.ts lights them in turn. */ -.mk-lens__log { - margin-top: 2.25rem; - max-width: 26rem; - padding: 1rem 1.25rem; - border: 1px solid var(--lens-line); - border-radius: var(--ch-radius-m, 0.75rem); - background: rgb(255 255 255 / 0.03); -} -.mk-lens__log-title { - margin: 0 0 0.5rem; - font-size: 0.75rem; - font-weight: 600; - letter-spacing: 0.12em; - text-transform: uppercase; - color: var(--lens-subtle); -} -.mk-lens__log ol { - display: grid; - gap: 0.4rem; - margin: 0; - padding: 0; - list-style: none; -} -.mk-lens__log li { - display: flex; - align-items: center; - gap: 0.6rem; - font-size: 0.95rem; - color: var(--lens-muted); - transition: color 400ms ease; -} -.mk-lens__dot { - flex: none; - width: 0.55rem; - height: 0.55rem; - border-radius: 50%; - background: #f6821f; - transition: transform 400ms ease, box-shadow 400ms ease; -} -.mk-lens__log li[data-state="working"] .mk-lens__dot { background: #5dbcc5; } -.mk-lens__log li[data-state="waiting"] .mk-lens__dot { background: #f2c14e; } -.mk-lens__log li[data-state="live"] .mk-lens__dot { background: #4cc38a; } -.mk-lens[data-live] .mk-lens__log li { - color: var(--lens-subtle); -} -.mk-lens[data-live] .mk-lens__log li.is-done { - color: var(--lens-muted); -} -.mk-lens[data-live] .mk-lens__log li.is-on { - color: var(--lens-text); -} -.mk-lens[data-live] .mk-lens__log li.is-on .mk-lens__dot { - transform: scale(1.35); - box-shadow: 0 0 0 4px rgb(255 255 255 / 0.08); -} - -/* The lens. */ +/* The lens and its light. */ .mk-lens__stage { position: relative; - justify-self: center; - width: min(84vw, 380px); + width: var(--lens-size); aspect-ratio: 1; - order: -1; - transform: translateY(calc(var(--lens-exit) * -15%)) scale(calc(1 + var(--lens-exit) * 0.06)); -} -@media (min-width: 900px) { - .mk-lens__stage { - width: min(44vw, 600px); - order: 0; - } + margin-block: clamp(0.25rem, 1.5vh, 1.25rem); + opacity: calc(1 - var(--lens-exit) * 0.7); } -.mk-lens__glow { +/* The light and the ring field spread over the whole hero, centred on the lens. */ +.mk-lens__halo { position: absolute; - inset: -18%; - border-radius: 50%; - background: radial-gradient( - closest-side, - color-mix(in oklab, var(--lens-tint) 55%, transparent), - color-mix(in oklab, var(--lens-tint) 18%, transparent) 55%, - transparent - ); - opacity: calc(1 - var(--lens-exit) * 0.6); - transition: background 800ms ease; - animation: mk-lens-breathe 6.5s ease-in-out infinite; + inset: -140%; + z-index: -1; + background: + radial-gradient(closest-side, rgb(246 130 31 / 0.34), rgb(180 68 31 / 0.14) 38%, transparent 70%), + repeating-radial-gradient(circle, transparent 0 63px, var(--lens-line) 63px 64px); + mask-image: radial-gradient(closest-side, #000 20%, transparent 72%); + pointer-events: none; } .mk-lens__svg { position: relative; @@ -227,104 +119,175 @@ width: 100%; height: 100%; overflow: visible; + /* Its edge dissolves into the dark: no hard rim against the scene. */ + mask-image: radial-gradient(closest-side, #000 80%, transparent 100%); } -.mk-lens__ticks line { - stroke: #f6c3ad; - stroke-opacity: 0.28; - stroke-width: 1.2; +.mk-lens__light, +.mk-lens__pupil { + transform-box: view-box; + transform-origin: 50% 50%; } -.mk-lens__ticks { - transform-origin: 0 0; - animation: mk-lens-turn 140s linear infinite; -} -.mk-lens__arc { - fill: none; - stroke: var(--lens-tint); - stroke-width: 2.4; - stroke-linecap: round; - stroke-dasharray: calc(var(--lens-progress) * 100) 100; - transform: rotate(-90deg); - opacity: 0.85; - transition: stroke-dasharray 900ms cubic-bezier(0.2, 0.7, 0.2, 1), stroke 800ms ease; -} -.mk-lens__fibres line { - stroke: #5a200c; - stroke-opacity: 0.22; - stroke-width: 1; -} -.mk-lens__fibres { - transform-origin: 0 0; - animation: mk-lens-iris 6.5s ease-in-out infinite; -} -.mk-lens__tint { - fill: var(--lens-tint); - opacity: var(--lens-tint-alpha); - mix-blend-mode: color; - transition: fill 800ms ease, opacity 800ms ease; -} -.mk-lens__shine { +.mk-lens__glass { fill: #ffffff; - opacity: 0.14; + opacity: 0.06; } .mk-lens__ping { fill: none; - stroke: #4cc38a; + stroke: #ffd2b3; stroke-width: 2; opacity: 0; transform-box: fill-box; transform-origin: center; } -.mk-lens.is-ping .mk-lens__ping { - animation: mk-lens-ping 1100ms cubic-bezier(0.2, 0.7, 0.2, 1) 1; -} -.mk-lens__pause { - position: absolute; - inset-block-end: 0; - inset-inline-end: 0; - padding: 0.35rem 0.75rem; - border: 1px solid var(--lens-line); - border-radius: 999px; - background: rgb(11 8 7 / 0.7); - color: var(--lens-muted); - font-size: 0.8rem; - cursor: pointer; +/* Lighting up, then breathing; the halo (outside the SVG's mask) breathes with it. */ +.mk-lens__svg { + animation: mk-lens-emerge 2.4s cubic-bezier(0.2, 0.6, 0.2, 1) both; } -.mk-lens__pause:hover { - color: var(--lens-text); +.mk-lens__light { + animation: + mk-lens-ignite 2.8s cubic-bezier(0.3, 0, 0.2, 1) both, + mk-lens-breathe 6s ease-in-out 2.8s infinite; } -.mk-lens__pause:focus-visible { - outline: 2px solid #ffb98f; - outline-offset: 2px; +.mk-lens__pupil { + animation: + mk-lens-ignite 2.8s cubic-bezier(0.3, 0, 0.2, 1) both, + mk-lens-breathe 6s ease-in-out 2.8s infinite; +} +.mk-lens__halo { + animation: + mk-lens-fade-in 3.2s ease-out both, + mk-lens-halo 6s ease-in-out 3.2s infinite; +} +.mk-lens.is-ping .mk-lens__ping { + animation: mk-lens-ping 1400ms cubic-bezier(0.2, 0.7, 0.2, 1) 1; } +@keyframes mk-lens-emerge { + from { + opacity: 0; + transform: scale(1.04); + } +} +@keyframes mk-lens-ignite { + 0%, + 25% { + opacity: 0; + transform: scale(0.6); + } +} @keyframes mk-lens-breathe { + 0%, + 100% { + opacity: 1; + transform: scale(1); + } 50% { - transform: scale(1.03); - opacity: calc(0.88 - var(--lens-exit) * 0.6); + opacity: 0.55; + transform: scale(0.94); } } -@keyframes mk-lens-iris { - 50% { - transform: rotate(4deg) scale(1.015); +@keyframes mk-lens-fade-in { + from { + opacity: 0; } } -@keyframes mk-lens-turn { - to { - transform: rotate(360deg); +@keyframes mk-lens-halo { + 50% { + opacity: 0.6; } } @keyframes mk-lens-ping { from { - opacity: 0.7; + opacity: 0.6; transform: scale(1); } to { opacity: 0; - transform: scale(1.5); + transform: scale(1.35); } } +/* The activity log: a list without JavaScript; one line at a time when lens.ts runs. */ +.mk-lens__status { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 0.5rem 0.75rem; + margin-top: 1.25rem; + font-size: 0.9rem; +} +.mk-lens__status-label { + margin: 0; + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--lens-subtle); +} +.mk-lens__status ol { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.35rem 1rem; + margin: 0; + padding: 0; + list-style: none; +} +.mk-lens__status li { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--lens-muted); +} +.mk-lens[data-live] .mk-lens__status ol { + display: grid; + padding: 0.4rem 0.9rem; + border: 1px solid var(--lens-line); + border-radius: 999px; + background: rgb(255 255 255 / 0.04); +} +.mk-lens[data-live] .mk-lens__status li { + grid-area: 1 / 1; + justify-content: center; + color: var(--lens-text); + opacity: 0; + transform: translateY(0.35rem); + transition: opacity 500ms ease, transform 500ms ease; +} +.mk-lens[data-live] .mk-lens__status li.is-on { + opacity: 1; + transform: none; +} +.mk-lens__dot { + flex: none; + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + background: #f6821f; +} +.mk-lens__status li[data-state="working"] .mk-lens__dot { background: #5dbcc5; } +.mk-lens__status li[data-state="waiting"] .mk-lens__dot { background: #f2c14e; } +.mk-lens__status li[data-state="live"] .mk-lens__dot { background: #4cc38a; } + +.mk-lens__pause { + padding: 0.3rem 0.7rem; + border: 1px solid var(--lens-line); + border-radius: 999px; + background: transparent; + color: var(--lens-subtle); + font-size: 0.78rem; + cursor: pointer; +} +.mk-lens__pause:hover { + color: var(--lens-text); +} +.mk-lens__pause:focus-visible { + outline: 2px solid #ffb98f; + outline-offset: 2px; +} + .mk-lens[data-paused] *, .mk-lens[data-paused] { animation-play-state: paused !important; @@ -336,7 +299,7 @@ transition: none !important; } .mk-lens__stage { - transform: none; + opacity: 1; } } :root[data-ch-motion="none"] .mk-lens *, @@ -344,3 +307,75 @@ animation: none !important; transition: none !important; } + +/* The header over the lens hero: transparent, light words; on a scrolled sticky header, dark glass. */ +.ch-header[data-transparent][data-over="lens"]:not([data-ch-scrolled]) { + color: #f7efe9; +} +.ch-header[data-transparent][data-over="lens"]:not([data-ch-scrolled]) :is(input, .ch-menu > summary) { + border-color: rgb(247 239 233 / 0.3); + background: rgb(255 255 255 / 0.06); + color: #f7efe9; +} +.ch-header[data-transparent][data-over="lens"]:not([data-ch-scrolled]) input::placeholder { + color: #c9bdb4; +} +.ch-header[data-transparent][data-over="lens"][data-ch-scrolled] { + background: rgb(7 5 4 / 0.82); + backdrop-filter: blur(14px); + color: #f7efe9; +} +/* The lens hero starts under the transparent header and makes its own room above its content. */ +.ch-header[data-transparent][data-over="lens"] ~ main { + padding-bottom: 0; +} +.ch-header[data-transparent][data-over="lens"] ~ main > :first-child { + margin-block-start: 0; + padding-block-start: 0; +} + +/* The page ends in the same dark: a closing cta-band right before the footer becomes a full-width + scene with the lens's light rising from below, and the footer continues it. */ +body:has(.mk-lens) main > .ch-block[data-block-type="cta-band"]:last-child { + position: relative; + isolation: isolate; + overflow: clip; + margin-inline: calc(50% - 50vw); + padding: clamp(4rem, 12vh, 7rem) 1rem clamp(3rem, 8vh, 5rem); + background: #070504; +} +body:has(.mk-lens) main > .ch-block[data-block-type="cta-band"]:last-child::before { + content: ""; + position: absolute; + inset: auto -20% -60% -20%; + height: 120%; + z-index: -1; + background: radial-gradient(closest-side, rgb(246 130 31 / 0.3), rgb(180 68 31 / 0.1) 45%, transparent 75%); +} +body:has(.mk-lens) main > .ch-block[data-block-type="cta-band"]:last-child .mk-cta { + max-width: 44rem; + margin-inline: auto; + background: transparent; + color: #f7efe9; + box-shadow: none; + border: 0; +} +body:has(.mk-lens) main > .ch-block[data-block-type="cta-band"]:last-child .mk-cta h2 { + font-size: clamp(2rem, 4.5vw, 3.25rem); + letter-spacing: -0.03em; + color: #f7efe9; +} +body:has(.mk-lens) main > .ch-block[data-block-type="cta-band"]:last-child .mk-cta p { + color: #c9bdb4; +} +body:has(.mk-lens) .ch-footer { + background: #070504; + color: #a3968d; + border-top: 1px solid rgb(246 195 173 / 0.12); +} +body:has(.mk-lens) .ch-footer a { + color: #c9bdb4; +} +body:has(.mk-lens) .ch-footer a:hover { + color: #f7efe9; +}