diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml
new file mode 100644
index 0000000..756e97b
--- /dev/null
+++ b/.gitea/workflows/deploy.yml
@@ -0,0 +1,15 @@
+name: Next.js Deploy
+on:
+ push:
+ branches: [main, feature/migration-nextjs]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: "20"
+ - run: npm install
+ - run: npm run build
+ - run: npm run lint
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d455b38
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+node_modules/
+.next/
+out/
+build/
+.env*.local
+.vercel
+*.log
diff --git a/app/components/Door.js b/app/components/Door.js
new file mode 100644
index 0000000..21a09dc
--- /dev/null
+++ b/app/components/Door.js
@@ -0,0 +1,7 @@
+export default function Door({ direction, target, label, onNavigate }) {
+ return (
+
+ );
+}
diff --git a/app/components/Room.js b/app/components/Room.js
new file mode 100644
index 0000000..2d16031
--- /dev/null
+++ b/app/components/Room.js
@@ -0,0 +1,7 @@
+export default function Room({ id, isActive, children }) {
+ return (
+
+ );
+}
diff --git a/app/components/Viewport.js b/app/components/Viewport.js
new file mode 100644
index 0000000..52fca6f
--- /dev/null
+++ b/app/components/Viewport.js
@@ -0,0 +1,3 @@
+export default function Viewport({ children }) {
+ return
{children}
;
+}
diff --git a/app/components/World.js b/app/components/World.js
new file mode 100644
index 0000000..d2debe4
--- /dev/null
+++ b/app/components/World.js
@@ -0,0 +1,9 @@
+export default function World({ children, currentPos }) {
+ const translateX = -currentPos.x * 100;
+ const translateY = -currentPos.y * 100;
+ return (
+
+ {children}
+
+ );
+}
diff --git a/app/globals.css b/app/globals.css
new file mode 100644
index 0000000..fa4e9e9
--- /dev/null
+++ b/app/globals.css
@@ -0,0 +1,17 @@
+:root { --gold: #f1c40f; --emerald: #2ecc71; --dark-green: #1b4d3e; --night: #2c3e50; --cloud: #ecf0f1; --pixel-border: 4px; }
+* { margin: 0; padding: 0; box-sizing: border-box; image-rendering: pixelated; }
+body { font-family: 'Courier New', Courier, monospace; background-color: var(--night); color: var(--cloud); overflow: hidden; line-height: 1.6; }
+#viewport { width: 100vw; height: 100vh; overflow: hidden; position: relative; }
+#world { display: grid; grid-template-columns: repeat(2, 100vw); grid-template-rows: repeat(2, 100vh); transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); width: 200vw; height: 200vh; }
+.room { width: 100vw; height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; position: relative; border: var(--pixel-border) solid var(--gold); background: linear-gradient(135deg, var(--dark-green) 0%, var(--night) 100%); text-align: center; padding: 2rem; }
+.content { background: rgba(0, 0, 0, 0.7); padding: 2rem; border: var(--pixel-border) double var(--emerald); box-shadow: 10px 10px 0px var(--dark-green); max-width: 80%; }
+h1, h2 { color: var(--gold); text-transform: uppercase; margin-bottom: 1rem; font-size: 3rem; text-shadow: 4px 4px 0px var(--dark-green); }
+p { font-size: 1.2rem; margin-bottom: 2rem; }
+.doors { position: absolute; width: 100%; height: 100%; pointer-events: none; top: 0; left: 0; }
+.door { pointer-events: auto; position: absolute; padding: 1rem; background: var(--gold); color: var(--night); border: var(--pixel-border) solid var(--dark-green); cursor: pointer; font-family: inherit; font-weight: bold; font-size: 1rem; transition: all 0.2s; }
+.door:hover { background: var(--emerald); color: white; transform: scale(1.1); }
+.door-north { top: 20px; left: 50%; transform: translateX(-50%); }
+.door-south { bottom: 20px; left: 50%; transform: translateX(-50%); }
+.door-east { right: 20px; top: 50%; transform: translateY(-50%); }
+.door-west { left: 20px; top: 50%; transform: translateY(-50%); }
+@media (max-width: 768px) { h1, h2 { font-size: 2rem; } .content { max-width: 95%; } }
diff --git a/app/layout.js b/app/layout.js
new file mode 100644
index 0000000..a61fb5d
--- /dev/null
+++ b/app/layout.js
@@ -0,0 +1,12 @@
+import './globals.css';
+export const metadata = {
+ title: 'faebits | Tír na nÓg',
+ description: 'Welcome to the Land of Eternal Youth.',
+};
+export default function RootLayout({ children }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/app/page.js b/app/page.js
new file mode 100644
index 0000000..8209db7
--- /dev/null
+++ b/app/page.js
@@ -0,0 +1,70 @@
+'use client';
+import { useState, useEffect } from 'react';
+import Viewport from './components/Viewport';
+import World from './components/World';
+import Room from './components/Room';
+import Door from './components/Door';
+export default function Home() {
+ const [currentPos, setCurrentPos] = useState({ x: 0, y: 0 });
+ const gridSize = { width: 2, height: 2 };
+ const navigateTo = (targetStr) => {
+ const [x, y] = targetStr.split(',').map(Number);
+ if (!isNaN(x) && !isNaN(y)) { setCurrentPos({ x, y }); }
+ };
+ const moveRelative = (dx, dy) => {
+ setCurrentPos((prev) => {
+ const newX = prev.x + dx;
+ const newY = prev.y + dy;
+ if (newX >= 0 && newX < gridSize.width && newY >= 0 && newY < gridSize.height) {
+ return { x: newX, y: newY };
+ }
+ return prev;
+ });
+ };
+ useEffect(() => {
+ const handleKeyDown = (e) => {
+ switch (e.key.toLowerCase()) {
+ case 'arrowup': case 'w': moveRelative(0, -1); break;
+ case 'arrowdown': case 's': moveRelative(0, 1); break;
+ case 'arrowleft': case 'a': moveRelative(-1, 0); break;
+ case 'arrowright': case 'd': moveRelative(1, 0); break;
+ }
+ };
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, []);
+ return (
+
+
+
+ Tír na nÓg
Welcome to the Land of Eternal Youth.
+
+
+
+
+
+
+ The Meadow
Where pixels bloom and legends grow.
+
+
+
+
+
+
+ The Alleys
Navigating the narrow paths of creation.
+
+
+
+
+
+
+ The Portal
Step through to join the faebits.
+
+
+
+
+
+
+
+ );
+}
diff --git a/next.config.js b/next.config.js
new file mode 100644
index 0000000..7d00513
--- /dev/null
+++ b/next.config.js
@@ -0,0 +1,5 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ reactStrictMode: true,
+};
+module.exports = nextConfig;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..35c9e2a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "pixel-faebits",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "next": "14.2.3",
+ "react": "^18",
+ "react-dom": "^18"
+ },
+ "devDependencies": {
+ "eslint": "^8",
+ "eslint-config-next": "14.2.3"
+ }
+}