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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Posthog
NEXT_PUBLIC_POSTHOG_KEY=abc
NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ yarn-error.log*

# env files (can opt-in for committing if needed)
.env*
!.env*.example

# vercel
.vercel
Expand Down
28 changes: 28 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
"lucide-react": "^0.502.0",
"motion": "^12.7.4",
"next": "15.3.1-canary.15",
"next-intl": "^4.0.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-use-measure": "^2.1.7",
"server-only": "^0.0.1",
"tailwind-merge": "^3.2.0",
"tw-animate-css": "^1.2.7",
"zod": "^3.24.3",
Expand Down Expand Up @@ -140,6 +142,16 @@

"@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="],

"@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.4", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.1", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA=="],

"@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.7", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ=="],

"@formatjs/icu-messageformat-parser": ["@formatjs/icu-messageformat-parser@2.11.2", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "@formatjs/icu-skeleton-parser": "1.8.14", "tslib": "^2.8.0" } }, "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA=="],

"@formatjs/icu-skeleton-parser": ["@formatjs/icu-skeleton-parser@1.8.14", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "tslib": "^2.8.0" } }, "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ=="],

"@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.5.10", "", { "dependencies": { "tslib": "2" } }, "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q=="],

"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],

"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
Expand Down Expand Up @@ -286,6 +298,8 @@

"@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.11.0", "", {}, "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ=="],

"@schummar/icu-type-parser": ["@schummar/icu-type-parser@1.21.5", "", {}, "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw=="],

"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],

"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
Expand Down Expand Up @@ -610,6 +624,8 @@

"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],

"decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="],

"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],

"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
Expand Down Expand Up @@ -772,6 +788,8 @@

"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],

"intl-messageformat": ["intl-messageformat@10.7.16", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "@formatjs/fast-memoize": "2.2.7", "@formatjs/icu-messageformat-parser": "2.11.2", "tslib": "^2.8.0" } }, "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug=="],

"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],

"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
Expand Down Expand Up @@ -914,8 +932,12 @@

"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],

"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],

"next": ["next@15.3.1-canary.15", "", { "dependencies": { "@next/env": "15.3.1-canary.15", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.1-canary.15", "@next/swc-darwin-x64": "15.3.1-canary.15", "@next/swc-linux-arm64-gnu": "15.3.1-canary.15", "@next/swc-linux-arm64-musl": "15.3.1-canary.15", "@next/swc-linux-x64-gnu": "15.3.1-canary.15", "@next/swc-linux-x64-musl": "15.3.1-canary.15", "@next/swc-win32-arm64-msvc": "15.3.1-canary.15", "@next/swc-win32-x64-msvc": "15.3.1-canary.15", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-K04SKrZ+HKrx1+rma4y9nfxilL5HnQ5KiL0biKwyQyhiG5xFLUqaHGzpiYflbWW+ufJWejk3cJJkhK/DUuB37Q=="],

"next-intl": ["next-intl@4.0.3", "", { "dependencies": { "@formatjs/intl-localematcher": "^0.5.4", "negotiator": "^1.0.0", "use-intl": "^4.0.3" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0", "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-vw25WvRheZG6NzyXYePuhc/xrYHMhnu7cVcB49XoXaQsO4WMV0YDV6BllhRgI5Ne79hpoAQMyk/fxIKtkBVbNg=="],

"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],

"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
Expand Down Expand Up @@ -1012,6 +1034,8 @@

"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],

"server-only": ["server-only@0.0.1", "", {}, "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="],

"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],

"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
Expand Down Expand Up @@ -1104,6 +1128,8 @@

"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],

"use-intl": ["use-intl@4.0.3", "", { "dependencies": { "@formatjs/fast-memoize": "^2.2.0", "@schummar/icu-type-parser": "1.21.5", "intl-messageformat": "^10.5.14" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" } }, "sha512-AeXB+5gPORJHj1BPCj0aNga0y4gpucHcqNk6B+xRSLlBt0Bz+QVtMmdtV1nvxG308rWqqkmfo6RVu9yIcZt+Hw=="],

"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],

"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
Expand All @@ -1130,6 +1156,8 @@

"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],

"@formatjs/ecma402-abstract/@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.1", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg=="],

"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],

"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
Expand Down
9 changes: 9 additions & 0 deletions global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { routing } from "@/i18n/routing";
import type messages from "./messages/en.json";

declare module "next-intl" {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof messages;
}
}
24 changes: 24 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"home": {
"title": "Sorting Algorithm Visualization",
"about": "I am a developer who loves to learn new things. I have been programming for over 10 years and I love to explore new technologies and languages. I am currently learning TypeScript and React."
},
"metadata": {
"title": "Sorting Algorithm Visualization",
"description": "A simple visualization of some basic sorting algorithms"
},
"not-found": {
"title": "Oops! This page has been <newline>sucked into the void</newline>",
"description": "The page you're looking for seems to have been pulled into the event horizon. Even light can't escape, but you still can!",
"link": "Escape to Safety"
},
"locale-switcher": {
"label": "Change Language",
"locale": "{locale, select, en {🇺 English} sk {🇸 Slovenský} other {Unknown}}"
},
"navigation": {
"home": "Home",
"playground": "Playground"
},
"visualizer": {}
}
23 changes: 23 additions & 0 deletions messages/sk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"home": {
"title": "Vizualizácia algoritmov pre triedenie",
"about": "Som vývojár, ktorý rád sa zaujíma nových vecí. Som programoval od viac ako 10 rokov a rád sa zaujíma nových technológií a jazykov. V súčasnosti sa učím TypeScript a React."
},
"metadata": {
"title": "Vizualizácia algoritmov pre triedenie",
"description": "Jednoduchá vizualizácia niektorých základných algoritmov triedenia"
},
"not-found": {
"title": "Oops! Stránka bola <newline>zhltnutá do prázdnoty</newline>",
"description": "Stránka, ktorú hľadáte, bola vtiahnutá do horizontu. Ani svetlo nevie uniknúť, ale vy stále môžete!",
"link": "Späť do bezpečia"
},
"locale-switcher": {
"label": "Zmeniť jazyk",
"locale": "{locale, select, en {🇺 Anglický} sk {🇸 Slovenský} other {Neznámy}}"
},
"navigation": {
"home": "Domov",
"playground": "Vizualizácia"
}
}
7 changes: 4 additions & 3 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";

/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
Expand All @@ -11,8 +12,6 @@ const nextConfig: NextConfig = {
experimental: {
reactCompiler: true,
ppr: true,
dynamicIO: true,
useCache: true,
},
typescript: {
ignoreBuildErrors: true,
Expand All @@ -22,4 +21,6 @@ const nextConfig: NextConfig = {
},
};

export default nextConfig;
const withNextIntl = createNextIntlPlugin();

export default withNextIntl(nextConfig);
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
"lucide-react": "^0.502.0",
"motion": "^12.7.4",
"next": "15.3.1-canary.15",
"next-intl": "^4.0.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-use-measure": "^2.1.7",
"server-only": "^0.0.1",
"tailwind-merge": "^3.2.0",
"tw-animate-css": "^1.2.7",
"zod": "^3.24.3"
Expand Down
5 changes: 5 additions & 0 deletions src/app/[locale]/[...rest]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { notFound } from "next/navigation";

export default function CatchAllPage() {
notFound();
}
File renamed without changes.
62 changes: 62 additions & 0 deletions src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Geist, Geist_Mono } from "next/font/google";
import { notFound } from "next/navigation";
import { hasLocale, type Locale, NextIntlClientProvider } from "next-intl";
import { getTranslations, setRequestLocale } from "next-intl/server";
import { routing } from "@/i18n/routing";
import "./globals.css";
import Navigation from "@/components/navigation";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});

export function generateStaticParams() {
return routing.locales.map(locale => ({ locale }));
}

export async function generateMetadata({
params,
}: {
params: Promise<{ locale: Locale }>;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "metadata" });

return {
title: t("title"),
description: t("description"),
};
}

export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: Locale }>;
}) {
const { locale } = await params;
if (!hasLocale(routing.locales, locale)) notFound();

// Enable static rendering
setRequestLocale(locale);

return (
<html lang={locale}>
<body
className={`${geistSans.variable} ${geistMono.variable} dark font-sans antialiased`}
>
<NextIntlClientProvider>
<Navigation />
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
84 changes: 84 additions & 0 deletions src/app/[locale]/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use client";

import { ArrowRight, Home } from "lucide-react";
import { motion } from "motion/react";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import { HoleBackground } from "@/components/animate-ui/hole-background";
import { Link } from "@/i18n/navigation";

export default function NotFoundPage() {
const t = useTranslations("not-found");
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

if (!mounted) return null;

return (
<div className="relative flex min-h-screen items-center justify-center">
<HoleBackground className="absolute inset-0" />

<div className="relative z-10 max-w-3xl px-4 text-center">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="mb-8"
>
<h1 className="mb-2 bg-gradient-to-r from-pink-500 via-purple-500 to-cyan-500 bg-clip-text font-black text-7xl text-transparent">
404
</h1>
<motion.h2
className="mb-4 font-bold text-4xl text-white md:text-5xl"
initial={{ scale: 1 }}
animate={{
scale: [1, 1.02, 0.98, 1],
rotate: [0, 0.5, -0.5, 0],
}}
transition={{
duration: 4,
repeat: Number.POSITIVE_INFINITY,
repeatType: "reverse",
}}
>
{t.rich("title", {
newline: chunks => (
<span className="block md:inline">{chunks}</span>
),
})}
</motion.h2>
<motion.p
className="mx-auto max-w-2xl text-gray-200 text-lg md:text-xl"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5, duration: 1 }}
>
{t("description")}
</motion.p>
</motion.div>

<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.8, duration: 0.8 }}
className="mt-8"
>
<Link
href="/"
className="group relative inline-flex items-center justify-center"
>
<div className="-inset-0.5 absolute rounded-lg bg-gradient-to-r from-pink-600 via-purple-600 to-cyan-600 opacity-70 blur-sm transition-all duration-300 group-hover:opacity-100 group-hover:blur-md"></div>
<div className="relative flex items-center space-x-2 rounded-lg bg-black px-6 py-4 text-white transition-all duration-200 group-hover:bg-opacity-90">
<Home className="h-5 w-5" />
<span className="font-medium">{t("link")}</span>
<ArrowRight className="h-5 w-5 transition-transform duration-300 group-hover:translate-x-1" />
</div>
</Link>
</motion.div>
</div>
</div>
);
}
7 changes: 7 additions & 0 deletions src/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useTranslations } from "next-intl";

export default function Home() {
const t = useTranslations("home");

return <div>{t("title")}</div>;
}
19 changes: 19 additions & 0 deletions src/app/[locale]/playground/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useTranslations } from "next-intl";
import { HexagonBackground } from "@/components/animate-ui/hexagon-background";
import SortingVisualizer from "@/components/sorting-visualizer";

export default function Playground() {
const t = useTranslations("home");

return (
<HexagonBackground className="absolute inset-0 flex min-h-screen flex-col items-center justify-between p-8">
<div className="pointer-events-none z-10 w-full max-w-5xl">
<h1 className="pointer-events-auto mb-28 text-center font-bold text-5xl">
{t("title")}
</h1>
<p>{t("about")}</p>
<SortingVisualizer />
</div>
</HexagonBackground>
);
}
Loading