diff --git a/public/assets/icons/resonate_branding_dark.svg b/public/assets/icons/resonate_branding_dark.svg
new file mode 100644
index 0000000..dd00b29
--- /dev/null
+++ b/public/assets/icons/resonate_branding_dark.svg
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/assets/icons/resonate_branding_light.svg b/public/assets/icons/resonate_branding_light.svg
new file mode 100644
index 0000000..0fb8636
--- /dev/null
+++ b/public/assets/icons/resonate_branding_light.svg
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/[locale]/globals.css b/src/app/[locale]/globals.css
index 842e5aa..d6b44d7 100644
--- a/src/app/[locale]/globals.css
+++ b/src/app/[locale]/globals.css
@@ -14,7 +14,7 @@
:root {
/* Surface Colors */
--background: #F5F5F5;
- --nav-background: rgba(245, 245, 245, 0.8); /* Added for glassmorphism */
+ --nav-background: rgb(245, 245, 245, 0.8); /* Added for glassmorphism */
/* Text Colors */
--foreground: #1C1C1C;
@@ -36,13 +36,22 @@
--button-primary-hover-bg: var(--color-neutral-50);
--button-primary-hover-border: #121212;
--button-subtle-bg: #9797971e;
+
+ /* Background Tile Colors */
+ --tile-bg: rgb(227, 226, 246);
+ --tile-stroke: rgb(80, 80, 80, 0.1);
+ --line-color: rgb(0, 0, 0, 0.03);
+
+ /* Footer Colors */
+ --footer-headings: rgb(0, 0, 0, 0.9);
+ --footer-text: rgb(0, 0, 0, 0.75);
}
/* DARK MODE */
.dark {
/* Surface Colors */
--background: #101010;
- --nav-background: rgba(16, 16, 16, 0.8); /* Added for glassmorphism */
+ --nav-background: rgb(16, 16, 16, 0.8); /* Added for glassmorphism */
/* Text Colors */
--foreground: #D3D3D3;
@@ -64,6 +73,15 @@
--button-primary-hover-bg: var(--color-neutral-100);
--button-primary-hover-border: var(--color-neutral-100);
--button-subtle-bg: #bbbbbb0e;
+
+ /* Background Tile Colors */
+ --tile-bg: rgb(30, 29, 28);
+ --tile-stroke: rgb(80, 80, 80, 0.5);
+ --line-color: rgb(0, 0, 0, 0.12);
+
+ /* Footer Colors */
+ --footer-headings: rgb(255, 255, 255);
+ --footer-text: rgb(255, 255, 255, 0.5);
}
/* --- GLOBAL STYLES --- */
@@ -96,5 +114,4 @@ body {
/* Dark Mode: Icon flips to White */
:is(.dark .theme-icon) {
filter: invert(1) brightness(100);
-}
-
+}
\ No newline at end of file
diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx
index 67f2d5a..3fd359a 100644
--- a/src/app/[locale]/page.tsx
+++ b/src/app/[locale]/page.tsx
@@ -1,6 +1,7 @@
'use client';
import { useTranslations } from 'next-intl';
+import Footer from '@/components/ui/Footer';
import dynamic from 'next/dynamic';
@@ -12,9 +13,17 @@ import dynamic from 'next/dynamic';
export default function Home() {
const t = useTranslations('Home');
return (
-
-
{t('title')}
-
{t('subtitle')}
+
+ {/* Main content */}
+
+ {t('title')}
+ {t('subtitle')}
+
+
+ {/* Footer*/}
+
+
+
);
}
diff --git a/src/components/ui/BackgroundTile.tsx b/src/components/ui/BackgroundTile.tsx
new file mode 100644
index 0000000..80a4e48
--- /dev/null
+++ b/src/components/ui/BackgroundTile.tsx
@@ -0,0 +1,249 @@
+"use client";
+
+import { useEffect, useRef } from "react";
+
+// Use this component in below shown way and adjust the height according to your use case.
+/*
+
+
+
+*/
+
+// Stable random values generated once at module level
+const PHASES = Array.from({ length: 6 }, () => Math.random() * Math.PI * 2);
+const FREQS = Array.from({ length: 6 }, (_, i) => 0.3 + i * 0.13);
+
+// ── SPEED CONFIGURATION ───────────────────────────────────────────
+// Change this! 0.5 is subtle, 5.0 is incredibly fast.
+const TRANSLATION_SPEED = 0.75;
+
+// Vertical squiggling speed
+const UNDULATION_SPEED = 0.055;
+// ──────────────────────────────────────────────────────────────────
+
+const BackgroundTile = () => {
+ const canvasRef = useRef
(null);
+ const rafRef = useRef(null);
+ const lineColorRef = useRef("");
+ const isRunning = useRef(false);
+ const timeRef = useRef(0);
+ const ampRef = useRef(0);
+ const ampSmoothed = useRef(0);
+
+ // Forward translation trackers
+ const xOffsetRef = useRef(0);
+ const targetXSpeed = useRef(0);
+ const currentXSpeed = useRef(0);
+
+ const stopTimer = useRef | null>(null);
+
+ useEffect(() => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return;
+
+ const updateLineColor = () => {
+ lineColorRef.current = getComputedStyle(document.documentElement)
+ .getPropertyValue("--line-color")
+ .trim();
+ };
+ updateLineColor();
+
+ const themeObserver = new MutationObserver((mutations) => {
+ for (const m of mutations) {
+ if (m.attributeName === "class" || m.attributeName === "data-theme") {
+ updateLineColor();
+ }
+ }
+ });
+ themeObserver.observe(document.documentElement, { attributes: true });
+
+ const setSize = () => {
+ const dpr = window.devicePixelRatio || 1;
+ canvas.width = canvas.offsetWidth * dpr;
+ canvas.height = canvas.offsetHeight * dpr;
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ };
+ setSize();
+
+ const render = () => {
+ const w = canvas.offsetWidth;
+ const h = canvas.offsetHeight;
+ const amp = ampSmoothed.current;
+ const t = timeRef.current;
+
+ ctx.clearRect(0, 0, w, h);
+
+ const N = 6;
+ const spacing = w / (N - 1);
+ const patternWidth = N * spacing;
+
+ const xOffset = ((xOffsetRef.current % patternWidth) + patternWidth) % patternWidth;
+
+ const pts = [];
+ for (let i = -N * 2; i <= N * 2; i++) {
+ const idx = ((i % N) + N) % N;
+ const w1 = Math.sin(t * FREQS[idx] + PHASES[idx]) * 0.50;
+ const w2 = Math.sin(t * FREQS[idx] * 0.43 + PHASES[idx] * 1.77) * 0.30;
+ const w3 = Math.cos(t * FREQS[idx] * 0.27 + PHASES[idx] * 0.85) * 0.20;
+
+ pts.push({
+ x: i * spacing + xOffset,
+ y: h * 0.5 + (w1 + w2 + w3) * h * amp * 0.95,
+ });
+ }
+
+ ctx.beginPath();
+ ctx.moveTo(pts[0].x, pts[0].y);
+
+ for (let i = 0; i < pts.length - 1; i++) {
+ const p0 = pts[Math.max(0, i - 1)];
+ const p1 = pts[i];
+ const p2 = pts[i + 1];
+ const p3 = pts[Math.min(pts.length - 1, i + 2)];
+ const k = 0.5;
+ ctx.bezierCurveTo(
+ p1.x + (p2.x - p0.x) * k / 3,
+ p1.y + (p2.y - p0.y) * k / 3,
+ p2.x - (p3.x - p1.x) * k / 3,
+ p2.y - (p3.y - p1.y) * k / 3,
+ p2.x, p2.y,
+ );
+ }
+
+ const gradient = ctx.createLinearGradient(0, 0, w, 0);
+ gradient.addColorStop(0, "transparent");
+ gradient.addColorStop(0.12, lineColorRef.current);
+ gradient.addColorStop(0.88, lineColorRef.current);
+ gradient.addColorStop(1, "transparent");
+
+ ctx.strokeStyle = gradient;
+ ctx.lineWidth = 14;
+ ctx.lineJoin = "round";
+ ctx.lineCap = "round";
+ ctx.stroke();
+ };
+
+ render();
+
+ const ro = new ResizeObserver(() => { setSize(); render(); });
+ ro.observe(canvas);
+
+ const tick = () => {
+ // Lerp toward target, then decay target — order matters
+ currentXSpeed.current += (targetXSpeed.current - currentXSpeed.current) * 0.15;
+ targetXSpeed.current *= 0.85;
+ xOffsetRef.current += currentXSpeed.current;
+
+ const targetAmp = ampRef.current;
+
+ if (!isRunning.current) {
+ ampSmoothed.current += (targetAmp - ampSmoothed.current) * 0.18;
+
+ if (Math.abs(ampSmoothed.current - targetAmp) > 0.001 || Math.abs(currentXSpeed.current) > 0.01) {
+ render();
+ rafRef.current = requestAnimationFrame(tick);
+ } else {
+ ampSmoothed.current = targetAmp;
+ render();
+ rafRef.current = null;
+ }
+ return;
+ }
+
+ timeRef.current += UNDULATION_SPEED;
+ ampSmoothed.current += (targetAmp - ampSmoothed.current) * 0.18;
+
+ render();
+ rafRef.current = requestAnimationFrame(tick);
+ };
+
+ const startLoop = () => {
+ if (!isRunning.current) {
+ isRunning.current = true;
+ if (rafRef.current === null) rafRef.current = requestAnimationFrame(tick);
+ }
+ };
+
+ const stopLoop = () => {
+ isRunning.current = false;
+ };
+
+ let lastWheelTime = 0;
+ let lastScrollY = typeof window !== "undefined" ? window.scrollY : 0;
+
+ const triggerScroll = (amp: number) => {
+ ampRef.current = amp;
+ startLoop();
+ if (stopTimer.current) clearTimeout(stopTimer.current);
+ stopTimer.current = setTimeout(stopLoop, 80);
+ };
+
+ const onWheel = (e: WheelEvent) => {
+ lastWheelTime = Date.now();
+ const vel = Math.min(Math.abs(e.deltaY) / 60, 1);
+ triggerScroll(0.18 + vel * 0.74);
+
+ // Direct, readable speed — TRANSLATION_SPEED is 1:1 with px/frame
+ targetXSpeed.current = Math.sign(e.deltaY) * vel * TRANSLATION_SPEED * 8;
+
+ if (rafRef.current === null) rafRef.current = requestAnimationFrame(tick);
+ };
+
+ const onScroll = () => {
+ const currentScrollY = window.scrollY;
+ const deltaY = currentScrollY - lastScrollY;
+ lastScrollY = currentScrollY;
+
+ if (Math.abs(deltaY) > 0) {
+ targetXSpeed.current = Math.sign(deltaY) * Math.min(Math.abs(deltaY) / 60, 1) * TRANSLATION_SPEED * 8;
+ if (rafRef.current === null) rafRef.current = requestAnimationFrame(tick);
+ }
+
+ if (Date.now() - lastWheelTime < 100) return;
+ triggerScroll(0.55);
+ };
+
+ window.addEventListener("wheel", onWheel, { passive: true });
+ window.addEventListener("scroll", onScroll, { passive: true });
+
+ return () => {
+ stopLoop();
+ ro.disconnect();
+ themeObserver.disconnect();
+ if (rafRef.current) cancelAnimationFrame(rafRef.current);
+ if (stopTimer.current) clearTimeout(stopTimer.current);
+ window.removeEventListener("wheel", onWheel);
+ window.removeEventListener("scroll", onScroll);
+ };
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+export default BackgroundTile;
\ No newline at end of file
diff --git a/src/components/ui/Footer.tsx b/src/components/ui/Footer.tsx
new file mode 100644
index 0000000..6376958
--- /dev/null
+++ b/src/components/ui/Footer.tsx
@@ -0,0 +1,110 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+import BackgroundTile from "./BackgroundTile";
+import Image from "next/image";
+import { useTheme } from "next-themes";
+import { useTranslations } from "next-intl";
+
+// Defined structure with translation keys and actual URLs
+const NAV_CONFIG = [
+ {
+ titleKey: "Resources.title",
+ links: [
+ { labelKey: "Resources.links.website", href: "https://github.com/AOSSIE-Org/Resonate-Website" },
+ { labelKey: "Resources.links.app", href: "https://github.com/AOSSIE-Org/Resonate" },
+ { labelKey: "Resources.links.backend", href: "https://github.com/AOSSIE-Org/Resonate-Backend" },
+ ],
+ },
+ {
+ titleKey: "AboutUs.title",
+ links: [
+ { labelKey: "AboutUs.links.aossie", href: "https://aossie.org" },
+ { labelKey: "AboutUs.links.otherProjects", href: "https://aossie.org/projects" },
+ ],
+ },
+ {
+ titleKey: "Social.title",
+ links: [
+ { labelKey: "Social.links.discord", href: "https://discord.gg/hjUhu33uAn" },
+ { labelKey: "Social.links.youtube", href: "https://www.youtube.com/@AOSSIE-Org" },
+ { labelKey: "Social.links.twitter", href: "https://twitter.com/aossie_org" },
+ { labelKey: "Social.links.linkedin", href: "https://www.linkedin.com/company/aossie/" },
+ ],
+ },
+];
+
+const Footer = () => {
+ const { theme } = useTheme();
+ const [mounted, setMounted] = useState(false);
+ const t = useTranslations("Footer");
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ const brandingSrc =
+ !mounted
+ ? "/assets/icons/resonate_branding_light.svg"
+ : theme === "dark"
+ ? "/assets/icons/resonate_branding_dark.svg"
+ : "/assets/icons/resonate_branding_light.svg";
+
+ return (
+
+ {/* Background wrapper */}
+
+
+
+
+
+ {/* Footer content */}
+
+
+ {/* Brand */}
+
+
+ {/* Nav columns */}
+
+ {NAV_CONFIG.map((col) => (
+
+
+ {t(col.titleKey)}
+
+
+
+
+ ))}
+
+
+
+
+ {/* Bottom bar */}
+
+
{t("copyright")}
+
{t("madeWith")}
+
+
+ );
+};
+
+export default Footer;
\ No newline at end of file
diff --git a/src/messages/en.json b/src/messages/en.json
index 9cdaa80..c2ccb97 100644
--- a/src/messages/en.json
+++ b/src/messages/en.json
@@ -2,5 +2,33 @@
"Home": {
"title": "Resonate Website",
"subtitle": "Visual Overhaul in Progress..."
+ },
+ "Footer": {
+ "Resources": {
+ "title": "Resources",
+ "links": {
+ "website": "Resonate Website",
+ "app": "Resonate App",
+ "backend": "Resonate Backend"
+ }
+ },
+ "AboutUs": {
+ "title": "About Us",
+ "links": {
+ "aossie": "AOSSIE",
+ "otherProjects": "Other Projects"
+ }
+ },
+ "Social": {
+ "title": "Social",
+ "links": {
+ "discord": "Discord",
+ "youtube": "YouTube",
+ "twitter": "X (Twitter)",
+ "linkedin": "LinkedIn"
+ }
+ },
+ "copyright": "© 2026 Resonate",
+ "madeWith": "Made with 🤍 by AOSSIE Community"
}
-}
+}
\ No newline at end of file
diff --git a/src/messages/hi.json b/src/messages/hi.json
index eadaa63..c8a6975 100644
--- a/src/messages/hi.json
+++ b/src/messages/hi.json
@@ -2,5 +2,33 @@
"Home": {
"title": "रेज़ोनेट वेबसाइट",
"subtitle": "विज़ुअल बदलाव पर काम चल रहा है..."
+ },
+ "Footer": {
+ "Resources": {
+ "title": "संसाधन",
+ "links": {
+ "website": "रेज़ोनेट वेबसाइट",
+ "app": "रेज़ोनेट ऐप",
+ "backend": "रेज़ोनेट बैकएंड"
+ }
+ },
+ "AboutUs": {
+ "title": "हमारे बारे में",
+ "links": {
+ "aossie": "AOSSIE (ओसी)",
+ "otherProjects": "अन्य प्रोजेक्ट्स"
+ }
+ },
+ "Social": {
+ "title": "सोशल",
+ "links": {
+ "discord": "डिस्कॉर्ड",
+ "youtube": "यूट्यूब",
+ "twitter": "एक्स (ट्विटर)",
+ "linkedin": "लिंक्डइन"
+ }
+ },
+ "copyright": "© 2026 रेज़ोनेट",
+ "madeWith": "AOSSIE समुदाय द्वारा 🤍 से निर्मित"
}
-}
+}
\ No newline at end of file