diff --git a/src/app/associations/page.tsx b/src/app/associations/page.tsx index aa70906..58304b1 100644 --- a/src/app/associations/page.tsx +++ b/src/app/associations/page.tsx @@ -2,6 +2,7 @@ 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", @@ -14,7 +15,8 @@ export default async function AssociationsPage() { const associations = await getAllAssociations() return ( -
+
+
diff --git a/src/app/associations/shapes.tsx b/src/app/associations/shapes.tsx new file mode 100644 index 0000000..448fd48 --- /dev/null +++ b/src/app/associations/shapes.tsx @@ -0,0 +1,38 @@ +import { Shape, ShapeLayer } from "@/components/shapes" + +export function AssociationsShapes() { + return ( + + {/* Mobile Shapes */} + + + + + + + + + + + {/* Desktop Shapes */} + + {/* */} + + {/* */} + + {/* */} + + ) +} diff --git a/src/app/faqs/page.tsx b/src/app/faqs/page.tsx index d029fee..02b6ab2 100644 --- a/src/app/faqs/page.tsx +++ b/src/app/faqs/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next" import { FaqsTabs } from "@/components/faqs/faqs-tabs" +import { ResourcesHeroShapes } from "@/components/resources/shapes" import { Hero } from "@/components/ui/hero" import { getAllFaqs } from "@/queries/faqs" @@ -14,7 +15,8 @@ export default async function FAQsPage() { const categories = await getAllFaqs() return ( -
+
+
diff --git a/src/app/groups/page.tsx b/src/app/groups/page.tsx index 1b6a4e1..58970af 100644 --- a/src/app/groups/page.tsx +++ b/src/app/groups/page.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next" import { FiBook, FiStar } from "react-icons/fi" import { CardIcon } from "@/components/card-icon" import { GroupSearch } from "@/components/home/group-search" +import { GroupsShapes } from "./shapes" const groups = [ { @@ -28,7 +29,8 @@ export const metadata: Metadata = { export default function Home() { return (
-
+
+

Groups diff --git a/src/app/groups/shapes.tsx b/src/app/groups/shapes.tsx new file mode 100644 index 0000000..84e4083 --- /dev/null +++ b/src/app/groups/shapes.tsx @@ -0,0 +1,23 @@ +import { Shape, ShapeLayer } from "@/components/shapes" + +export function GroupsShapes() { + return ( + + {/* Mobile Shapes */} + + + + + + + + {/* Desktop Shapes */} + + + + + + + + ) +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 40ca3b1..99eac89 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,5 @@ import { ConditionalFooter } from "@/components/conditional-footer" import { Header } from "@/components/header/header" -import { Shape } from "@/components/shapes" import { ThemeProvider } from "@/components/theme-provider" import "@/styles/globals.css" import type { Metadata } from "next" @@ -73,12 +72,7 @@ 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 91334bf..729e925 100644 --- a/src/app/matricole/guida/page.tsx +++ b/src/app/matricole/guida/page.tsx @@ -1,6 +1,7 @@ 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" @@ -23,6 +24,7 @@ export default async function GuidaMatricolaPage() { return (
+

Guida della Matricola diff --git a/src/app/matricole/page.tsx b/src/app/matricole/page.tsx index 9d1e36d..7b7575a 100644 --- a/src/app/matricole/page.tsx +++ b/src/app/matricole/page.tsx @@ -2,6 +2,8 @@ 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", @@ -11,9 +13,18 @@ export const metadata: Metadata = { export default function MatricolePage() { return (
- - - +
+ + +
+
+ + +
+
+ + +
) } diff --git a/src/app/matricole/shapes.tsx b/src/app/matricole/shapes.tsx new file mode 100644 index 0000000..9372982 --- /dev/null +++ b/src/app/matricole/shapes.tsx @@ -0,0 +1,52 @@ +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 0296908..66b8b37 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,13 +2,20 @@ 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 fbfb0d7..a431936 100644 --- a/src/app/projects/page.tsx +++ b/src/app/projects/page.tsx @@ -3,6 +3,7 @@ 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", @@ -20,10 +21,22 @@ export default async function Home() { return (
- - - - +
+ + +
+
+ + +
+
+ + +
+
+ + +
) } diff --git a/src/app/projects/shapes.tsx b/src/app/projects/shapes.tsx new file mode 100644 index 0000000..90ec5bb --- /dev/null +++ b/src/app/projects/shapes.tsx @@ -0,0 +1,97 @@ +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 new file mode 100644 index 0000000..98a159b --- /dev/null +++ b/src/assets/shapes/hero-glow.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/shapes/matricole-glow-1.svg b/src/assets/shapes/matricole-glow-1.svg new file mode 100644 index 0000000..1c9425e --- /dev/null +++ b/src/assets/shapes/matricole-glow-1.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/shapes/projects-glow.svg b/src/assets/shapes/projects-glow.svg new file mode 100644 index 0000000..07df19c --- /dev/null +++ b/src/assets/shapes/projects-glow.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/shapes/resources-glow-2.svg b/src/assets/shapes/resources-glow-2.svg new file mode 100644 index 0000000..23e7c87 --- /dev/null +++ b/src/assets/shapes/resources-glow-2.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/shapes/resources-top-glow.svg b/src/assets/shapes/resources-top-glow.svg new file mode 100644 index 0000000..36e654d --- /dev/null +++ b/src/assets/shapes/resources-top-glow.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/conditional-global-shapes.tsx b/src/components/conditional-global-shapes.tsx new file mode 100644 index 0000000..d034b7e --- /dev/null +++ b/src/components/conditional-global-shapes.tsx @@ -0,0 +1,26 @@ +"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 f403e40..7ccad65 100644 --- a/src/components/footer/index.tsx +++ b/src/components/footer/index.tsx @@ -4,11 +4,13 @@ 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 ( -
-

+
+ +

Keep in touch!

@@ -40,7 +42,7 @@ export function Footer() {

-
+
diff --git a/src/components/footer/shapes.tsx b/src/components/footer/shapes.tsx new file mode 100644 index 0000000..018629d --- /dev/null +++ b/src/components/footer/shapes.tsx @@ -0,0 +1,12 @@ +import { Shape, ShapeLayer } from "@/components/shapes" + +export function FooterShapes() { + return ( + + {/* Desktop Shapes */} + + + + + ) +} diff --git a/src/components/home/hero.tsx b/src/components/home/hero.tsx index a2d679f..1e9ff50 100644 --- a/src/components/home/hero.tsx +++ b/src/components/home/hero.tsx @@ -2,11 +2,13 @@ import Link from "next/link" import { FiNavigation, FiUserPlus } from "react-icons/fi" import { Button } from "../ui/button" import { GroupSearch } from "./group-search" +import { HomeHeroShapes } from "./shapes" export function Hero() { return (
-
+ +

Trova gruppi, risorse e supporto tra gli studenti del Polimi

diff --git a/src/components/home/shapes.tsx b/src/components/home/shapes.tsx new file mode 100644 index 0000000..8307868 --- /dev/null +++ b/src/components/home/shapes.tsx @@ -0,0 +1,72 @@ +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 homepage + * 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 + * (Materials or Projects), not to the page. Materials and Projects/AboutUs + * used to share one absolute layer with cumulative offsets, but AboutUs uses + * `min-h-screen` (fluid height, depends on the viewport) — if a shape ever + * needs to sit inside AboutUs, mount it scoped to that section too, the same + * way this file already splits Materials from Projects. Otherwise any + * mismatch between a fluid section's real height and Figma's fixed height + * assumption compounds into every shape positioned after it (e.g. shapes + * drifting away from their section when the browser is zoomed out — see + * src/app/projects/shapes.tsx, which had exactly this bug). + */ + +export function HomeHeroShapes() { + return ( + + {/* Mobile Shapes */} + + + + + {/* Desktop Shapes */} + + + + ) +} + +export function HomeMaterialsShapes() { + return ( + + {/* Mobile Shapes */} + + + + + + + + {/* Desktop Shapes */} + + + + + ) +} + +export function HomeProjectsShapes() { + return ( + + {/* Mobile Shapes */} + + + + + {/* Desktop Shapes */} + + + + ) +} diff --git a/src/components/resources/shapes.tsx b/src/components/resources/shapes.tsx new file mode 100644 index 0000000..33c0144 --- /dev/null +++ b/src/components/resources/shapes.tsx @@ -0,0 +1,38 @@ +import { Shape, ShapeLayer } from "@/components/shapes" + +/** + * Hero background shared by the Matricole and FAQs pages (same Figma design). + */ + +export function ResourcesHeroShapes() { + return ( + + {/* Mobile Shapes */} + + + + + + {/* Desktop Shapes */} + + + + + + ) +} diff --git a/src/components/shapes.tsx b/src/components/shapes.tsx index 19dd3e2..3650ab0 100644 --- a/src/components/shapes.tsx +++ b/src/components/shapes.tsx @@ -1,11 +1,25 @@ import Image from "next/image" import bigBlueSvg from "@/assets/shapes/big-blue.svg" import bigTealSvg from "@/assets/shapes/big-teal.svg" +import heroGlowSvg from "@/assets/shapes/hero-glow.svg" import looperSvg from "@/assets/shapes/looper.svg" +import matricoleGlow1Svg from "@/assets/shapes/matricole-glow-1.svg" +import projectsGlowSvg from "@/assets/shapes/projects-glow.svg" +import resourcesGlow2Svg from "@/assets/shapes/resources-glow-2.svg" +import resourcesTopGlowSvg from "@/assets/shapes/resources-top-glow.svg" import smallBlueSvg from "@/assets/shapes/small-blue.svg" import { cn } from "@/lib/utils" -export type ShapeVariant = "big-blue" | "big-teal" | "small-blue" | "looper" +export type ShapeVariant = + | "big-blue" + | "big-teal" + | "small-blue" + | "hero-glow" + | "projects-glow" //TODO: remove these page specific shapes + | "matricole-glow-1" + | "resources-glow-2" + | "resources-top-glow" + | "looper" export type ShapeProps = { variant: ShapeVariant @@ -21,6 +35,16 @@ export const Shape: React.FC = ({ variant, className }) => { return bigTealSvg case "small-blue": return smallBlueSvg + case "hero-glow": + return heroGlowSvg + case "projects-glow": + return projectsGlowSvg + case "matricole-glow-1": + return matricoleGlow1Svg + case "resources-glow-2": + return resourcesGlow2Svg + case "resources-top-glow": + return resourcesTopGlowSvg case "looper": return looperSvg default: @@ -28,5 +52,32 @@ export const Shape: React.FC = ({ variant, className }) => { } } - return + return ( + + ) } + +export type ShapeLayerProps = { + children: React.ReactNode + className?: string +} + +/** + * Wraps a group of ``s positioned behind a section. `overflow-x-clip` + * (not `overflow-hidden`/`overflow-x-hidden`) is required: pairing it with + * `overflow-y-visible` clips horizontal bleed only — CSS forces the "visible" + * axis to compute as `auto` (which still clips, just via an invisible + * scrollbar) whenever the other axis is `hidden`/`auto`/`scroll`, but not + * when it's `clip`, since `clip` doesn't create a scroll container. This lets + * shapes bleed vertically into the next section (as several are designed to) + * without shapes positioned off-canvas horizontally causing a page-wide + * horizontal scrollbar. + */ +export const ShapeLayer: React.FC = ({ children, className }) => ( +
+ {children} +
+)