Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95e0be3ac0 | |||
| f6d50d8def |
@@ -0,0 +1,14 @@
|
|||||||
|
name: Next.js Deploy
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, feature/migration-nextjs-8285342661616568434]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Install Bun
|
||||||
|
uses: oven-sh/setup-bun@v1
|
||||||
|
- run: bun install
|
||||||
|
- run: bun run build
|
||||||
|
- run: bun run lint
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
node_modules/
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
build/
|
||||||
|
.env*.local
|
||||||
|
.vercel
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export default function Door({ direction, target, label, onNavigate }) {
|
||||||
|
return (
|
||||||
|
<button className={`door door-${direction}`} data-target={target} onClick={() => onNavigate(target)}>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export default function Room({ id, isActive, children }) {
|
||||||
|
return (
|
||||||
|
<section id={id} className={`room ${isActive ? 'active' : ''}`}>
|
||||||
|
{children}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export default function Viewport({ children }) {
|
||||||
|
return <div id="viewport">{children}</div>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export default function World({ children, currentPos }) {
|
||||||
|
const translateX = -currentPos.x * 100;
|
||||||
|
const translateY = -currentPos.y * 100;
|
||||||
|
return (
|
||||||
|
<div id="world" style={{ transform: `translate(${translateX}vw, ${translateY}vh)` }}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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%; } }
|
||||||
@@ -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 (
|
||||||
|
<html lang="en">
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
+70
@@ -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 (
|
||||||
|
<Viewport>
|
||||||
|
<World currentPos={currentPos}>
|
||||||
|
<Room id="room-0-0" isActive={currentPos.x === 0 && currentPos.y === 0}>
|
||||||
|
<div className="content"><h1>Tír na nÓg</h1><p>Welcome to the Land of Eternal Youth.</p></div>
|
||||||
|
<div className="doors">
|
||||||
|
<Door direction="east" target="1,0" label="To the Meadow →" onNavigate={navigateTo} />
|
||||||
|
<Door direction="south" target="0,1" label="Down the Alleys ↓" onNavigate={navigateTo} />
|
||||||
|
</div>
|
||||||
|
</Room>
|
||||||
|
<Room id="room-1-0" isActive={currentPos.x === 1 && currentPos.y === 0}>
|
||||||
|
<div className="content"><h2>The Meadow</h2><p>Where pixels bloom and legends grow.</p></div>
|
||||||
|
<div className="doors">
|
||||||
|
<Door direction="west" target="0,0" label="← Back to the Gate" onNavigate={navigateTo} />
|
||||||
|
<Door direction="south" target="1,1" label="To the Portal ↓" onNavigate={navigateTo} />
|
||||||
|
</div>
|
||||||
|
</Room>
|
||||||
|
<Room id="room-0-1" isActive={currentPos.x === 0 && currentPos.y === 1}>
|
||||||
|
<div className="content"><h2>The Alleys</h2><p>Navigating the narrow paths of creation.</p></div>
|
||||||
|
<div className="doors">
|
||||||
|
<Door direction="north" target="0,0" label="↑ Up to the Gate" onNavigate={navigateTo} />
|
||||||
|
<Door direction="east" target="1,1" label="To the Portal →" onNavigate={navigateTo} />
|
||||||
|
</div>
|
||||||
|
</Room>
|
||||||
|
<Room id="room-1-1" isActive={currentPos.x === 1 && currentPos.y === 1}>
|
||||||
|
<div className="content"><h2>The Portal</h2><p>Step through to join the faebits.</p></div>
|
||||||
|
<div className="doors">
|
||||||
|
<Door direction="north" target="1,0" label="↑ Up to the Meadow" onNavigate={navigateTo} />
|
||||||
|
<Door direction="west" target="0,1" label="← Back to the Alleys" onNavigate={navigateTo} />
|
||||||
|
</div>
|
||||||
|
</Room>
|
||||||
|
</World>
|
||||||
|
</Viewport>
|
||||||
|
);
|
||||||
|
}
|
||||||
-63
@@ -1,63 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>faebits | Tír na nÓg</title>
|
|
||||||
<link rel="stylesheet" href="styles.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="viewport">
|
|
||||||
<div id="world">
|
|
||||||
<!-- Room (0,0): The Gate -->
|
|
||||||
<section id="room-0-0" class="room active">
|
|
||||||
<div class="content">
|
|
||||||
<h1>Tír na nÓg</h1>
|
|
||||||
<p>Welcome to the Land of Eternal Youth.</p>
|
|
||||||
</div>
|
|
||||||
<div class="doors">
|
|
||||||
<button class="door door-east" data-target="1,0">To the Meadow →</button>
|
|
||||||
<button class="door door-south" data-target="0,1">Down the Alleys ↓</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Room (1,0): The Meadow -->
|
|
||||||
<section id="room-1-0" class="room">
|
|
||||||
<div class="content">
|
|
||||||
<h2>The Meadow</h2>
|
|
||||||
<p>Where pixels bloom and legends grow.</p>
|
|
||||||
</div>
|
|
||||||
<div class="doors">
|
|
||||||
<button class="door door-west" data-target="0,0">← Back to the Gate</button>
|
|
||||||
<button class="door door-south" data-target="1,1">To the Portal ↓</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Room (0,1): The Alleys -->
|
|
||||||
<section id="room-0-1" class="room">
|
|
||||||
<div class="content">
|
|
||||||
<h2>The Alleys</h2>
|
|
||||||
<p>Navigating the narrow paths of creation.</p>
|
|
||||||
</div>
|
|
||||||
<div class="doors">
|
|
||||||
<button class="door door-north" data-target="0,0">↑ Up to the Gate</button>
|
|
||||||
<button class="door door-east" data-target="1,1">To the Portal →</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Room (1,1): The Portal -->
|
|
||||||
<section id="room-1-1" class="room">
|
|
||||||
<div class="content">
|
|
||||||
<h2>The Portal</h2>
|
|
||||||
<p>Step through to join the faebits.</p>
|
|
||||||
</div>
|
|
||||||
<div class="doors">
|
|
||||||
<button class="door door-north" data-target="1,0">↑ Up to the Meadow</button>
|
|
||||||
<button class="door door-west" data-target="0,1">← Back to the Alleys</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="script.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
};
|
||||||
|
module.exports = nextConfig;
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tír na nÓg - Spatial Navigation
|
|
||||||
* Logic for moving through the grid of rooms using doors and keys.
|
|
||||||
*/
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const world = document.getElementById('world');
|
|
||||||
const doors = document.querySelectorAll('.door');
|
|
||||||
const rooms = document.querySelectorAll('.room');
|
|
||||||
|
|
||||||
let currentPos = { x: 0, y: 0 };
|
|
||||||
const gridSize = { width: 2, height: 2 };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the viewport position based on current x, y coordinates
|
|
||||||
*/
|
|
||||||
function updateView() {
|
|
||||||
const translateX = -currentPos.x * 100;
|
|
||||||
const translateY = -currentPos.y * 100;
|
|
||||||
world.style.transform = `translate(${translateX}vw, ${translateY}vh)`;
|
|
||||||
|
|
||||||
// Update active class for rooms
|
|
||||||
rooms.forEach(room => {
|
|
||||||
const id = `room-${currentPos.x}-${currentPos.y}`;
|
|
||||||
if (room.id === id) {
|
|
||||||
room.classList.add('active');
|
|
||||||
} else {
|
|
||||||
room.classList.remove('active');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Moved to room: ${currentPos.x}, ${currentPos.y}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles navigation to a specific target coordinate
|
|
||||||
*/
|
|
||||||
function navigateTo(targetStr) {
|
|
||||||
const [x, y] = targetStr.split(',').map(Number);
|
|
||||||
if (!isNaN(x) && !isNaN(y)) {
|
|
||||||
currentPos.x = x;
|
|
||||||
currentPos.y = y;
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves the position relative to current pos
|
|
||||||
*/
|
|
||||||
function moveRelative(dx, dy) {
|
|
||||||
const newX = currentPos.x + dx;
|
|
||||||
const newY = currentPos.y + dy;
|
|
||||||
|
|
||||||
if (newX >= 0 && newX < gridSize.width && newY >= 0 && newY < gridSize.height) {
|
|
||||||
currentPos.x = newX;
|
|
||||||
currentPos.y = newY;
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Door Click Listeners
|
|
||||||
doors.forEach(door => {
|
|
||||||
door.addEventListener('click', (e) => {
|
|
||||||
const target = e.target.getAttribute('data-target');
|
|
||||||
navigateTo(target);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Keyboard Listeners (WASD + Arrows)
|
|
||||||
document.addEventListener('keydown', (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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Tír na nÓg navigation initialized');
|
|
||||||
});
|
|
||||||
-114
@@ -1,114 +0,0 @@
|
|||||||
/* Tír na nÓg - Pixel Art Theme */
|
|
||||||
: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; /* Viewport handles navigation */
|
|
||||||
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 - Navigation UI */
|
|
||||||
.doors {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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%); }
|
|
||||||
|
|
||||||
/* Mobile optimization */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
h1, h2 { font-size: 2rem; }
|
|
||||||
.content { max-width: 95%; }
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user