Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c994c1e
feat: Create interactive footer component with particle effects
Oliverr48 Nov 25, 2025
90b119d
chore: update footer logo asset
Oliverr48 Nov 26, 2025
e913a20
Merge remote-tracking branch 'origin/main' into issue-3-Create_footer…
Oliverr48 Nov 30, 2025
a07990c
Merge main into issue-3-Create_footer_component, integrate Footer com…
Oliverr48 Nov 30, 2025
5e16c4c
Merge main into issue-3-Create_footer_component and enhance Footer co…
Oliverr48 Nov 30, 2025
4dff562
Fix Prettier formatting
Oliverr48 Dec 3, 2025
c9de6a0
Add root package-lock.json
Oliverr48 Dec 3, 2025
eeb8f95
Merge remote-tracking branch 'origin/fix-colours' into issue-3-Create…
samjjacko Dec 4, 2025
d6ee4af
Improved colour usage, removed the tree of divs, moved Footer into co…
samjjacko Dec 4, 2025
db31a49
refactor(footer): address code review feedback and improve code organ…
Oliverr48 Dec 6, 2025
3a070fe
style: run prettier to fix code formatting
Oliverr48 Dec 6, 2025
20aa223
refactor(footer): extract constants, components, and data; fix types/…
Oliverr48 Dec 9, 2025
17d4c82
Merge remote-tracking branch 'origin/main' into issue-3-Create_footer…
Oliverr48 Dec 9, 2025
ee5433f
fix: resolve footer data path alias and pass typecheck
Oliverr48 Dec 9, 2025
97d0d26
fix: renamed data folder to static-data and update footer import
Oliverr48 Dec 9, 2025
44e5d8e
fix: resolve lint errors - sort imports and remove unused Code2 import
Oliverr48 Dec 10, 2025
84662ef
Remove accidental package-lock.json from root directory
Oliverr48 Jan 18, 2026
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
1 change: 1 addition & 0 deletions client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next
- Axios for data fetching
- shadcn/ui for components
- lucide for icons
- react-social-icons for social media/brand icons

## Getting Started

Expand Down
43 changes: 43 additions & 0 deletions client/package-lock.json

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

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"axios": "^1.12.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.23.24",
"is-inside-container": "^1.0.0",
"lucide-react": "^0.516.0",
"next": "15.4.8",
Expand Down
Binary file added client/public/navbar_arr.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 77 additions & 0 deletions client/src/components/footer/FooterLinkList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"use client";

import { ChevronRight } from "lucide-react";
import Link from "next/link";
import type { ReactNode } from "react";
import { useState } from "react";

interface FooterLink {
label: string;
href: string;
icon?: ReactNode;
}

interface FooterLinkListProps {
title: string;
titleIcon: ReactNode;
links: FooterLink[];
useChevron?: boolean; // If true, uses ChevronRight; if false, uses icon from link
}

/**
* Reusable footer link list component
* Supports both icon-based links (mainLinks) and chevron-based links (quickLinks)
* Provides consistent hover states and styling
*/
export default function FooterLinkList({
title,
titleIcon,
links,
useChevron = false,
}: FooterLinkListProps) {
const [isHovered, setIsHovered] = useState<string | null>(null);

return (
<div className="space-y-3 lg:col-span-1">
<h4 className="flex items-center gap-2 font-jersey10 text-xl font-semibold uppercase tracking-wider text-white">
{titleIcon}
{title}
</h4>
<ul className="space-y-2">
{links.map((link) => (
<li key={link.label}>
<Link
href={link.href}
className="group flex items-center gap-2 font-jersey10 text-xl text-gray-400 transition-all duration-300 hover:text-purple-400"
onMouseEnter={() => setIsHovered(link.label)}
onMouseLeave={() => setIsHovered(null)}
>
{useChevron ? (
<>
<ChevronRight
className={`h-3 w-3 transition-transform duration-300 ${
isHovered === link.label ? "translate-x-1" : ""
}`}
/>
<span className="relative">
{link.label}
{isHovered === link.label && (
<span className="absolute inset-x-0 -bottom-0.5 h-px bg-gradient-to-r from-purple-400 to-pink-400" />
)}
</span>
</>
) : (
<>
<span className="text-purple-500/50 transition-transform duration-300 group-hover:scale-110 group-hover:text-purple-400">
{link.icon}
</span>
{link.label}
</>
)}
</Link>
</li>
))}
</ul>
</div>
);
}
79 changes: 79 additions & 0 deletions client/src/components/footer/SocialIconButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use client";

import { motion } from "framer-motion";
import { SocialIcon } from "react-social-icons";

import {
MOTION_COLOUR_SOCIAL_BG_HOV_ALPHA,
MOTION_COLOUR_SOCIAL_BORDER_HOV_ALPHA,
SOCIAL_ICON_HOVER_SCALE,
SOCIAL_ICON_HOVER_Y,
SOCIAL_ICON_ROTATE_DEGREES,
SOCIAL_ICON_SPRING_DAMPING,
SOCIAL_ICON_SPRING_STIFFNESS,
SOCIAL_ICON_TAP_SCALE,
} from "@/lib/footer-constants";
import { cssVarAsHSL } from "@/lib/utils";

interface SocialIconButtonProps {
url: string;
label: string;
motionColours: {
socialBGHov: string;
socialBorderHov: string;
};
}

/**
* Reusable social media icon button component
* Handles hover animations and styling with Motion values for colors
*/
export default function SocialIconButton({
url,
label,
motionColours,
}: SocialIconButtonProps) {
return (
<motion.div
className="group rounded-xl border border-white/10 bg-white/5 p-2.5"
aria-label={label}
whileHover={{
scale: SOCIAL_ICON_HOVER_SCALE,
y: SOCIAL_ICON_HOVER_Y,
backgroundColor: motionColours.socialBGHov,
borderColor: motionColours.socialBorderHov,
}}
whileTap={{ scale: SOCIAL_ICON_TAP_SCALE }}
transition={{
type: "spring",
stiffness: SOCIAL_ICON_SPRING_STIFFNESS,
damping: SOCIAL_ICON_SPRING_DAMPING,
}}
>
<motion.span
whileHover={{ rotate: SOCIAL_ICON_ROTATE_DEGREES }}
transition={{
type: "spring",
stiffness: SOCIAL_ICON_SPRING_STIFFNESS,
damping: SOCIAL_ICON_SPRING_DAMPING,
}}
>
<SocialIcon url={url} className="h-5 w-5" />
</motion.span>
</motion.div>
);
}

/**
* Helper function to create motion colours for social icons
* This ensures colors use Motion values instead of hard-coded rgba
*/
export function createSocialMotionColours() {
return {
socialBGHov: cssVarAsHSL("--light-1", MOTION_COLOUR_SOCIAL_BG_HOV_ALPHA),
socialBorderHov: cssVarAsHSL(
"--light-alt",
MOTION_COLOUR_SOCIAL_BORDER_HOV_ALPHA,
),
};
}
Loading