Skip to content

fix(website): reduce CPU-heavy homepage motion - #880

Merged
ndizazzo merged 2 commits into
mainfrom
codex/website-cpu-explainer
Jun 19, 2026
Merged

fix(website): reduce CPU-heavy homepage motion#880
ndizazzo merged 2 commits into
mainfrom
codex/website-cpu-explainer

Conversation

@ndizazzo

@ndizazzo ndizazzo commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • gate SVG motion by visibility
  • polish animation segments
  • perf-tune to 60 FPS target

Summary by CodeRabbit

  • New Features

    • Added dark-mode meta tags and mobile status bar styling.
    • Introduced a motion-aware hero animation system with Stage 2 timeline debug toggle and safer, accessibility-friendly scrubber behavior.
  • Improvements

    • Enhanced reduced-motion and visibility-based pause/resume behavior for diagram animations.
    • Refined Stage 2 hero/aperture visuals, animation timing, and scrubber visibility/input gating for smoother, more consistent rendering.
  • Bug Fixes

    • Prevented title reveal behavior while Stage 2 is active.

* gate SVG motion by visibility
* polish animation segments
* perf-tune to 60 FPS target
@ndizazzo ndizazzo self-assigned this Jun 19, 2026
@github-actions
github-actions Bot requested a review from i386 June 19, 2026 00:55
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR overhauls the stage2 scroll-driven hero animation system through a shared is-motion-active visibility pattern, lazy ambient animations with pause/resume control, a smoothed aperture visual state machine, comprehensive geometry caching with CSS quantization, flight-layer animation refactored to translate helpers, persistent debug scrubber controls with localStorage integration, and extensive CSS updates for model-layer cutter effects, aperture portal clip-path, final-hook glows, and topology animation gating.

Changes

Stage2 Hero Animation System Overhaul

Layer / File(s) Summary
Entry contracts and title reveal guards
website/src/_includes/components/document-head.njk, website/src/assets/title-reveal.js
Document head adds dark color-scheme and iOS status-bar meta tags; title-reveal blocks all scheduling and cancels queued reveals when is-stage2-active is set.
Motion visibility lifecycle across topology and plugin-architecture
website/src/assets/topology-switcher.js, website/src/assets/plugin-architecture.js
Topology-switcher adds bindMotionVisibility using IntersectionObserver and prefers-reduced-motion to toggle is-motion-active CSS class with change-event reactivity; plugin-architecture introduces pause/resume helpers, observer retention, paused-state tracking, and resize guards to manage timeline lifecycle.
Hero-viz subtitle loop and lazy ambient motion switch
website/src/assets/hero-viz.js
Routes array updated with new timing; ambient packets carry data-route-edge metadata; subtitle loop refactored from per-phrase timers to frame-budget-gated RAF scheduling with activation checks respecting reduced motion and visibility; createSvgMotionSwitch lazily builds animations and toggles pause/resume on mesh:hero-viz:svg-motion events; beginStableState wired to svgMotion.ready().
Aperture visual state machine and stage progress orchestration
website/src/assets/model-slicing.js
New aperture smoothing system with exponential-approach animation, settled detection, and RAF scheduling via syncApertureVisual with immediate-seek toggle; applyProgress reorganized with fine-grained aperture/hero/grid opacity intermediates; final-hook pulse queued via idle-delay; setStage2TitleProgress drives opacity and pulse sync; currentStageProgress tracked for consistent progress derivation.
Geometry caching and CSS value normalization
website/src/assets/model-slicing.js
Sticky metrics refactored to shared stickyMetrics() cache; CSS writes quantized and normalized via normalizeCssValue and setInlineStyle; hero viz transform and origin cached with update-only-if-changed checks; flight-layer and scroll bounds cached; node card placement clamped within viewport margins; localStorage and URL query flag helpers added for scrubber debug state.
Flight-layer and placement animation pipeline
website/src/assets/model-slicing.js
Flight-layer creation uses translate-based helper setters (setLayerTranslate, setLayerFlightMotion, setLayerFlightSize); animateLayerFromRect rewritten around cached flight-rect with translate updates via anime onUpdate; ensureAutoPlacement refactored to use placementTravelProgress easing and setLayerFlightFrame helper; placement reservation uses --stage2-layer-gap-before; translate clearing uses dedicated timers; slice timeline painting immediate; anime scroll observer gated behind feature flag; pointer-driven scrubber scheduling guarded by debug visibility.
Debug scrubber UI, controls, and public API extension
website/src/_includes/sections/hero.njk, website/src/assets/model-slicing.js
Debug toggle button added with switch role and accessibility labels; scrubber marked aria-hidden and disabled by default; initScrubberDebugControls wires localStorage and URL query persistence; syncScrubberAccessibility gates interaction; setScrubberDebugVisible/setScrubberDebugView extend public window.__meshStage2 debug API; init() caches panel elements and precomputes node slot/color lookup maps; reduced-motion disables debug visibility.
CSS foundation, catalog tables, and topology motion gating
website/src/assets/site.css
HTML gains min-height: 100% and background; safe-area bottom overlay for non-doc pages; top navigation hidden during active stage2; catalog table size column centered and badge aligned; mobile breakpoints adjusted to hide .col-ref and modify grid sizing; topology edges, mesh-core, and split return edges disable animation when is-motion-active; stage2 hero container sizing reset and variable initialization.
CSS stage2 foundation and model shell/layer visuals
website/src/assets/site.css
Introduces --stage2-model-detail-opacity base variable and redefines stage2 hero bridge container sizing with new viewport height; model-block opacity/transform consolidated with translateY, scale, blur; model-shell background transparent, scale tied to placement, shell::before opacity gated by detail opacity and lock progress; header and metrics opacity updated; new --stage2-layer-cutter-opacity/left/right variables for in-flight layers; in-flight overlay unconditionally opacity: 1; ::after replaced with gradient highlight bar using cutter clipping.
CSS aperture portal, final-hook glows, and scrubber UI
website/src/assets/site.css
Hero-viz overrides disable masks and filters when stage2 active but incomplete; aperture portal rewritten with full-viewport clip-path geometry; aperture ring hidden in settled mode, fixed when active; final-hook p > span updated with scale/translate3d and will-change; bigger keyword glow text-shadow and ::before radial gradient reworked; debug toggle UI styled with positioning and checked-state; scrubber hidden by default, shown via is-stage2-debug-scrubber-visible; hero min-height tuned across responsive breakpoints; reduced-motion suppresses cutter effects.

Sequence Diagram(s)

sequenceDiagram
  participant Viewport as IntersectionObserver
  participant Switcher as bindMotionVisibility
  participant Plugin as pauseDiagram/resumeDiagram
  participant Timeline as timeline state

  Viewport->>Switcher: entry.isIntersecting change
  Switcher->>Switcher: toggle is-motion-active class
  Viewport->>Plugin: out of view
  Plugin->>Timeline: pause
  Viewport->>Plugin: back in view
  Plugin->>Timeline: resume
Loading
stateDiagram-v2
  [*] --> Expanding: progress increases
  Expanding --> Settling: aperture reaches target radius
  Settling --> Settled: settled detection triggered
  Settled --> Expanding: progress changes again
  
  note right of Settling
    syncApertureVisual applies
    smooth easing via RAF
    calculates settled state
  end note
Loading
sequenceDiagram
  participant Anime as anime timeline
  participant FlightHelpers as translate helpers
  participant Placement as ensureAutoPlacement
  participant DOM as DOM layer element

  Anime->>Anime: onUpdate fired each frame
  Anime->>FlightHelpers: call setLayerFlightMotion(...)
  Anime->>FlightHelpers: call setLayerFlightSize(...)
  FlightHelpers->>DOM: apply translate/width/height
  Placement->>FlightHelpers: setLayerFlightFrame(travelProgress)
  Placement->>Placement: sync source magazine motion
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Mesh-LLM/mesh-llm#835: Both PRs modify stage2 aperture animation timing, aperture-active class toggling logic, and corresponding aperture/ring styles in model-slicing.js and site.css.
  • Mesh-LLM/mesh-llm#843: Both PRs refactor stage2 final-hook pulsing and word animation logic in model-slicing.js and adjust related styling in hero.njk and site.css.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective of the changeset: reducing CPU-heavy motion on the homepage through visibility gating and animation optimizations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/website-cpu-explainer

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
website/src/_includes/sections/hero.njk (1)

177-188: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Initialize scrubber input as non-focusable while container is aria-hidden.

aria-hidden="true" is set on .stage2-scrubber, but the nested range input starts focusable until JS sync runs. Set initial disabled and tabindex="-1" in markup to keep DOM/accessibility state consistent from first paint.

Suggested patch
-      <input class="stage2-scrubber-input" type="range" min="0" max="1000" value="0" aria-label="Stage 2 sequence position" aria-valuemin="0" aria-valuemax="1000" aria-valuenow="0">
+      <input class="stage2-scrubber-input" type="range" min="0" max="1000" value="0" aria-label="Stage 2 sequence position" aria-valuemin="0" aria-valuemax="1000" aria-valuenow="0" disabled tabindex="-1">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/src/_includes/sections/hero.njk` around lines 177 - 188, The range
input element with class stage2-scrubber-input is currently focusable even
though its parent container stage2-scrubber has aria-hidden="true", creating an
accessibility inconsistency. Add the disabled attribute and tabindex="-1" to the
stage2-scrubber-input element to ensure it remains non-focusable from initial
page load, keeping the DOM and accessibility state consistent throughout the
component lifecycle.
🧹 Nitpick comments (1)
website/src/assets/site.css (1)

1174-1184: ⚡ Quick win

Route the new stage2/debug colors through design tokens.

These changed blocks add raw hex/RGBA colors in theme-sensitive UI surfaces, which bypasses the dark-first token system and makes future palette changes harder. Prefer existing custom properties or define stage2-specific variables at the stage root.

As per coding guidelines, “Use CSS custom properties for theming in stylesheets” and “Use CSS custom properties matching the dark-first design system in src/assets/site.css.”

Also applies to: 6262-6312, 6540-6545, 6875-6880

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/src/assets/site.css` around lines 1174 - 1184, The
body:not(.docs-body)::after pseudo-element rule contains a hard-coded background
color of `#08090b` instead of using CSS custom properties from the design token
system. Replace the background property value with an appropriate CSS custom
property that aligns with the dark-first design system (such as an existing
token for background colors or a new stage2-specific variable if one doesn't
exist). Ensure any new custom properties are defined at the appropriate root
scope and follow the established naming conventions for CSS tokens in the
site.css file.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@website/src/assets/model-slicing.js`:
- Around line 620-625: In the heroSvgMotionResumeTimer callback function,
replace the stale latestScrollProgress variable with a fresh computation from
the current scroll bounds using the observer path. Instead of passing
latestScrollProgress directly to stageProgressFromScrollProgress, recompute the
scroll progress from the current scroll bounds before passing it to
stageProgressFromScrollProgress to ensure canRunHeroSvgMotion receives accurate
current scroll data.

In `@website/src/assets/plugin-architecture.js`:
- Around line 238-245: The `is-motion-active` class is added to the diagram
before animation playback begins, but the completion callback function passed to
timeline.call() does not remove this class after the observer is disconnected
and timeline is cleared. Add a statement to remove the `is-motion-active` class
from the diagram element within this callback, after the existing observer
cleanup code, so that motion-gated CSS styles do not remain active indefinitely
after the animation completes.

In `@website/src/assets/site.css`:
- Around line 5680-5686: Fix the stylelint errors in the opacity property
declarations in site.css. The multiline calc() function needs proper operator
placement formatting - ensure the multiplication and addition operators are
positioned according to stylelint rules for operator placement in calc()
expressions. Additionally, add empty lines before any non-custom property
declarations (like opacity) that appear after custom property declarations to
separate them properly. Apply these formatting fixes to the opacity property
with calc() at lines 5680-5686, and also to the similar opacity declarations at
lines 5817-5820 and 6539-6540.

In `@website/src/assets/title-reveal.js`:
- Line 130: The early return at the stage2 check in the title-reveal.js file
exits before canceling any pending title reveal timers, allowing previously
queued reveals to still execute during stage2. Move the timer cancellation logic
that clears any queued reveal timers before the early return statement when
is-stage2-active is detected, and also apply the same stage2 guard check before
any direct queueReveal calls to prevent them from being scheduled when stage2 is
already active.

In `@website/src/assets/topology-switcher.js`:
- Around line 116-143: The bindMotionVisibility function does not properly keep
the MOTION_ACTIVE_CLASS synchronized when the user changes their reduced motion
preference, and the early return prevents proper initialization. Refactor by
creating a separate sync() function that handles toggling MOTION_ACTIVE_CLASS
based on both the intersection observer visibility state and the
reduceMotion.matches state, then call this sync function from both the
IntersectionObserver callback entries.forEach and the reduceMotion
addEventListener change handler to ensure consistent synchronization with the
prefers-reduced-motion media query at all times.

---

Outside diff comments:
In `@website/src/_includes/sections/hero.njk`:
- Around line 177-188: The range input element with class stage2-scrubber-input
is currently focusable even though its parent container stage2-scrubber has
aria-hidden="true", creating an accessibility inconsistency. Add the disabled
attribute and tabindex="-1" to the stage2-scrubber-input element to ensure it
remains non-focusable from initial page load, keeping the DOM and accessibility
state consistent throughout the component lifecycle.

---

Nitpick comments:
In `@website/src/assets/site.css`:
- Around line 1174-1184: The body:not(.docs-body)::after pseudo-element rule
contains a hard-coded background color of `#08090b` instead of using CSS custom
properties from the design token system. Replace the background property value
with an appropriate CSS custom property that aligns with the dark-first design
system (such as an existing token for background colors or a new stage2-specific
variable if one doesn't exist). Ensure any new custom properties are defined at
the appropriate root scope and follow the established naming conventions for CSS
tokens in the site.css file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 83fef3bd-452a-4aaa-a85c-04a2562e7e5d

📥 Commits

Reviewing files that changed from the base of the PR and between 8260aa2 and 5020557.

📒 Files selected for processing (8)
  • website/src/_includes/components/document-head.njk
  • website/src/_includes/sections/hero.njk
  • website/src/assets/hero-viz.js
  • website/src/assets/model-slicing.js
  • website/src/assets/plugin-architecture.js
  • website/src/assets/site.css
  • website/src/assets/title-reveal.js
  • website/src/assets/topology-switcher.js

Comment thread website/src/assets/model-slicing.js
Comment thread website/src/assets/plugin-architecture.js
Comment thread website/src/assets/site.css
Comment thread website/src/assets/title-reveal.js Outdated
Comment thread website/src/assets/topology-switcher.js
@ndizazzo
ndizazzo requested a review from michaelneale June 19, 2026 03:41
@ndizazzo
ndizazzo force-pushed the codex/website-cpu-explainer branch from 020545b to 7e95806 Compare June 19, 2026 05:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
website/src/assets/model-slicing.js (1)

2833-2833: 💤 Low value

Consider using progressFromScroll() for consistency with the SVG motion resume fix.

This fallback still uses latestScrollProgress, which can be stale when using the scroll observer. The primary currentStageProgress path is usually populated, but for consistency with the fix at line 638, consider:

-      stageProgress = currentStageProgress || stageProgressFromScrollProgress(latestScrollProgress);
+      stageProgress = currentStageProgress || stageProgressFromScrollProgress(progressFromScroll());

The risk is low since currentStageProgress is typically populated before this runs, but fresh computation is more reliable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/src/assets/model-slicing.js` at line 2833, The fallback logic in the
line with the stageProgress assignment is using latestScrollProgress which can
be stale when the scroll observer is active. For consistency with the SVG motion
resume fix applied at line 638, replace the fallback from
stageProgressFromScrollProgress(latestScrollProgress) with a call to
progressFromScroll() instead. This ensures that fresh computation is used rather
than potentially stale scroll progress values, maintaining consistency across
the codebase even though currentStageProgress is typically populated before this
line executes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@website/src/assets/model-slicing.js`:
- Line 2833: The fallback logic in the line with the stageProgress assignment is
using latestScrollProgress which can be stale when the scroll observer is
active. For consistency with the SVG motion resume fix applied at line 638,
replace the fallback from stageProgressFromScrollProgress(latestScrollProgress)
with a call to progressFromScroll() instead. This ensures that fresh computation
is used rather than potentially stale scroll progress values, maintaining
consistency across the codebase even though currentStageProgress is typically
populated before this line executes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 77744a4d-13aa-4adb-88e9-f39b51e02d5e

📥 Commits

Reviewing files that changed from the base of the PR and between 020545b and 7e95806.

📒 Files selected for processing (6)
  • website/src/_includes/sections/hero.njk
  • website/src/assets/model-slicing.js
  • website/src/assets/plugin-architecture.js
  • website/src/assets/site.css
  • website/src/assets/title-reveal.js
  • website/src/assets/topology-switcher.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • website/src/_includes/sections/hero.njk
  • website/src/assets/topology-switcher.js
  • website/src/assets/plugin-architecture.js
  • website/src/assets/site.css

@ndizazzo
ndizazzo merged commit de48d55 into main Jun 19, 2026
21 checks passed
@ndizazzo
ndizazzo deleted the codex/website-cpu-explainer branch June 19, 2026 05:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants