From ddc6518fa6544e3c27cd50e01eac7f20bd179f5b Mon Sep 17 00:00:00 2001 From: Tommaso Morganti Date: Thu, 15 Oct 2026 14:36:03 +0200 Subject: [PATCH 1/4] feat: animated, procedurally generated page background Replace the per-page, hand-placed background shapes with a single `` 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. --- src/app/associations/page.tsx | 4 +- src/app/associations/shapes.tsx | 42 ------ src/app/collabora/page.tsx | 26 +--- src/app/collabora/shapes.tsx | 133 ------------------- src/app/contact/page.tsx | 2 - src/app/faqs/page.tsx | 4 +- src/app/groups/layout.tsx | 6 +- src/app/groups/shapes.tsx | 23 ---- src/app/guides/layout.tsx | 11 -- src/app/layout.tsx | 5 +- src/app/matricole/guida/page.tsx | 2 - src/app/matricole/page.tsx | 17 +-- src/app/matricole/shapes.tsx | 52 -------- src/app/page.tsx | 11 +- src/app/projects/page.tsx | 21 +-- src/app/projects/shapes.tsx | 97 -------------- src/assets/shapes/hero-glow.svg | 30 ----- src/assets/shapes/matricole-glow-1.svg | 12 -- src/assets/shapes/projects-glow.svg | 12 -- src/assets/shapes/resources-glow-2.svg | 12 -- src/assets/shapes/resources-top-glow.svg | 36 ----- src/components/background/index.tsx | 114 ++++++++++++++++ src/components/background/looper.tsx | 65 +++++++++ src/components/background/scene.ts | 108 +++++++++++++++ src/components/conditional-global-shapes.tsx | 26 ---- src/components/footer/index.tsx | 4 +- src/components/footer/shapes.tsx | 12 -- src/components/home/hero.tsx | 2 - src/components/home/shapes.tsx | 72 ---------- src/components/resources/shapes.tsx | 38 ------ src/components/shapes.tsx | 93 +++---------- src/styles/background.css | 104 +++++++++++++++ src/styles/globals.css | 1 + 33 files changed, 436 insertions(+), 761 deletions(-) delete mode 100644 src/app/associations/shapes.tsx delete mode 100644 src/app/collabora/shapes.tsx delete mode 100644 src/app/groups/shapes.tsx delete mode 100644 src/app/guides/layout.tsx delete mode 100644 src/app/matricole/shapes.tsx delete mode 100644 src/app/projects/shapes.tsx delete mode 100644 src/assets/shapes/hero-glow.svg delete mode 100644 src/assets/shapes/matricole-glow-1.svg delete mode 100644 src/assets/shapes/projects-glow.svg delete mode 100644 src/assets/shapes/resources-glow-2.svg delete mode 100644 src/assets/shapes/resources-top-glow.svg create mode 100644 src/components/background/index.tsx create mode 100644 src/components/background/looper.tsx create mode 100644 src/components/background/scene.ts delete mode 100644 src/components/conditional-global-shapes.tsx delete mode 100644 src/components/footer/shapes.tsx delete mode 100644 src/components/home/shapes.tsx delete mode 100644 src/components/resources/shapes.tsx create mode 100644 src/styles/background.css diff --git a/src/app/associations/page.tsx b/src/app/associations/page.tsx index 58304b1..e4760a9 100644 --- a/src/app/associations/page.tsx +++ b/src/app/associations/page.tsx @@ -2,7 +2,6 @@ import type { Metadata } from "next" import { AssociationsList } from "@/components/associations/associations-list" import { Hero } from "@/components/ui/hero" import { getAllAssociations } from "@/queries/associations" -import { AssociationsShapes } from "./shapes" export const metadata: Metadata = { title: "Associazioni", @@ -15,8 +14,7 @@ export default async function AssociationsPage() { const associations = await getAllAssociations() return ( -
- +
diff --git a/src/app/associations/shapes.tsx b/src/app/associations/shapes.tsx deleted file mode 100644 index 1ffeb2d..0000000 --- a/src/app/associations/shapes.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Shape, ShapeLayer } from "@/components/shapes" - -export function AssociationsShapes() { - return ( - - {/* Mobile Shapes */} - - - - - - - - - - - {/* Desktop Shapes */} - {/* bottom-anchored: the list length (so
's height) is data-dependent, a fixed top would drift from the footer */} - - - - - - - - ) -} diff --git a/src/app/collabora/page.tsx b/src/app/collabora/page.tsx index f680bbd..fc29ae3 100644 --- a/src/app/collabora/page.tsx +++ b/src/app/collabora/page.tsx @@ -5,14 +5,6 @@ import { CollaborationSection } from "@/components/collabora/collaboration-secti import { CompanySection } from "@/components/collabora/company-section" import { ContactSection } from "@/components/collabora/contact-section" import { Hero } from "@/components/ui/hero" -import { - CollaboraAboutShapes, - CollaboraAssociationShapes, - CollaboraCollaborationShapes, - CollaboraCompanyShapes, - CollaboraContactShapes, - CollaboraHeroShapes, -} from "./shapes" export const metadata: Metadata = { title: "Collabora con Noi", @@ -22,33 +14,27 @@ export const metadata: Metadata = { export default function CollaboraPage() { return ( -
+
{/* This page is broken on mobile */} - -
- +
-
- +
-
- +
-
- +
-
- +
diff --git a/src/app/collabora/shapes.tsx b/src/app/collabora/shapes.tsx deleted file mode 100644 index 014aa58..0000000 --- a/src/app/collabora/shapes.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { Shape, ShapeLayer } from "@/components/shapes" - -/** - * `left` offsets are `calc(50% + Npx)`, relative to the horizontal center of - * the 1728px (desktop) / 402px (mobile) Figma frames — see - * src/components/home/shapes.tsx. - * - * The real sections are much shorter/taller than the Figma frames (up to - * ~300px drift on desktop, and the mobile page is ~60% taller), so each - * section's shapes are `top`-anchored to that section's heading (the section's - * top edge): `top = figmaY - figmaHeadingY`. The hero shapes are instead on a - * layer over the whole `
`, where page y = Figma y. - * - * On mobile the photo decorations of "associazione"/"azienda" are anchored to - * the section bottom, since the photo is the last element there. - * - * Section layers use `-inset-x-4` because the sections sit inside `
`'s - * `px-4`: without it the layer (and its horizontal clip) would stop 16px short - * of the viewport edges. - */ - -export function CollaboraHeroShapes() { - return ( - - {/* Mobile Shapes */} - - - - - {/* Desktop Shapes */} - - - - - - - - ) -} - -export function CollaboraAboutShapes() { - return ( - - {/* Mobile Shapes */} - - - - {/* Desktop Shapes */} - - - - - ) -} - -export function CollaboraCollaborationShapes() { - return ( - - {/* Mobile Shapes */} - - - - - - - {/* Desktop Shapes */} - - - - ) -} - -export function CollaboraAssociationShapes() { - return ( - - {/* Mobile Shapes */} - - {/* Photo decoration, bottom-anchored to the photo */} - - - - - {/* Desktop Shapes */} - - - - - - - ) -} - -export function CollaboraCompanyShapes() { - return ( - - {/* Mobile Shapes */} - - {/* Photo decoration, bottom-anchored to the photo */} - - - - - {/* Desktop Shapes */} - - - - - ) -} - -export function CollaboraContactShapes() { - return ( - - {/* Mobile Shapes */} - - - - {/* Desktop Shapes */} - - - - - ) -} diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx index 54ce541..256adc9 100644 --- a/src/app/contact/page.tsx +++ b/src/app/contact/page.tsx @@ -1,5 +1,4 @@ import type { Metadata } from "next" -import { ConditionalGlobalShapes } from "@/components/conditional-global-shapes" import { CollaboraSection } from "@/components/contact/collabora-section" import { EmailSection } from "@/components/contact/email-section" import { GroupsSection } from "@/components/contact/groups-section" @@ -13,7 +12,6 @@ export const metadata: Metadata = { export default function Contact() { return (
- - +
diff --git a/src/app/groups/layout.tsx b/src/app/groups/layout.tsx index b82d45d..c0623a4 100644 --- a/src/app/groups/layout.tsx +++ b/src/app/groups/layout.tsx @@ -1,14 +1,10 @@ import type { ReactNode } from "react" import { ReportFab } from "@/components/groups/report/fab" -import { GroupsShapes } from "./shapes" export default function GroupsLayout({ children }: { children: ReactNode }) { return ( <> -
- - {children} -
+
{children}
) diff --git a/src/app/groups/shapes.tsx b/src/app/groups/shapes.tsx deleted file mode 100644 index 84e4083..0000000 --- a/src/app/groups/shapes.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Shape, ShapeLayer } from "@/components/shapes" - -export function GroupsShapes() { - return ( - - {/* Mobile Shapes */} - - - - - - - - {/* Desktop Shapes */} - - - - - - - - ) -} diff --git a/src/app/guides/layout.tsx b/src/app/guides/layout.tsx deleted file mode 100644 index 2692944..0000000 --- a/src/app/guides/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import type { ReactNode } from "react" -import { ConditionalGlobalShapes } from "@/components/conditional-global-shapes" - -export default function GuidesLayout({ children }: { children: ReactNode }) { - return ( - <> - - {children} - - ) -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6530047..807a860 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,3 +1,4 @@ +import { Background } from "@/components/background" import { ConditionalFooter } from "@/components/conditional-footer" import { Header } from "@/components/header/header" import { ThemeProvider } from "@/components/theme-provider" @@ -72,8 +73,8 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac // storageKey="polinetwork_darkmode_temp" // TODO: enable when dark mode design is ready disableTransitionOnChange > - {/* */} -
+
+
{children} diff --git a/src/app/matricole/guida/page.tsx b/src/app/matricole/guida/page.tsx index 729e925..91334bf 100644 --- a/src/app/matricole/guida/page.tsx +++ b/src/app/matricole/guida/page.tsx @@ -1,7 +1,6 @@ import type { Metadata } from "next" import { FiDownload } from "react-icons/fi" import { CardSplit } from "@/components/card-split" -import { ResourcesHeroShapes } from "@/components/resources/shapes" import { Button } from "@/components/ui/button" import { getLatestGuidaMatricola } from "@/queries/guida-matricola" @@ -24,7 +23,6 @@ export default async function GuidaMatricolaPage() { return (
-

Guida della Matricola diff --git a/src/app/matricole/page.tsx b/src/app/matricole/page.tsx index 7b7575a..9d1e36d 100644 --- a/src/app/matricole/page.tsx +++ b/src/app/matricole/page.tsx @@ -2,8 +2,6 @@ import type { Metadata } from "next" import { FAQsPage } from "@/components/matricole/faqs" import { MatricoleGuides } from "@/components/matricole/guides" import { MatricoleIntro } from "@/components/matricole/intro" -import { ResourcesHeroShapes } from "@/components/resources/shapes" -import { MatricoleFAQsShapes, MatricoleGuidesShapes } from "./shapes" export const metadata: Metadata = { title: "Matricole", @@ -13,18 +11,9 @@ export const metadata: Metadata = { export default function MatricolePage() { return (
-
- - -
-
- - -
-
- - -
+ + +
) } diff --git a/src/app/matricole/shapes.tsx b/src/app/matricole/shapes.tsx deleted file mode 100644 index 9372982..0000000 --- a/src/app/matricole/shapes.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Shape, ShapeLayer } from "@/components/shapes" - -/** - * `left` offsets are `calc(50% + Npx)`, relative to the horizontal center of - * the 1728px-wide (desktop) / 402px-wide (mobile) Figma reference frames — - * see src/components/home/shapes.tsx for why. - * - * `top` is relative to the top of the section each component is mounted in - * (MatricoleGuides, FAQsPage), matching how the section - * boundaries are grouped in the Figma file, not to the page as a whole. - */ - -export function MatricoleGuidesShapes() { - return ( - - {/* Mobile Shapes */} - - - - - - - - - - - - - - {/* Desktop Shapes */} - - - - - - - ) -} - -export function MatricoleFAQsShapes() { - return ( - - {/* Mobile Shapes (this section has no shapes on desktop) */} - - - - - ) -} diff --git a/src/app/page.tsx b/src/app/page.tsx index 66b8b37..0296908 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,20 +2,13 @@ import { AboutUs } from "@/components/home/about-us" import { Hero } from "@/components/home/hero" import { Materials } from "@/components/home/materials" import { Projects } from "@/components/home/projects" -import { HomeMaterialsShapes, HomeProjectsShapes } from "@/components/home/shapes" export default function Home() { return (
-
- - -
-
- - -
+ +
) diff --git a/src/app/projects/page.tsx b/src/app/projects/page.tsx index a431936..fbfb0d7 100644 --- a/src/app/projects/page.tsx +++ b/src/app/projects/page.tsx @@ -3,7 +3,6 @@ import { CommunityNews } from "@/components/projects/community-news" import { Deprecated } from "@/components/projects/deprecated" import { Upload } from "@/components/projects/upload" import { getAllProjects } from "@/queries/projects" -import { CollectionShapes, CommunityNewsShapes, DeprecatedShapes, UploadShapes } from "./shapes" export const metadata = { title: "Progetti", @@ -21,22 +20,10 @@ export default async function Home() { return (
-
- - -
-
- - -
-
- - -
-
- - -
+ + + +
) } diff --git a/src/app/projects/shapes.tsx b/src/app/projects/shapes.tsx deleted file mode 100644 index 90ec5bb..0000000 --- a/src/app/projects/shapes.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { Shape, ShapeLayer } from "@/components/shapes" - -/** - * `left` offsets below are expressed as `calc(50% + Npx)`, i.e. relative to the - * horizontal center of the section rather than its left edge. The projects - * content is itself center-anchored (not width-capped), so anchoring shapes to - * the edge would make them drift away from the content on screens wider or - * narrower than the 1728px/402px Figma reference frames. - * - * `top` is relative to the top of the section each component is mounted in - * (CommunityNews, Collection, Upload, Deprecated), not to the page. Every - * section on this page uses `min-h-screen` (fluid height, depends on the viewport) - * — each shape below is therefore scoped and `top`-anchored to the section it - * visually belongs to, rather than using cumulative page offsets. - */ - -export function CommunityNewsShapes() { - return ( - - {/* Mobile Shapes */} - - - - - - - - {/* Desktop Shapes */} - - - - - - - ) -} - -export function CollectionShapes() { - return ( - - {/* Mobile Shapes */} - - - - {/* Desktop Shapes */} - - - - - ) -} - -export function UploadShapes() { - return ( - - {/* Mobile Shapes */} - - - - {/* Desktop Shapes */} - - - - - ) -} - -export function DeprecatedShapes() { - return ( - - {/* Mobile Shapes */} - - - - - {/* Desktop Shapes */} - - - - - ) -} diff --git a/src/assets/shapes/hero-glow.svg b/src/assets/shapes/hero-glow.svg deleted file mode 100644 index 98a159b..0000000 --- a/src/assets/shapes/hero-glow.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/assets/shapes/matricole-glow-1.svg b/src/assets/shapes/matricole-glow-1.svg deleted file mode 100644 index 1c9425e..0000000 --- a/src/assets/shapes/matricole-glow-1.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/assets/shapes/projects-glow.svg b/src/assets/shapes/projects-glow.svg deleted file mode 100644 index 07df19c..0000000 --- a/src/assets/shapes/projects-glow.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/assets/shapes/resources-glow-2.svg b/src/assets/shapes/resources-glow-2.svg deleted file mode 100644 index 23e7c87..0000000 --- a/src/assets/shapes/resources-glow-2.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/assets/shapes/resources-top-glow.svg b/src/assets/shapes/resources-top-glow.svg deleted file mode 100644 index 36e654d..0000000 --- a/src/assets/shapes/resources-top-glow.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/components/background/index.tsx b/src/components/background/index.tsx new file mode 100644 index 0000000..d498979 --- /dev/null +++ b/src/components/background/index.tsx @@ -0,0 +1,114 @@ +"use client" + +import { usePathname } from "next/navigation" +import { type CSSProperties, useEffect, useRef, useState } from "react" +import { Shape } from "@/components/shapes" +import { LOOPER_ASPECT, Looper } from "./looper" +import { type SceneShape, sceneSegment } from "./scene" + +/** Rendered on the server, before the page height can be measured */ +const INITIAL_SEGMENTS = 2 + +/** + * Animated decorative background shared by every page. It covers the whole + * page (footer included) and is split into `--bg-segment` tall segments, each + * filled with shapes from `sceneSegment`. Animations live in + * `styles/background.css`: scroll parallax (each shape has its own depth), + * passive drift/sway/breathing, the loopers' fan opening and closing, and + * shapes gliding to their new place when navigating to another page. + */ +export function Background() { + const pathname = usePathname() + const ref = useRef(null) + const [segments, setSegments] = useState(INITIAL_SEGMENTS) + + useEffect(() => { + const el = ref.current + if (!el) return + const observer = new ResizeObserver(() => { + const segment = Number.parseFloat(getComputedStyle(el).getPropertyValue("--bg-segment")) + setSegments(Math.max(1, Math.ceil(el.offsetHeight / segment))) + }) + observer.observe(el) + return () => observer.disconnect() + }, []) + + return ( +
+ {Array.from({ length: segments }, (_, i) => sceneSegment(pathname, i)) + .flat() + .map((shape) => ( + + ))} +
+ ) +} + +function SceneItem({ shape }: { shape: SceneShape }) { + const looper = shape.variant === "looper" + const width = shape.size + const height = looper ? shape.size * LOOPER_ASPECT : shape.size + // negative delays start every animation mid-cycle and out of phase with the others + const delay = (duration: number, phase: number) => `${-duration * phase}s` + + return ( +
+
+
+ {looper ? ( + + ) : ( + + )} +
+
+
+ ) +} diff --git a/src/components/background/looper.tsx b/src/components/background/looper.tsx new file mode 100644 index 0000000..dfbefa5 --- /dev/null +++ b/src/components/background/looper.tsx @@ -0,0 +1,65 @@ +import type { CSSProperties } from "react" + +/** + * Inline version of `assets/shapes/looper.svg`, so each line can be animated. + * + * The original is 19 copies of the same curve, each rotated 6° more than the + * previous one around its own pivot (the copies also drift slightly). Only + * the last curve is kept here, the others are rebuilt from their pivot. The + * `looper-fan` animation adds a tiny extra rotation proportional to the + * line's distance from the middle one, which opens and closes the fan. + */ +const CURVE = + "M588.026 1.419c-53.471 18.339-158.931 98.214-153 271.006 7.414 215.991 169.177 351.154 209.617 554.919 29.848 150.393 25.612 527.066-156.37 685.326" + +const PIVOTS = [ + [814, 799], + [811, 785], + [808, 771], + [805, 757], + [801, 744], + [797, 731], + [792, 718], + [787, 706], + [782, 694], + [776, 682], + [770, 671], + [764, 660], + [757, 650], + [750, 639], + [743, 630], + [735, 620], + [728, 610], + [719, 601], + [710, 592], +] as const + +const LAST = PIVOTS.length - 1 +const MIDDLE = LAST / 2 + +export const LOOPER_ASPECT = 1562 / 1610 + +export function Looper({ className, style }: { className?: string; style?: CSSProperties }) { + return ( + + ) +} diff --git a/src/components/background/scene.ts b/src/components/background/scene.ts new file mode 100644 index 0000000..3523cbe --- /dev/null +++ b/src/components/background/scene.ts @@ -0,0 +1,108 @@ +import type { BlobVariant } from "@/components/shapes" + +export type SceneShape = { + id: string + variant: BlobVariant | "looper" + /** Horizontal center, from -1 (left edge) to 1 (right edge) of the page */ + x: number + /** Vertical center, in segments from the top of the page */ + y: number + /** Width, in `--bg-unit`s (≈px on a 1728px wide screen) */ + size: number + /** Static rotation, in degrees */ + rotate: number + /** Scroll parallax offset in px, negative is further away (slower than the page) */ + depth: number + /** Passive motion: drift amplitude (px), sway (deg), breathing scale and timings (s) */ + driftX: number + driftY: number + sway: number + breathe: number + durX: number + durY: number + durSway: number + /** Looper only: extra rotation per line (deg) and duration (s) of the fan animation */ + fan: number + durFan: number +} + +/** FNV-1a, to turn the pathname into a seed */ +function hash(str: string) { + let h = 0x811c9dc5 + for (let i = 0; i < str.length; i++) h = Math.imul(h ^ str.charCodeAt(i), 0x01000193) + return h >>> 0 +} + +/** mulberry32, a tiny seeded PRNG */ +function prng(seed: number) { + let a = seed + return () => { + a = (a + 0x6d2b79f5) | 0 + let t = Math.imul(a ^ (a >>> 15), 1 | a) + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t + return ((t ^ (t >>> 14)) >>> 0) / 4294967296 + } +} + +/** + * Deterministically generates the shapes of one segment (a screen-ish tall + * slice) of the page background. Every segment has the same composition as + * the original Figma backgrounds: a teal star overlapping a couple of blue + * blobs on one side, crossed by a looper, and a smaller counterweight on the + * other side. Sides alternate between segments, everything else is jittered + * from a seed derived from the pathname and the segment index, so each page + * gets its own (but stable across reloads) variation. + * + * Animation parameters are seeded by the segment index only: when navigating + * the same elements glide to the new layout, and changing the parameters of a + * running animation would make it jump. + */ +export function sceneSegment(pathname: string, index: number): SceneShape[] { + const seed = hash(pathname) + const segment = Math.imul(index + 1, 0x9e3779b1) + const rand = prng(seed ^ segment) + const between = (min: number, max: number) => min + rand() * (max - min) + const motion = prng(segment) + const vary = (min: number, max: number) => min + motion() * (max - min) + const side = (seed + index) % 2 ? 1 : -1 + + const shapes: SceneShape[] = [] + const add = (variant: SceneShape["variant"], x: number, y: number, size: number, depth: [number, number]) => { + const looper = variant === "looper" + shapes.push({ + id: `${index}-${shapes.length}`, + variant, + x, + y: index + y, + size, + rotate: between(0, 360), + depth: vary(...depth), + driftX: vary(12, 32), + driftY: vary(12, 32), + sway: looper ? vary(2, 4) : vary(5, 12), + breathe: looper ? 0 : vary(0.02, 0.05), + durX: vary(17, 29), + durY: vary(19, 31), + durSway: vary(29, 47), + fan: looper ? vary(0.25, 0.45) : 0, + durFan: vary(11, 19), + }) + } + + // main cluster: the blobs overlap, so the looper's white lines show on top of them + const cx = side * between(0.35, 0.8) + const cy = between(0.15, 0.45) + add("big-blue", cx + side * between(-0.1, 0.15), cy - between(0, 0.2), between(650, 900), [-90, -40]) + add("big-teal", cx, cy, between(800, 1050), [-40, 20]) + add("small-blue", cx - side * between(0.1, 0.25), cy + between(0.05, 0.2), between(380, 520), [60, 140]) + add("looper", cx + side * between(-0.3, 0.1), cy + between(-0.05, 0.2), between(1400, 1800), [-160, -90]) + + // counterweight on the other side + const ox = -side * between(0.55, 0.95) + const oy = between(0.6, 0.9) + add(rand() < 0.5 ? "big-blue" : "big-teal", ox, oy, between(550, 850), [-80, 0]) + if (rand() < 0.5) + add("small-blue", ox + side * between(0.1, 0.3), oy - between(0, 0.15), between(250, 400), [80, 160]) + + return shapes +} diff --git a/src/components/conditional-global-shapes.tsx b/src/components/conditional-global-shapes.tsx deleted file mode 100644 index d034b7e..0000000 --- a/src/components/conditional-global-shapes.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client" - -import { usePathname } from "next/navigation" -import { Shape } from "@/components/shapes" - -const HIDDEN_EXACT = ["/"] - -/** - * Generic top-of-page decorative shapes, reused on every page that doesn't - * define its own (e.g. the homepage, which has a Figma-accurate composition). - */ -export function ConditionalGlobalShapes() { - const pathname = usePathname() - if (HIDDEN_EXACT.includes(pathname)) return null - - return ( -
-
- - - - -
-
- ) -} diff --git a/src/components/footer/index.tsx b/src/components/footer/index.tsx index 8fa811e..98709b1 100644 --- a/src/components/footer/index.tsx +++ b/src/components/footer/index.tsx @@ -4,12 +4,10 @@ import { FiFacebook, FiGithub, FiInstagram, FiLinkedin } from "react-icons/fi" import discord from "@/assets/icons/discord.svg" import telegram from "@/assets/icons/telegram.svg" import { CardMultipleIcons } from "../card-multiple-icons" -import { FooterShapes } from "./shapes" export function Footer() { return ( -