Skip to content

feat: animated, procedurally generated page background - #183

Merged
BIA3IA merged 5 commits into
mainfrom
feat/animated-background
Oct 20, 2026
Merged

BIA3IA merged 5 commits into
mainfrom
feat/animated-background

Conversation

@toto04

@toto04 toto04 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

Reworks the decorative background. The hand-placed, per-page shapes.tsx files are replaced with a single <Background /> in the root layout, so every page uses the same components and behaves the same way.

The look matches the existing design. Each screen-tall segment of the page gets a teal star overlapping blue blobs, crossed by a looper, plus a smaller counterweight on the other side. Sides alternate between segments. Positions, sizes and rotations come from a seed derived from the pathname, so each page gets its own variation that doesn't change between reloads.

Animations

Everything runs from a single requestAnimationFrame loop (src/components/background/animate.ts) using plain sine math, so it behaves the same in every browser. It's disabled under prefers-reduced-motion.

  • Passive motion: each shape drifts on x and y, sways and breathes, each with its own period and phase, so the motion never visibly loops.
  • Scroll:
    • Parallax: each shape has its own depth. Loopers lag behind, small blobs move ahead, so shapes slide relative to each other.
    • Rotation: shapes rotate slightly as you scroll.
    • Inertia: scroll is smoothed, which gives the motion a bit of inertia.
    • Looper fan: it opens as the looper crosses the viewport, from tight lines (about 4.4° apart) when it enters to wide ones (about 7.5°) when it leaves. The original design is 6°. A subtle passive oscillation runs on top.
  • Page transitions: every page mounts its own scene, already in place: the first frame is applied before paint, and scroll smoothing is skipped right after mounting. Nothing animates from one page to the next.

Performance

The first version, using CSS animations and inline SVG, ran at about 10 FPS in Firefox-based browsers. The likely causes, both fixed by design:

  • Blobs: they were SVGs with a 50px feGaussianBlur, which Firefox re-runs whenever an SVG image is drawn at a new scale. They are now small pre-rasterized WebPs (15–19KB each, 256px). They're so blurry that upscaling is invisible.
  • Looper: it was an inline SVG with 19 animated paths, which Firefox repaints on the CPU every frame. The original looper is 19 copies of one curve, each rotated in 6° steps around its own pivot. Each line is now an element showing the same rasterized curve (looper-line.webp, 5KB), rotated around its pivot. The GPU only transforms one shared texture, and nothing gets repainted. I checked the static result against the original SVG with an overlay, and they match.
  • The loop: it only touches transform. It reads every position before writing anything, to avoid a style flush per shape. It skips off-screen shapes and only sets will-change on the visible ones, because Firefox ignores will-change past a memory budget and these shapes are huge.

Cleanup

  • Removed every per-page shapes file (home, projects, matricole, resources, collabora, associations, groups, footer), ConditionalGlobalShapes, the guides layout that only mounted it, and the page-specific glow SVGs.
  • Removed the relative wrappers that only existed to host shape layers.
  • Shape is kept, now limited to 4 variants, for the in-component decorations (dialog, card hover, accordion). It now uses the raster blobs too, loaded eagerly.

Notes

  • Segment count: computed from the measured page height with a ResizeObserver. Server rendering produces the first 2 segments, and the rest is added after hydration, below the fold. Motion fades in over the first 1.5s, so the first animated frame doesn't jump from the server-rendered layout.
  • Tuning: shape parameters (sizes, depths, motion ranges) live in src/components/background/scene.ts. The global constants (scroll inertia, intro duration) are in animate.ts. Global scale and segment height are in src/styles/background.css.

Testing

  • pnpm check, pnpm typecheck and pnpm build pass.
  • In Chromium, 120 fps both idle and while scrolling. Checked visually on desktop and mobile on home, collabora, contact and matricole.
  • Not measured in Firefox: Firefox couldn't run in my sandboxed environment. Please check in Firefox/Zen.
  • Pages that need the backend (groups, associations, projects) weren't checked visually locally.

Replace the per-page, hand-placed background shapes with a single
`<Background />` mounted in the root layout and shared by every page.

The background is split into screen-tall segments covering the whole
page (footer included), each filled with the same composition as the
Figma designs: a teal star overlapping blue blobs crossed by a looper,
plus a counterweight on the opposite side. Layouts are generated from
a seed derived from the pathname, so every page gets its own variation
that stays the same across reloads.

Animations (CSS only, disabled with prefers-reduced-motion):
- scroll parallax via scroll-driven `view()` timelines, with a different
  depth per shape so they move relative to each other
- passive drift, sway and breathing with desynced sine-like periods
- the looper is now rendered inline from a single curve, and its fan
  slowly opens and closes
- on client navigation shapes glide to the new page's layout

Remove the page-specific shape files, the unused glow SVGs and
ConditionalGlobalShapes; `Shape` is kept for in-component decorations.
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The pull request adds a shared, pathname-seeded animated background to the root layout. It removes page-specific decorative shape components and updates the shared shape API and background styles. The declared pnpm version also changes.

Changes

Shared Background

Layer / File(s) Summary
Shared shape contract
src/components/shapes.tsx
Shape variants now use the available source assets. Shape accepts an optional style, and the ShapeLayer wrapper is removed.
Scene generation and Looper artwork
src/components/background/scene.ts, src/components/background/looper.tsx
sceneSegment generates deterministic layouts and motion parameters. Looper renders its artwork as an SVG with 19 rotated paths.
Background rendering and animation
src/components/background/index.tsx, src/styles/background.css, src/styles/globals.css
Background renders scene shapes and observes its container height to update the segment count. The imported background stylesheet defines shape sizing, motion, and supported scroll-driven parallax.
Mount shared background and remove page decorations
src/app/layout.tsx, src/app/*, src/components/conditional-global-shapes.tsx, src/components/footer/*, src/components/home/*, src/components/resources/shapes.tsx
RootLayout renders Background. Page and layout components stop rendering the prior decorative shape layers, and related shape components are removed.

Package Manager Version

Layer / File(s) Summary
Update pnpm version
package.json
The declared package manager changes from pnpm 10.33.0 to pnpm 10.34.7 with a SHA-512 integrity value.

Sequence Diagram(s)

sequenceDiagram
  participant RootLayout
  participant Background
  participant ResizeObserver
  participant sceneSegment
  participant SceneItem
  participant Shape
  participant Looper
  RootLayout->>Background: render shared background
  Background->>ResizeObserver: observe container size
  ResizeObserver-->>Background: report container height
  Background->>sceneSegment: generate shapes for pathname and segment
  sceneSegment-->>Background: return scene shape data
  Background->>SceneItem: render each scene shape
  SceneItem->>Shape: render non-looper variant
  SceneItem->>Looper: render looper variant
Loading

Priority: ➖ Normal

Change: Feature

Merge Risk: ⚪ Minimal · up to c0856

No demonstrated issue currently blocks merging; complete the normal checks before release.

Pre-merge checks: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 13 files. (3 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 3 passed
Check name Status Explanation
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.
Title check Passed The title clearly and concisely describes the main change: replacing page-specific shapes with an animated, procedurally generated page background.
Full details: Docstring Coverage

Explanation

Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 13 files. (3 skipped: 3 unsupported.)

✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
⚔️ Resolve merge conflicts: 💡
  • Resolve merge conflict in branch feat/animated-background
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@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.

🔇 Additional comments: 2
package.json: 1

61-61: LGTM!

src/styles/background.css: 1

69-69: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

⚠️ Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.

Move shape navigation transitions to transforms.

When navigation changes a long page’s scene, every shape transitions left, top, width, and height for 1.5 seconds. These properties require repeated layout work, although the shapes do not affect surrounding content. Use a transform-based placement and size layer while keeping parallax and drift on their separate layers. Based on learnings, prefer transform or opacity when an animation does not need to change surrounding layout.

Source: Learnings


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1aac042e-173a-4cbf-923a-6afba2ec37ce
📥 Commits

Reviewing files that changed from the base of the PR and between e8ab380 and c085607.

⛔ Files ignored due to path filters: 5
  • src/assets/shapes/hero-glow.svg is excluded by !**/*.svg
  • src/assets/shapes/matricole-glow-1.svg is excluded by !**/*.svg
  • src/assets/shapes/projects-glow.svg is excluded by !**/*.svg
  • src/assets/shapes/resources-glow-2.svg is excluded by !**/*.svg
  • src/assets/shapes/resources-top-glow.svg is excluded by !**/*.svg
📒 Files selected for processing: 29
  • package.json
  • src/app/associations/page.tsx
  • src/app/associations/shapes.tsx
  • src/app/collabora/page.tsx
  • src/app/collabora/shapes.tsx
  • src/app/contact/page.tsx
  • src/app/faqs/page.tsx
  • src/app/groups/layout.tsx
  • src/app/groups/shapes.tsx
  • src/app/guides/layout.tsx
  • src/app/layout.tsx
  • src/app/matricole/guida/page.tsx
  • src/app/matricole/page.tsx
  • src/app/matricole/shapes.tsx
  • src/app/page.tsx
  • src/app/projects/page.tsx
  • src/app/projects/shapes.tsx
  • src/components/background/index.tsx
  • src/components/background/looper.tsx
  • src/components/background/scene.ts
  • src/components/conditional-global-shapes.tsx
  • src/components/footer/index.tsx
  • src/components/footer/shapes.tsx
  • src/components/home/hero.tsx
  • src/components/home/shapes.tsx
  • src/components/resources/shapes.tsx
  • src/components/shapes.tsx
  • src/styles/background.css
  • src/styles/globals.css
💤 Files with no reviewable changes: 13
  • src/components/resources/shapes.tsx
  • src/app/guides/layout.tsx
  • src/app/projects/shapes.tsx
  • src/components/home/shapes.tsx
  • src/app/associations/shapes.tsx
  • src/app/matricole/guida/page.tsx
  • src/app/matricole/shapes.tsx
  • src/components/conditional-global-shapes.tsx
  • src/app/contact/page.tsx
  • src/components/footer/shapes.tsx
  • src/app/collabora/shapes.tsx
  • src/app/groups/shapes.tsx
  • src/components/home/hero.tsx

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

The CSS version ran at ~10 FPS in Firefox-based browsers and behaved
differently across engines (no scroll-driven animations in Firefox).

- Blobs are now small pre-rasterized WebP images instead of SVGs with a
  50px gaussian blur, which Firefox re-runs whenever the image is drawn
  at a new scale.
- The looper is no longer an inline SVG with animated paths (repainted
  on the CPU every frame in Firefox): each line is an element showing
  the same rasterized curve, rotated around its pivot, so the GPU only
  ever transforms a single texture.
- Every animation now runs from one requestAnimationFrame loop with
  plain sine math, so it looks the same in every browser. It reads all
  positions before writing transforms, skips off-screen shapes, only
  promotes on-screen ones with will-change, and eases in after
  hydration so the first frame doesn't jump.
- More scroll feedback: stronger parallax, a scroll-driven rotation,
  smoothed scroll for some inertia, and the loopers' fan now opens from
  tight (~4.4°) to wide (~7.5°) as they cross the viewport.
Navigating kept the same shape elements and transitioned them to the new
page's layout, which looked like shapes flying around the screen. The
scene is now remounted for every page, its first frame is applied
synchronously before paint, and scroll smoothing is skipped right after
mounting so the scroll reset done by the navigation doesn't sweep the
parallax. The fade-in of the motion only runs on the first page load.
Port the animated background to the pages added on main (about, chi-siamo,
join, storia, team, projects/[id], collabora): drop their per-page shapes
files, `ShapeLayer`/`FigmaShape` backgrounds and the team hero glow. The
shapes drawn over the collabora and join photos stay as static
decorations, now defined in their components via `PhotoShapes`.
@BIA3IA
BIA3IA merged commit 1ed3930 into main Oct 20, 2026
2 checks passed
@BIA3IA
BIA3IA deleted the feat/animated-background branch October 20, 2026 10:03
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