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/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ConditionalFooter } from "@/components/conditional-footer"
import { NavigationEasterEgg } from "@/components/easter-egg/navigation-easter-egg"
import { Header } from "@/components/header/header"
import { Shape } from "@/components/shapes"
import { ThemeProvider } from "@/components/theme-provider"
Expand Down Expand Up @@ -79,11 +80,11 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac
<Shape variant="big-blue" className="-translate-x-1/2 -translate-y-1/2 top-0 left-1/2" />
<Shape variant="looper" className="-translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2" />
</div>
<div className="flex min-h-screen w-full flex-col items-center justify-start">
<NavigationEasterEgg className="flex min-h-screen w-full flex-col items-center justify-start">
<Header />
{children}
<ConditionalFooter />
</div>
</NavigationEasterEgg>
</ThemeProvider>
</body>
</html>
Expand Down
29 changes: 29 additions & 0 deletions src/components/easter-egg/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { matchesRoutePrefix } from "@/utils/route-match"
import type { EasterEggPhase, NavCategory, PhaseTransition } from "./types"

const HOME_PATH = "/"
const GROUPS_PREFIX = "/groups"

export const TRIGGER_COUNT = 10

// chiavi opzionali perché non tutte le fasi hanno una fase successiva (cioé idle)
export const PHASE_TRANSITIONS: Partial<Record<EasterEggPhase, PhaseTransition>> = {
glitch: { to: "card", delay: 1200 },
card: { to: "idle", delay: 10000 },
}

export const PUNCHLINES = [
"Hai trovato l'easter egg.\nOra torna a studiare.",
"Avanti e indietro, avanti e indietro...\nTi senti realizzato?",
`COMPLIMENTI.\nHai sprecato ${TRIGGER_COUNT} click per questo.`,
"Non c'è nient'altro qui.\nPuoi tornare alla tua vita.",
"Il PoliNetwork ti osserva\nda quando hai iniziato a cliccare.",
"Bel tentativo.\nMa qui non succede nient'altro di strano.",
"Home, Groups, Home, Groups...\nHai capito il concetto di loop?",
]

export function getCategory(pathname: string): NavCategory {
if (pathname === HOME_PATH) return "home"
if (matchesRoutePrefix(pathname, GROUPS_PREFIX)) return "groups"
return "other"
}
72 changes: 72 additions & 0 deletions src/components/easter-egg/navigation-easter-egg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"use client"

import Image from "next/image"
import { usePathname } from "next/navigation"
import type { ReactNode } from "react"
import { useEffect, useRef, useState } from "react"
import { GenericCard } from "@/components/ui/generic-card"
import { cn } from "@/lib/utils"
import { getCategory, PHASE_TRANSITIONS, PUNCHLINES, TRIGGER_COUNT } from "./constants"
import type { EasterEggPhase, NavCategory } from "./types"

export function NavigationEasterEgg({ children, className }: { children: ReactNode; className?: string }) {
const pathname = usePathname()
const lastCategoryRef = useRef<NavCategory | null>(null)
const toggleCountRef = useRef(0)
const [phase, setPhase] = useState<EasterEggPhase>("idle")
const [punchline, setPunchline] = useState(PUNCHLINES[0])

useEffect(() => {
const category = getCategory(pathname)
const lastCategory = lastCategoryRef.current

if (category === "other") {
toggleCountRef.current = 0
lastCategoryRef.current = null
return
}

if (lastCategory && lastCategory !== category) {
toggleCountRef.current += 1
if (toggleCountRef.current >= TRIGGER_COUNT) {
toggleCountRef.current = 0
setPunchline(PUNCHLINES[Math.floor(Math.random() * PUNCHLINES.length)])
setPhase("glitch")
}
}

lastCategoryRef.current = category
}, [pathname])

useEffect(() => {
const next = PHASE_TRANSITIONS[phase]
if (!next) return
const timeout = setTimeout(() => setPhase(next.to), next.delay)
return () => clearTimeout(timeout)
}, [phase])

return (
<>
<div className={cn(className, phase === "glitch" && "animate-easter-egg-shake")}>{children}</div>

{phase === "glitch" && (
<div role="presentation" aria-hidden="true" className="pointer-events-none fixed inset-0 z-9999">
<div className="absolute inset-0 animate-easter-egg-flicker bg-black mix-blend-difference" />
<div className="absolute inset-0 animate-easter-egg-glitch bg-blue-600/40 mix-blend-screen" />
<div className="absolute inset-0 animate-easter-egg-glitch bg-sky-300/40 mix-blend-screen [animation-direction:reverse]" />
</div>
)}

{phase === "card" && (
<div className="fixed inset-0 z-9999 flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm">
<GenericCard className="flex animate-easter-egg-card-pop flex-col items-center gap-6 px-10 py-12">
<Image src="/polinetwork_meta.png" alt="PoliNetwork" width={120} height={120} priority />
<p className="typo-title-large sm:typo-headline-small max-w-sm whitespace-pre-line bg-linear-to-b from-blue-secondary to-blue-primary bg-clip-text text-center font-medium text-transparent">
{punchline}
</p>
</GenericCard>
</div>
)}
</>
)
}
4 changes: 4 additions & 0 deletions src/components/easter-egg/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type NavCategory = "home" | "groups" | "other"
export type EasterEggPhase = "idle" | "glitch" | "card"

export type PhaseTransition = { to: EasterEggPhase; delay: number }
6 changes: 6 additions & 0 deletions src/components/ui/generic-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type * as React from "react"
import { cn } from "@/lib/utils"

export function GenericCard({ className, ...props }: React.ComponentProps<"div">) {
return <div className={cn("rounded-3xl bg-white p-6 shadow-2xl", className)} {...props} />
}
2 changes: 1 addition & 1 deletion src/components/ui/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function Hero({
}) {
return (
<div className="flex flex-col items-center gap-6">
<h2 className="typo-display-large sm:typo-display-extralarge w-fit self-stretch bg-linear-to-b from-text-primary to-text-secondary bg-clip-text py-4 text-center text-transparent sm:py-14">
<h2 className="typo-display-large sm:typo-display-extralarge w-fit bg-linear-to-b from-text-primary to-text-secondary bg-clip-text py-4 text-center text-transparent sm:py-14">
{title}
</h2>
<p
Expand Down
110 changes: 110 additions & 0 deletions src/styles/animations.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,113 @@
@utility animate-accordion-up {
animation: accordion-up 0.2s ease-out;
}

@keyframes easter-egg-shake {
0%,
100% {
transform: translate(0, 0);
}
10% {
transform: translate(-6px, 3px);
}
20% {
transform: translate(5px, -4px);
}
30% {
transform: translate(-4px, -3px);
}
40% {
transform: translate(6px, 2px);
}
50% {
transform: translate(-3px, 4px);
}
60% {
transform: translate(4px, -2px);
}
70% {
transform: translate(-5px, 3px);
}
80% {
transform: translate(3px, -3px);
}
90% {
transform: translate(-2px, 2px);
}
}

@keyframes easter-egg-flicker {
0%,
100% {
opacity: 0;
}
15%,
35%,
55%,
75% {
opacity: 0.55;
}
25%,
45%,
65%,
85% {
opacity: 0.15;
}
}

@keyframes easter-egg-glitch {
0%,
100% {
opacity: 0;
transform: translateX(0);
}
20% {
opacity: 0.6;
transform: translateX(8px);
}
40% {
opacity: 0;
transform: translateX(-6px);
}
60% {
opacity: 0.5;
transform: translateX(5px);
}
80% {
opacity: 0;
transform: translateX(-3px);
}
}

@utility animate-easter-egg-shake {
animation: easter-egg-shake 1.2s ease-in-out;
}

@utility animate-easter-egg-flicker {
animation: easter-egg-flicker 1.2s steps(1, end);
}

@utility animate-easter-egg-glitch {
animation: easter-egg-glitch 1.2s steps(1, end);
}

@keyframes easter-egg-card-pop {
0% {
transform: scale(0.3);
opacity: 0;
}
60% {
transform: scale(1.06);
opacity: 1;
}
80% {
transform: scale(0.97);
}
100% {
transform: scale(1);
}
}

@utility animate-easter-egg-card-pop {
animation: easter-egg-card-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
Loading