From 76102e47b8c0050f931b892efb5cf777adbe52be Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Sun, 31 Aug 2025 21:07:55 +0300 Subject: [PATCH 01/37] Remove layout metadata It was overriding other pages, which we don't want. --- src/app/layout.tsx | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 766c349..3b7b4ce 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,12 +1,10 @@ -import { OrgName } from '@/constants'; import { Suspense } from 'react'; -import type { Metadata } from 'next'; import { Geist, Geist_Mono } from 'next/font/google'; import Analytics from '@/app/analytics'; import CookieConsent from '@/app/components/cookieconsent'; -import './globals.css'; import Navbar from './components/navbar'; import Footer from './components/footer'; +import './globals.css'; const geistSans = Geist({ variable: '--font-geist-sans', @@ -18,29 +16,6 @@ const geistMono = Geist_Mono({ subsets: ['latin'], }); -export const metadata: Metadata = { - title: OrgName, - description: `${OrgName}'s Website`, - icons: { - icon: '/logo.ico', - }, - openGraph: { - title: OrgName, - description: `${OrgName}'s Website`, - url: 'https://www.kuo-team.com', - siteName: OrgName, - images: [ - { - url: '/logo.png', - width: 128, - height: 128, - alt: `${OrgName} Logo`, - }, - ], - type: 'website', - }, -}; - export default function RootLayout({ children, }: Readonly<{ From 1d38beba080f9cc5bd3de44c5f2bb58267452b2c Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Sun, 31 Aug 2025 21:08:06 +0300 Subject: [PATCH 02/37] Update apply page metadata slightly --- src/app/apply/page.tsx | 3 ++- src/app/privacy/page.tsx | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/apply/page.tsx b/src/app/apply/page.tsx index b3e9d67..488bfd1 100644 --- a/src/app/apply/page.tsx +++ b/src/app/apply/page.tsx @@ -1,9 +1,10 @@ import React from 'react'; import type { Metadata } from 'next'; +import { OrgName } from '@/constants'; export const metadata: Metadata = { title: 'Apply', - description: 'Join our team!', + description: `Join the ${OrgName} team!`, }; export default function Apply() { diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx index caa51e6..9f94b62 100644 --- a/src/app/privacy/page.tsx +++ b/src/app/privacy/page.tsx @@ -2,8 +2,7 @@ import React from 'react'; export const metadata = { title: 'Privacy Policy', - description: - 'Our privacy policy explains what data we collect, how we use it, and how you can control it across our services.', + description: 'Our privacy policy explains what data we collect, how we use it, and how you can control it across our services.', }; export default function PrivacyPolicy() { From 4da6e58c22ccda9284a22f81e14f6c8060d946ef Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Sun, 31 Aug 2025 23:02:40 +0300 Subject: [PATCH 03/37] privacy page metadata must be Metadata type --- src/app/news/[slug]/page.tsx | 2 +- src/app/privacy/page.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/news/[slug]/page.tsx b/src/app/news/[slug]/page.tsx index 8bd4eca..98a96fd 100644 --- a/src/app/news/[slug]/page.tsx +++ b/src/app/news/[slug]/page.tsx @@ -3,7 +3,7 @@ import Image from 'next/image'; import { notFound } from 'next/navigation'; import { getAllNewsPosts, getNewsPost } from '@/lib/markdown'; import { ArticleFooter } from '@/app/components/articlefooter'; -import { Metadata } from 'next'; +import type { Metadata } from 'next'; // Generate static params for all news articles at build time export async function generateStaticParams() { diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx index 9f94b62..212120c 100644 --- a/src/app/privacy/page.tsx +++ b/src/app/privacy/page.tsx @@ -1,6 +1,7 @@ +import type { Metadata } from 'next'; import React from 'react'; -export const metadata = { +export const metadata: Metadata = { title: 'Privacy Policy', description: 'Our privacy policy explains what data we collect, how we use it, and how you can control it across our services.', }; From 8d62f17fafa4d07a95768076043a61782afbf2d7 Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Sun, 31 Aug 2025 23:04:03 +0300 Subject: [PATCH 04/37] Main page metadata --- src/app/news/page.tsx | 3 ++- src/app/page.tsx | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/app/news/page.tsx b/src/app/news/page.tsx index 130083a..1d6b82d 100644 --- a/src/app/news/page.tsx +++ b/src/app/news/page.tsx @@ -1,8 +1,9 @@ import React from 'react'; import Link from 'next/link'; import { getAllNewsPosts } from '@/lib/markdown'; +import type { Metadata } from 'next'; -export const metadata = { +export const metadata: Metadata = { title: 'News', description: 'Stay updated with the latest news', }; diff --git a/src/app/page.tsx b/src/app/page.tsx index 740b219..038af0b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -8,6 +8,30 @@ import Team from '@/app/components/team'; import { FaDiscord } from 'react-icons/fa'; import { FaBluesky } from 'react-icons/fa6'; import { OrgName } from '@/constants'; +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: OrgName, + description: `${OrgName} is an indie game development studio making fun and engaging games!`, + icons: { + icon: '/logo.ico', + }, + openGraph: { + title: OrgName, + description: `${OrgName}'s Website`, + url: 'https://www.kuo-team.com', + siteName: OrgName, + images: [ + { + url: '/logo.png', + width: 128, + height: 128, + alt: `${OrgName} Logo`, + }, + ], + type: 'website', + }, +}; type BackgroundType = { type: 'image' | 'video'; src: string }; type SocialLink = { From 518deb9f4079d3e6424c99b5b68bca4745db50d3 Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Sun, 31 Aug 2025 23:08:42 +0300 Subject: [PATCH 05/37] Can't actually have metadata here --- src/app/page.tsx | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 038af0b..740b219 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -8,30 +8,6 @@ import Team from '@/app/components/team'; import { FaDiscord } from 'react-icons/fa'; import { FaBluesky } from 'react-icons/fa6'; import { OrgName } from '@/constants'; -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: OrgName, - description: `${OrgName} is an indie game development studio making fun and engaging games!`, - icons: { - icon: '/logo.ico', - }, - openGraph: { - title: OrgName, - description: `${OrgName}'s Website`, - url: 'https://www.kuo-team.com', - siteName: OrgName, - images: [ - { - url: '/logo.png', - width: 128, - height: 128, - alt: `${OrgName} Logo`, - }, - ], - type: 'website', - }, -}; type BackgroundType = { type: 'image' | 'video'; src: string }; type SocialLink = { From 414e0b8f9f76f4fae4d630baf65a8f9ea9149075 Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Mon, 1 Sep 2025 00:50:39 +0300 Subject: [PATCH 06/37] Add 'Wacky' abjective --- src/app/page.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/page.tsx b/src/app/page.tsx index 740b219..5c01fab 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -36,6 +36,7 @@ const adjectives: string[] = [ 'Inspiring', 'Awesome', 'Ambitious', + 'Wacky' ]; export default function Home() { From c2168f425fb6ede86ec4951c7c8ff378d9a461d8 Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Mon, 1 Sep 2025 00:50:48 +0300 Subject: [PATCH 07/37] Unoptimize all images --- next.config.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/next.config.ts b/next.config.ts index 6b0a4ea..34107bb 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,7 +4,10 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - output: 'export' + output: 'export', + images: { + unoptimized: true, + }, }; export default nextConfig; From e03ce996ab8253f3556526b05f6f815f7405971a Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Mon, 1 Sep 2025 01:39:24 +0300 Subject: [PATCH 08/37] Add Organization constants, define once here --- src/app/components/footer.tsx | 37 ++++++++++++++--------------------- src/app/page.tsx | 29 ++++----------------------- src/constants.ts | 36 +++++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 48 deletions(-) diff --git a/src/app/components/footer.tsx b/src/app/components/footer.tsx index 5c9cfeb..08ed35f 100644 --- a/src/app/components/footer.tsx +++ b/src/app/components/footer.tsx @@ -1,6 +1,4 @@ -import { OrgName } from '@/constants'; -import { FaDiscord } from 'react-icons/fa'; -import { FaBluesky } from 'react-icons/fa6'; +import { organization } from '@/constants'; import Link from 'next/link'; export default function Footer() { @@ -10,25 +8,20 @@ export default function Footer() {
{/* Company info */}
-

{OrgName}

-

Making video games.

+

{organization.name}

+

{organization.description}

- - - - - - + {organization.socialLinks.map((link) => ( + + + + ))}
@@ -37,7 +30,7 @@ export default function Footer() {
- © 2025 {OrgName}. All rights reserved. + © 2025 {organization.name}. All rights reserved.
diff --git a/src/app/page.tsx b/src/app/page.tsx index 5c01fab..e5d233b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -5,30 +5,9 @@ import { useEffect, useState } from 'react'; import Games from '@/app/components/games'; import Team from '@/app/components/team'; -import { FaDiscord } from 'react-icons/fa'; -import { FaBluesky } from 'react-icons/fa6'; -import { OrgName } from '@/constants'; +import { organization } from '@/constants'; type BackgroundType = { type: 'image' | 'video'; src: string }; -type SocialLink = { - name: string; - icon: React.JSX.Element; - url: string; -}; - -const socialLinks: SocialLink[] = [ - { - name: 'Discord', - icon: , - url: 'https://discord.gg/kKU6a4AYNk', - }, - { - name: 'Bluesky', - icon: , - url: 'https://bsky.app/profile/kuo-team.com', - }, -]; - const adjectives: string[] = [ 'Innovative', 'Creative', @@ -180,7 +159,7 @@ export default function Home() {

- {OrgName} + {organization.name}

Making{' '} @@ -197,7 +176,7 @@ export default function Home() { Video Games

- {socialLinks.map((link) => ( + {organization.socialLinks.map((link) => ( - {link.icon} + ))}
diff --git a/src/constants.ts b/src/constants.ts index 2fd0a6f..7b07b56 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,2 +1,36 @@ -export const OrgName = 'Wacky Wizards'; +import React from "react"; +import { FaDiscord } from "react-icons/fa"; +import { FaBluesky } from "react-icons/fa6"; + export const GTag = 'G-MH5E7L88G5'; + +export interface Organization { + name: string; + description: string; + website: string; + socialLinks: SocialLink[]; +} + +export interface SocialLink { + name: string; + icon: React.ComponentType<{ className?: string }>; + url: string; +} + +export const organization: Organization = { + name: 'Wacky Wizards', + description: 'An indie game development studio making fun and engaging games!', + website: 'https://www.wackywizards.org', + socialLinks: [ + { + name: 'Discord', + icon: FaDiscord, + url: 'https://discord.gg/kKU6a4AYNk', + }, + { + name: 'Bluesky', + icon: FaBluesky, + url: 'https://bsky.app/profile/wackywizards.org', + }, + ], +}; From 3d70c3afa6bbf58e99dddb19843826a28cf3c368 Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Mon, 1 Sep 2025 01:39:36 +0300 Subject: [PATCH 09/37] News slug p:leading-relaxed --- src/app/news/[slug]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/news/[slug]/page.tsx b/src/app/news/[slug]/page.tsx index 98a96fd..3d52333 100644 --- a/src/app/news/[slug]/page.tsx +++ b/src/app/news/[slug]/page.tsx @@ -122,7 +122,7 @@ export default async function ArticlePage({
From 7af1a6dcbc2cc94709e8c64917e9932c93b09adc Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Mon, 1 Sep 2025 01:43:37 +0300 Subject: [PATCH 10/37] These use organization now instead of old OrgName --- src/app/apply/page.tsx | 4 ++-- src/app/components/navbar.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/apply/page.tsx b/src/app/apply/page.tsx index 488bfd1..0f45c46 100644 --- a/src/app/apply/page.tsx +++ b/src/app/apply/page.tsx @@ -1,10 +1,10 @@ import React from 'react'; import type { Metadata } from 'next'; -import { OrgName } from '@/constants'; +import { organization } from '@/constants'; export const metadata: Metadata = { title: 'Apply', - description: `Join the ${OrgName} team!`, + description: `Join the ${organization.name} team!`, }; export default function Apply() { diff --git a/src/app/components/navbar.tsx b/src/app/components/navbar.tsx index 2cf2fad..a50f315 100644 --- a/src/app/components/navbar.tsx +++ b/src/app/components/navbar.tsx @@ -1,6 +1,6 @@ 'use client'; -import { OrgName } from '@/constants'; +import { organization } from '@/constants'; import { useState } from 'react'; import { FiMenu, FiX } from 'react-icons/fi'; import { FaHome, FaGamepad, FaNewspaper } from 'react-icons/fa'; @@ -34,7 +34,7 @@ export default function Navbar() { onClick={() => scrollToSection('home')} className="text-xl font-bold cursor-pointer flex-grow text-left" > - {OrgName} + {organization.name}
From 81b777282a6d65a17c93d77f9113842d14a4ffdd Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Mon, 1 Sep 2025 15:44:23 +0300 Subject: [PATCH 11/37] Just stop exporting for now --- next.config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/next.config.ts b/next.config.ts index 34107bb..67bbbb6 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,10 +4,10 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - output: 'export', - images: { - unoptimized: true, - }, + //output: 'export', + //images: { + // unoptimized: true, + //}, }; export default nextConfig; From 24bdd1b40fbca4b9ce3c852978c674e6a7e95519 Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Tue, 23 Sep 2025 15:35:40 +0300 Subject: [PATCH 12/37] Consistent headers --- src/app/components/games.tsx | 6 ++++-- src/app/components/team.tsx | 6 ++++-- src/app/news/page.tsx | 2 +- src/app/privacy/page.tsx | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/app/components/games.tsx b/src/app/components/games.tsx index bae03d1..c3a1f5b 100644 --- a/src/app/components/games.tsx +++ b/src/app/components/games.tsx @@ -37,8 +37,10 @@ export default function Games() {
{/* Header */} -
-

Our Games

+
+

+ Our Games +

Check out our latest games!

diff --git a/src/app/components/team.tsx b/src/app/components/team.tsx index b7c45eb..49e981a 100644 --- a/src/app/components/team.tsx +++ b/src/app/components/team.tsx @@ -255,8 +255,10 @@ export default function Team() { >
{/* Header */} -
-

Our Team

+
+

+ Our Team +

We are a global team who loves what we do.

diff --git a/src/app/news/page.tsx b/src/app/news/page.tsx index 1d6b82d..8e5c225 100644 --- a/src/app/news/page.tsx +++ b/src/app/news/page.tsx @@ -18,7 +18,7 @@ export default async function NewsPage() {

Latest News

-

Hear about our latest updates and announcements.

+

Hear about our latest updates and announcements.

diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx index 212120c..d13c15f 100644 --- a/src/app/privacy/page.tsx +++ b/src/app/privacy/page.tsx @@ -14,7 +14,7 @@ export default function PrivacyPolicy() {

Privacy Policy

-

Last Updated: August 6th, 2025

+

Last Updated: August 6th, 2025

From b53e2b2ab2f71ffcf3c93cc9f904db4ed2d5a528 Mon Sep 17 00:00:00 2001 From: kellie <53048761+kEllieDev@users.noreply.github.com> Date: Sun, 28 Sep 2025 16:16:52 +0300 Subject: [PATCH 13/37] Create README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..25d1cd6 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Wacky Wizards Official Website +Source code for the Wacky Wizards website From 94c182ff3b92efa499d9e857d0400d101cd3ab68 Mon Sep 17 00:00:00 2001 From: kellie <53048761+kEllieDev@users.noreply.github.com> Date: Sun, 28 Sep 2025 16:19:34 +0300 Subject: [PATCH 14/37] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e8a999a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Wacky Wizards + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 33f21ff3757cd59b37612ed5125787bcf3ecde69 Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Sun, 28 Sep 2025 16:46:43 +0300 Subject: [PATCH 15/37] Add main page metadata --- src/app/page.tsx | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index e5d233b..efc7879 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,12 +1,38 @@ 'use client'; import { useEffect, useState } from 'react'; - +import { Metadata } from 'next'; import Games from '@/app/components/games'; import Team from '@/app/components/team'; - import { organization } from '@/constants'; +export const metadata: Metadata = { + title: organization.name, + description: organization.description, + icons: { + icon: '/favicon.ico', + apple: '/apple-touch-icon.png', + other: [ + { rel: 'icon', url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' }, + { rel: 'icon', url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' } + ] + }, + openGraph: { + title: organization.name, + description: organization.description, + url: organization.website, + siteName: organization.name, + images: [], + locale: 'en_US', + type: 'website' + }, + twitter: { + card: 'summary_large_image', + title: organization.name, + description: organization.description + } +}; + type BackgroundType = { type: 'image' | 'video'; src: string }; const adjectives: string[] = [ 'Innovative', From c22b8a1f73d179f6ad7d588a99659304ebdb3915 Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Sun, 28 Sep 2025 16:54:17 +0300 Subject: [PATCH 16/37] Move home page into separate component --- src/app/components/home.tsx | 208 ++++++++++++++++++++++++++++++++++++ src/app/page.tsx | 208 +----------------------------------- 2 files changed, 211 insertions(+), 205 deletions(-) create mode 100644 src/app/components/home.tsx diff --git a/src/app/components/home.tsx b/src/app/components/home.tsx new file mode 100644 index 0000000..8cafc00 --- /dev/null +++ b/src/app/components/home.tsx @@ -0,0 +1,208 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import Games from '@/app/components/games'; +import Team from '@/app/components/team'; +import { organization } from '@/constants'; + +type BackgroundType = { type: 'image' | 'video'; src: string }; +const adjectives: string[] = [ + 'Innovative', + 'Creative', + 'Visionary', + 'Inspiring', + 'Awesome', + 'Ambitious', + 'Wacky', +]; + +export default function HomeClient() { + const [backgroundIndex, setBackgroundIndex] = useState(0); + const [currentAdjective, setCurrentAdjective] = useState(adjectives[0]); + const [displayedText, setDisplayedText] = useState(''); + const [isTyping, setIsTyping] = useState(true); + const [showCursor, setShowCursor] = useState(true); + + const backgrounds: BackgroundType[] = [ + { type: 'image', src: '/observation-1.png' }, + { type: 'image', src: '/observation-2.jpg' }, + ]; + + // Background rotation timer (every 10 seconds) + useEffect(() => { + const bgTimer = setInterval(() => { + setBackgroundIndex((prev) => (prev + 1) % backgrounds.length); + }, 10000); + + return () => { + clearInterval(bgTimer); + }; + }, [backgrounds.length]); + + // Typewriter effect + useEffect(() => { + let typeTimer: NodeJS.Timeout; + let eraseTimer: NodeJS.Timeout; + let nextWordTimer: NodeJS.Timeout; + + const typeText = (text: string) => { + let i = 0; + setDisplayedText(''); + setIsTyping(true); + + const type = () => { + if (i < text.length) { + setDisplayedText(text.slice(0, i + 1)); + i++; + typeTimer = setTimeout(type, 100); + } else { + setIsTyping(false); + // Wait 3 seconds before starting to erase + eraseTimer = setTimeout(() => eraseText(text), 8000); + } + }; + type(); + }; + + const eraseText = (text: string) => { + let i = text.length; + setIsTyping(true); + + const erase = () => { + if (i > 0) { + setDisplayedText(text.slice(0, i - 1)); + i--; + typeTimer = setTimeout(erase, 50); + } else { + setIsTyping(false); + // Wait 500ms before next word + nextWordTimer = setTimeout(() => { + const nextIndex = (adjectives.indexOf(currentAdjective) + 1) % adjectives.length; + setCurrentAdjective(adjectives[nextIndex]); + }, 500); + } + }; + erase(); + }; + + typeText(currentAdjective); + + return () => { + clearTimeout(typeTimer); + clearTimeout(eraseTimer); + clearTimeout(nextWordTimer); + }; + }, [currentAdjective]); + + // Cursor blink effect + useEffect(() => { + const cursorTimer = setInterval(() => { + setShowCursor((prev) => !prev); + }, 500); + + return () => clearInterval(cursorTimer); + }, []); + + const [isAtTop, setIsAtTop] = useState(true); + + useEffect(() => { + const handleScroll = () => { + setIsAtTop(window.scrollY === 0); + }; + + window.addEventListener('scroll', handleScroll); + handleScroll(); // in case we load mid-scroll because users can't behave + + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return ( +
+ {/* Background */} +
+ {backgrounds.map((bg, idx) => ( +
+ {bg.type === 'image' ? ( +
+ ) : ( +
+ ))} + + {/* Background tint */} +
+
+ +
+
+
+
+

+ {organization.name} +

+

+ Making{' '} + + {displayedText} + + | + + {' '} + Video Games +

+
+ {organization.socialLinks.map((link) => ( + + + + ))} +
+
+
+ + {/* Scroll indicator */} + +
+ + + +
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index efc7879..3677d76 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,10 +1,6 @@ -'use client'; - -import { useEffect, useState } from 'react'; import { Metadata } from 'next'; -import Games from '@/app/components/games'; -import Team from '@/app/components/team'; import { organization } from '@/constants'; +import HomeClient from './components/home'; export const metadata: Metadata = { title: organization.name, @@ -33,204 +29,6 @@ export const metadata: Metadata = { } }; -type BackgroundType = { type: 'image' | 'video'; src: string }; -const adjectives: string[] = [ - 'Innovative', - 'Creative', - 'Visionary', - 'Inspiring', - 'Awesome', - 'Ambitious', - 'Wacky' -]; - -export default function Home() { - const [backgroundIndex, setBackgroundIndex] = useState(0); - const [currentAdjective, setCurrentAdjective] = useState(adjectives[0]); - const [displayedText, setDisplayedText] = useState(''); - const [isTyping, setIsTyping] = useState(true); - const [showCursor, setShowCursor] = useState(true); - - const backgrounds: BackgroundType[] = [ - { type: 'image', src: '/observation-1.png' }, - { type: 'image', src: '/observation-2.jpg' }, - ]; - - // Background rotation timer (every 10 seconds) - useEffect(() => { - const bgTimer = setInterval(() => { - setBackgroundIndex((prev) => (prev + 1) % backgrounds.length); - }, 10000); - - return () => { - clearInterval(bgTimer); - }; - }, [backgrounds.length]); - - // Typewriter effect - useEffect(() => { - let typeTimer: NodeJS.Timeout; - let eraseTimer: NodeJS.Timeout; - let nextWordTimer: NodeJS.Timeout; - - const typeText = (text: string) => { - let i = 0; - setDisplayedText(''); - setIsTyping(true); - - const type = () => { - if (i < text.length) { - setDisplayedText(text.slice(0, i + 1)); - i++; - typeTimer = setTimeout(type, 100); - } else { - setIsTyping(false); - // Wait 3 seconds before starting to erase - eraseTimer = setTimeout(() => eraseText(text), 8000); - } - }; - type(); - }; - - const eraseText = (text: string) => { - let i = text.length; - setIsTyping(true); - - const erase = () => { - if (i > 0) { - setDisplayedText(text.slice(0, i - 1)); - i--; - typeTimer = setTimeout(erase, 50); - } else { - setIsTyping(false); - // Wait 500ms before next word - nextWordTimer = setTimeout(() => { - const nextIndex = (adjectives.indexOf(currentAdjective) + 1) % adjectives.length; - setCurrentAdjective(adjectives[nextIndex]); - }, 500); - } - }; - erase(); - }; - - typeText(currentAdjective); - - return () => { - clearTimeout(typeTimer); - clearTimeout(eraseTimer); - clearTimeout(nextWordTimer); - }; - }, [currentAdjective]); - - // Cursor blink effect - useEffect(() => { - const cursorTimer = setInterval(() => { - setShowCursor((prev) => !prev); - }, 500); - - return () => clearInterval(cursorTimer); - }, []); - - const [isAtTop, setIsAtTop] = useState(true); - - useEffect(() => { - const handleScroll = () => { - setIsAtTop(window.scrollY === 0); - }; - - window.addEventListener('scroll', handleScroll); - handleScroll(); // in case we load mid-scroll because users can't behave - - return () => window.removeEventListener('scroll', handleScroll); - }, []); - - return ( -
- {/* Background */} -
- {backgrounds.map((bg, idx) => ( -
- {bg.type === 'image' ? ( -
- ) : ( -
- ))} - - {/* Background tint */} -
-
- -
-
-
-
-

- {organization.name} -

-

- Making{' '} - - {displayedText} - - | - - {' '} - Video Games -

-
- {organization.socialLinks.map((link) => ( - - - - ))} -
-
-
- - {/* Scroll indicator */} - -
- - - -
-
- ); +export default function HomePage() { + return ; } From a3f3049fd54e418ebab312cc78eb8c4e4fd2c195 Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Thu, 2 Oct 2025 18:55:59 +0300 Subject: [PATCH 17/37] Add github to org social links --- src/constants.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/constants.ts b/src/constants.ts index 7b07b56..0d83f72 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,5 @@ import React from "react"; -import { FaDiscord } from "react-icons/fa"; +import { FaDiscord, FaGithub } from "react-icons/fa"; import { FaBluesky } from "react-icons/fa6"; export const GTag = 'G-MH5E7L88G5'; @@ -32,5 +32,10 @@ export const organization: Organization = { icon: FaBluesky, url: 'https://bsky.app/profile/wackywizards.org', }, + { + name: 'GitHub', + icon: FaGithub, + url: 'https://github.com/wackywizards', + } ], }; From 2ccd88ad858ad687a6b1f27c45b73b115c1aaef0 Mon Sep 17 00:00:00 2001 From: kEllieDev Date: Mon, 6 Oct 2025 13:33:17 +0300 Subject: [PATCH 18/37] Update my name's spelling to the new one --- src/app/components/team.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/team.tsx b/src/app/components/team.tsx index 49e981a..91f757f 100644 --- a/src/app/components/team.tsx +++ b/src/app/components/team.tsx @@ -103,7 +103,7 @@ const countryNames: Record = { const teamMembers: TeamMember[] = [ { - name: 'kEllie', + name: 'kellie', role: 'Founder, Lead Programmer', avatar: '/kEllieDev.jpg', country: 'FI', From 8f6a3d9335eb5d08297b138cce0d87ef00d10a7e Mon Sep 17 00:00:00 2001 From: kellie Date: Fri, 17 Oct 2025 13:16:34 +0300 Subject: [PATCH 19/37] Game titles are uppercase --- src/app/components/games.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/games.tsx b/src/app/components/games.tsx index c3a1f5b..81f92d2 100644 --- a/src/app/components/games.tsx +++ b/src/app/components/games.tsx @@ -71,7 +71,7 @@ export default function Games() {
-

+

{game.title}

From 836131df0acdb52f0d2caed68dcaf9518ec48ba2 Mon Sep 17 00:00:00 2001 From: kellie Date: Sun, 19 Oct 2025 19:30:08 +0300 Subject: [PATCH 20/37] Central gamelauncher component, metadata --- .../page.tsx => components/gamelauncher.tsx} | 40 ++++++-------- src/app/components/games.tsx | 16 +++--- src/app/play/[slug]/page.tsx | 52 +++++++++++++++++++ src/games.ts | 42 +++++++++++++-- 4 files changed, 111 insertions(+), 39 deletions(-) rename src/app/{play/observation/page.tsx => components/gamelauncher.tsx} (85%) create mode 100644 src/app/play/[slug]/page.tsx diff --git a/src/app/play/observation/page.tsx b/src/app/components/gamelauncher.tsx similarity index 85% rename from src/app/play/observation/page.tsx rename to src/app/components/gamelauncher.tsx index 8f29915..7ab5a30 100644 --- a/src/app/play/observation/page.tsx +++ b/src/app/components/gamelauncher.tsx @@ -1,21 +1,16 @@ 'use client'; import { useState, useEffect, useRef } from 'react'; -import { usePathname } from 'next/navigation'; -import { games, Game } from '@/games'; +import { Game } from '@/games'; -export default function GameLauncher() { - const pathname = usePathname(); +type GameLauncherProps = { + game: Game | undefined; +}; + +export default function GameLauncher({ game }: GameLauncherProps) { const [status, setStatus] = useState<'idle' | 'launching' | 'success' | 'error'>('idle'); const fallbackTimeoutRef = useRef(null); - // Automatically find the game based on the current route - const game: Game | undefined = games.find(g => { - // Extract game name from pathname (e.g., /play/observation -> observation) - const gameNameFromPath = pathname.split('/').pop()?.toLowerCase(); - return g.title.toLowerCase() === gameNameFromPath; - }); - // Cleanup timeout on unmount useEffect(() => { return () => { @@ -32,10 +27,11 @@ export default function GameLauncher() {

Game Not Found

- Could not find a game matching the current route: {pathname} + The requested game could not be found. It may have been removed or the link is + incorrect.

- {game.launcherUri && ( - - )} +
diff --git a/src/app/play/[slug]/page.tsx b/src/app/play/[slug]/page.tsx new file mode 100644 index 0000000..40a7595 --- /dev/null +++ b/src/app/play/[slug]/page.tsx @@ -0,0 +1,52 @@ +import { Metadata } from 'next'; +import { findGameBySlug, getAllGameSlugs } from '@/games'; +import GameLauncher from '@/app/components/gamelauncher'; + +type Props = { + params: Promise<{ slug: string }>; +}; + +// Generate metadata for SEO +export async function generateMetadata({ params }: Props): Promise { + const { slug } = await params; + const game = findGameBySlug(slug); + + if (!game) { + return { + title: 'Game Not Found | Game Launcher', + description: 'The requested game could not be found.', + }; + } + + return { + title: game.metadata.title, + description: game.metadata.description, + keywords: game.metadata.keywords, + openGraph: { + title: game.metadata.title, + description: game.metadata.description, + images: game.metadata.ogImage ? [game.metadata.ogImage] : undefined, + type: 'website', + }, + twitter: { + card: 'summary_large_image', + title: game.metadata.title, + description: game.metadata.description, + images: game.metadata.ogImage ? [game.metadata.ogImage] : undefined, + }, + }; +} + +// Generate static params for all games +export async function generateStaticParams() { + return getAllGameSlugs().map((slug) => ({ + slug, + })); +} + +export default async function GameLauncherPage({ params }: Props) { + const { slug } = await params; + const game = findGameBySlug(slug); + + return ; +} diff --git a/src/games.ts b/src/games.ts index e2feb87..25094f8 100644 --- a/src/games.ts +++ b/src/games.ts @@ -1,10 +1,14 @@ export type Game = { title: string; + /** URL-friendly identifier */ + slug: string; description: string; - launcherUri?: string; - launchUri?: string; + /** Steam launch URI */ + launchUri: string; + /** External link (e.g., sbox.game) */ link?: string; images: GameImage[]; + metadata: GameMetadata; }; export type GameImage = { @@ -12,23 +16,51 @@ export type GameImage = { alt: string; }; +export type GameMetadata = { + /** SEO title */ + title: string; + /** SEO description */ + description: string; + /** Open Graph image URL */ + ogImage?: string; + /** SEO keywords */ + keywords?: string[]; +}; + export const games: Game[] = [ { title: 'Observation', + slug: 'observation', description: "Work for a mysterious company, observe security cameras, report anomalies.\nInspired by I'm on Observation Duty.", - launcherUri: '/play/observation', launchUri: 'steam://run/590830//-rungame spoonstuff.observation', link: 'https://sbox.game/spoonstuff/observation', images: [ { src: '/observation-1.png', - alt: 'Observation 1', + alt: 'Observation Game Screenshot 1', }, { src: '/observation-2.png', - alt: 'Observation 2', + alt: 'Observation Game Screenshot 2', }, ], + metadata: { + title: 'Launch Observation | Game Launcher', + description: + 'Launch Observation directly from your browser. Work for a mysterious company, observe security cameras, and report anomalies in this thrilling S&Box game.', + ogImage: '/observation-1.png', + keywords: ['observation', 'sbox', 'game', 'security cameras', 'anomalies'], + }, }, ]; + +/** Find a game by its slug */ +export function findGameBySlug(slug: string): Game | undefined { + return games.find((g) => g.slug.toLowerCase() === slug.toLowerCase()); +} + +/** Get all game slugs */ +export function getAllGameSlugs(): string[] { + return games.map((g) => g.slug); +} From f445b6424d89024396108cacfe562214335c12c5 Mon Sep 17 00:00:00 2001 From: kellie Date: Sun, 19 Oct 2025 19:35:32 +0300 Subject: [PATCH 21/37] HomeClient -> Home --- src/app/components/home.tsx | 2 +- src/app/page.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/components/home.tsx b/src/app/components/home.tsx index 8cafc00..2522769 100644 --- a/src/app/components/home.tsx +++ b/src/app/components/home.tsx @@ -16,7 +16,7 @@ const adjectives: string[] = [ 'Wacky', ]; -export default function HomeClient() { +export default function Home() { const [backgroundIndex, setBackgroundIndex] = useState(0); const [currentAdjective, setCurrentAdjective] = useState(adjectives[0]); const [displayedText, setDisplayedText] = useState(''); diff --git a/src/app/page.tsx b/src/app/page.tsx index 3677d76..753da14 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next'; import { organization } from '@/constants'; -import HomeClient from './components/home'; +import Home from './components/home'; export const metadata: Metadata = { title: organization.name, @@ -30,5 +30,5 @@ export const metadata: Metadata = { }; export default function HomePage() { - return ; + return ; } From ea603a22e7f889cd9d60625dd73c422ac635248a Mon Sep 17 00:00:00 2001 From: kellie Date: Sun, 19 Oct 2025 19:43:38 +0300 Subject: [PATCH 22/37] Update observation metadata --- src/games.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/games.ts b/src/games.ts index 25094f8..65735f4 100644 --- a/src/games.ts +++ b/src/games.ts @@ -47,10 +47,18 @@ export const games: Game[] = [ ], metadata: { title: 'Launch Observation | Game Launcher', - description: - 'Launch Observation directly from your browser. Work for a mysterious company, observe security cameras, and report anomalies in this thrilling S&Box game.', + description: 'Work for a mysterious company, observe security cameras, report anomalies.', ogImage: '/observation-1.png', - keywords: ['observation', 'sbox', 'game', 'security cameras', 'anomalies'], + keywords: [ + 'observation', + 'sbox', + 's&box', + 'game', + 'horror', + 'horror game', + 'security cameras', + 'anomalies', + ], }, }, ]; From f87411295744757c46a630a624321fd184ac1304 Mon Sep 17 00:00:00 2001 From: kellie Date: Sun, 19 Oct 2025 20:16:43 +0300 Subject: [PATCH 23/37] Add option to hide game from game list --- src/app/components/games.tsx | 2 +- src/games.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/components/games.tsx b/src/app/components/games.tsx index fc03e61..119c8a1 100644 --- a/src/app/components/games.tsx +++ b/src/app/components/games.tsx @@ -50,7 +50,7 @@ export default function Games() { className="flex w-full transition-transform duration-300 ease-in-out" style={{ transform: `translateX(-${currentGameIndex * 100}%)` }} > - {games.map((game, index) => ( + {games.filter((game) => !game.hide).map((game, index) => (
); } diff --git a/src/games.ts b/src/games.ts index 0d51162..b99b4c9 100644 --- a/src/games.ts +++ b/src/games.ts @@ -10,6 +10,7 @@ export type Game = { /** External link (e.g., sbox.game) */ link?: string; images: GameImage[]; + videos?: GameVideo[]; metadata: GameMetadata; }; @@ -18,6 +19,11 @@ export type GameImage = { alt: string; }; +export type GameVideo = { + src: string; + alt: string; +}; + export type GameMetadata = { /** SEO title */ title: string; @@ -46,7 +52,23 @@ export const games: Game[] = [ src: '/games/observation/observation-2.jpg', alt: 'Observation Game Screenshot 2', }, + { + src: '/games/observation/observation-3.jpg', + alt: 'Observation Game Screenshot 3', + }, + { + src: '/games/observation/observation-4.jpg', + alt: 'Observation Game Screenshot 4', + }, + { + src: '/games/observation/observation-5.jpg', + alt: 'Observation Game Screenshot 5', + }, ], + videos: [{ + src: '/games/observation/observation-gameplay.mp4', + alt: 'Observation Game Video' + }], metadata: { title: 'Observation', description: 'Work for a mysterious company, observe security cameras, report anomalies.', @@ -66,8 +88,9 @@ export const games: Game[] = [ { title: 'Untitled Card Game', slug: 'untitledcardgame', - description: 'Go on an epic quest, fight all sorts of enemies, buy upgrades from the store and clear levels.', - launchUri: 'stream://run/590830//-rungame spoonstuff.card_game', + description: + 'Go on an epic quest, fight all sorts of enemies, buy upgrades from the store and clear levels.', + launchUri: 'steam://run/590830//-rungame spoonstuff.card_game', link: 'https://sbox.game/spoonstuff/card_game', images: [ { @@ -81,7 +104,8 @@ export const games: Game[] = [ ], metadata: { title: 'Untitled Card Game', - description: 'Go on an epic quest, fight all sorts of enemies, buy upgrades from the store and clear levels.', + description: + 'Go on an epic quest, fight all sorts of enemies, buy upgrades from the store and clear levels.', ogImage: '/games/untitledcardgame/untitledcardgame1.jpg', keywords: [ 'cards', From ecd14eb35f9e76ffb3204c010b43fcf35db1c8e2 Mon Sep 17 00:00:00 2001 From: Dutchy Date: Tue, 4 Nov 2025 18:39:34 +0100 Subject: [PATCH 33/37] Videos now are hosted on youtube. --- src/app/games/[slug]/page.tsx | 20 ++++++++------------ src/games.ts | 13 ++++++++----- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/app/games/[slug]/page.tsx b/src/app/games/[slug]/page.tsx index 8182792..ecb00ba 100644 --- a/src/app/games/[slug]/page.tsx +++ b/src/app/games/[slug]/page.tsx @@ -29,11 +29,7 @@ export default async function GamePage({ params }: PageProps) { key={`img-${i}`} className="w-full sm:w-1/2 lg:w-1/3 max-w-[400px] h-auto rounded-lg overflow-hidden shadow-lg flex justify-center items-center" > - {img.alt} + {img.alt}
))} @@ -41,15 +37,15 @@ export default async function GamePage({ params }: PageProps) { game.videos.map((vid, i) => (
- + title={`video-${i}`} + className="w-full h-full" + allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" + allowFullScreen + >
))} diff --git a/src/games.ts b/src/games.ts index b99b4c9..e9abe20 100644 --- a/src/games.ts +++ b/src/games.ts @@ -21,7 +21,6 @@ export type GameImage = { export type GameVideo = { src: string; - alt: string; }; export type GameMetadata = { @@ -65,10 +64,14 @@ export const games: Game[] = [ alt: 'Observation Game Screenshot 5', }, ], - videos: [{ - src: '/games/observation/observation-gameplay.mp4', - alt: 'Observation Game Video' - }], + videos: [ + { + src: 'https://www.youtube.com/embed/Thk5dnNKP-E?si=edtJC1H5oPvJdwAF', + }, + { + src: 'https://www.youtube.com/embed/V3oU_zBL-3g?si=s1VLh7lI8BxTQOLt', + }, + ], metadata: { title: 'Observation', description: 'Work for a mysterious company, observe security cameras, report anomalies.', From 616f37cef5f02462bd21297b8760199f528e252c Mon Sep 17 00:00:00 2001 From: Dutchy Date: Tue, 4 Nov 2025 19:12:43 +0100 Subject: [PATCH 34/37] Navbar rework, probably not final but significantly better --- src/app/components/navbar.tsx | 103 ++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/src/app/components/navbar.tsx b/src/app/components/navbar.tsx index 50abf88..e14b014 100644 --- a/src/app/components/navbar.tsx +++ b/src/app/components/navbar.tsx @@ -3,81 +3,86 @@ import { organization } from '@/constants'; import { useState } from 'react'; import { FiMenu, FiX } from 'react-icons/fi'; -import { FaHome, FaGamepad, FaNewspaper } from 'react-icons/fa'; +import { FaGamepad, FaNewspaper } from 'react-icons/fa'; import { LuUsers } from 'react-icons/lu'; import { redirect } from 'next/navigation'; export default function Navbar() { const [isOpen, setIsOpen] = useState(false); - function scrollToSection(sectionId: string) { - if (location.pathname !== '/') { - location.href = '/#' + sectionId; + const scrollToSection = (sectionId: string) => { + setIsOpen(false); + + if (window.location.pathname !== '/') { + window.location.href = '/#' + sectionId; + return; } - setIsOpen(false); const element = document.getElementById(sectionId); if (element) { element.scrollIntoView({ behavior: 'smooth' }); } - } + }; - function redirectTo(url: string) { + const handleRedirect = (url: string) => { setIsOpen(false); redirect(url); - } + }; + + const menuItems = [ + { name: 'Games', icon: , action: () => handleRedirect('/games') }, + { name: 'Team', icon: , action: () => scrollToSection('team') }, + { name: 'News', icon: , action: () => handleRedirect('/news') }, + ]; return ( -