Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
"clsx": "^2.1.1",
"cmdk": "1.1.1",
"geist": "^1.5.1",
"lucide-react": "^0.545.0",
"next": "^15.5.14",
"next-themes": "^0.4.6",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-hook-form": "^7.68.0",
"react-icons": "^5.6.0",
"tailwind-merge": "^3.4.0",
"tailwindcss-animate": "^1.0.7",
"zod": "^4.2.1"
Expand Down
24 changes: 12 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 57 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,61 @@
import { FiBook, FiBookOpen, FiClipboard, FiFileText, FiPenTool, FiTriangle, FiUploadCloud } from "react-icons/fi"
import { CardIcon } from "@/components/card-icon"
import { Hero } from "@/components/home/hero"

const schoolCards = [
{ title: "Scuola di Architettura", icon: FiTriangle, size: "md" },
{ title: "Scuola di Design", icon: FiPenTool, size: "md" },
{ title: "Scuola di Ingegneria", icon: FiBookOpen, size: "md" },
] as const

const materialCards = [
{
title: "Carica",
description:
"Hai appunti, dispense o temi d'esame che vuoi condividere? Caricali qui! Il tuo contributo è prezioso per aiutare migliaia di colleghi con materiale aggiornato!",
icon: FiUploadCloud,
size: "lg",
},
{
title: "Visualizza",
description:
"Cerca ciò che ti serve per il tuo prossimo esame. Naviga tra i corsi di studio e trova facilmente appunti, esercizi e dispense condivisi da altri studenti come te.",
icon: FiBookOpen,
size: "lg",
},
] as const

const otherCards = [
{ title: "Dispense", icon: FiBook, size: "sm" },
{ title: "Appunti", icon: FiFileText, size: "sm" },
{ title: "Esami", icon: FiClipboard, size: "sm" },
] as const
Comment thread
BIA3IA marked this conversation as resolved.

export default function Home() {
return <Hero />
return (
<main className="w-full">
<Hero />
<div className="mx-auto flex max-w-6xl flex-col gap-12">
<section>
<div className="grid gap-6 sm:grid-cols-2 md:grid-cols-3">
{schoolCards.map((card) => (
<CardIcon key={card.title} {...card} href="#" hoverEffect />
))}
</div>
</section>
<section className="flex max-w-4xl flex-col gap-6">
<div className="grid gap-32 sm:grid-cols-2">
{materialCards.map((card) => (
<CardIcon key={card.title} {...card} href="#" />
))}
</div>
<div className="grid gap-16 sm:grid-cols-3">
{otherCards.map((card) => (
<CardIcon key={card.title} {...card} href="#" />
))}
</div>
</section>
</div>
</main>
)
}
7 changes: 7 additions & 0 deletions src/components/card-icon/basic-card-media.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { GradientIcon, type GradientIconType } from "../gradient-icon"
import type { CardSize } from "./types"
import { getIconSizeClasses } from "./utils"

export function BasicCardMedia({ icon: Icon, size }: { icon: GradientIconType; size: CardSize }) {
return <GradientIcon icon={Icon} className={getIconSizeClasses(size)} />
}
12 changes: 12 additions & 0 deletions src/components/card-icon/description-card-media.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { cn } from "@/lib/utils"
import { GradientIcon, type GradientIconType } from "../gradient-icon"
import type { CardSize } from "./types"
import { getIconSizeClasses } from "./utils"

export function DescriptionCardMedia({ icon: Icon, size }: { icon: GradientIconType; size: CardSize }) {
return (
<div className={cn("relative", getIconSizeClasses(size))}>
<GradientIcon icon={Icon} className="h-full w-full" />
</div>
)
}
15 changes: 15 additions & 0 deletions src/components/card-icon/hover-background.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Shape } from "@/components/shapes"

export function CardHoverBackground() {
return (
<div className="pointer-events-none absolute inset-0 z-0 overflow-hidden opacity-0 transition-opacity duration-300 group-hover/card:opacity-100">
<Shape variant="big-teal" className="-top-16 -right-20 absolute h-40 w-40" />
<Shape variant="big-teal" className="-bottom-21 -left-2 absolute h-70 w-70" />
<div className="-top-36 -left-32 absolute h-168 w-2xl">
<div className="-rotate-70 relative h-full w-full origin-center">
Comment thread
toto04 marked this conversation as resolved.
<Shape variant="looper" className="absolute inset-0 h-full w-full object-contain" />
</div>
</div>
</div>
)
}
61 changes: 61 additions & 0 deletions src/components/card-icon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Glass } from "@/components/glass"
import { cn } from "@/lib/utils"
import { BasicCardMedia } from "./basic-card-media"
import { DescriptionCardMedia } from "./description-card-media"
import { CardHoverBackground } from "./hover-background"
import type { CardIconProps } from "./types"
import { getCardPaddingClasses, getContentGapClasses } from "./utils"

export function CardIcon(props: CardIconProps) {
const { title, icon, size = "md", href, hoverEffect = false, className } = props
const description = "description" in props ? props.description : undefined
const Root = href ? "a" : "div"
const isDescriptionCard = Boolean(description)

return (
<Glass
className={cn(
"w-full overflow-hidden rounded-rectangles border-white/50 bg-background-blur p-0 text-card-foreground",
className
)}
>
<Root
href={href}
className={cn(
"group/card relative flex h-full flex-col text-left",
getCardPaddingClasses(size, isDescriptionCard)
)}
>
{hoverEffect && <CardHoverBackground />}

<div
className={cn(
"relative z-10 flex h-full flex-1 flex-col",
getContentGapClasses(size),
isDescriptionCard ? "justify-between" : "items-center justify-center text-center"
)}
>
<div className="flex justify-center">
{isDescriptionCard ? (
<DescriptionCardMedia icon={icon} size={size} />
) : (
<BasicCardMedia icon={icon} size={size} />
)}
</div>

<div className={cn("flex flex-col", isDescriptionCard ? "gap-2 text-left" : "items-center text-center")}>
<h3
className={cn(
"typo-headline-medium bg-linear-to-b from-blue-secondary to-blue-primary bg-clip-text text-transparent",
isDescriptionCard ? "text-left" : "text-center"
)}
>
{title}
</h3>
{description && <p className="typo-body-medium max-w-sm text-left text-text-primary">{description}</p>}
</div>
</div>
</Root>
</Glass>
)
}
18 changes: 18 additions & 0 deletions src/components/card-icon/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { GradientIconType } from "@/components/gradient-icon"

export type CardSize = "sm" | "md" | "lg"

export type SharedCardProps = {
title: string
icon: GradientIconType
size?: CardSize
href?: string
hoverEffect?: boolean
className?: string
}

export type CardWithDescriptionProps = SharedCardProps & {
description: string
}

export type CardIconProps = SharedCardProps | CardWithDescriptionProps
17 changes: 17 additions & 0 deletions src/components/card-icon/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { CardSize } from "./types"

export function getIconSizeClasses(size: CardSize) {
if (size === "sm") return "h-14 w-14"
if (size === "lg") return "h-44 w-44"
return "h-32 w-32"
}

export function getCardPaddingClasses(size: CardSize, hasDescription: boolean) {
if (!hasDescription && size === "sm") return "px-8 py-4"
return "p-8"
}

export function getContentGapClasses(size: CardSize) {
if (size === "sm") return "gap-2"
return "gap-6"
}
29 changes: 29 additions & 0 deletions src/components/gradient-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { type FunctionComponent, type SVGProps, useId } from "react"

export type GradientIconType = FunctionComponent<SVGProps<SVGSVGElement>>

type GradientIconProps = {
icon: GradientIconType
className: string
}

export function GradientIcon({ icon: Icon, className }: GradientIconProps) {
const iconId = useId().replaceAll(":", "")
const gradientId = `icon-gradient-${iconId}`
const maskId = `icon-mask-${iconId}`

return (
<svg className={className} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<defs>
<linearGradient id={gradientId} x1="0%" x2="0%" y1="0%" y2="100%">
<stop offset="0%" stopColor="var(--color-blue-secondary)" />
<stop offset="100%" stopColor="var(--color-blue-primary)" />
</linearGradient>
<mask id={maskId} maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
<Icon className={className} stroke="white" fill="none" />
</mask>
</defs>
<rect x="0" y="0" width="16" height="16" fill={`url(#${gradientId})`} mask={`url(#${maskId})`} />
</svg>
)
}
4 changes: 2 additions & 2 deletions src/components/header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GlobeIcon } from "lucide-react"
import Image from "next/image"
import Link from "next/link"
import { FiGlobe } from "react-icons/fi"
import { ThemeButton } from "@/components/theme-button"

export const HEADER_HEIGHT = "4.5rem"
Expand Down Expand Up @@ -28,7 +28,7 @@ export async function Header() {
<nav className="flex items-center space-x-6">
<ThemeButton />
<button type="button" className="hover:text-accent-foreground">
<GlobeIcon className="h-6 w-6" />
<FiGlobe className="h-6 w-6" />
</button>
</nav>
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/components/home/hero.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Search, Send, UserPlus } from "lucide-react"
import { FiNavigation, FiSearch, FiUserPlus } from "react-icons/fi"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"

Expand All @@ -15,24 +15,24 @@ export function Hero() {

<div className="flex w-full flex-col items-center gap-8 md:gap-16">
<Input
icon={<Search className="h-5 w-5" />}
icon={<FiSearch className="h-5 w-5" />}
type="text"
placeholder="Find your group"
aria-label="Find your group"
containerClassName="max-w-xl"
className="typo-body-medium"
/>

<Button variant="primary" size="sm">
<Button variant="primary" size="lg">
More groups
<Send />
<FiNavigation />
</Button>
</div>
</div>

<div className="flex justify-center md:justify-end">
<Button variant="tertiaryBlur" size="lg" className="text-blue-secondary">
<UserPlus />
<FiUserPlus />
Sei una matricola?
</Button>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/components/theme-button.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use client"
import { MoonIcon, SunIcon } from "lucide-react"
import { useTheme } from "next-themes"
import { FiMoon, FiSun } from "react-icons/fi"

export function ThemeButton() {
const { resolvedTheme, setTheme } = useTheme()
return (
// TODO: enable when dark mode design is ready
<button type="button" disabled={true} onClick={() => setTheme(resolvedTheme === "light" ? "dark" : "light")}>
<SunIcon className="block h-6 w-6 dark:hidden" />
<MoonIcon className="hidden h-6 w-6 dark:block" />
<FiSun className="block h-6 w-6 dark:hidden" />
<FiMoon className="hidden h-6 w-6 dark:block" />
</button>
)
}
4 changes: 2 additions & 2 deletions src/components/ui/buttonWithIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { LucideIcon } from "lucide-react"
import type { IconType } from "react-icons"
import { Button } from "./button"

export function ButtonWithIcon({
Expand All @@ -8,7 +8,7 @@ export function ButtonWithIcon({
iconPosition = "left",
}: {
variant?: "primary" | "tertiary" | "tertiaryBlur"
icon: LucideIcon
icon: IconType
text: string
iconPosition?: "left" | "right"
}) {
Expand Down
Loading
Loading