motion: adopt house-motion-system - token block (DUR/EASE/STAGGER/SCRUB) with zero inline values, staged masthead intro entrance with opacity-only reduced branch, CSS duration vars shared by markup and script
This commit is contained in:
+39
-5
@@ -766,6 +766,7 @@
|
||||
:root{
|
||||
--void:#070b14; --ink:#c9d7ef; --dim:#7286a8; --line:#1b2740;
|
||||
--cyan:#3fbaf5; --magenta:#e879f9; --amber:#e8b44a;
|
||||
--dur-fast:.2s; --dur-base:.35s; --dur-slow:.6s; /* house-motion-system tokens */
|
||||
--sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
||||
--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
||||
}
|
||||
@@ -834,7 +835,7 @@ b{color:var(--ink)} .mono{font-family:var(--mono);font-size:.9em;color:var(--cya
|
||||
|
||||
.legend{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:13px}
|
||||
.legend li{padding-left:22px;border-left:1px solid var(--line);
|
||||
opacity:.34;transition:opacity .35s ease,border-color .35s ease}
|
||||
opacity:.34;transition:opacity var(--dur-base) ease,border-color var(--dur-base) ease}
|
||||
.legend li.lit{opacity:1;border-left-color:var(--cyan)}
|
||||
.legend .t{font-size:.92rem;font-weight:600;margin:0 0 .22rem;letter-spacing:-.01em}
|
||||
.legend .b{font-size:.76rem;line-height:1.5;color:var(--dim);margin:0}
|
||||
@@ -847,7 +848,8 @@ b{color:var(--ink)} .mono{font-family:var(--mono);font-size:.9em;color:var(--cya
|
||||
color:var(--ink);margin:0;white-space:nowrap;padding-bottom:.12em}
|
||||
.crt .line.ok{color:var(--dim)}
|
||||
.crt .line.q{color:var(--magenta);margin-top:.5rem}
|
||||
body.fx .crt .line{opacity:0;transform:translateY(4px);transition:opacity .3s ease,transform .3s ease}
|
||||
body.fx .crt .line{opacity:0;transform:translateY(4px);
|
||||
transition:opacity var(--dur-base) ease,transform var(--dur-base) ease}
|
||||
body.fx .crt .line.on{opacity:1;transform:none}
|
||||
@media (max-width:700px){.crt{inset:10% 8%;gap:.2rem}.crt .line{line-height:1.7}}
|
||||
|
||||
@@ -900,6 +902,13 @@ body:not(.fx) .slab{display:none}
|
||||
<script>
|
||||
(() => {
|
||||
"use strict";
|
||||
// House motion tokens (skill: house-motion-system). Durations in seconds; nothing inline below.
|
||||
const TOK = {
|
||||
DUR: { fast: 0.2, base: 0.35, slow: 0.6, epic: 1.6 },
|
||||
EASE: { enter: "power3.out", move: "power2.inOut", hover: "power1.out", scrub: "none" },
|
||||
STAGGER: { tight: 0.03, base: 0.06, loose: 0.12 },
|
||||
SCRUB: 0.6
|
||||
};
|
||||
const SCENES = { surround: __MAN_SURROUND__, docker: __MAN_DOCKER__, cluster: __MAN_CLUSTER__, node: __MAN_NODE__, pod: __MAN_POD__, traffic: __MAN_TRAFFIC__, orbit: __MAN_ORBIT__, gitops: __MAN_GITOPS__, supply: __MAN_SUPPLY__, helm: __MAN_HELM__ };
|
||||
const RM = matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
const units = [];
|
||||
@@ -920,6 +929,30 @@ body:not(.fx) .slab{display:none}
|
||||
units.push({ sec, man, scene, flat, legend });
|
||||
});
|
||||
|
||||
// ---- intro (class: entrance) --------------------------------------------------------
|
||||
// Staged masthead reveal. from() sets the start state at runtime, so with JS off the
|
||||
// masthead is simply visible - no opacity:0 baked into CSS.
|
||||
if (window.gsap) {
|
||||
const intro = [...document.querySelectorAll(
|
||||
".masthead .eyebrow, .masthead h1, .masthead .lede, .masthead .scrollcue")];
|
||||
if (intro.length) {
|
||||
if (RM) {
|
||||
// reduced: opacity only, fast, no movement
|
||||
gsap.from(intro, { opacity: 0, duration: TOK.DUR.fast, ease: TOK.EASE.enter });
|
||||
} else {
|
||||
gsap.from(intro, {
|
||||
opacity: 0, y: 20, duration: TOK.DUR.slow, ease: TOK.EASE.enter,
|
||||
stagger: TOK.STAGGER.loose
|
||||
});
|
||||
// the rule under the scroll cue draws itself once the words have landed
|
||||
gsap.from(".masthead .scrollcue span", {
|
||||
scaleY: 0, transformOrigin: "top center", duration: TOK.DUR.slow,
|
||||
ease: TOK.EASE.move, delay: TOK.STAGGER.loose * 4
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reduced motion / no GSAP: leave the flat exploded hero in place (it IS the end state).
|
||||
if (RM || !window.gsap || !window.ScrollTrigger) return;
|
||||
document.body.classList.add("fx");
|
||||
@@ -981,7 +1014,7 @@ body:not(.fx) .slab{display:none}
|
||||
u.mode = "pin+scrub";
|
||||
ScrollTrigger.create({
|
||||
trigger: u.sec.querySelector(".pin"),
|
||||
start: "top top", end: "+=1800", pin: true, scrub: 0.6,
|
||||
start: "top top", end: "+=1800", pin: true, scrub: TOK.SCRUB,
|
||||
onToggle: self => wc(u, self.isActive),
|
||||
onUpdate: self => u.apply(self.progress),
|
||||
onRefreshInit: () => u.apply(0)
|
||||
@@ -995,7 +1028,7 @@ body:not(.fx) .slab{display:none}
|
||||
ScrollTrigger.create({
|
||||
trigger: u.scene, start: "top 80%", once: true,
|
||||
onEnter: () => { wc(u, true); gsap.to({ v: 0 }, {
|
||||
v: 1, duration: 1.6, ease: "power3.out",
|
||||
v: 1, duration: TOK.DUR.epic, ease: TOK.EASE.enter,
|
||||
onUpdate: function () { u.apply(this.targets()[0].v); },
|
||||
onComplete: () => wc(u, false) }); }
|
||||
});
|
||||
@@ -1009,7 +1042,8 @@ body:not(.fx) .slab{display:none}
|
||||
const io = new IntersectionObserver(es => {
|
||||
if (!es.some(x => x.isIntersecting)) return;
|
||||
io.disconnect();
|
||||
lines.forEach((l, i) => setTimeout(() => l.classList.add("on"), 350 * i + 200));
|
||||
lines.forEach((l, i) => setTimeout(() => l.classList.add("on"),
|
||||
TOK.DUR.base * 1000 * i + TOK.DUR.fast * 1000));
|
||||
}, { threshold: 0.4 });
|
||||
io.observe(crt);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user