Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/app/matricole/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Metadata } from "next"
import { CONTAINER, PAGE_MAIN } from "@/components/layout/page"
import { FAQsPage } from "@/components/matricole/faqs"
import { MatricoleGuides } from "@/components/matricole/guides"
import { MatricoleIntro } from "@/components/matricole/intro"
Expand All @@ -14,15 +15,15 @@ export const dynamic = "force-dynamic"
export default async function MatricolePage() {
const content = await getFreshmanContent()
return (
<main className="w-full">
<main className={PAGE_MAIN}>
<MatricoleIntro resources={content?.resources ?? []} />
{content ? (
<>
<MatricoleGuides content={content} />
<FAQsPage faqs={content.faqs} />
</>
) : (
<p className="typo-body-large px-6 pb-32 text-center text-text-primary">
<p className={`${CONTAINER} typo-body-large text-center text-text-primary`}>
Le risorse per le matricole non sono disponibili al momento. Riprova più tardi.
</p>
)}
Expand Down
8 changes: 5 additions & 3 deletions src/components/card-group/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import type { CardGroupProps } from "./types"

export function CardGroup({ icon, title, description, children, horizontal = false, className }: CardGroupProps) {
return (
<Glass className={cn("w-full rounded-rectangles border-white/50 bg-background-blur p-12", className)}>
<Glass
className={cn("w-full rounded-rectangles border-white/50 bg-background-blur p-6 sm:p-10 lg:p-12", className)}
>
{horizontal ? (
<div className="flex flex-col gap-12 lg:flex-row lg:items-center">
<div className="flex flex-col gap-8 sm:gap-12 lg:flex-row lg:items-center">
<CardGroupHeader icon={icon} title={title} description={description} horizontal />
<div className="flex-1">{children}</div>
</div>
) : (
<>
<CardGroupHeader icon={icon} title={title} description={description} />
<div className="mt-12">{children}</div>
<div className="mt-8 sm:mt-12">{children}</div>
</>
)}
</Glass>
Expand Down
12 changes: 12 additions & 0 deletions src/components/matricole/cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,17 @@ export function freshmanCardProps(card: FreshmanCard): CardIconProps {
description: card.description,
icon: icons[card.icon],
href: card.href || undefined,
// Only resources that lead somewhere react to hover
hoverEffect: Boolean(card.href),
}
}

/**
* The large resource cards (featured, "Guida della matricola", "Materiali"). Their one-sentence descriptions were
* capped at 240px inside cards three times as wide; 448px keeps them to one or two lines.
*/
export const FEATURED_CARD = {
align: "start",
size: "compact",
className: "h-full min-h-50 [&_p]:max-w-md",
} as const satisfies Partial<CardIconProps>
10 changes: 5 additions & 5 deletions src/components/matricole/faqs.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import AccordionList from "@/components/accordion-list"
import { CONTAINER, SECTION_TITLE } from "@/components/layout/page"
import { cn } from "@/lib/utils"
import type { FreshmanContent } from "@/queries/types"

export function FAQsPage({ faqs }: { faqs: FreshmanContent["faqs"] }) {
Expand All @@ -8,12 +10,10 @@ export function FAQsPage({ faqs }: { faqs: FreshmanContent["faqs"] }) {
content: faq.descriptionIt,
}))
return (
<section className="mx-auto flex w-full max-w-400 flex-col items-center justify-center gap-16 px-9 py-32 sm:gap-20 sm:py-38">
<h2 className="typo-headline-medium sm:typo-display-medium text-center text-text-primary">
Domande Frequenti tra le Matricole
</h2>
<section className={cn(CONTAINER, "flex flex-col items-center gap-10 sm:gap-14")}>
<h2 className={cn(SECTION_TITLE, "text-center")}>Domande Frequenti tra le Matricole</h2>

<div className="mx-auto flex w-full max-w-255 flex-col">
<div className="flex w-full max-w-255 flex-col">
<AccordionList items={accordionItems} />
</div>
</section>
Expand Down
56 changes: 30 additions & 26 deletions src/components/matricole/guides.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,67 @@
import { CardIcon } from "@/components/card-icon"
import { CONTAINER, SECTION_TITLE } from "@/components/layout/page"
import { cn } from "@/lib/utils"
import type { FreshmanContent } from "@/queries/types"
import { CardGroup } from "../card-group"
import { freshmanCardProps } from "./cards"
import { FEATURED_CARD, freshmanCardProps } from "./cards"
import { hub, techTools } from "./constants"

export function MatricoleGuides({ content }: { content: FreshmanContent }) {
const guides = content.resources.filter((card) => card.group === "guides")
const materials = content.resources.filter((card) => card.group === "materials")
const tools = content.resources.filter((card) => card.group === "tools")
return (
<section className="flex w-full flex-col items-center px-6 sm:px-10">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6 text-start sm:gap-13">
<h3 className="typo-title-large sm:typo-display-medium">Guide e Utility</h3>
<section className={cn(CONTAINER, "flex flex-col gap-8 sm:gap-12")}>
<h2 className={SECTION_TITLE}>Guide e Utility</h2>

<div className="grid w-full gap-6 sm:grid-cols-2">
{/* Hidden in mobile */}
<div className="flex flex-col gap-16 sm:gap-6">
{/*
The large guide sits beside the small ones only from xl, and gets 5/12: at 1:1 the 2×2 grid squeezed the
titles onto two lines. The Materiali / Tech Tools row repeats the split so the columns line up.
*/}
<div className="grid w-full gap-6 xl:grid-cols-[minmax(0,5fr)_minmax(0,7fr)]">
{/* Hidden on phones, as in the Figma mobile frame */}
{guides.slice(0, 1).map((guide) => (
<CardIcon
key={guide.id}
{...freshmanCardProps(guide)}
align="start"
className="hidden h-full sm:block"
size="compact"
{...FEATURED_CARD}
className={cn(FEATURED_CARD.className, "hidden sm:block")}
/>
))}
<div className="grid w-full gap-3 sm:gap-6 lg:grid-cols-2">
<div className="grid w-full gap-3 sm:grid-cols-2 sm:gap-6">
{guides.slice(1).map((guide) => (
<CardIcon key={guide.id} {...freshmanCardProps(guide)} align="inline" className="h-full" size="inline" />
<CardIcon
key={guide.id}
{...freshmanCardProps(guide)}
align="inline"
className="h-full"
size="inline-sm"
/>
))}
</div>
</div>

<CardGroup title={hub.title} description={hub.description} className="mt-18 w-full sm:mt-0">
<div className="grid w-full grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
<CardGroup title={hub.title} description={hub.description}>
<div className="grid w-full grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-6 xl:grid-cols-4">
{content.telegram.map((card) => (
<CardIcon key={card.id} {...freshmanCardProps(card)} align="inline" size="inline" />
))}
</div>
</CardGroup>

<div className="grid w-full gap-6 sm:grid-cols-2">
{/* hidden in mobile */}
<div className="grid w-full gap-6 xl:grid-cols-[minmax(0,5fr)_minmax(0,7fr)]">
{/* Hidden on phones, as in the Figma mobile frame */}
{materials.map((material) => (
<CardIcon
key={material.id}
{...freshmanCardProps(material)}
align="start"
className="hidden h-full sm:block"
size="compact"
{...FEATURED_CARD}
className={cn(FEATURED_CARD.className, "hidden sm:block")}
/>
))}
<CardGroup
title={techTools.title}
description={techTools.description}
icon={techTools.icon}
className="mt-18 w-full sm:mt-0"
horizontal
>
<div className="flex flex-col gap-6">
<CardGroup title={techTools.title} description={techTools.description} icon={techTools.icon} horizontal>
<div className="flex flex-col gap-3 sm:gap-6">
{tools.map((card) => (
<CardIcon key={card.id} {...freshmanCardProps(card)} align="inline" size="inline" />
))}
Expand Down
41 changes: 17 additions & 24 deletions src/components/matricole/intro.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
import { CardIcon } from "@/components/card-icon"
import { CONTAINER, PAGE_HERO } from "@/components/layout/page"
import { Hero } from "@/components/ui/hero"
import { cn } from "@/lib/utils"
import type { FreshmanContent } from "@/queries/types"
import { freshmanCardProps } from "./cards"
import { FEATURED_CARD, freshmanCardProps } from "./cards"

export function MatricoleIntro({ resources }: { resources: FreshmanContent["resources"] }) {
return (
<section className="flex w-full flex-col items-center px-6 pt-40 pb-32 sm:px-10 sm:pb-13">
<div className="mx-auto flex w-full max-w-7xl flex-col items-center">
<div className="flex w-full max-w-200 flex-col items-center gap-6 text-center">
<h1 className="typo-display-large md:typo-display-extralarge bg-linear-to-b from-text-primary to-text-secondary bg-clip-text py-8 text-transparent">
Matricole
</h1>
<p className="typo-title-large md:typo-headline-small w-full text-text-primary">
Ecco una raccolta curata di risorse utili, guide e strumenti per supportare il tuo percorso.
</p>
</div>
// The featured resources close the hero, so the hero's extra bottom air is dropped: the page gap follows
<section className={cn(CONTAINER, PAGE_HERO, "flex flex-col gap-16 pb-0 md:pb-0 xl:gap-20 xl:pb-0")}>
<Hero
title="Matricole"
description="Ecco una raccolta curata di risorse utili, guide e strumenti per supportare il tuo percorso."
/>

<div className="mt-30 hidden w-full gap-6 sm:grid sm:grid-cols-2">
{resources
.filter((resource) => resource.group === "featured")
.map((resource) => (
<CardIcon
key={resource.id}
{...freshmanCardProps(resource)}
align="start"
className="h-full min-h-50"
size="compact"
/>
))}
</div>
{/* Hidden on phones as in the Figma mobile frame (3351:13333): an open design decision, not changed here */}
<div className="hidden w-full gap-6 sm:grid sm:grid-cols-2">
{resources
.filter((resource) => resource.group === "featured")
.map((resource) => (
<CardIcon key={resource.id} {...freshmanCardProps(resource)} {...FEATURED_CARD} />
))}
</div>
</section>
)
Expand Down
7 changes: 7 additions & 0 deletions src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@

@custom-variant dark (&:is(.dark *));

/*
CardIcon's responsive `size` ({ base, sm, md, lg }) prefixes the classes in card-icon/classes.ts at runtime, so
Tailwind never sees "sm:h-14" and friends in the source. Generate every prefixed variant here.
tests/card-icon-classes.test.mjs fails if classes.ts gains a class that is missing from this list.
*/
@source inline("{sm:,md:,lg:}{gap-2,gap-4,gap-6,h-6,h-10,h-12,h-14,h-16,h-32,h-44,w-6,w-10,w-12,w-14,w-16,w-32,w-44,p-3,p-6,p-8,px-8,py-4,typo-headline-extrasmall,typo-headline-medium,typo-label-large,typo-title-large}");

@theme {
--breakpoint-1xl: 85rem; /* 1360px */
--breakpoint-3xl: 90rem; /* 1440px */
Expand Down
18 changes: 18 additions & 0 deletions tests/card-icon-classes.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import assert from "node:assert/strict"
import { readFileSync } from "node:fs"
import test from "node:test"

const read = (path) => readFileSync(new URL(path, import.meta.url), "utf8")

test("every CardIcon size class has its responsive variants generated", () => {
const classes = read("../src/components/card-icon/classes.ts")
const used = new Set([...classes.matchAll(/:\s*"([^"]+)"/g)].flatMap((match) => match[1].split(/\s+/)))

const css = read("../src/styles/globals.css")
const inline = css.match(/@source inline\("\{sm:,md:,lg:\}\{([^}]+)\}"\)/)
assert.ok(inline, "the CardIcon @source inline() list is missing from globals.css")
const generated = new Set(inline[1].split(","))

const missing = [...used].filter((name) => !generated.has(name))
assert.deepEqual(missing, [], `add these classes to the @source inline() list in globals.css: ${missing.join(", ")}`)
})
Loading