Skip to content
Closed
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ Every card walks out in a finish:

<br/>

## πŸ† &nbsp;Achievements & Awards

Developers can earn premium 3D trophies that display directly on their profile page and duel views. Clicking on any trophy opens an interactive modal to view details and qualifying statistics:

| Award | 3D Model | Qualification Criteria |
| :---: | :--- | :--- |
| **World Cup Trophy** | `/3D-Models/world_cup_trophy.glb` | **Generational Champion**: Earned by achieving an Overall rating of **85 or higher**, reflecting complete mastery. In duels, awarded to the overall match winner (or both in case of a draw). |
| **Golden Boot** | `/3D-Models/golden_boot.glb` | **Elite Star Attraction**: Earned by achieving a Shooting (`SHO`) stat of **80 or higher** (representing stellar repo star metrics). In duels, awarded to the player who wins the SHO category (or both in a tie). |
| **Golden Glove** | `/3D-Models/golden_glove.glb` | **Clean Sheet Defender**: Earned by achieving a Defending (`DEF`) stat of **60 or higher** (representing outstanding code reviews and issues closed). In duels, awarded to the player who wins the DEF category (or both in a tie). |

<br/>

<div align="center">

**Built with** Next.js Β· TypeScript Β· Tailwind Β· Redis
Expand Down
10 changes: 10 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -386,3 +386,13 @@
transition-duration: 0.001ms !important;
}
}

/* Custom pointer overrides for fine-pointer devices */
@media (pointer: fine) {
body, a, button, [role="button"], label {
cursor: none !important;
}
input, textarea, [contenteditable="true"] {
cursor: text !important;
}
}
3 changes: 3 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Bebas_Neue, Inter, JetBrains_Mono } from "next/font/google";
import localFont from "next/font/local";
import Script from "next/script";
import "./globals.css";
import { Analytics } from "@vercel/analytics/next";
import { CustomPointer } from "@/components/CustomPointer";

// Display β€” ultra-condensed all-caps for the WC26 "tournament" impact.
const display = Bebas_Neue({
Expand Down Expand Up @@ -74,6 +76,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
className={`${display.variable} ${sans.variable} ${mono.variable} ${dinCond.variable} ${dinBold.variable} ${dinMedium.variable} antialiased`}
>
<body>
<CustomPointer />
{children}
<Script
defer
Expand Down
4 changes: 4 additions & 0 deletions components/Background.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
"use client";

const noiseSvg =
'<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120"><filter id="n"><feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="2"/></filter><rect width="120" height="120" filter="url(#n)"/></svg>';
const NOISE = `url("data:image/svg+xml;utf8,${encodeURIComponent(noiseSvg)}")`;



// Faint GitHub-contribution-grid motif β€” a brand signature drawn into the
// backdrop. A few cells gently pulse green (see .gf-grid-cell in globals.css).
//
Expand Down
142 changes: 142 additions & 0 deletions components/CustomPointer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
"use client";

import { useEffect, useRef, Suspense } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import { useGLTF, Environment } from "@react-three/drei";
import * as THREE from "three";

function FootballPointer() {
const { scene } = useGLTF("/3D-Models/fifa_trionda_ball_world_cup_2026.glb");
const meshRef = useRef<THREE.Group>(null);
const scaleRef = useRef(6);
const isHoveringRef = useRef(false);
const isHiddenRef = useRef(false);

useEffect(() => {
if (window.matchMedia("(pointer: coarse)").matches) return;

const onMouseMove = (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (!target) return;

const isInput = target.tagName.toLowerCase() === "input" ||
target.tagName.toLowerCase() === "textarea" ||
target.isContentEditable;

isHiddenRef.current = isInput;
isHoveringRef.current =
window.getComputedStyle(target).cursor === "pointer" ||
target.tagName.toLowerCase() === "a" ||
target.tagName.toLowerCase() === "button" ||
target.closest('a') !== null ||
target.closest('button') !== null;
};

window.addEventListener("mousemove", onMouseMove);
return () => window.removeEventListener("mousemove", onMouseMove);
}, []);

useFrame((state, delta) => {
if (meshRef.current) {
// Continuous rotation
meshRef.current.rotation.y += delta * 2;
meshRef.current.rotation.x += delta * 1.5;

// Smooth scale interpolation when hovering
const targetScale = isHiddenRef.current ? 0 : (isHoveringRef.current ? 11 : 6);
scaleRef.current += (targetScale - scaleRef.current) * 0.2;
meshRef.current.scale.setScalar(scaleRef.current);
}
});

return (
<group ref={meshRef} dispose={null}>
<primitive object={scene} />
</group>
);
}

useGLTF.preload("/3D-Models/fifa_trionda_ball_world_cup_2026.glb");

export function CustomPointer() {
const containerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
// Disable on touch devices
if (window.matchMedia("(pointer: coarse)").matches) return;

let mouseX = -100;
let mouseY = -100;
let currentX = -100;
let currentY = -100;
let hiddenState = false;

const onMouseMove = (e: MouseEvent) => {
mouseX = e.clientX;
mouseY = e.clientY;

const target = e.target as HTMLElement;
if (!target) return;

hiddenState = target.tagName.toLowerCase() === "input" ||
target.tagName.toLowerCase() === "textarea" ||
target.isContentEditable;
};

let hasMoved = false;
const onFirstMouseMove = (e: MouseEvent) => {
if (!hasMoved) {
currentX = e.clientX;
currentY = e.clientY;
hasMoved = true;
}
};

window.addEventListener("mousemove", onFirstMouseMove, { once: true });
window.addEventListener("mousemove", onMouseMove);

const animate = () => {
// Ease the pointer towards the mouse for a smooth trailing effect
currentX += (mouseX - currentX) * 0.4;
currentY += (mouseY - currentY) * 0.4;

if (containerRef.current) {
containerRef.current.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`;
containerRef.current.style.opacity = hiddenState ? "0" : "1";
}

requestAnimationFrame(animate);
};

const animId = requestAnimationFrame(animate);

return () => {
window.removeEventListener("mousemove", onMouseMove);
cancelAnimationFrame(animId);
};
}, []);

// Use a fixed w-28 h-28 container, offset by -56px so the mouse is exactly in the center
return (
<div
ref={containerRef}
className="pointer-events-none fixed z-[999999] hidden sm:block will-change-transform transition-opacity duration-200 w-28 h-28"
style={{ left: '-56px', top: '-56px', pointerEvents: 'none' }}
>
<Canvas
camera={{ position: [0, 0, 5], fov: 45 }}
dpr={[1, 2]}
style={{ pointerEvents: 'none' }}
>
<ambientLight intensity={1} />
{/* Brand green light to tie it into the theme */}
<directionalLight position={[5, 5, 5]} intensity={2.5} color="#39d353" />
<directionalLight position={[-5, -5, -5]} intensity={1.5} color="#ffffff" />
<Environment preset="city" />
<Suspense fallback={null}>
<FootballPointer />
</Suspense>
</Canvas>
</div>
);
}
Loading