diff --git a/.gitignore b/.gitignore index 5ef6a52..bd4bbf2 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ # misc .DS_Store *.pem +*.code-workspace # debug npm-debug.log* 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. 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 diff --git a/next.config.ts b/next.config.ts index 6b0a4ea..67bbbb6 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; diff --git a/package-lock.json b/package-lock.json index 340b50d..cdd662b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,8 +6,9 @@ "packages": { "": { "name": "org-site", - "version": "0.1.0", + "version": "1.0.0", "dependencies": { + "framer-motion": "^12.23.24", "gray-matter": "^4.0.3", "next": "15.2.4", "react": "^19.0.0", @@ -1260,6 +1261,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.12.tgz", "integrity": "sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==", "dev": true, + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1313,6 +1315,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.28.0", "@typescript-eslint/types": "8.28.0", @@ -1712,6 +1715,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2068,6 +2072,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -2733,6 +2738,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -2900,6 +2906,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -3306,6 +3313,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.23.24", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz", + "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5181,6 +5215,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5567,6 +5616,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5660,6 +5710,7 @@ "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5668,6 +5719,7 @@ "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -6390,7 +6442,8 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.2.2", @@ -6455,6 +6508,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -6614,6 +6668,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 3ab3347..35fc6f4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "lint": "next lint" }, "dependencies": { + "framer-motion": "^12.23.24", "gray-matter": "^4.0.3", "next": "15.2.4", "react": "^19.0.0", diff --git a/public/observation-1.png b/public/games/observation/observation-1.png similarity index 100% rename from public/observation-1.png rename to public/games/observation/observation-1.png diff --git a/public/observation-2.jpg b/public/games/observation/observation-2.jpg similarity index 100% rename from public/observation-2.jpg rename to public/games/observation/observation-2.jpg diff --git a/public/games/observation/observation-3.jpg b/public/games/observation/observation-3.jpg new file mode 100644 index 0000000..e7af150 Binary files /dev/null and b/public/games/observation/observation-3.jpg differ diff --git a/public/games/observation/observation-4.jpg b/public/games/observation/observation-4.jpg new file mode 100644 index 0000000..c7a5c1e Binary files /dev/null and b/public/games/observation/observation-4.jpg differ diff --git a/public/games/observation/observation-5.jpg b/public/games/observation/observation-5.jpg new file mode 100644 index 0000000..ad8a3dc Binary files /dev/null and b/public/games/observation/observation-5.jpg differ diff --git a/public/games/untitledcardgame/untitledcardgame1.jpg b/public/games/untitledcardgame/untitledcardgame1.jpg new file mode 100644 index 0000000..a74fe1a Binary files /dev/null and b/public/games/untitledcardgame/untitledcardgame1.jpg differ diff --git a/public/games/untitledcardgame/untitledcardgame2.jpg b/public/games/untitledcardgame/untitledcardgame2.jpg new file mode 100644 index 0000000..89fc44c Binary files /dev/null and b/public/games/untitledcardgame/untitledcardgame2.jpg differ diff --git a/public/kEllieDev.jpg b/public/kellie.jpg similarity index 100% rename from public/kEllieDev.jpg rename to public/kellie.jpg diff --git a/public/ptsd.jpg b/public/ptsd.jpg new file mode 100644 index 0000000..21c47d6 Binary files /dev/null and b/public/ptsd.jpg differ diff --git a/src/app/apply/page.tsx b/src/app/apply/page.tsx index b3e9d67..0f45c46 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 { organization } from '@/constants'; export const metadata: Metadata = { title: 'Apply', - description: 'Join our team!', + description: `Join the ${organization.name} team!`, }; export default function Apply() { diff --git a/src/app/code-of-conduct/page.tsx b/src/app/code-of-conduct/page.tsx deleted file mode 100644 index 8a08b93..0000000 --- a/src/app/code-of-conduct/page.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react'; -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: 'Code Of Conduct', - description: 'Read our community code of conduct!', -}; - -export default function CodeOfConduct() { - return ( -
-
-
-

Code Of Conduct

-

Last Updated: July 4th, 2025

-
- -
-

- Our goal is to foster an inclusive, welcoming, and safe community for people of all - backgrounds. We value diversity and are committed to ensuring everyone can participate - free from harassment or discrimination. -

-

- This Code of Conduct outlines expectations for behavior, outlines unacceptable conduct, - and explains our enforcement policies. We encourage all participants to help us make - this a positive and respectful environment. -

- -

1. Expected Behavior

-
    -
  • Participate authentically and actively to support the community.
  • -
  • Be considerate and respectful in speech and actions.
  • -
  • Collaborate before engaging in conflict.
  • -
  • Avoid demeaning, discriminatory, or harassing language or behavior.
  • -
  • Be aware of your surroundings and report any concerns or violations.
  • -
  • Respect that our spaces may be shared with the public; act accordingly.
  • -
  • - Follow the Discord Terms of Service if - participating in our server. -
  • -
- -

- 2. Unacceptable Behavior -

-
    -
  • Violence or threats of violence against others.
  • -
  • - Discriminatory jokes, language, or behavior targeting protected characteristics. -
  • -
  • Posting sexually explicit or violent material.
  • -
  • Doxxing: sharing someone’s personal information without consent.
  • -
  • Personal insults, especially those related to identity or background.
  • -
  • Unwelcome sexual attention or advances.
  • -
  • Deliberate intimidation, stalking, or following others.
  • -
  • Disrupting events or community activities.
  • -
  • Encouraging or promoting any of the above behavior.
  • -
  • Continuing unwanted interactions after being asked to stop.
  • -
- -

- 3. Consequences Of Unacceptable Behavior -

-

- Unacceptable behavior from any community member, including sponsors and those with - decision-making authority, will not be tolerated. Anyone asked to stop unacceptable - behavior is expected to comply immediately. If a community member engages in - unacceptable behavior, the staff team may take any action they deem appropriate, up to - and including a temporary ban or permanent expulsion from the community without warning - (and without refund in the case of a paid event or service). If you are subject to or - witness unacceptable behavior, or have any other concerns, please notify a staff member - as soon as possible. Additionally, staff members are available to help community members - engage with local law enforcement or to otherwise help those experiencing unacceptable - behavior feel safe. -

- -

4. Scope

-

- We expect all community participants (contributors, paid or otherwise; sponsors; and - other guests) to abide by this Code of Conduct in all community venues--online and - in-person--as well as in all one-on-one communications pertaining to community business. - This code of conduct and its related procedures also applies to unacceptable behavior - occurring outside the scope of community activities when such behavior has the potential - to adversely affect the safety and well-being of community members. -

- -

- 5. License and Attribution -

-

- The Citizen Code of Conduct is distributed by Stumptown Syndicate under a Creative - Commons Attribution-ShareAlike license. Portions of text derived from the Django Code of - Conduct and the Geek Feminism Anti-Harassment Policy. -

-

- Questions or concerns? Email us at{' '} - contact.kuo.team@gmail.com -

-
-
-
- ); -} 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/components/gameclient.tsx b/src/app/components/gameclient.tsx new file mode 100644 index 0000000..0c5fec7 --- /dev/null +++ b/src/app/components/gameclient.tsx @@ -0,0 +1,161 @@ +'use client'; + +import { Game } from '@/games'; +import { easeOut, motion } from 'framer-motion'; + +interface Props { + game: Game; +} + +export default function GameClient({ game }: Props) { + return ( +
+ {/* Hero Section */} +
+
+ {game.images[0].alt} +
+
+ + +

{game.title}

+

{game.description}

+ +
+ + Play Now + + {game.link && ( + + Learn More + + )} +
+
+
+ + {/* Overview */} +
+ + Overview + + + {game.overview} + +
+ + {/* Feature Sections */} + {game.sections && game.sections.length > 0 && ( +
+
+ {game.sections.map((section, i) => ( +
+ + {section.featureTitle} + + + +

{section.featureTitle}

+

{section.featureDescription}

+
+
+ ))} +
+
+ )} + + {/* Media Gallery */} + {(game.images.length > 1 || (game.videos && game.videos.length > 0)) && ( +
+

Media Gallery

+
+ {game.images.slice(1).map((img, i) => ( + + {img.alt} + + ))} + + {game.videos?.map((vid, i) => ( + + + + ))} +
+
+ )} + + {/* Call to Action */} +
+

Ready to experience {game.title}?

+ + Launch Now + +
+
+ ); +} 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.

-
- + {/* Desktop menu */} +
+ {menuItems.map((item) => ( +
+ {item.name} + {item.icon} +
+ ))}
- {isOpen && ( -
-
-
scrollToSection('home')} - className="flex p-1 w-full hover:text-gray-400 cursor-pointer" - > -

Home

- -
-
scrollToSection('games')} - className="flex p-1 w-full hover:border-left hover:text-gray-400 cursor-pointer" - > -

Games

- -
-
scrollToSection('team')} - className="flex p-1 w-full hover:border-left hover:text-gray-400 cursor-pointer" - > -

Team

- -
-
redirectTo('/news')} - className="flex p-1 w-full hover:border-left hover:text-gray-400 cursor-pointer" - > -

News

- -
-
-
- )} + {/* Mobile toggle button */} +
+ + {/* Mobile dropdown */} + {isOpen && ( +
+ {menuItems.map((item) => ( +
+ {item.name} + {item.icon} +
+ ))} +
+ )} ); } diff --git a/src/app/components/games.tsx b/src/app/components/old_games.tsx similarity index 63% rename from src/app/components/games.tsx rename to src/app/components/old_games.tsx index bae03d1..3c7a677 100644 --- a/src/app/components/games.tsx +++ b/src/app/components/old_games.tsx @@ -8,7 +8,7 @@ export default function Games() { const [currentGameIndex, setCurrentGameIndex] = useState(0); const [isAnimating, setIsAnimating] = useState(false); - const launchGame = (uri: string) => (window.location.href = uri); + const launchGame = (slug: string) => (window.location.href = `/play/${slug}`); const handleGameChange = (direction: number) => { if (isAnimating) { @@ -37,8 +37,10 @@ export default function Games() {
{/* Header */} -
-

Our Games

+
+

+ Our Games +

Check out our latest games!

@@ -48,48 +50,48 @@ 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) => (
0 ? `url(${game.images[0].src})` : undefined, - backgroundSize: 'cover', - backgroundRepeat: 'no-repeat', - backgroundPosition: 'center', - backgroundColor: game.images.length === 0 ? '#2d2d2d' : '#1f2937', - }} + key={index} + className="w-full flex-shrink-0 px-1 sm:px-2 flex items-center justify-center" > - {/* Dark overlay for better text readability */} -
+
0 ? `url(${game.images[0].src})` : undefined, + backgroundSize: 'cover', + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center', + backgroundColor: game.images.length === 0 ? '#2d2d2d' : '#1f2937', + }} + > + {/* Dark overlay for better text readability */} +
-
-
-

- {game.title} -

-

- {game.description} -

-
-
- {game.launcherUri && ( +
+
+

+ {game.title} +

+

+ {game.description} +

+
+
- )} +
-
- ))} + ))}
{/* Navigation Arrows - Only show if more than one game */} @@ -102,7 +104,7 @@ export default function Games() { aria-label="Previous game" disabled={isAnimating} > - +
@@ -110,7 +112,7 @@ export default function Games() { onClick={() => handleGameChange(1)} className="bg-gray-900/80 hover:bg-gray-600/80 p-2 sm:p-3 rounded-full text-white cursor-pointer transition duration-300 backdrop-blur-sm" > - +
diff --git a/src/app/components/team.tsx b/src/app/components/team.tsx index b7c45eb..54618b6 100644 --- a/src/app/components/team.tsx +++ b/src/app/components/team.tsx @@ -103,9 +103,9 @@ const countryNames: Record = { const teamMembers: TeamMember[] = [ { - name: 'kEllie', + name: 'kellie', role: 'Founder, Lead Programmer', - avatar: '/kEllieDev.jpg', + avatar: '/kellie.jpg', country: 'FI', socialLinks: [], messages: [ @@ -148,11 +148,22 @@ const teamMembers: TeamMember[] = [ }, { name: 'Dutchy42', - role: 'Programmer', + role: 'Backend Programmer, ', avatar: '/Dutchy42.png', country: 'NL', socialLinks: [], - messages: [], + messages: [ + { message: '', weight: 1 }, + { message: 'NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE
NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE
NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE
NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE NIGHTMARE
', weight: 1 }, + { message: 'Have you considered?', weight: 5 }, + { message: 'You know what that means, FISH!', weight: 5 }, + { message: 'Ctrl + Z enthusiast.', weight: 5 }, + { message: 'Dark mode advocate.', weight: 5 }, + { message: 'Currently fixing what I broke yesterday.', weight: 5 }, + { message: 'It\'s not a bug, it\'s a feature!', weight: 5 }, + { message: '👁 I see your cursor…', weight: 5 }, + { message: 'Every day I wake up and choose chaos.', weight: 5 }, + ], }, { name: 'SharpMars', @@ -255,8 +266,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/games/[slug]/page.tsx b/src/app/games/[slug]/page.tsx new file mode 100644 index 0000000..d089bef --- /dev/null +++ b/src/app/games/[slug]/page.tsx @@ -0,0 +1,20 @@ +import { findGameBySlug, getAllGameSlugs, Game } from '@/games'; +import { notFound } from 'next/navigation'; +import GameClient from '@/app/components/gameclient'; + +export async function generateStaticParams() { + return getAllGameSlugs().map((slug) => ({ slug })); +} + +interface PageProps { + params: { slug: string }; +} + +export default async function GamePage({ params }: PageProps) { + const { slug } = await params; + const game: Game | undefined = findGameBySlug(slug); + + if (!game) notFound(); + + return ; +} diff --git a/src/app/games/page.tsx b/src/app/games/page.tsx new file mode 100644 index 0000000..5ec9b07 --- /dev/null +++ b/src/app/games/page.tsx @@ -0,0 +1,11 @@ +'use client'; + +import GameShowcase from '../components/gameshowcase'; + +export default function GamesPage() { + return ( +
+ +
+ ); +} 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<{ diff --git a/src/app/news/[slug]/page.tsx b/src/app/news/[slug]/page.tsx index 8bd4eca..3d52333 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() { @@ -122,7 +122,7 @@ export default async function ArticlePage({
diff --git a/src/app/news/page.tsx b/src/app/news/page.tsx index 130083a..8e5c225 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', }; @@ -17,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/page.tsx b/src/app/page.tsx index 740b219..753da14 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,230 +1,34 @@ -'use client'; - -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'; - -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', +import { Metadata } from 'next'; +import { organization } from '@/constants'; +import Home from './components/home'; + +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' } + ] }, - { - name: 'Bluesky', - icon: , - url: 'https://bsky.app/profile/kuo-team.com', + openGraph: { + title: organization.name, + description: organization.description, + url: organization.website, + siteName: organization.name, + images: [], + locale: 'en_US', + type: 'website' }, -]; - -const adjectives: string[] = [ - 'Innovative', - 'Creative', - 'Visionary', - 'Inspiring', - 'Awesome', - 'Ambitious', -]; - -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 */} -
-
- -
-
-
-
-

- {OrgName} -

-

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

-
- {socialLinks.map((link) => ( - - {link.icon} - - ))} -
-
-
- - {/* Scroll indicator */} - -
+ twitter: { + card: 'summary_large_image', + title: organization.name, + description: organization.description + } +}; - - -
-
- ); +export default function HomePage() { + return ; } diff --git a/src/app/play/[slug]/page.tsx b/src/app/play/[slug]/page.tsx new file mode 100644 index 0000000..533dbca --- /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', + 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/app/privacy/page.tsx b/src/app/privacy/page.tsx index caa51e6..883d0cf 100644 --- a/src/app/privacy/page.tsx +++ b/src/app/privacy/page.tsx @@ -1,9 +1,9 @@ +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.', + 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() { @@ -14,12 +14,12 @@ export default function PrivacyPolicy() {

Privacy Policy

-

Last Updated: August 6th, 2025

+

Last Updated: August 6th, 2025

-

+

We are committed to protecting your privacy and being transparent about how we handle your data. This policy explains what information we collect, why we collect it, how we use it, and your rights regarding your personal data when you use our services. diff --git a/src/app/support/page.tsx b/src/app/support/page.tsx new file mode 100644 index 0000000..a49a4f1 --- /dev/null +++ b/src/app/support/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from 'next/navigation'; + +export default function SupportPage() { + redirect('https://ko-fi.com/kelliedev'); +} diff --git a/src/constants.ts b/src/constants.ts index 2fd0a6f..0d83f72 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,2 +1,41 @@ -export const OrgName = 'Wacky Wizards'; +import React from "react"; +import { FaDiscord, FaGithub } 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', + }, + { + name: 'GitHub', + icon: FaGithub, + url: 'https://github.com/wackywizards', + } + ], +}; diff --git a/src/games.ts b/src/games.ts index e2feb87..934c648 100644 --- a/src/games.ts +++ b/src/games.ts @@ -1,10 +1,19 @@ export type Game = { + /** Whether this game is hidden from the game list */ + hide?: boolean; title: string; + /** URL-friendly identifier */ + slug: string; description: string; - launcherUri?: string; - launchUri?: string; + overview: string; + /** Steam launch URI */ + launchUri: string; + /** External link (e.g., sbox.game) */ link?: string; images: GameImage[]; + videos?: GameVideo[]; + sections?: GameSection[]; + metadata: GameMetadata; }; export type GameImage = { @@ -12,23 +21,125 @@ export type GameImage = { alt: string; }; +export type GameVideo = { + src: string; +}; + +export type GameSection = { + featureTitle: string; + featureDescription: 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', + overview: ` +Step into the shoes of a lone security operator working for a secretive organization. +Your job is simple: monitor a network of cameras, track unusual activities, and report any anomalies you see. +However, the more you observe, the more you realize that not everything is as it seems. +With each shift, the environment becomes increasingly unpredictable, challenging your perception, memory, and decision-making. +Every flicker of a monitor could hide a critical clue—or a threat. +Observation is not just a test of vigilance but also a journey into the unknown, where curiosity can either save you or lead to disaster. + `, launchUri: 'steam://run/590830//-rungame spoonstuff.observation', link: 'https://sbox.game/spoonstuff/observation', images: [ + { src: '/games/observation/observation-1.png', alt: 'Observation Game Screenshot 1' }, + { src: '/games/observation/observation-2.jpg', alt: 'Observation Game Screenshot 2' }, + ], + 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.', + ogImage: '/games/observation/observation-1.png', + keywords: ['observation', 'horror', 'sbox', 'anomalies'], + }, + sections: [ { - src: '/observation-1.png', - alt: 'Observation 1', + featureTitle: 'Stay Vigilant. Every Glitch Is a Clue.', + featureDescription: + 'In Observation, every flicker on a monitor could mean the difference between success and chaos. Report anomalies, stay alert, and uncover what your employer isn’t telling you.', }, { - src: '/observation-2.png', - alt: 'Observation 2', + featureTitle: 'Anomalies Evolve Over Time', + featureDescription: + 'Each playthrough becomes more unpredictable. New anomalies, altered camera feeds, and shifting environments keep you on edge.', + }, + ], + }, + { + 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.', + overview: ` +Embark on a thrilling journey where strategy, skill, and creativity determine your success. +In this card-based adventure, you will collect hundreds of unique cards, each with powerful abilities and synergies. +Build the ultimate deck to face increasingly challenging enemies and overcome intricate level designs. +Every decision counts—from the cards you choose to play to the timing of your abilities—making each encounter a test of strategy and adaptability. +Adventure through richly themed environments, battle epic bosses, and uncover the secrets hidden within every deck you craft. +The path to victory is as rewarding as it is challenging. + `, + launchUri: 'steam://run/590830//-rungame spoonstuff.card_game', + link: 'https://sbox.game/spoonstuff/card_game', + images: [ + { src: '/games/untitledcardgame/untitledcardgame1.jpg', alt: 'Card Game Screenshot 1' }, + { src: '/games/untitledcardgame/untitledcardgame2.jpg', alt: 'Card Game Screenshot 2' }, + ], + metadata: { + title: 'Untitled Card Game', + description: 'Go on an epic quest, fight enemies, buy upgrades, and build your perfect deck.', + ogImage: '/games/untitledcardgame/untitledcardgame1.jpg', + keywords: ['cards', 'strategy', 'sbox', 'deckbuilding'], + }, + sections: [ + { + featureTitle: 'Build. Battle. Conquer.', + featureDescription: + 'Strategize your every move. Collect powerful cards, combine abilities, and take down enemies as you climb through each level of adventure.', + }, + { + featureTitle: 'Deck Customization at Its Finest', + featureDescription: + 'Create your ultimate deck by choosing from hundreds of cards. Adapt your strategy for every encounter and unlock powerful synergies to outsmart your opponents.', + }, + { + featureTitle: 'Epic Boss Battles', + featureDescription: + 'Face off against challenging bosses that test your planning, resource management, and card combinations. Victory requires skill, strategy, and the perfect deck.', + }, + { + featureTitle: 'Adventure Mode', + featureDescription: + 'Explore different levels and themed environments, each filled with enemies, rewards, and surprises. Progress through a thrilling journey full of twists and strategic challenges.', }, ], }, ]; + +/** 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); +}