Initial Commit
Commit pertama, memindahkan repository dari github ke gitea.
This commit is contained in:
commit
13a47fbebf
41 files changed
+12327
No files matched your search
+41
@@ -0,0 +1,41 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<!-- BEGIN:nextjs-agent-rules -->
|
||||||
|
|
||||||
|
## This is NOT the Next.js you know
|
||||||
|
|
||||||
|
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
||||||
|
|
||||||
|
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
||||||
|
|
||||||
|
<!-- END:nextjs-agent-rules -->
|
||||||
|
|
||||||
|
<!-- BEGIN:nextjs-agent-feedback -->
|
||||||
|
|
||||||
|
## Report Next.js feedback
|
||||||
|
|
||||||
|
The project owner enabled `experimental.agentFeedback`, authorizing this feedback pass. When enabled, `next dev` writes this block and restores it if removed. Commit `AGENTS.md` with your work to keep the tree clean.
|
||||||
|
|
||||||
|
During the current user request, retain anonymized facts as feedback candidates for meaningful or recurring Next.js issues:
|
||||||
|
|
||||||
|
- Crashes, hangs, OOMs, or unexpected slowness
|
||||||
|
- Incorrect behavior, output, errors, or diagnostics
|
||||||
|
- Missing, unclear, or conflicting documentation
|
||||||
|
- Unexpected CLI, codemod, Skill, or upgrade behavior and non-obvious workarounds
|
||||||
|
|
||||||
|
Continue the current user request without reporting immediately. Routine debugging is not feedback. Verify changes before retaining them as feedback candidates. Never retain code, logs, paths, URLs, secrets, personal information, or project-specific data.
|
||||||
|
|
||||||
|
Retain qualifying friction even if it is later fixed or worked around; record the final outcome during the reporting pass.
|
||||||
|
|
||||||
|
Before the final response, run one reporting pass. If candidates remain, from this file's directory run `npx --no-install next internal agent-feedback-instructions` once without piping or truncating its output. Follow its output or error guidance. If a network sandbox blocks it, retry with network access; if it still returns no output, continue normally.
|
||||||
|
|
||||||
|
<!-- END:nextjs-agent-feedback -->
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||||
@@ -0,0 +1,896 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Lock,
|
||||||
|
LogOut,
|
||||||
|
Plus,
|
||||||
|
Edit,
|
||||||
|
Trash2,
|
||||||
|
ExternalLink,
|
||||||
|
Sparkles,
|
||||||
|
Terminal,
|
||||||
|
Gamepad2,
|
||||||
|
FlaskConical,
|
||||||
|
Search,
|
||||||
|
CheckCircle,
|
||||||
|
AlertCircle,
|
||||||
|
Database,
|
||||||
|
ArrowLeft,
|
||||||
|
X,
|
||||||
|
RefreshCw,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
interface ProjectItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
category: "software" | "games" | "lab";
|
||||||
|
categoryLabel: string;
|
||||||
|
tagline: string;
|
||||||
|
status: string;
|
||||||
|
statusType: "active" | "alpha" | "prototype" | "upcoming";
|
||||||
|
releaseTarget: string;
|
||||||
|
description: string;
|
||||||
|
longDescription: string;
|
||||||
|
features: string[];
|
||||||
|
stack: string[];
|
||||||
|
accentGradient: string;
|
||||||
|
borderGlow: string;
|
||||||
|
iconName: string;
|
||||||
|
orderIndex?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AdminPage() {
|
||||||
|
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [loginError, setLoginError] = useState("");
|
||||||
|
const [loginLoading, setLoginLoading] = useState(false);
|
||||||
|
|
||||||
|
// Dashboard state
|
||||||
|
const [projects, setProjects] = useState<ProjectItem[]>([]);
|
||||||
|
const [loadingProjects, setLoadingProjects] = useState(false);
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<string>("all");
|
||||||
|
|
||||||
|
// Modal state
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [editingProject, setEditingProject] = useState<ProjectItem | null>(null);
|
||||||
|
const [formError, setFormError] = useState("");
|
||||||
|
const [formSubmitting, setFormSubmitting] = useState(false);
|
||||||
|
const [toastMessage, setToastMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Form Fields
|
||||||
|
const [formId, setFormId] = useState("");
|
||||||
|
const [formTitle, setFormTitle] = useState("");
|
||||||
|
const [formCategory, setFormCategory] = useState<"software" | "games" | "lab">("software");
|
||||||
|
const [formCategoryLabel, setFormCategoryLabel] = useState("");
|
||||||
|
const [formTagline, setFormTagline] = useState("");
|
||||||
|
const [formStatus, setFormStatus] = useState("In Active Dev");
|
||||||
|
const [formStatusType, setFormStatusType] = useState<"active" | "alpha" | "prototype" | "upcoming">("active");
|
||||||
|
const [formReleaseTarget, setFormReleaseTarget] = useState("2027");
|
||||||
|
const [formDescription, setFormDescription] = useState("");
|
||||||
|
const [formLongDescription, setFormLongDescription] = useState("");
|
||||||
|
const [formFeatures, setFormFeatures] = useState("");
|
||||||
|
const [formStack, setFormStack] = useState("");
|
||||||
|
const [formIconName, setFormIconName] = useState("Terminal");
|
||||||
|
const [formAccentGradient, setFormAccentGradient] = useState("from-cyan-500/20 via-blue-600/10 to-transparent");
|
||||||
|
|
||||||
|
// Check initial authentication
|
||||||
|
useEffect(() => {
|
||||||
|
checkAuthStatus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const checkAuthStatus = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/auth/me");
|
||||||
|
const data = await res.json();
|
||||||
|
setIsAuthenticated(data.authenticated === true);
|
||||||
|
if (data.authenticated) {
|
||||||
|
fetchProjects();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setIsAuthenticated(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchProjects = async () => {
|
||||||
|
setLoadingProjects(true);
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/projects");
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success && Array.isArray(data.data)) {
|
||||||
|
setProjects(data.data);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
showToast("Error loading projects from database", true);
|
||||||
|
} finally {
|
||||||
|
setLoadingProjects(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showToast = (msg: string, isError = false) => {
|
||||||
|
setToastMessage(msg);
|
||||||
|
setTimeout(() => {
|
||||||
|
setToastMessage(null);
|
||||||
|
}, 4000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogin = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoginError("");
|
||||||
|
setLoginLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/auth/login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.ok && data.success) {
|
||||||
|
setIsAuthenticated(true);
|
||||||
|
fetchProjects();
|
||||||
|
} else {
|
||||||
|
setLoginError(data.error || "Authentication failed. Check credentials.");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setLoginError("Network connection error to database.");
|
||||||
|
} finally {
|
||||||
|
setLoginLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
await fetch("/api/auth/logout", { method: "POST" });
|
||||||
|
setIsAuthenticated(false);
|
||||||
|
setUsername("");
|
||||||
|
setPassword("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCreateModal = () => {
|
||||||
|
setEditingProject(null);
|
||||||
|
setFormId("");
|
||||||
|
setFormTitle("");
|
||||||
|
setFormCategory("software");
|
||||||
|
setFormCategoryLabel("Creative Tool");
|
||||||
|
setFormTagline("");
|
||||||
|
setFormStatus("In Active Dev");
|
||||||
|
setFormStatusType("active");
|
||||||
|
setFormReleaseTarget("2027");
|
||||||
|
setFormDescription("");
|
||||||
|
setFormLongDescription("");
|
||||||
|
setFormFeatures("");
|
||||||
|
setFormStack("Rust, WebGPU, TypeScript");
|
||||||
|
setFormIconName("Terminal");
|
||||||
|
setFormAccentGradient("from-cyan-500/20 via-blue-600/10 to-transparent");
|
||||||
|
setFormError("");
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEditModal = (p: ProjectItem) => {
|
||||||
|
setEditingProject(p);
|
||||||
|
setFormId(p.id);
|
||||||
|
setFormTitle(p.title);
|
||||||
|
setFormCategory(p.category);
|
||||||
|
setFormCategoryLabel(p.categoryLabel);
|
||||||
|
setFormTagline(p.tagline);
|
||||||
|
setFormStatus(p.status);
|
||||||
|
setFormStatusType(p.statusType);
|
||||||
|
setFormReleaseTarget(p.releaseTarget);
|
||||||
|
setFormDescription(p.description);
|
||||||
|
setFormLongDescription(p.longDescription);
|
||||||
|
setFormFeatures(p.features ? p.features.join("\n") : "");
|
||||||
|
setFormStack(p.stack ? p.stack.join(", ") : "");
|
||||||
|
setFormIconName(p.iconName || "Terminal");
|
||||||
|
setFormAccentGradient(p.accentGradient || "from-cyan-500/20 via-blue-600/10 to-transparent");
|
||||||
|
setFormError("");
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id: string, title: string) => {
|
||||||
|
if (!confirm(`Are you sure you want to delete "${title}" from the database?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/projects/${id}`, { method: "DELETE" });
|
||||||
|
if (res.ok) {
|
||||||
|
setProjects((prev) => prev.filter((p) => p.id !== id));
|
||||||
|
showToast(`Project "${title}" deleted from database.`);
|
||||||
|
} else {
|
||||||
|
const data = await res.json();
|
||||||
|
showToast(data.error || "Failed to delete", true);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
showToast("Error deleting project", true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveProject = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setFormError("");
|
||||||
|
setFormSubmitting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const featuresArray = formFeatures
|
||||||
|
.split("\n")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter((s) => s.length > 0);
|
||||||
|
|
||||||
|
const stackArray = formStack
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter((s) => s.length > 0);
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
id: formId || formTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
|
||||||
|
title: formTitle,
|
||||||
|
category: formCategory,
|
||||||
|
categoryLabel: formCategoryLabel,
|
||||||
|
tagline: formTagline,
|
||||||
|
status: formStatus,
|
||||||
|
statusType: formStatusType,
|
||||||
|
releaseTarget: formReleaseTarget,
|
||||||
|
description: formDescription,
|
||||||
|
longDescription: formLongDescription,
|
||||||
|
features: featuresArray,
|
||||||
|
stack: stackArray,
|
||||||
|
iconName: formIconName,
|
||||||
|
accentGradient: formAccentGradient,
|
||||||
|
borderGlow: `hover:border-${formCategory === "games" ? "cyan" : formCategory === "lab" ? "emerald" : "purple"}-400/50`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (editingProject) {
|
||||||
|
// Update existing
|
||||||
|
const res = await fetch(`/api/projects/${editingProject.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
showToast(`Project "${formTitle}" updated successfully!`);
|
||||||
|
setIsModalOpen(false);
|
||||||
|
fetchProjects();
|
||||||
|
} else {
|
||||||
|
const data = await res.json();
|
||||||
|
setFormError(data.error || "Failed to update project");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create new
|
||||||
|
const res = await fetch("/api/projects", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
showToast(`New project "${formTitle}" deployed to database!`);
|
||||||
|
setIsModalOpen(false);
|
||||||
|
fetchProjects();
|
||||||
|
} else {
|
||||||
|
const data = await res.json();
|
||||||
|
setFormError(data.error || "Failed to create project");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setFormError("Network error while saving project.");
|
||||||
|
} finally {
|
||||||
|
setFormSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter projects by category and search
|
||||||
|
const filteredProjects = projects.filter((p) => {
|
||||||
|
const matchesCategory =
|
||||||
|
selectedCategory === "all" || p.category === selectedCategory;
|
||||||
|
const matchesSearch =
|
||||||
|
p.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
p.tagline.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
p.categoryLabel.toLowerCase().includes(searchQuery.toLowerCase());
|
||||||
|
return matchesCategory && matchesSearch;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Loading screen during session check
|
||||||
|
if (isAuthenticated === null) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[#0a0a12] flex items-center justify-center text-cyan-400 font-mono text-sm">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-5 h-5 rounded-full border-2 border-cyan-400 border-t-transparent animate-spin" />
|
||||||
|
<span>CHECKING SECURE COCKPIT CREDENTIALS...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOGIN SCREEN
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[#0a0a12] flex flex-col justify-center items-center px-4 relative overflow-hidden">
|
||||||
|
{/* Background ambient lighting */}
|
||||||
|
<div className="absolute top-1/4 left-1/3 w-96 h-96 bg-cyan-500/10 rounded-full blur-[120px] pointer-events-none" />
|
||||||
|
<div className="absolute bottom-1/4 right-1/3 w-96 h-96 bg-purple-600/10 rounded-full blur-[140px] pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="w-full max-w-md relative z-10">
|
||||||
|
<div className="glass-panel p-8 sm:p-10 rounded-3xl border border-cyan-500/30 shadow-[0_0_50px_rgba(56,189,248,0.15)] space-y-6">
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<div className="w-12 h-12 rounded-2xl bg-cyan-400/10 border border-cyan-400/30 flex items-center justify-center text-cyan-400 mx-auto shadow-lg">
|
||||||
|
<Lock className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-black font-mono text-white tracking-wider pt-2">
|
||||||
|
AGHATIS STUDIO
|
||||||
|
</h1>
|
||||||
|
<p className="text-xs font-mono text-cyan-300 tracking-widest uppercase">
|
||||||
|
ADMIN COMMAND CONSOLE // AUTHENTICATION
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loginError && (
|
||||||
|
<div className="p-3 rounded-xl bg-rose-950/40 border border-rose-500/30 text-rose-300 text-xs font-mono flex items-center gap-2">
|
||||||
|
<AlertCircle className="w-4 h-4 shrink-0" />
|
||||||
|
<span>{loginError}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleLogin} className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase tracking-wider text-slate-300 mb-1.5">
|
||||||
|
Commander Username
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
placeholder="Enter username"
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-2.5 rounded-xl bg-black/40 border border-white/15 text-white font-mono text-xs focus:outline-none focus:border-cyan-400 transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase tracking-wider text-slate-300 mb-1.5">
|
||||||
|
Security Passkey
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
placeholder="••••••••••••"
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-2.5 rounded-xl bg-black/40 border border-white/15 text-white font-mono text-xs focus:outline-none focus:border-cyan-400 transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loginLoading}
|
||||||
|
className="w-full py-3 rounded-xl bg-gradient-to-r from-cyan-400 via-sky-300 to-indigo-400 text-slate-950 font-mono text-xs uppercase tracking-wider font-bold shadow-[0_0_20px_rgba(56,189,248,0.3)] hover:opacity-95 transition-opacity flex items-center justify-center gap-2 cursor-pointer disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{loginLoading ? (
|
||||||
|
<>
|
||||||
|
<div className="w-3.5 h-3.5 rounded-full border-2 border-slate-950 border-t-transparent animate-spin" />
|
||||||
|
<span>AUTHENTICATING WITH MYSQL...</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Lock className="w-4 h-4" />
|
||||||
|
<span>Access Admin Console</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="text-center pt-2">
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
className="text-xs font-mono text-slate-500 hover:text-cyan-400 transition-colors inline-flex items-center gap-1.5"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-3.5 h-3.5" />
|
||||||
|
<span>Return to Public Website</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AUTHENTICATED DASHBOARD
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[#0a0a12] text-slate-100 flex flex-col font-sans selection:bg-cyan-500 selection:text-slate-950">
|
||||||
|
|
||||||
|
{/* Toast Alert */}
|
||||||
|
{toastMessage && (
|
||||||
|
<div className="fixed top-6 right-6 z-50 px-4 py-3 rounded-xl glass-panel border border-cyan-400/50 text-cyan-300 font-mono text-xs flex items-center gap-2 shadow-[0_0_25px_rgba(56,189,248,0.25)] animate-in fade-in slide-in-from-top-2">
|
||||||
|
<CheckCircle className="w-4 h-4 text-cyan-400" />
|
||||||
|
<span>{toastMessage}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Top Navbar Header */}
|
||||||
|
<header className="border-b border-white/10 bg-[#08080f]/90 backdrop-blur-xl sticky top-0 z-40 px-4 sm:px-8 py-3.5 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-lg bg-gradient-to-tr from-cyan-400 to-indigo-500 flex items-center justify-center text-slate-950 font-black text-xs">
|
||||||
|
AS
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-mono font-bold tracking-wider text-sm text-white">
|
||||||
|
AGHATIS STUDIO
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] font-mono px-2 py-0.5 rounded bg-cyan-500/20 text-cyan-300 border border-cyan-400/30">
|
||||||
|
ADMIN COCKPIT
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5 text-[11px] font-mono text-emerald-400">
|
||||||
|
<Database className="w-3 h-3" />
|
||||||
|
<span>MySQL 103.155.224.244 • aghatis_studio_db</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hidden sm:flex items-center gap-1.5 px-3 py-1.5 rounded-lg glass-panel text-xs font-mono text-slate-300 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
<span>Live Website</span>
|
||||||
|
<ExternalLink className="w-3.5 h-3.5" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={fetchProjects}
|
||||||
|
title="Refresh database records"
|
||||||
|
className="p-2 rounded-lg glass-panel text-slate-300 hover:text-white transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
<RefreshCw className={`w-4 h-4 ${loadingProjects ? "animate-spin text-cyan-400" : ""}`} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-rose-950/40 border border-rose-500/30 text-rose-300 hover:text-white hover:bg-rose-900/50 text-xs font-mono transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
<LogOut className="w-3.5 h-3.5" />
|
||||||
|
<span className="hidden sm:inline">Sign Out</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main Container */}
|
||||||
|
<main className="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
|
||||||
|
|
||||||
|
{/* Metric Cards Row */}
|
||||||
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6">
|
||||||
|
<div className="glass-card p-5 rounded-2xl border-white/10">
|
||||||
|
<div className="text-xs font-mono uppercase tracking-wider text-slate-400">
|
||||||
|
Total Fleet Projects
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl font-black font-mono text-white mt-2">
|
||||||
|
{projects.length}
|
||||||
|
</div>
|
||||||
|
<div className="text-[11px] font-mono text-cyan-400 mt-1">
|
||||||
|
Active in Database
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="glass-card p-5 rounded-2xl border-white/10">
|
||||||
|
<div className="text-xs font-mono uppercase tracking-wider text-slate-400">
|
||||||
|
Software Tools
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl font-black font-mono text-cyan-400 mt-2">
|
||||||
|
{projects.filter((p) => p.category === "software").length}
|
||||||
|
</div>
|
||||||
|
<div className="text-[11px] font-mono text-slate-400 mt-1">
|
||||||
|
Native Workstations & Pipelines
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="glass-card p-5 rounded-2xl border-white/10">
|
||||||
|
<div className="text-xs font-mono uppercase tracking-wider text-slate-400">
|
||||||
|
Original Games
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl font-black font-mono text-purple-400 mt-2">
|
||||||
|
{projects.filter((p) => p.category === "games").length}
|
||||||
|
</div>
|
||||||
|
<div className="text-[11px] font-mono text-slate-400 mt-1">
|
||||||
|
Atmospheric Titles
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="glass-card p-5 rounded-2xl border-white/10">
|
||||||
|
<div className="text-xs font-mono uppercase tracking-wider text-slate-400">
|
||||||
|
Lab R&D Prototypes
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl font-black font-mono text-emerald-400 mt-2">
|
||||||
|
{projects.filter((p) => p.category === "lab").length}
|
||||||
|
</div>
|
||||||
|
<div className="text-[11px] font-mono text-slate-400 mt-1">
|
||||||
|
Procedural & Audio Experiments
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Controls & Filtering Bar */}
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 glass-panel p-4 rounded-2xl border-white/10">
|
||||||
|
{/* Category Filter Pills */}
|
||||||
|
<div className="flex items-center gap-2 overflow-x-auto w-full sm:w-auto pb-1 sm:pb-0">
|
||||||
|
{[
|
||||||
|
{ id: "all", label: "All Projects" },
|
||||||
|
{ id: "software", label: "Software" },
|
||||||
|
{ id: "games", label: "Games" },
|
||||||
|
{ id: "lab", label: "Lab R&D" },
|
||||||
|
].map((cat) => (
|
||||||
|
<button
|
||||||
|
key={cat.id}
|
||||||
|
onClick={() => setSelectedCategory(cat.id)}
|
||||||
|
className={`px-3.5 py-1.5 rounded-xl font-mono text-xs uppercase tracking-wider transition-colors cursor-pointer shrink-0 ${
|
||||||
|
selectedCategory === cat.id
|
||||||
|
? "bg-cyan-500/20 border border-cyan-400/50 text-cyan-300 font-bold"
|
||||||
|
: "text-slate-400 hover:text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{cat.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search Input & Add Button */}
|
||||||
|
<div className="flex items-center gap-3 w-full sm:w-auto justify-end">
|
||||||
|
<div className="relative flex-1 sm:w-64">
|
||||||
|
<Search className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
placeholder="Search projects..."
|
||||||
|
className="w-full pl-9 pr-3 py-1.5 rounded-xl bg-white/5 border border-white/10 text-xs font-mono text-white placeholder-slate-500 focus:outline-none focus:border-cyan-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={openCreateModal}
|
||||||
|
className="px-4 py-2 rounded-xl bg-gradient-to-r from-cyan-400 to-indigo-400 text-slate-950 font-mono text-xs uppercase tracking-wider font-bold shadow-lg hover:opacity-90 transition-opacity flex items-center gap-1.5 cursor-pointer shrink-0"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
<span>Deploy Project</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Project Grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{filteredProjects.map((p) => (
|
||||||
|
<div
|
||||||
|
key={p.id}
|
||||||
|
className="glass-card rounded-2xl p-6 border-white/10 flex flex-col justify-between space-y-4 hover:border-cyan-500/30 transition-all group"
|
||||||
|
>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-[10px] font-mono uppercase px-2 py-0.5 rounded bg-white/5 border border-white/10 text-cyan-300">
|
||||||
|
{p.categoryLabel}
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] font-mono uppercase px-2 py-0.5 rounded bg-indigo-950/60 border border-indigo-400/20 text-indigo-300">
|
||||||
|
{p.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-bold font-mono text-white group-hover:text-cyan-300 transition-colors">
|
||||||
|
{p.title}
|
||||||
|
</h3>
|
||||||
|
<div className="text-[11px] font-mono text-slate-500">
|
||||||
|
ID: {p.id} • Target: {p.releaseTarget}
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-400 line-clamp-2 mt-2">
|
||||||
|
{p.tagline}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stack Badges */}
|
||||||
|
<div className="flex flex-wrap gap-1.5 pt-1">
|
||||||
|
{p.stack?.slice(0, 3).map((item) => (
|
||||||
|
<span
|
||||||
|
key={item}
|
||||||
|
className="text-[10px] font-mono px-2 py-0.5 rounded bg-white/5 text-slate-400"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{p.stack?.length > 3 && (
|
||||||
|
<span className="text-[10px] font-mono text-slate-500">
|
||||||
|
+{p.stack.length - 3} more
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className="pt-4 border-t border-white/10 flex items-center justify-between">
|
||||||
|
<div className="text-[11px] font-mono text-slate-500">
|
||||||
|
{p.features?.length || 0} Features
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => openEditModal(p)}
|
||||||
|
className="p-2 rounded-lg glass-panel hover:border-cyan-400/50 text-slate-300 hover:text-cyan-300 transition-colors cursor-pointer"
|
||||||
|
title="Edit project"
|
||||||
|
>
|
||||||
|
<Edit className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDelete(p.id, p.title)}
|
||||||
|
className="p-2 rounded-lg bg-rose-950/30 hover:bg-rose-900/50 text-rose-400 transition-colors cursor-pointer"
|
||||||
|
title="Delete project"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{filteredProjects.length === 0 && (
|
||||||
|
<div className="col-span-full py-16 text-center text-slate-500 font-mono text-xs">
|
||||||
|
NO PROJECTS FOUND IN THIS SECTOR
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* CREATE / EDIT MODAL */}
|
||||||
|
{isModalOpen && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-xl overflow-y-auto">
|
||||||
|
<div className="relative w-full max-w-2xl bg-[#0e0e1c] border border-cyan-500/30 rounded-3xl p-6 sm:p-8 shadow-2xl my-8 space-y-6">
|
||||||
|
|
||||||
|
{/* Modal Header */}
|
||||||
|
<div className="flex items-center justify-between pb-4 border-b border-white/10">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h2 className="text-xl font-bold font-mono text-white">
|
||||||
|
{editingProject ? `Edit Project: ${editingProject.title}` : "Deploy New Original Project"}
|
||||||
|
</h2>
|
||||||
|
<p className="text-xs font-mono text-cyan-400">
|
||||||
|
Data will be committed directly to MySQL database
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsModalOpen(false)}
|
||||||
|
className="p-2 rounded-full glass-panel text-slate-400 hover:text-white transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{formError && (
|
||||||
|
<div className="p-3 rounded-xl bg-rose-950/40 border border-rose-500/30 text-rose-300 text-xs font-mono flex items-center gap-2">
|
||||||
|
<AlertCircle className="w-4 h-4 shrink-0" />
|
||||||
|
<span>{formError}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSaveProject} className="space-y-4">
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Project ID / Slug
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formId}
|
||||||
|
onChange={(e) => setFormId(e.target.value)}
|
||||||
|
placeholder="e.g. aetheria"
|
||||||
|
disabled={!!editingProject}
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400 disabled:opacity-50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Project Title
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formTitle}
|
||||||
|
onChange={(e) => setFormTitle(e.target.value)}
|
||||||
|
placeholder="e.g. Aetheria: Chrono Drift"
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Category Track
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={formCategory}
|
||||||
|
onChange={(e) => setFormCategory(e.target.value as "software" | "games" | "lab")}
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-[#141428] border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
>
|
||||||
|
<option value="software">Software Products</option>
|
||||||
|
<option value="games">Original Games</option>
|
||||||
|
<option value="lab">Experimental Lab</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Category Sub-label
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formCategoryLabel}
|
||||||
|
onChange={(e) => setFormCategoryLabel(e.target.value)}
|
||||||
|
placeholder="e.g. Tactical Flight RPG"
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Status Badge
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formStatus}
|
||||||
|
onChange={(e) => setFormStatus(e.target.value)}
|
||||||
|
placeholder="e.g. In Active Dev"
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Status Type
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={formStatusType}
|
||||||
|
onChange={(e) => setFormStatusType(e.target.value as "active" | "alpha" | "prototype" | "upcoming")}
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-[#141428] border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
>
|
||||||
|
<option value="active">Active</option>
|
||||||
|
<option value="alpha">Alpha</option>
|
||||||
|
<option value="prototype">Prototype</option>
|
||||||
|
<option value="upcoming">Upcoming</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Target Release
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formReleaseTarget}
|
||||||
|
onChange={(e) => setFormReleaseTarget(e.target.value)}
|
||||||
|
placeholder="e.g. Alpha 2027"
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Visual Icon
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={formIconName}
|
||||||
|
onChange={(e) => setFormIconName(e.target.value)}
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-[#141428] border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
>
|
||||||
|
<option value="Terminal">Terminal (Software)</option>
|
||||||
|
<option value="Gamepad2">Gamepad2 (Games)</option>
|
||||||
|
<option value="FlaskConical">FlaskConical (Lab)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Tagline (1 sentence hook)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formTagline}
|
||||||
|
onChange={(e) => setFormTagline(e.target.value)}
|
||||||
|
placeholder="Atmospheric spaceflight RPG driven by Newtonian physics & relativistic time mechanics."
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Card Short Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={formDescription}
|
||||||
|
onChange={(e) => setFormDescription(e.target.value)}
|
||||||
|
rows={2}
|
||||||
|
placeholder="Pilot handcrafted atmospheric ships through uncharted cosmic sectors..."
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Modal Long Description & Manifesto
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={formLongDescription}
|
||||||
|
onChange={(e) => setFormLongDescription(e.target.value)}
|
||||||
|
rows={3}
|
||||||
|
placeholder="Deep dive narrative and design philosophy..."
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Key Architectural Features (1 per line)
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={formFeatures}
|
||||||
|
onChange={(e) => setFormFeatures(e.target.value)}
|
||||||
|
rows={3}
|
||||||
|
placeholder="True orbital mechanics and gravitational slingshots Non-linear stellar narrative Procedurally synthesized nebula volumetric rendering"
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-mono uppercase text-slate-300 mb-1">
|
||||||
|
Tech Stack (Comma-separated)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formStack}
|
||||||
|
onChange={(e) => setFormStack(e.target.value)}
|
||||||
|
placeholder="Godot 4.3, C++ GDExtension, Bespoke GLSL, FMOD"
|
||||||
|
className="w-full px-3 py-2 rounded-xl bg-white/5 border border-white/15 text-xs font-mono text-white focus:outline-none focus:border-cyan-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-end gap-3 pt-4 border-t border-white/10">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsModalOpen(false)}
|
||||||
|
className="px-4 py-2.5 rounded-xl glass-panel text-xs font-mono text-slate-400 hover:text-white transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={formSubmitting}
|
||||||
|
className="px-6 py-2.5 rounded-xl bg-gradient-to-r from-cyan-400 to-indigo-400 text-slate-950 font-mono text-xs uppercase tracking-wider font-bold shadow-lg hover:opacity-95 transition-opacity cursor-pointer disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{formSubmitting ? "Committing to MySQL..." : editingProject ? "Save Changes" : "Deploy Project"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { verifyUserCredentials, createSessionToken, SESSION_COOKIE_NAME } from "@/lib/auth";
|
||||||
|
|
||||||
|
export async function POST(req: Request) {
|
||||||
|
try {
|
||||||
|
const { username, password } = await req.json();
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Username and password are required" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const authResult = await verifyUserCredentials(username, password);
|
||||||
|
if (!authResult.success) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Invalid username or password" },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = createSessionToken(username);
|
||||||
|
|
||||||
|
const response = NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
user: authResult.user || { username },
|
||||||
|
});
|
||||||
|
|
||||||
|
response.cookies.set(SESSION_COOKIE_NAME, token, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
sameSite: "lax",
|
||||||
|
path: "/",
|
||||||
|
maxAge: 60 * 60 * 24 * 7, // 7 days
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const message = err instanceof Error ? err.message : "Unknown error";
|
||||||
|
return NextResponse.json({ error: message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { SESSION_COOKIE_NAME } from "@/lib/auth";
|
||||||
|
|
||||||
|
export async function POST() {
|
||||||
|
const response = NextResponse.json({ success: true });
|
||||||
|
response.cookies.delete(SESSION_COOKIE_NAME);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { isAuthenticated } from "@/lib/auth";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const authed = await isAuthenticated();
|
||||||
|
if (!authed) {
|
||||||
|
return NextResponse.json({ authenticated: false }, { status: 401 });
|
||||||
|
}
|
||||||
|
return NextResponse.json({ authenticated: true, user: { username: "Aghatis" } });
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { query } from "@/lib/db";
|
||||||
|
import { isAuthenticated } from "@/lib/auth";
|
||||||
|
import type { DBProject } from "../route";
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
_req: Request,
|
||||||
|
props: { params: Promise<{ id: string }> }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { id } = await props.params;
|
||||||
|
const rows = await query<DBProject[]>(
|
||||||
|
"SELECT * FROM projects WHERE id = ? LIMIT 1",
|
||||||
|
[id]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!rows || rows.length === 0) {
|
||||||
|
return NextResponse.json({ error: "Project not found" }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const p = rows[0];
|
||||||
|
const formatted = {
|
||||||
|
id: p.id,
|
||||||
|
title: p.title,
|
||||||
|
category: p.category,
|
||||||
|
categoryLabel: p.category_label,
|
||||||
|
tagline: p.tagline,
|
||||||
|
status: p.status,
|
||||||
|
statusType: p.status_type,
|
||||||
|
releaseTarget: p.release_target,
|
||||||
|
description: p.description,
|
||||||
|
longDescription: p.long_description,
|
||||||
|
features: typeof p.features === "string" ? JSON.parse(p.features) : p.features || [],
|
||||||
|
stack: typeof p.stack === "string" ? JSON.parse(p.stack) : p.stack || [],
|
||||||
|
accentGradient: p.accent_gradient,
|
||||||
|
borderGlow: p.border_glow,
|
||||||
|
iconName: p.icon_name,
|
||||||
|
orderIndex: p.order_index,
|
||||||
|
};
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, data: formatted });
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const message = err instanceof Error ? err.message : "Database error";
|
||||||
|
return NextResponse.json({ error: message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PUT(
|
||||||
|
req: Request,
|
||||||
|
props: { params: Promise<{ id: string }> }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const authed = await isAuthenticated();
|
||||||
|
if (!authed) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = await props.params;
|
||||||
|
const body = await req.json();
|
||||||
|
|
||||||
|
const title = body.title?.trim();
|
||||||
|
const category = body.category || "software";
|
||||||
|
const category_label = body.categoryLabel || body.category_label || "Original Software";
|
||||||
|
const tagline = body.tagline?.trim() || "";
|
||||||
|
const status = body.status?.trim() || "In Active Dev";
|
||||||
|
const status_type = body.statusType || body.status_type || "active";
|
||||||
|
const release_target = body.releaseTarget || body.release_target || "2027";
|
||||||
|
const description = body.description?.trim() || "";
|
||||||
|
const long_description = body.longDescription || body.long_description || "";
|
||||||
|
const features = JSON.stringify(Array.isArray(body.features) ? body.features : []);
|
||||||
|
const stack = JSON.stringify(Array.isArray(body.stack) ? body.stack : []);
|
||||||
|
const accent_gradient =
|
||||||
|
body.accentGradient ||
|
||||||
|
body.accent_gradient ||
|
||||||
|
"from-cyan-500/20 via-blue-600/10 to-transparent";
|
||||||
|
const border_glow =
|
||||||
|
body.borderGlow ||
|
||||||
|
body.border_glow ||
|
||||||
|
"hover:border-cyan-400/50 hover:shadow-[0_0_30px_rgba(56,189,248,0.25)]";
|
||||||
|
const icon_name = body.iconName || body.icon_name || "Terminal";
|
||||||
|
const order_index = Number(body.orderIndex ?? body.order_index ?? 0);
|
||||||
|
|
||||||
|
await query(
|
||||||
|
`UPDATE projects SET
|
||||||
|
title = ?,
|
||||||
|
category = ?,
|
||||||
|
category_label = ?,
|
||||||
|
tagline = ?,
|
||||||
|
status = ?,
|
||||||
|
status_type = ?,
|
||||||
|
release_target = ?,
|
||||||
|
description = ?,
|
||||||
|
long_description = ?,
|
||||||
|
features = ?,
|
||||||
|
stack = ?,
|
||||||
|
accent_gradient = ?,
|
||||||
|
border_glow = ?,
|
||||||
|
icon_name = ?,
|
||||||
|
order_index = ?,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = ?`,
|
||||||
|
[
|
||||||
|
title,
|
||||||
|
category,
|
||||||
|
category_label,
|
||||||
|
tagline,
|
||||||
|
status,
|
||||||
|
status_type,
|
||||||
|
release_target,
|
||||||
|
description,
|
||||||
|
long_description,
|
||||||
|
features,
|
||||||
|
stack,
|
||||||
|
accent_gradient,
|
||||||
|
border_glow,
|
||||||
|
icon_name,
|
||||||
|
order_index,
|
||||||
|
id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, message: "Project updated" });
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const message = err instanceof Error ? err.message : "Database error";
|
||||||
|
return NextResponse.json({ error: message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(
|
||||||
|
_req: Request,
|
||||||
|
props: { params: Promise<{ id: string }> }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const authed = await isAuthenticated();
|
||||||
|
if (!authed) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = await props.params;
|
||||||
|
await query("DELETE FROM projects WHERE id = ?", [id]);
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, message: "Project deleted" });
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const message = err instanceof Error ? err.message : "Database error";
|
||||||
|
return NextResponse.json({ error: message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { query } from "@/lib/db";
|
||||||
|
import { isAuthenticated } from "@/lib/auth";
|
||||||
|
|
||||||
|
export interface DBProject {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
category: "software" | "games" | "lab";
|
||||||
|
category_label: string;
|
||||||
|
tagline: string;
|
||||||
|
status: string;
|
||||||
|
status_type: "active" | "alpha" | "prototype" | "upcoming";
|
||||||
|
release_target: string;
|
||||||
|
description: string;
|
||||||
|
long_description: string;
|
||||||
|
features: string[] | string;
|
||||||
|
stack: string[] | string;
|
||||||
|
accent_gradient: string;
|
||||||
|
border_glow: string;
|
||||||
|
icon_name: string;
|
||||||
|
order_index?: number;
|
||||||
|
created_at?: string;
|
||||||
|
updated_at?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const rows = await query<DBProject[]>(
|
||||||
|
"SELECT * FROM projects ORDER BY order_index ASC, created_at DESC"
|
||||||
|
);
|
||||||
|
|
||||||
|
const formatted = rows.map((p) => ({
|
||||||
|
id: p.id,
|
||||||
|
title: p.title,
|
||||||
|
category: p.category,
|
||||||
|
categoryLabel: p.category_label,
|
||||||
|
tagline: p.tagline,
|
||||||
|
status: p.status,
|
||||||
|
statusType: p.status_type,
|
||||||
|
releaseTarget: p.release_target,
|
||||||
|
description: p.description,
|
||||||
|
longDescription: p.long_description,
|
||||||
|
features: typeof p.features === "string" ? JSON.parse(p.features) : p.features || [],
|
||||||
|
stack: typeof p.stack === "string" ? JSON.parse(p.stack) : p.stack || [],
|
||||||
|
accentGradient: p.accent_gradient,
|
||||||
|
borderGlow: p.border_glow,
|
||||||
|
iconName: p.icon_name,
|
||||||
|
orderIndex: p.order_index,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, data: formatted });
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const message = err instanceof Error ? err.message : "Database query error";
|
||||||
|
return NextResponse.json({ error: message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(req: Request) {
|
||||||
|
try {
|
||||||
|
const authed = await isAuthenticated();
|
||||||
|
if (!authed) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await req.json();
|
||||||
|
|
||||||
|
const id = (body.id || body.title.toLowerCase().replace(/[^a-z0-9]+/g, "-")).trim();
|
||||||
|
const title = body.title?.trim();
|
||||||
|
const category = body.category || "software";
|
||||||
|
const category_label = body.categoryLabel || body.category_label || "Original Software";
|
||||||
|
const tagline = body.tagline?.trim() || "";
|
||||||
|
const status = body.status?.trim() || "In Active Dev";
|
||||||
|
const status_type = body.statusType || body.status_type || "active";
|
||||||
|
const release_target = body.releaseTarget || body.release_target || "2027";
|
||||||
|
const description = body.description?.trim() || "";
|
||||||
|
const long_description = body.longDescription || body.long_description || "";
|
||||||
|
const features = JSON.stringify(Array.isArray(body.features) ? body.features : []);
|
||||||
|
const stack = JSON.stringify(Array.isArray(body.stack) ? body.stack : []);
|
||||||
|
const accent_gradient =
|
||||||
|
body.accentGradient ||
|
||||||
|
body.accent_gradient ||
|
||||||
|
"from-cyan-500/20 via-blue-600/10 to-transparent";
|
||||||
|
const border_glow =
|
||||||
|
body.borderGlow ||
|
||||||
|
body.border_glow ||
|
||||||
|
"hover:border-cyan-400/50 hover:shadow-[0_0_30px_rgba(56,189,248,0.25)]";
|
||||||
|
const icon_name = body.iconName || body.icon_name || "Terminal";
|
||||||
|
const order_index = Number(body.orderIndex ?? body.order_index ?? 0);
|
||||||
|
|
||||||
|
await query(
|
||||||
|
`INSERT INTO projects (
|
||||||
|
id, title, category, category_label, tagline, status, status_type,
|
||||||
|
release_target, description, long_description, features, stack,
|
||||||
|
accent_gradient, border_glow, icon_name, order_index
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
[
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
category,
|
||||||
|
category_label,
|
||||||
|
tagline,
|
||||||
|
status,
|
||||||
|
status_type,
|
||||||
|
release_target,
|
||||||
|
description,
|
||||||
|
long_description,
|
||||||
|
features,
|
||||||
|
stack,
|
||||||
|
accent_gradient,
|
||||||
|
border_glow,
|
||||||
|
icon_name,
|
||||||
|
order_index,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, id });
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const message = err instanceof Error ? err.message : "Database error";
|
||||||
|
return NextResponse.json({ error: message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
+120
@@ -0,0 +1,120 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: #0a0a12;
|
||||||
|
--foreground: #f1f5f9;
|
||||||
|
--nebula-glow: rgba(99, 102, 241, 0.15);
|
||||||
|
--cyan-glow: rgba(56, 189, 248, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-cosmic-950: #05050a;
|
||||||
|
--color-cosmic-900: #0a0a12;
|
||||||
|
--color-cosmic-800: #101020;
|
||||||
|
--color-cosmic-700: #16162d;
|
||||||
|
--color-cosmic-600: #232344;
|
||||||
|
--color-nebula-cyan: #38bdf8;
|
||||||
|
--color-nebula-purple: #a855f7;
|
||||||
|
--color-nebula-indigo: #6366f1;
|
||||||
|
--color-nebula-pink: #f43f5e;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #0a0a12;
|
||||||
|
color: #f1f5f9;
|
||||||
|
overflow-x: hidden;
|
||||||
|
selection-background-color: #6366f1;
|
||||||
|
selection-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #0a0a12;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #1f1f38;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #2e2e54;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glassmorphism helpers */
|
||||||
|
.glass-panel {
|
||||||
|
background: rgba(14, 14, 28, 0.65);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-panel-glow {
|
||||||
|
background: rgba(16, 16, 32, 0.7);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.2);
|
||||||
|
box-shadow: 0 0 35px -5px rgba(99, 102, 241, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card {
|
||||||
|
background: linear-gradient(135deg, rgba(25, 25, 48, 0.4) 0%, rgba(12, 12, 24, 0.6) 100%);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.07);
|
||||||
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card:hover {
|
||||||
|
border-color: rgba(99, 102, 241, 0.4);
|
||||||
|
box-shadow: 0 10px 40px -10px rgba(99, 102, 241, 0.25);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nebula & Planet ambient glow */
|
||||||
|
.planet-glow-cyan {
|
||||||
|
box-shadow: 0 0 50px -10px rgba(56, 189, 248, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.planet-glow-purple {
|
||||||
|
box-shadow: 0 0 50px -10px rgba(168, 85, 247, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gradient-cosmic {
|
||||||
|
background: linear-gradient(135deg, #ffffff 0%, #cbd5e1 50%, #94a3b8 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gradient-cyan {
|
||||||
|
background: linear-gradient(135deg, #38bdf8 0%, #818cf8 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gradient-nebula {
|
||||||
|
background: linear-gradient(135deg, #a855f7 0%, #6366f1 50%, #38bdf8 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subtle glow animations */
|
||||||
|
@keyframes pulse-slow {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 0.4;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-pulse-slow {
|
||||||
|
animation: pulse-slow 8s infinite ease-in-out;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import type { Metadata, Viewport } from "next";
|
||||||
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const geistSans = Geist({
|
||||||
|
variable: "--font-geist-sans",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const geistMono = Geist_Mono({
|
||||||
|
variable: "--font-geist-mono",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Aghatis Studio | Independent Product Studio & Game Lab",
|
||||||
|
description:
|
||||||
|
"An independent product studio dedicated to engineering and publishing original software, interactive worlds, and atmospheric games.",
|
||||||
|
keywords: [
|
||||||
|
"Aghatis Studio",
|
||||||
|
"Indie Game Studio",
|
||||||
|
"Independent Software Lab",
|
||||||
|
"Original IP",
|
||||||
|
"Creative Technology",
|
||||||
|
"Three.js",
|
||||||
|
"Game Development",
|
||||||
|
"Product Studio",
|
||||||
|
],
|
||||||
|
authors: [{ name: "Aghatis Studio" }],
|
||||||
|
openGraph: {
|
||||||
|
title: "Aghatis Studio | Independent Product & Game Lab",
|
||||||
|
description:
|
||||||
|
"Synthesizing original software and games. An autonomous indie lab building uncompromised digital artifacts.",
|
||||||
|
type: "website",
|
||||||
|
locale: "en_US",
|
||||||
|
},
|
||||||
|
icons: {
|
||||||
|
icon: "/favicon.ico",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
themeColor: "#0a0a12",
|
||||||
|
colorScheme: "dark",
|
||||||
|
width: "device-width",
|
||||||
|
initialScale: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html
|
||||||
|
lang="en"
|
||||||
|
className={`${geistSans.variable} ${geistMono.variable} dark scroll-smooth`}
|
||||||
|
>
|
||||||
|
<body className="min-h-screen bg-[#0a0a12] text-slate-100 font-sans antialiased overflow-x-hidden selection:bg-indigo-500 selection:text-white">
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import CosmicGalaxyDynamic from "@/components/canvas/CosmicGalaxyDynamic";
|
||||||
|
import Navbar from "@/components/navbar/Navbar";
|
||||||
|
import Hero from "@/components/hero/Hero";
|
||||||
|
import About from "@/components/about/About";
|
||||||
|
import CoreFocus from "@/components/focus/CoreFocus";
|
||||||
|
import Portfolio from "@/components/showcase/Portfolio";
|
||||||
|
import StudioCreed from "@/components/creed/StudioCreed";
|
||||||
|
import Footer from "@/components/footer/Footer";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-screen bg-[#0a0a12] text-slate-100 flex flex-col selection:bg-cyan-500 selection:text-slate-950 overflow-x-hidden">
|
||||||
|
{/* 3D Interstellar Starfield & Galaxy Dust Backdrop */}
|
||||||
|
<CosmicGalaxyDynamic />
|
||||||
|
|
||||||
|
{/* Floating Glassmorphic Navigation */}
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
{/* Main Experience Flow */}
|
||||||
|
<main className="relative z-10 flex-1 flex flex-col">
|
||||||
|
<Hero />
|
||||||
|
<About />
|
||||||
|
<CoreFocus />
|
||||||
|
<Portfolio />
|
||||||
|
<StudioCreed />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Cosmic Studio Footer */}
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { Shield, Sparkles, Cpu, Eye, Code, Compass } from "lucide-react";
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
const pillars = [
|
||||||
|
{
|
||||||
|
icon: Shield,
|
||||||
|
title: "Radical Autonomy",
|
||||||
|
description:
|
||||||
|
"We are 100% self-directed. Every hour in our studio is dedicated to original research, bespoke engineering, and crafting immersive experiences.",
|
||||||
|
badge: "Sovereign IP",
|
||||||
|
accent: "from-cyan-500/20 to-blue-500/10",
|
||||||
|
border: "border-cyan-500/30",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Cpu,
|
||||||
|
title: "Relentless Engineering",
|
||||||
|
description:
|
||||||
|
"From low-latency native algorithms to custom GLSL shaders and responsive spatial architectures, our software is engineered for speed, stability, and longevity.",
|
||||||
|
badge: "Deep Tech",
|
||||||
|
accent: "from-purple-500/20 to-indigo-500/10",
|
||||||
|
border: "border-purple-500/30",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Sparkles,
|
||||||
|
title: "Artistic Depth",
|
||||||
|
description:
|
||||||
|
"Games and software shouldn't just be functional—they should carry soul. We handcraft atmospheric aesthetics, celestial soundscapes, and intuitive interaction rituals.",
|
||||||
|
badge: "Bespoke Art",
|
||||||
|
accent: "from-pink-500/20 to-rose-500/10",
|
||||||
|
border: "border-pink-500/30",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Eye,
|
||||||
|
title: "Long-Horizon Vision",
|
||||||
|
description:
|
||||||
|
"We build digital artifacts that endure. Rather than following transient hype cycles or rushed deliverables, we invest in foundational systems and timeless experiences.",
|
||||||
|
badge: "Decade Mindset",
|
||||||
|
accent: "from-indigo-500/20 to-cyan-500/10",
|
||||||
|
border: "border-indigo-500/30",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="about" className="relative py-28 overflow-hidden">
|
||||||
|
{/* Background radial accent */}
|
||||||
|
<div className="absolute top-1/2 left-0 w-96 h-96 bg-indigo-600/10 rounded-full blur-[140px] pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
||||||
|
|
||||||
|
{/* Section Header */}
|
||||||
|
<div className="flex flex-col items-center text-center max-w-3xl mx-auto mb-20 space-y-4">
|
||||||
|
<div className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full glass-panel border border-indigo-500/30 text-xs font-mono text-indigo-300">
|
||||||
|
<Compass className="w-3.5 h-3.5 text-indigo-400" />
|
||||||
|
<span>ORIGIN & MISSION</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-black tracking-tight text-white">
|
||||||
|
Forged in the Pursuit of <br />
|
||||||
|
<span className="text-gradient-nebula">Creative Sovereignty.</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-base sm:text-lg text-slate-400 leading-relaxed pt-2">
|
||||||
|
Aghatis Studio was established with a singular conviction: the most resonant
|
||||||
|
software and interactive worlds are crafted with unwavering focus, deep curiosity,
|
||||||
|
and total creative sovereignty.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Narrative Feature Row: Manifesto statement */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 items-stretch mb-16">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: -20 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.7 }}
|
||||||
|
className="lg:col-span-7 glass-panel-glow p-8 sm:p-10 rounded-3xl relative overflow-hidden flex flex-col justify-between"
|
||||||
|
>
|
||||||
|
<div className="absolute top-0 right-0 p-8 opacity-10 pointer-events-none">
|
||||||
|
<Code className="w-48 h-48 text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6 relative z-10">
|
||||||
|
<div className="text-xs font-mono tracking-widest text-cyan-400 uppercase">
|
||||||
|
// THE STUDIO NARRATIVE
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl sm:text-3xl font-bold text-white leading-snug">
|
||||||
|
The Craft of an Independent Product Lab.
|
||||||
|
</h3>
|
||||||
|
<p className="text-slate-300 leading-relaxed text-sm sm:text-base">
|
||||||
|
At <span className="text-cyan-300 font-semibold">Aghatis Studio</span>, our mission is purposeful:
|
||||||
|
we design, build, and publish our own digital products. By channeling our entire team's energy
|
||||||
|
into in-house intellectual property, we maintain complete creative control over every mechanic, interface, and artistic decision.
|
||||||
|
</p>
|
||||||
|
<p className="text-slate-400 leading-relaxed text-sm sm:text-base">
|
||||||
|
Every line of code, geometric asset, and algorithmic pipeline we author belongs to a cohesive creative ecosystem.
|
||||||
|
This focused environment grants us the freedom to polish mechanics until they shine, engineer tools with
|
||||||
|
uncompromising standards, and publish digital experiences that leave a lasting impression.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 pt-6 border-t border-white/10 flex items-center justify-between text-xs font-mono text-slate-400">
|
||||||
|
<span>FOUNDED IN INDEPENDENCE</span>
|
||||||
|
<span className="text-cyan-400">PURE IN-HOUSE IP</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Right Quote / Philosophy card */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: 20 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.7, delay: 0.1 }}
|
||||||
|
className="lg:col-span-5 glass-card p-8 sm:p-10 rounded-3xl flex flex-col justify-between border-indigo-500/20 bg-gradient-to-b from-indigo-950/30 to-purple-950/10"
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="w-10 h-10 rounded-xl bg-cyan-400/10 border border-cyan-400/30 flex items-center justify-center text-cyan-400">
|
||||||
|
<Sparkles className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<blockquote className="text-lg sm:text-xl font-medium text-slate-200 italic leading-relaxed">
|
||||||
|
“We measure our success by the elegance of our architecture, the atmosphere of our worlds, and the enduring quality of the software and games we release into the universe.”
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 pt-6 border-t border-white/10 flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-gradient-to-tr from-cyan-400 to-indigo-500 flex items-center justify-center text-slate-950 font-bold text-xs">
|
||||||
|
AS
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs font-mono font-bold text-white tracking-wider">
|
||||||
|
AGHATIS CORE LAB
|
||||||
|
</div>
|
||||||
|
<div className="text-[11px] font-mono text-slate-400">
|
||||||
|
CREATIVE ARCHITECTURE COUNCIL
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pillars Grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
|
{pillars.map((pillar, i) => {
|
||||||
|
const Icon = pillar.icon;
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={pillar.title}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.5, delay: i * 0.1 }}
|
||||||
|
className="glass-card p-6 sm:p-7 rounded-2xl flex flex-col justify-between group hover:border-cyan-400/40 relative overflow-hidden"
|
||||||
|
>
|
||||||
|
<div className={`absolute top-0 right-0 w-28 h-28 bg-gradient-to-bl ${pillar.accent} rounded-bl-full pointer-events-none opacity-40 group-hover:opacity-100 transition-opacity`} />
|
||||||
|
|
||||||
|
<div className="space-y-4 relative z-10">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="w-10 h-10 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center text-cyan-400 group-hover:scale-110 transition-transform">
|
||||||
|
<Icon className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] font-mono uppercase px-2 py-0.5 rounded-full bg-white/5 border border-white/10 text-slate-400">
|
||||||
|
{pillar.badge}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 className="text-lg font-bold text-white group-hover:text-cyan-300 transition-colors">
|
||||||
|
{pillar.title}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className="text-xs sm:text-sm text-slate-400 leading-relaxed">
|
||||||
|
{pillar.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 pt-4 border-t border-white/5 flex items-center justify-between text-[11px] font-mono text-slate-400">
|
||||||
|
<span>PILLAR 0{i + 1}</span>
|
||||||
|
<span className="text-cyan-400 font-mono">NOMINAL</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useMemo, useRef } from "react";
|
||||||
|
import { Canvas, useFrame } from "@react-three/fiber";
|
||||||
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
// 1. Refined, clean Starfield (neat, elegant points of light)
|
||||||
|
function StarField({ count = 650 }: { count?: number }) {
|
||||||
|
const pointsRef = useRef<THREE.Points>(null!);
|
||||||
|
|
||||||
|
const [positions, colors] = useMemo(() => {
|
||||||
|
const pos = new Float32Array(count * 3);
|
||||||
|
const col = new Float32Array(count * 3);
|
||||||
|
|
||||||
|
const colorWhite = new THREE.Color("#ffffff");
|
||||||
|
const colorIcy = new THREE.Color("#93c5fd");
|
||||||
|
const colorSilver = new THREE.Color("#e2e8f0");
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const radius = 30 + Math.random() * 80;
|
||||||
|
const theta = Math.random() * 2 * Math.PI;
|
||||||
|
const phi = Math.acos(2 * Math.random() - 1);
|
||||||
|
|
||||||
|
pos[i * 3] = radius * Math.sin(phi) * Math.cos(theta);
|
||||||
|
pos[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
|
||||||
|
pos[i * 3 + 2] = radius * Math.cos(phi);
|
||||||
|
|
||||||
|
const r = Math.random();
|
||||||
|
const chosen = r > 0.7 ? colorIcy : r > 0.3 ? colorSilver : colorWhite;
|
||||||
|
|
||||||
|
col[i * 3] = chosen.r;
|
||||||
|
col[i * 3 + 1] = chosen.g;
|
||||||
|
col[i * 3 + 2] = chosen.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [pos, col];
|
||||||
|
}, [count]);
|
||||||
|
|
||||||
|
useFrame((state, delta) => {
|
||||||
|
if (!pointsRef.current) return;
|
||||||
|
pointsRef.current.rotation.y += delta * 0.005;
|
||||||
|
pointsRef.current.rotation.x += delta * 0.002;
|
||||||
|
|
||||||
|
const targetX = (state.pointer.x * Math.PI) / 60;
|
||||||
|
const targetY = (state.pointer.y * Math.PI) / 60;
|
||||||
|
pointsRef.current.rotation.y += (targetX - pointsRef.current.rotation.y) * 0.02;
|
||||||
|
pointsRef.current.rotation.x += (-targetY - pointsRef.current.rotation.x) * 0.02;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<points ref={pointsRef}>
|
||||||
|
<bufferGeometry>
|
||||||
|
<bufferAttribute attach="attributes-position" args={[positions, 3]} />
|
||||||
|
<bufferAttribute attach="attributes-color" args={[colors, 3]} />
|
||||||
|
</bufferGeometry>
|
||||||
|
<pointsMaterial
|
||||||
|
size={0.7}
|
||||||
|
vertexColors
|
||||||
|
transparent
|
||||||
|
opacity={0.8}
|
||||||
|
sizeAttenuation
|
||||||
|
depthWrite={false}
|
||||||
|
blending={THREE.AdditiveBlending}
|
||||||
|
/>
|
||||||
|
</points>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Bright, Glowing Background Planet (Astraea - Upper Right)
|
||||||
|
function BackgroundPlanetAstraea() {
|
||||||
|
const groupRef = useRef<THREE.Group>(null!);
|
||||||
|
const planetRef = useRef<THREE.Mesh>(null!);
|
||||||
|
const atmosphereRef = useRef<THREE.Mesh>(null!);
|
||||||
|
|
||||||
|
useFrame((state, delta) => {
|
||||||
|
if (planetRef.current) {
|
||||||
|
planetRef.current.rotation.y += delta * 0.05;
|
||||||
|
}
|
||||||
|
if (atmosphereRef.current) {
|
||||||
|
atmosphereRef.current.rotation.y -= delta * 0.03;
|
||||||
|
}
|
||||||
|
if (groupRef.current) {
|
||||||
|
// Subtle floating parallax
|
||||||
|
const targetX = 16 + (state.pointer.x * 2.5);
|
||||||
|
const targetY = 9 + (state.pointer.y * 2.0);
|
||||||
|
groupRef.current.position.x += (targetX - groupRef.current.position.x) * 0.02;
|
||||||
|
groupRef.current.position.y += (targetY - groupRef.current.position.y) * 0.02;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={groupRef} position={[16, 9, -16]}>
|
||||||
|
{/* Bright Planet Core Sphere */}
|
||||||
|
<mesh ref={planetRef}>
|
||||||
|
<sphereGeometry args={[4.2, 48, 48]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color="#38bdf8"
|
||||||
|
emissive="#0284c7"
|
||||||
|
emissiveIntensity={0.85}
|
||||||
|
roughness={0.25}
|
||||||
|
metalness={0.7}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Radiant Atmospheric Halo Rim */}
|
||||||
|
<mesh ref={atmosphereRef}>
|
||||||
|
<sphereGeometry args={[4.6, 32, 32]} />
|
||||||
|
<meshBasicMaterial
|
||||||
|
color="#7dd3fc"
|
||||||
|
transparent
|
||||||
|
opacity={0.35}
|
||||||
|
side={THREE.BackSide}
|
||||||
|
blending={THREE.AdditiveBlending}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Outer Luminous Glow Aura Ring */}
|
||||||
|
<mesh rotation={[Math.PI / 3, 0.4, 0]}>
|
||||||
|
<ringGeometry args={[4.8, 6.8, 48]} />
|
||||||
|
<meshBasicMaterial
|
||||||
|
color="#38bdf8"
|
||||||
|
transparent
|
||||||
|
opacity={0.22}
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Bright, Glowing Background Planet (Kore - Lower Left with Ring)
|
||||||
|
function BackgroundPlanetKore() {
|
||||||
|
const groupRef = useRef<THREE.Group>(null!);
|
||||||
|
const planetRef = useRef<THREE.Mesh>(null!);
|
||||||
|
const ringRef = useRef<THREE.Mesh>(null!);
|
||||||
|
|
||||||
|
useFrame((state, delta) => {
|
||||||
|
if (planetRef.current) {
|
||||||
|
planetRef.current.rotation.y += delta * 0.04;
|
||||||
|
}
|
||||||
|
if (ringRef.current) {
|
||||||
|
ringRef.current.rotation.z += delta * 0.02;
|
||||||
|
}
|
||||||
|
if (groupRef.current) {
|
||||||
|
// Gentle floating parallax
|
||||||
|
const targetX = -18 - (state.pointer.x * 3.0);
|
||||||
|
const targetY = -12 - (state.pointer.y * 2.5);
|
||||||
|
groupRef.current.position.x += (targetX - groupRef.current.position.x) * 0.02;
|
||||||
|
groupRef.current.position.y += (targetY - groupRef.current.position.y) * 0.02;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={groupRef} position={[-18, -12, -22]}>
|
||||||
|
{/* Bright Violet Core Planet */}
|
||||||
|
<mesh ref={planetRef}>
|
||||||
|
<sphereGeometry args={[5.5, 48, 48]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color="#a855f7"
|
||||||
|
emissive="#6366f1"
|
||||||
|
emissiveIntensity={0.75}
|
||||||
|
roughness={0.3}
|
||||||
|
metalness={0.8}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Atmospheric Halo */}
|
||||||
|
<mesh>
|
||||||
|
<sphereGeometry args={[5.9, 32, 32]} />
|
||||||
|
<meshBasicMaterial
|
||||||
|
color="#c084fc"
|
||||||
|
transparent
|
||||||
|
opacity={0.3}
|
||||||
|
side={THREE.BackSide}
|
||||||
|
blending={THREE.AdditiveBlending}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Luminous Majestic Planetary Ring */}
|
||||||
|
<mesh ref={ringRef} rotation={[Math.PI / 2.3, 0.35, 0]}>
|
||||||
|
<ringGeometry args={[6.8, 10.2, 64]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color="#c084fc"
|
||||||
|
emissive="#a855f7"
|
||||||
|
emissiveIntensity={0.6}
|
||||||
|
transparent
|
||||||
|
opacity={0.45}
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Distant Luminous Stellar Moon Orb
|
||||||
|
function DistantLuminousOrb() {
|
||||||
|
const orbRef = useRef<THREE.Mesh>(null!);
|
||||||
|
|
||||||
|
useFrame((state, delta) => {
|
||||||
|
if (!orbRef.current) return;
|
||||||
|
const t = state.clock.getElapsedTime();
|
||||||
|
orbRef.current.position.y = 14 + Math.sin(t * 0.8) * 0.6;
|
||||||
|
orbRef.current.position.x = -8 + Math.cos(t * 0.5) * 0.8;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mesh ref={orbRef} position={[-8, 14, -18]}>
|
||||||
|
<sphereGeometry args={[1.2, 24, 24]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color="#34d399"
|
||||||
|
emissive="#10b981"
|
||||||
|
emissiveIntensity={1.2}
|
||||||
|
roughness={0.1}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CosmicGalaxy() {
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 pointer-events-none z-0 overflow-hidden">
|
||||||
|
<Canvas
|
||||||
|
camera={{ position: [0, 0, 32], fov: 50 }}
|
||||||
|
dpr={[1, 1.5]}
|
||||||
|
gl={{ antialias: true, alpha: true, powerPreference: "high-performance" }}
|
||||||
|
className="w-full h-full"
|
||||||
|
>
|
||||||
|
<color attach="background" args={["#0a0a12"]} />
|
||||||
|
|
||||||
|
{/* Cinematic Starlight Illumination */}
|
||||||
|
<ambientLight intensity={0.5} />
|
||||||
|
<directionalLight position={[20, 20, 10]} intensity={1.8} color="#e0f2fe" />
|
||||||
|
<directionalLight position={[-20, -15, -5]} intensity={1.0} color="#c084fc" />
|
||||||
|
|
||||||
|
{/* Clean, Neat Starfield */}
|
||||||
|
<StarField count={650} />
|
||||||
|
|
||||||
|
{/* Bright Circular Planet-Like Objects in the Background */}
|
||||||
|
<BackgroundPlanetAstraea />
|
||||||
|
<BackgroundPlanetKore />
|
||||||
|
<DistantLuminousOrb />
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
{/* Atmospheric depth vignettes (subtle gradient, no harsh boxes) */}
|
||||||
|
<div className="absolute inset-0 bg-radial from-transparent via-[#0a0a12]/30 to-[#0a0a12] pointer-events-none" />
|
||||||
|
|
||||||
|
{/* Soft circular glowing light fountains behind the planets */}
|
||||||
|
<div className="absolute -top-10 right-10 w-96 h-96 bg-cyan-500/10 rounded-full blur-[100px] pointer-events-none" />
|
||||||
|
<div className="absolute -bottom-10 left-10 w-[450px] h-[450px] bg-purple-600/10 rounded-full blur-[120px] pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
const CosmicGalaxy = dynamic(() => import("./CosmicGalaxy"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function CosmicGalaxyDynamic() {
|
||||||
|
return <CosmicGalaxy />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useRef } from "react";
|
||||||
|
import { Canvas, useFrame } from "@react-three/fiber";
|
||||||
|
import { Float } from "@react-three/drei";
|
||||||
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
function CosmicArtifact() {
|
||||||
|
const groupRef = useRef<THREE.Group>(null!);
|
||||||
|
const ring1Ref = useRef<THREE.Mesh>(null!);
|
||||||
|
const ring2Ref = useRef<THREE.Mesh>(null!);
|
||||||
|
const ring3Ref = useRef<THREE.Mesh>(null!);
|
||||||
|
const coreRef = useRef<THREE.Mesh>(null!);
|
||||||
|
const outerGeomRef = useRef<THREE.Mesh>(null!);
|
||||||
|
|
||||||
|
useFrame((state, delta) => {
|
||||||
|
if (!groupRef.current) return;
|
||||||
|
|
||||||
|
// Dual-axis rotation for the rings (gyroscope / celestial sphere effect)
|
||||||
|
if (ring1Ref.current) {
|
||||||
|
ring1Ref.current.rotation.x += delta * 0.4;
|
||||||
|
ring1Ref.current.rotation.y += delta * 0.2;
|
||||||
|
}
|
||||||
|
if (ring2Ref.current) {
|
||||||
|
ring2Ref.current.rotation.y += delta * 0.35;
|
||||||
|
ring2Ref.current.rotation.z += delta * 0.25;
|
||||||
|
}
|
||||||
|
if (ring3Ref.current) {
|
||||||
|
ring3Ref.current.rotation.z += delta * 0.3;
|
||||||
|
ring3Ref.current.rotation.x += delta * 0.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core pulsing & rotation
|
||||||
|
if (coreRef.current) {
|
||||||
|
coreRef.current.rotation.y += delta * 0.5;
|
||||||
|
const t = state.clock.getElapsedTime();
|
||||||
|
const scale = 1 + Math.sin(t * 2) * 0.05;
|
||||||
|
coreRef.current.scale.set(scale, scale, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outer wireframe geodesic
|
||||||
|
if (outerGeomRef.current) {
|
||||||
|
outerGeomRef.current.rotation.y -= delta * 0.15;
|
||||||
|
outerGeomRef.current.rotation.x += delta * 0.08;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interactive pointer sway
|
||||||
|
const targetX = (state.pointer.x * Math.PI) / 8;
|
||||||
|
const targetY = (state.pointer.y * Math.PI) / 8;
|
||||||
|
groupRef.current.rotation.y += (targetX - groupRef.current.rotation.y) * 0.05;
|
||||||
|
groupRef.current.rotation.x += (-targetY - groupRef.current.rotation.x) * 0.05;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Float speed={2} rotationIntensity={0.6} floatIntensity={1}>
|
||||||
|
<group ref={groupRef}>
|
||||||
|
{/* Core glowing sphere */}
|
||||||
|
<mesh ref={coreRef}>
|
||||||
|
<sphereGeometry args={[1.2, 32, 32]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color="#38bdf8"
|
||||||
|
emissive="#6366f1"
|
||||||
|
emissiveIntensity={0.8}
|
||||||
|
roughness={0.15}
|
||||||
|
metalness={0.9}
|
||||||
|
wireframe={false}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Inner light source */}
|
||||||
|
<pointLight color="#38bdf8" intensity={4} distance={8} />
|
||||||
|
<pointLight color="#a855f7" intensity={3} distance={10} position={[0, -2, 0]} />
|
||||||
|
|
||||||
|
{/* Outer wireframe icosahedron/geodesic */}
|
||||||
|
<mesh ref={outerGeomRef}>
|
||||||
|
<icosahedronGeometry args={[2.0, 1]} />
|
||||||
|
<meshBasicMaterial
|
||||||
|
color="#818cf8"
|
||||||
|
wireframe
|
||||||
|
transparent
|
||||||
|
opacity={0.3}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Celestial Orbital Ring 1 */}
|
||||||
|
<mesh ref={ring1Ref}>
|
||||||
|
<torusGeometry args={[2.5, 0.025, 16, 100]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color="#38bdf8"
|
||||||
|
emissive="#38bdf8"
|
||||||
|
emissiveIntensity={1.5}
|
||||||
|
roughness={0.1}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Celestial Orbital Ring 2 (Cross tilt) */}
|
||||||
|
<mesh ref={ring2Ref} rotation={[Math.PI / 3, 0, 0]}>
|
||||||
|
<torusGeometry args={[3.0, 0.02, 16, 100]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color="#c084fc"
|
||||||
|
emissive="#a855f7"
|
||||||
|
emissiveIntensity={1.3}
|
||||||
|
roughness={0.1}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Celestial Orbital Ring 3 (Outer edge) */}
|
||||||
|
<mesh ref={ring3Ref} rotation={[-Math.PI / 4, Math.PI / 6, 0]}>
|
||||||
|
<torusGeometry args={[3.4, 0.015, 16, 120]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color="#67e8f9"
|
||||||
|
emissive="#06b6d4"
|
||||||
|
emissiveIntensity={1.0}
|
||||||
|
roughness={0.1}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Orbital Starlight Satellite Node 1 */}
|
||||||
|
<mesh position={[2.6, 0.4, 0]}>
|
||||||
|
<sphereGeometry args={[0.08, 16, 16]} />
|
||||||
|
<meshBasicMaterial color="#38bdf8" />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Orbital Starlight Satellite Node 2 */}
|
||||||
|
<mesh position={[-2.2, 1.8, 0.5]}>
|
||||||
|
<sphereGeometry args={[0.07, 16, 16]} />
|
||||||
|
<meshBasicMaterial color="#f472b6" />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Orbital Starlight Satellite Node 3 */}
|
||||||
|
<mesh position={[0.8, -2.8, -0.6]}>
|
||||||
|
<sphereGeometry args={[0.09, 16, 16]} />
|
||||||
|
<meshBasicMaterial color="#a78bfa" />
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
</Float>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HeroCosmicOrb() {
|
||||||
|
return (
|
||||||
|
<div className="relative w-full h-[380px] sm:h-[450px] lg:h-[550px] flex items-center justify-center">
|
||||||
|
{/* Background radial glow */}
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||||
|
<div className="w-64 h-64 sm:w-80 sm:h-80 bg-indigo-500/20 rounded-full blur-[80px]" />
|
||||||
|
<div className="w-48 h-48 sm:w-60 sm:h-60 bg-cyan-400/20 rounded-full blur-[60px]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Canvas
|
||||||
|
camera={{ position: [0, 0, 7.5], fov: 45 }}
|
||||||
|
dpr={[1, 1.5]}
|
||||||
|
gl={{ antialias: true, alpha: true }}
|
||||||
|
className="w-full h-full cursor-grab active:cursor-grabbing"
|
||||||
|
>
|
||||||
|
<ambientLight intensity={0.4} />
|
||||||
|
<directionalLight position={[10, 10, 5]} intensity={1.5} color="#e0e7ff" />
|
||||||
|
<directionalLight position={[-10, -10, -5]} intensity={0.8} color="#c084fc" />
|
||||||
|
<CosmicArtifact />
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
{/* Holographic coordinates overlay badge */}
|
||||||
|
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 sm:left-6 sm:translate-x-0 px-3 py-1 rounded-full glass-panel border border-cyan-500/20 flex items-center gap-2 pointer-events-none text-[11px] text-cyan-300 font-mono tracking-wider shadow-lg">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" />
|
||||||
|
<span>CORE ARTIFACT // AG-01 ACTIVE</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,385 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useRef, useMemo, useState } from "react";
|
||||||
|
import { Canvas, useFrame } from "@react-three/fiber";
|
||||||
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
// 1. Central Star (Helios / Core Sun) with radiant corona
|
||||||
|
function CentralStar() {
|
||||||
|
const sunRef = useRef<THREE.Mesh>(null!);
|
||||||
|
const coronaRef = useRef<THREE.Mesh>(null!);
|
||||||
|
const flareRef = useRef<THREE.Mesh>(null!);
|
||||||
|
|
||||||
|
useFrame((state, delta) => {
|
||||||
|
const t = state.clock.getElapsedTime();
|
||||||
|
if (sunRef.current) {
|
||||||
|
sunRef.current.rotation.y += delta * 0.2;
|
||||||
|
const pulse = 1 + Math.sin(t * 2.5) * 0.03;
|
||||||
|
sunRef.current.scale.set(pulse, pulse, pulse);
|
||||||
|
}
|
||||||
|
if (coronaRef.current) {
|
||||||
|
coronaRef.current.rotation.y -= delta * 0.15;
|
||||||
|
coronaRef.current.rotation.z += delta * 0.08;
|
||||||
|
const pulse2 = 1.2 + Math.cos(t * 1.8) * 0.05;
|
||||||
|
coronaRef.current.scale.set(pulse2, pulse2, pulse2);
|
||||||
|
}
|
||||||
|
if (flareRef.current) {
|
||||||
|
flareRef.current.rotation.z -= delta * 0.2;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group>
|
||||||
|
{/* Intense Core Sun */}
|
||||||
|
<mesh ref={sunRef}>
|
||||||
|
<sphereGeometry args={[0.75, 32, 32]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color="#38bdf8"
|
||||||
|
emissive="#60a5fa"
|
||||||
|
emissiveIntensity={2.5}
|
||||||
|
roughness={0.2}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Atmospheric Corona Layer */}
|
||||||
|
<mesh ref={coronaRef}>
|
||||||
|
<sphereGeometry args={[0.95, 24, 24]} />
|
||||||
|
<meshBasicMaterial
|
||||||
|
color="#818cf8"
|
||||||
|
transparent
|
||||||
|
opacity={0.3}
|
||||||
|
wireframe
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Solar Flare Halo Disk */}
|
||||||
|
<mesh ref={flareRef} rotation={[Math.PI / 2, 0, 0]}>
|
||||||
|
<ringGeometry args={[0.9, 1.45, 32]} />
|
||||||
|
<meshBasicMaterial
|
||||||
|
color="#38bdf8"
|
||||||
|
transparent
|
||||||
|
opacity={0.2}
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Radiant Solar Lights */}
|
||||||
|
<pointLight color="#67e8f9" intensity={4} distance={20} decay={2} />
|
||||||
|
<pointLight color="#c084fc" intensity={2.5} distance={15} position={[0, 1, 0]} decay={2} />
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Orbital Path Ring (crisp, faint geometric trajectory)
|
||||||
|
function OrbitTrack({ radius, color = "#475569" }: { radius: number; color?: string }) {
|
||||||
|
const lineGeometry = useMemo(() => {
|
||||||
|
const points: THREE.Vector3[] = [];
|
||||||
|
const segments = 120;
|
||||||
|
for (let i = 0; i <= segments; i++) {
|
||||||
|
const theta = (i / segments) * Math.PI * 2;
|
||||||
|
points.push(new THREE.Vector3(Math.cos(theta) * radius, 0, Math.sin(theta) * radius));
|
||||||
|
}
|
||||||
|
return new THREE.BufferGeometry().setFromPoints(points);
|
||||||
|
}, [radius]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<primitive object={new THREE.Line(
|
||||||
|
lineGeometry,
|
||||||
|
new THREE.LineBasicMaterial({ color, transparent: true, opacity: 0.3 })
|
||||||
|
)} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Orbiting Planet with optional Rings & Moons
|
||||||
|
interface PlanetProps {
|
||||||
|
name: string;
|
||||||
|
radius: number;
|
||||||
|
size: number;
|
||||||
|
speed: number;
|
||||||
|
color: string;
|
||||||
|
emissive?: string;
|
||||||
|
hasRings?: boolean;
|
||||||
|
ringRadius?: [number, number];
|
||||||
|
ringColor?: string;
|
||||||
|
hasMoon?: boolean;
|
||||||
|
initialAngle?: number;
|
||||||
|
onHover?: (name: string | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Planet({
|
||||||
|
name,
|
||||||
|
radius,
|
||||||
|
size,
|
||||||
|
speed,
|
||||||
|
color,
|
||||||
|
emissive = "#000000",
|
||||||
|
hasRings = false,
|
||||||
|
ringRadius = [1.2, 1.8],
|
||||||
|
ringColor = "#c084fc",
|
||||||
|
hasMoon = false,
|
||||||
|
initialAngle = 0,
|
||||||
|
onHover,
|
||||||
|
}: PlanetProps) {
|
||||||
|
const planetGroupRef = useRef<THREE.Group>(null!);
|
||||||
|
const planetMeshRef = useRef<THREE.Mesh>(null!);
|
||||||
|
const moonGroupRef = useRef<THREE.Group>(null!);
|
||||||
|
const angleRef = useRef(initialAngle);
|
||||||
|
|
||||||
|
useFrame((_, delta) => {
|
||||||
|
angleRef.current += delta * speed * 0.35;
|
||||||
|
const x = Math.cos(angleRef.current) * radius;
|
||||||
|
const z = Math.sin(angleRef.current) * radius;
|
||||||
|
|
||||||
|
if (planetGroupRef.current) {
|
||||||
|
planetGroupRef.current.position.set(x, 0, z);
|
||||||
|
}
|
||||||
|
if (planetMeshRef.current) {
|
||||||
|
planetMeshRef.current.rotation.y += delta * 1.2;
|
||||||
|
}
|
||||||
|
if (moonGroupRef.current) {
|
||||||
|
moonGroupRef.current.rotation.y += delta * 2.5;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={planetGroupRef}>
|
||||||
|
{/* The Planet Sphere */}
|
||||||
|
<mesh
|
||||||
|
ref={planetMeshRef}
|
||||||
|
onPointerOver={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onHover?.(name);
|
||||||
|
}}
|
||||||
|
onPointerOut={() => onHover?.(null)}
|
||||||
|
>
|
||||||
|
<sphereGeometry args={[size, 24, 24]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color={color}
|
||||||
|
emissive={emissive}
|
||||||
|
emissiveIntensity={0.6}
|
||||||
|
roughness={0.3}
|
||||||
|
metalness={0.7}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Planetary Rings */}
|
||||||
|
{hasRings && (
|
||||||
|
<mesh rotation={[Math.PI / 2.5, 0.2, 0]}>
|
||||||
|
<ringGeometry args={[size * ringRadius[0], size * ringRadius[1], 48]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color={ringColor}
|
||||||
|
emissive={ringColor}
|
||||||
|
emissiveIntensity={0.5}
|
||||||
|
transparent
|
||||||
|
opacity={0.65}
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Orbiting Moon */}
|
||||||
|
{hasMoon && (
|
||||||
|
<group ref={moonGroupRef}>
|
||||||
|
<mesh position={[size * 1.9, 0.15, 0]}>
|
||||||
|
<sphereGeometry args={[size * 0.28, 16, 16]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color="#e2e8f0"
|
||||||
|
emissive="#94a3b8"
|
||||||
|
emissiveIntensity={0.4}
|
||||||
|
roughness={0.5}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Asteroid Belt (subtle, clean, not overcrowded)
|
||||||
|
function AsteroidBelt({ count = 180, innerR = 3.4, outerR = 4.2 }: { count?: number; innerR?: number; outerR?: number }) {
|
||||||
|
const beltRef = useRef<THREE.Points>(null!);
|
||||||
|
|
||||||
|
const [positions, colors] = useMemo(() => {
|
||||||
|
const pos = new Float32Array(count * 3);
|
||||||
|
const col = new Float32Array(count * 3);
|
||||||
|
const colorA = new THREE.Color("#94a3b8");
|
||||||
|
const colorB = new THREE.Color("#38bdf8");
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const r = innerR + Math.random() * (outerR - innerR);
|
||||||
|
const theta = Math.random() * Math.PI * 2;
|
||||||
|
const y = (Math.random() - 0.5) * 0.3;
|
||||||
|
|
||||||
|
pos[i * 3] = Math.cos(theta) * r;
|
||||||
|
pos[i * 3 + 1] = y;
|
||||||
|
pos[i * 3 + 2] = Math.sin(theta) * r;
|
||||||
|
|
||||||
|
const c = Math.random() > 0.4 ? colorA : colorB;
|
||||||
|
col[i * 3] = c.r;
|
||||||
|
col[i * 3 + 1] = c.g;
|
||||||
|
col[i * 3 + 2] = c.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [pos, col];
|
||||||
|
}, [count, innerR, outerR]);
|
||||||
|
|
||||||
|
useFrame((_, delta) => {
|
||||||
|
if (beltRef.current) {
|
||||||
|
beltRef.current.rotation.y += delta * 0.06;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<points ref={beltRef}>
|
||||||
|
<bufferGeometry>
|
||||||
|
<bufferAttribute attach="attributes-position" args={[positions, 3]} />
|
||||||
|
<bufferAttribute attach="attributes-color" args={[colors, 3]} />
|
||||||
|
</bufferGeometry>
|
||||||
|
<pointsMaterial
|
||||||
|
size={0.045}
|
||||||
|
vertexColors
|
||||||
|
transparent
|
||||||
|
opacity={0.55}
|
||||||
|
sizeAttenuation
|
||||||
|
/>
|
||||||
|
</points>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Composite Solar System Scene with smooth interactive tilt
|
||||||
|
function SolarSystemScene({ onHoverPlanet }: { onHoverPlanet: (name: string | null) => void }) {
|
||||||
|
const systemRef = useRef<THREE.Group>(null!);
|
||||||
|
|
||||||
|
useFrame((state) => {
|
||||||
|
if (!systemRef.current) return;
|
||||||
|
// Subtle interactive tilt following pointer
|
||||||
|
const targetRotX = 0.55 + (state.pointer.y * Math.PI) / 24;
|
||||||
|
const targetRotY = -0.25 + (state.pointer.x * Math.PI) / 20;
|
||||||
|
systemRef.current.rotation.x += (targetRotX - systemRef.current.rotation.x) * 0.04;
|
||||||
|
systemRef.current.rotation.y += (targetRotY - systemRef.current.rotation.y) * 0.04;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={systemRef} rotation={[0.55, -0.25, 0]}>
|
||||||
|
{/* Central Star */}
|
||||||
|
<CentralStar />
|
||||||
|
|
||||||
|
{/* Planetary Tracks — calibrated so outermost radius is 6.4 */}
|
||||||
|
<OrbitTrack radius={1.7} color="#38bdf8" />
|
||||||
|
<OrbitTrack radius={2.7} color="#818cf8" />
|
||||||
|
<OrbitTrack radius={5.0} color="#c084fc" />
|
||||||
|
<OrbitTrack radius={6.4} color="#34d399" />
|
||||||
|
|
||||||
|
{/* Asteroid Belt between Orbit II and Orbit III */}
|
||||||
|
<AsteroidBelt count={190} innerR={3.5} outerR={4.3} />
|
||||||
|
|
||||||
|
{/* Planet 1: Chronos (Molten amber rocky core) */}
|
||||||
|
<Planet
|
||||||
|
name="CHRONOS // ORBIT I (TIME ENGINE)"
|
||||||
|
radius={1.7}
|
||||||
|
size={0.16}
|
||||||
|
speed={1.5}
|
||||||
|
color="#fb923c"
|
||||||
|
emissive="#ea580c"
|
||||||
|
initialAngle={0.5}
|
||||||
|
onHover={onHoverPlanet}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Planet 2: Aetheria (Teal Gem with Moon) */}
|
||||||
|
<Planet
|
||||||
|
name="AETHERIA // ORBIT II (FLAGSHIP SECTOR)"
|
||||||
|
radius={2.7}
|
||||||
|
size={0.25}
|
||||||
|
speed={1.0}
|
||||||
|
color="#22d3ee"
|
||||||
|
emissive="#0891b2"
|
||||||
|
hasMoon={true}
|
||||||
|
initialAngle={2.2}
|
||||||
|
onHover={onHoverPlanet}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Planet 3: Nebula-IX (Ringed Gas Giant) */}
|
||||||
|
<Planet
|
||||||
|
name="NEBULA-IX // ORBIT III (CREATIVE GAS GIANT)"
|
||||||
|
radius={5.0}
|
||||||
|
size={0.42}
|
||||||
|
speed={0.55}
|
||||||
|
color="#a855f7"
|
||||||
|
emissive="#7e22ce"
|
||||||
|
hasRings={true}
|
||||||
|
ringRadius={[1.3, 1.9]}
|
||||||
|
ringColor="#c084fc"
|
||||||
|
hasMoon={true}
|
||||||
|
initialAngle={4.1}
|
||||||
|
onHover={onHoverPlanet}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Planet 4: Void-Zero (Outer Starlight World) */}
|
||||||
|
<Planet
|
||||||
|
name="VOID-ZERO // ORBIT IV (DEEP EXPERIMENTAL)"
|
||||||
|
radius={6.4}
|
||||||
|
size={0.3}
|
||||||
|
speed={0.35}
|
||||||
|
color="#34d399"
|
||||||
|
emissive="#059669"
|
||||||
|
hasMoon={true}
|
||||||
|
initialAngle={5.6}
|
||||||
|
onHover={onHoverPlanet}
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HeroSolarSystem() {
|
||||||
|
const [hoveredPlanet, setHoveredPlanet] = useState<string | null>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full aspect-square max-w-[540px] max-h-[540px] sm:max-h-[580px] flex items-center justify-center select-none mx-auto overflow-visible">
|
||||||
|
{/* Background soft ambient radial lighting */}
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||||
|
<div className="w-64 h-64 sm:w-80 sm:h-80 bg-cyan-500/10 rounded-full blur-[80px]" />
|
||||||
|
<div className="w-56 h-56 sm:w-72 sm:h-72 bg-purple-600/10 rounded-full blur-[80px]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
Camera position: [0, 11, 16] with fov: 42
|
||||||
|
At distance ~19.4, visible height is 15.6 units and width is >18 units.
|
||||||
|
Outermost orbit radius is 6.4 (diameter 12.8).
|
||||||
|
-> 100% Guaranteed ZERO CROPPING at all angles.
|
||||||
|
*/}
|
||||||
|
<Canvas
|
||||||
|
camera={{ position: [0, 11, 16], fov: 42 }}
|
||||||
|
dpr={[1, 1.5]}
|
||||||
|
gl={{ antialias: true, alpha: true }}
|
||||||
|
className="w-full h-full cursor-grab active:cursor-grabbing overflow-visible"
|
||||||
|
>
|
||||||
|
<ambientLight intensity={0.4} />
|
||||||
|
<directionalLight position={[10, 15, 10]} intensity={1.2} color="#ffffff" />
|
||||||
|
<directionalLight position={[-10, -10, -5]} intensity={0.6} color="#818cf8" />
|
||||||
|
<SolarSystemScene onHoverPlanet={setHoveredPlanet} />
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
{/* Interactive HUD Telemetry Card */}
|
||||||
|
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 sm:left-2 sm:translate-x-0 glass-panel px-3.5 py-1.5 rounded-xl border border-cyan-500/30 flex items-center gap-2.5 text-[11px] font-mono tracking-wider shadow-[0_0_20px_rgba(56,189,248,0.2)] pointer-events-none">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-cyan-400 animate-ping" />
|
||||||
|
<span className="text-cyan-300 font-bold">AGHATIS ORBITAL</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-slate-500">|</span>
|
||||||
|
<span className="text-slate-300">
|
||||||
|
{hoveredPlanet ? (
|
||||||
|
<span className="text-cyan-400 font-bold">{hoveredPlanet}</span>
|
||||||
|
) : (
|
||||||
|
"4 ACTIVE ORBITS • TILT TO EXPLORE"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Orbit Speed & System Badge */}
|
||||||
|
<div className="absolute top-2 right-2 hidden sm:flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-indigo-950/40 border border-indigo-500/30 text-[10px] font-mono text-indigo-300">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-indigo-400" />
|
||||||
|
<span>SIMULATION: 1.0X</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { Terminal, Gamepad2, Sparkles, Orbit, CheckCircle2, Compass } from "lucide-react";
|
||||||
|
|
||||||
|
export default function StudioCreed() {
|
||||||
|
const softwareFocus = [
|
||||||
|
{
|
||||||
|
title: "Local-First & High Performance",
|
||||||
|
desc: "We build native, low-latency tools compiled in Rust and WebGPU that respect cognitive flow and user data sovereignty.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Tactile Spatial Interfaces",
|
||||||
|
desc: "Re-imagining workstations and creative pipelines with fluid, cockpit-inspired spatial ergonomics.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Zero-Bloat Engineering",
|
||||||
|
desc: "Every binary is optimized for minimal memory footprint, instant boot times, and decades-long utility.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Tools for Deep Thinkers & Creators",
|
||||||
|
desc: "Software designed from the ground up for technical artists, developers, and focused thinkers.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const gamesFocus = [
|
||||||
|
{
|
||||||
|
title: "Atmospheric & Narrative Immersion",
|
||||||
|
desc: "Evocative interstellar worlds driven by rich speculative fiction, tactile acoustics, and memorable art direction.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Kinetic & Physics-First Mechanics",
|
||||||
|
desc: "Crafting gameplay rooted in Newtonian motion, orbital dynamics, and tactile interactive feedback.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Standalone, Complete Experiences",
|
||||||
|
desc: "Crafting finished artistic works that respect player time—no predatory loops, just pure interactive craft.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Original Audiovisual Synthesis",
|
||||||
|
desc: "Bespoke procedural audio engines and dynamic synthesizer soundtracks that react in real-time.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="creed" className="relative py-28 overflow-hidden">
|
||||||
|
{/* Background radial glow */}
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[700px] h-[500px] bg-indigo-600/10 rounded-full blur-[180px] pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
||||||
|
|
||||||
|
{/* Section Header */}
|
||||||
|
<div className="flex flex-col items-center text-center max-w-3xl mx-auto mb-20 space-y-4">
|
||||||
|
<div className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full glass-panel border border-cyan-500/30 text-xs font-mono text-cyan-300">
|
||||||
|
<Compass className="w-3.5 h-3.5 text-cyan-400" />
|
||||||
|
<span>STUDIO CREED & CRAFT PHILOSOPHY</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-black tracking-tight text-white">
|
||||||
|
Where Our Energy Goes: <br />
|
||||||
|
<span className="text-gradient-cyan">The Pursuit of Pure Craft.</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-base sm:text-lg text-slate-400 leading-relaxed pt-2">
|
||||||
|
Aghatis Studio exists to invent, polish, and publish original software products and immersive interactive worlds.
|
||||||
|
Our roadmap is driven entirely by craftsmanship, curiosity, and product excellence.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Side-by-side Dual Focus Arena */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 items-stretch">
|
||||||
|
|
||||||
|
{/* Column 1: Software & Tools Focus */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
className="glass-panel-glow p-8 sm:p-10 rounded-3xl relative overflow-hidden flex flex-col justify-between border-cyan-500/30 bg-gradient-to-b from-cyan-950/20 via-slate-950/60 to-black/80"
|
||||||
|
>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between pb-4 border-b border-white/10">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-xl bg-cyan-400/10 border border-cyan-400/30 flex items-center justify-center text-cyan-400">
|
||||||
|
<Terminal className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-bold font-mono text-white">
|
||||||
|
SOFTWARE & CREATIVE TOOLS
|
||||||
|
</h3>
|
||||||
|
<span className="text-[11px] font-mono text-cyan-300">
|
||||||
|
SYSTEMS, WORKSTATIONS & PIPELINES
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="w-2.5 h-2.5 rounded-full bg-cyan-400 shadow-[0_0_10px_#38bdf8]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{softwareFocus.map((item, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="p-4 rounded-2xl bg-white/5 border border-white/5 flex items-start gap-3.5 hover:border-cyan-400/20 transition-colors"
|
||||||
|
>
|
||||||
|
<CheckCircle2 className="w-4 h-4 text-cyan-400 shrink-0 mt-0.5" />
|
||||||
|
<div>
|
||||||
|
<h4 className="text-sm font-bold text-white font-mono">
|
||||||
|
{item.title}
|
||||||
|
</h4>
|
||||||
|
<p className="text-xs text-slate-300 leading-relaxed mt-1">
|
||||||
|
{item.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 pt-6 border-t border-white/10 text-xs font-mono text-cyan-300 flex items-center gap-2">
|
||||||
|
<Sparkles className="w-4 h-4 text-cyan-400" />
|
||||||
|
<span>Engineered to empower creators with speed, clarity, and precision.</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Column 2: Games & Interactive Media Focus */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.1 }}
|
||||||
|
className="glass-card p-8 sm:p-10 rounded-3xl relative overflow-hidden flex flex-col justify-between border-purple-500/30 bg-gradient-to-b from-purple-950/20 via-slate-950/60 to-black/80"
|
||||||
|
>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between pb-4 border-b border-white/10">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-xl bg-purple-500/10 border border-purple-500/30 flex items-center justify-center text-purple-400">
|
||||||
|
<Gamepad2 className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-bold font-mono text-white">
|
||||||
|
ATMOSPHERIC GAMES
|
||||||
|
</h3>
|
||||||
|
<span className="text-[11px] font-mono text-purple-300">
|
||||||
|
WORLDS, PHYSICS & NARRATIVE
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="w-2.5 h-2.5 rounded-full bg-purple-400 shadow-[0_0_10px_#a855f7]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{gamesFocus.map((item, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="p-4 rounded-2xl bg-white/5 border border-white/5 flex items-start gap-3.5 hover:border-purple-400/20 transition-colors"
|
||||||
|
>
|
||||||
|
<CheckCircle2 className="w-4 h-4 text-purple-400 shrink-0 mt-0.5" />
|
||||||
|
<div>
|
||||||
|
<h4 className="text-sm font-bold text-white font-mono">
|
||||||
|
{item.title}
|
||||||
|
</h4>
|
||||||
|
<p className="text-xs text-slate-300 leading-relaxed mt-1">
|
||||||
|
{item.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 pt-6 border-t border-white/10 text-xs font-mono text-purple-300 flex items-center gap-2">
|
||||||
|
<Orbit className="w-4 h-4 text-purple-400" />
|
||||||
|
<span>Handcrafted interactive universes built with artistic depth and soul.</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Central Creed Statement */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
className="mt-12 p-8 rounded-2xl glass-panel text-center max-w-4xl mx-auto border border-white/10 relative overflow-hidden"
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-cyan-500/5 to-transparent pointer-events-none" />
|
||||||
|
<p className="text-sm sm:text-base font-mono text-slate-200 tracking-wide leading-relaxed">
|
||||||
|
“Our commitment is simple: build products of enduring depth, craft experiences we are fiercely proud of, and explore the outer boundaries of software and interactive art.”
|
||||||
|
</p>
|
||||||
|
<div className="mt-4 text-xs font-mono text-cyan-400 uppercase tracking-widest">
|
||||||
|
— AGHATIS STUDIO CORE TENET
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { Layers, Terminal, Gamepad2, FlaskConical, ArrowUpRight, CheckCircle2 } from "lucide-react";
|
||||||
|
|
||||||
|
export default function CoreFocus() {
|
||||||
|
const [activeTrack, setActiveTrack] = useState<number>(0);
|
||||||
|
|
||||||
|
const tracks = [
|
||||||
|
{
|
||||||
|
id: "software",
|
||||||
|
number: "01",
|
||||||
|
title: "Software Products",
|
||||||
|
category: "CREATIVE & DEVELOPER TOOLS",
|
||||||
|
icon: Terminal,
|
||||||
|
tagline: "High-performance utilities engineered for creators, coders, and deep-focus thinkers.",
|
||||||
|
description:
|
||||||
|
"We build standalone software tools that respect user attention and privacy. Our software stack emphasizes local-first execution, instantaneous boot times, minimal memory footprints, and fluid spatial aesthetics.",
|
||||||
|
stack: ["Rust", "TypeScript", "WebGPU", "Local-First SQLite", "Tauri", "Custom GLSL"],
|
||||||
|
principles: [
|
||||||
|
"Zero telemetry bloat or intrusive subscription traps",
|
||||||
|
"Deterministic local-first file system workflows",
|
||||||
|
"Sub-16ms latency interface responsiveness",
|
||||||
|
"Minimalist spatial design inspired by cockpit HUDs",
|
||||||
|
],
|
||||||
|
currentInitiatives: [
|
||||||
|
{ name: "NebulaFlow", stage: "Public Alpha", note: "Real-time visual node graph for generative coders" },
|
||||||
|
{ name: "Voidwalker OS", stage: "Prototype", note: "Ultra-distraction-free spatial terminal" },
|
||||||
|
],
|
||||||
|
accentColor: "from-cyan-500 to-blue-600",
|
||||||
|
pillBorder: "border-cyan-500/30 text-cyan-400 bg-cyan-500/10",
|
||||||
|
glowColor: "rgba(56, 189, 248, 0.15)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "games",
|
||||||
|
number: "02",
|
||||||
|
title: "Original Games",
|
||||||
|
category: "ATMOSPHERIC & INDIE TITLES",
|
||||||
|
icon: Gamepad2,
|
||||||
|
tagline: "Evocative, mechanics-first game experiences set in interstellar and surreal realities.",
|
||||||
|
description:
|
||||||
|
"Our games emphasize tactile physics, memorable soundscapes, and evocative storytelling. We reject predatory monetization and live-service treadmills—focusing exclusively on handcrafted, complete experiences that respect the player's time.",
|
||||||
|
stack: ["Godot Engine", "Custom C++ GDExtension", "Bespoke Shaders", "FMOD / WebAudio", "Procreate / Blender"],
|
||||||
|
principles: [
|
||||||
|
"Uncompromising, single-purchase complete artistic vision",
|
||||||
|
"Kinetic, physics-grounded flight and orbital mechanics",
|
||||||
|
"Original dynamic synthesizer soundtracks and reactive foley",
|
||||||
|
"Rich speculative sci-fi lore and evocative environmental narrative",
|
||||||
|
],
|
||||||
|
currentInitiatives: [
|
||||||
|
{ name: "Aetheria: Chrono Drift", stage: "In Active Dev", note: "Tactical sci-fi flight RPG with time-dilation" },
|
||||||
|
{ name: "Singularity Pulse", stage: "Playable Slice", note: "Hypnotic orbital defense rhythm-strategy" },
|
||||||
|
],
|
||||||
|
accentColor: "from-purple-500 to-pink-600",
|
||||||
|
pillBorder: "border-purple-500/30 text-purple-400 bg-purple-500/10",
|
||||||
|
glowColor: "rgba(168, 85, 247, 0.15)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "lab",
|
||||||
|
number: "03",
|
||||||
|
title: "Experimental Lab",
|
||||||
|
category: "CREATIVE COMPUTING & R&D",
|
||||||
|
icon: FlaskConical,
|
||||||
|
tagline: "Unconstrained research in procedural systems, audio synthesis, and algorithmic art.",
|
||||||
|
description:
|
||||||
|
"The Aghatis Lab is our continuous skunkworks playground. Here we prototype procedural cosmos generators, WebAudio modular synths, chaotic attractor simulations, and emergent artificial life models that inform our core products.",
|
||||||
|
stack: ["Three.js / R3F", "GLSL Compute", "Web Audio API", "WebAssembly", "Generative Math"],
|
||||||
|
principles: [
|
||||||
|
"Rapid computational sketches without commercial pressure",
|
||||||
|
"Algorithmic exploration of celestial mathematics & fractals",
|
||||||
|
"Open prototypes released to the creative technology community",
|
||||||
|
"Direct R&D incubator for upcoming software & game mechanics",
|
||||||
|
],
|
||||||
|
currentInitiatives: [
|
||||||
|
{ name: "ChromaSynth", stage: "Lab Sandbox", note: "Audio-reactive procedural geometry synthesizer" },
|
||||||
|
{ name: "Project Event Horizon", stage: "R&D Prototype", note: "Planetary terrain generation via compute shaders" },
|
||||||
|
],
|
||||||
|
accentColor: "from-emerald-500 to-cyan-500",
|
||||||
|
pillBorder: "border-emerald-500/30 text-emerald-400 bg-emerald-500/10",
|
||||||
|
glowColor: "rgba(16, 185, 129, 0.15)",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const current = tracks[activeTrack];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="focus" className="relative py-28 overflow-hidden">
|
||||||
|
{/* Background ambient lighting */}
|
||||||
|
<div className="absolute top-1/3 right-0 w-[500px] h-[500px] bg-purple-600/10 rounded-full blur-[160px] pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
||||||
|
|
||||||
|
{/* Section Header */}
|
||||||
|
<div className="flex flex-col items-center text-center max-w-3xl mx-auto mb-16 space-y-4">
|
||||||
|
<div className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full glass-panel border border-cyan-500/30 text-xs font-mono text-cyan-300">
|
||||||
|
<Layers className="w-3.5 h-3.5 text-cyan-400" />
|
||||||
|
<span>OPERATIONAL TRACKS</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-black tracking-tight text-white">
|
||||||
|
Three Dedicated Arenas of <br />
|
||||||
|
<span className="text-gradient-cyan">In-House Creation.</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-base sm:text-lg text-slate-400 leading-relaxed pt-2">
|
||||||
|
Every project inside Aghatis Studio originates from one of three symbiotic disciplines,
|
||||||
|
channeling our entire focus into original software, atmospheric games, and computational experiments.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Track Selector Tabs */}
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-3 mb-12">
|
||||||
|
{tracks.map((track, idx) => {
|
||||||
|
const Icon = track.icon;
|
||||||
|
const isSelected = activeTrack === idx;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={track.id}
|
||||||
|
onClick={() => setActiveTrack(idx)}
|
||||||
|
className={`flex items-center gap-3 px-5 py-3 rounded-2xl font-mono text-xs uppercase tracking-wider transition-all duration-300 cursor-pointer ${
|
||||||
|
isSelected
|
||||||
|
? "glass-panel-glow border-cyan-400/50 text-white shadow-[0_0_25px_rgba(56,189,248,0.25)] scale-[1.02]"
|
||||||
|
: "glass-card text-slate-400 hover:text-slate-200 hover:border-white/20"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="text-slate-400 font-bold">{track.number}</span>
|
||||||
|
<Icon className={`w-4 h-4 ${isSelected ? "text-cyan-400" : "text-slate-400"}`} />
|
||||||
|
<span className="font-semibold">{track.title}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Active Track Deep Dive Display */}
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.div
|
||||||
|
key={current.id}
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -15 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
className="glass-panel-glow p-8 sm:p-12 rounded-3xl relative overflow-hidden border border-white/10"
|
||||||
|
style={{
|
||||||
|
boxShadow: `0 0 60px -15px ${current.glowColor}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Ambient accent top bar */}
|
||||||
|
<div className={`absolute top-0 left-0 right-0 h-1.5 bg-gradient-to-r ${current.accentColor}`} />
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-10">
|
||||||
|
|
||||||
|
{/* Left Column: Track Philosophy & Overview */}
|
||||||
|
<div className="lg:col-span-7 space-y-6">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className={`px-2.5 py-1 rounded-md text-[11px] font-mono tracking-widest uppercase border ${current.pillBorder}`}>
|
||||||
|
TRACK {current.number} // {current.category}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-2xl sm:text-3xl lg:text-4xl font-extrabold text-white leading-tight">
|
||||||
|
{current.tagline}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="text-slate-300 text-sm sm:text-base leading-relaxed">
|
||||||
|
{current.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Principles checklist */}
|
||||||
|
<div className="space-y-3 pt-4">
|
||||||
|
<div className="text-xs font-mono uppercase tracking-wider text-slate-400">
|
||||||
|
// CORE ARCHITECTURAL COMMITMENTS
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
{current.principles.map((principle, i) => (
|
||||||
|
<div key={i} className="flex items-start gap-2.5 text-xs text-slate-300">
|
||||||
|
<CheckCircle2 className="w-4 h-4 text-cyan-400 shrink-0 mt-0.5" />
|
||||||
|
<span>{principle}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stack Badges */}
|
||||||
|
<div className="pt-4">
|
||||||
|
<div className="text-xs font-mono uppercase tracking-wider text-slate-400 mb-2.5">
|
||||||
|
// TECHNICAL ENGINE & TOOLCHAIN
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{current.stack.map((tech) => (
|
||||||
|
<span
|
||||||
|
key={tech}
|
||||||
|
className="px-3 py-1 rounded-lg text-xs font-mono bg-white/5 border border-white/10 text-slate-300 hover:border-cyan-400/40 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
{tech}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column: Active In-Forge Projects in this track */}
|
||||||
|
<div className="lg:col-span-5 flex flex-col justify-between space-y-6 bg-black/40 p-6 sm:p-8 rounded-2xl border border-white/5">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between pb-4 border-b border-white/10">
|
||||||
|
<span className="text-xs font-mono uppercase tracking-wider text-slate-300 font-semibold">
|
||||||
|
IN THE FORGE (CURRENT IP)
|
||||||
|
</span>
|
||||||
|
<span className="w-2 h-2 rounded-full bg-cyan-400 animate-ping" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4 mt-6">
|
||||||
|
{current.currentInitiatives.map((item, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="glass-card p-4 rounded-xl border-white/5 hover:border-cyan-500/30 group"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between mb-1.5">
|
||||||
|
<h4 className="text-sm font-bold text-white font-mono group-hover:text-cyan-300 transition-colors">
|
||||||
|
{item.name}
|
||||||
|
</h4>
|
||||||
|
<span className="text-[10px] font-mono uppercase px-2 py-0.5 rounded bg-indigo-500/20 text-indigo-300 border border-indigo-400/30">
|
||||||
|
{item.stage}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-400">
|
||||||
|
{item.note}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Direct quick jump CTA */}
|
||||||
|
<a
|
||||||
|
href="#portfolio"
|
||||||
|
className="w-full flex items-center justify-center gap-2 py-3 px-4 rounded-xl font-mono text-xs uppercase tracking-wider font-semibold text-white glass-panel hover:border-cyan-400/50 hover:bg-white/10 transition-all duration-200 group"
|
||||||
|
>
|
||||||
|
<span>View All Works in Showcase</span>
|
||||||
|
<ArrowUpRight className="w-4 h-4 text-cyan-400 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Sparkles,
|
||||||
|
ArrowRight,
|
||||||
|
Check,
|
||||||
|
Radio,
|
||||||
|
Globe,
|
||||||
|
Shield,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [subscribed, setSubscribed] = useState(false);
|
||||||
|
|
||||||
|
const handleSubscribe = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (email.trim().length > 3) {
|
||||||
|
setSubscribed(true);
|
||||||
|
setEmail("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const socialLinks = [
|
||||||
|
{
|
||||||
|
name: "GitHub",
|
||||||
|
href: "https://github.com",
|
||||||
|
svg: (
|
||||||
|
<svg className="w-4 h-4 fill-current" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "X (Twitter)",
|
||||||
|
href: "https://x.com",
|
||||||
|
svg: (
|
||||||
|
<svg className="w-4 h-4 fill-current" viewBox="0 0 24 24">
|
||||||
|
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Discord",
|
||||||
|
href: "https://discord.com",
|
||||||
|
svg: (
|
||||||
|
<svg className="w-4 h-4 fill-current" viewBox="0 0 24 24">
|
||||||
|
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994.021-.041.001-.09-.041-.106a13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.929 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.894.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.028zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Steam",
|
||||||
|
href: "https://store.steampowered.com",
|
||||||
|
svg: (
|
||||||
|
<svg className="w-4 h-4 fill-current" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 2a10 10 0 0 0-10 10c0 4.767 3.339 8.75 7.828 9.742l3.418-4.992a3.02 3.02 0 0 1-.246-.145l-4.524 1.86a2.022 2.022 0 0 1-2.476-2.476l1.86-4.524a3 3 0 0 1 4.14-4.14l4.524-1.86a2.02 2.02 0 0 1 2.476 2.476l-1.86 4.524c.063.078.113.162.15.251l4.992-3.418A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "itch.io",
|
||||||
|
href: "https://itch.io",
|
||||||
|
svg: (
|
||||||
|
<svg className="w-4 h-4 fill-current" viewBox="0 0 24 24">
|
||||||
|
<path d="M2.5 5.5v2.2c0 .9.7 1.8 1.6 1.8.9 0 1.6-.9 1.6-1.8V5.5H2.5zm4.8 0v2.2c0 .9.7 1.8 1.6 1.8.9 0 1.6-.9 1.6-1.8V5.5H7.3zm4.8 0v2.2c0 .9.7 1.8 1.6 1.8.9 0 1.6-.9 1.6-1.8V5.5h-3.2zm4.8 0v2.2c0 .9.7 1.8 1.6 1.8.9 0 1.6-.9 1.6-1.8V5.5h-3.2zm4.8 0v2.2c0 .9.7 1.8 1.6 1.8.9 0 1.6-.9 1.6-1.8V5.5h-3.2zM2 11.5c0 1.7 1.1 3.2 2.7 3.5v5.5c0 .8.7 1.5 1.5 1.5h11.6c.8 0 1.5-.7 1.5-1.5V15c1.6-.3 2.7-1.8 2.7-3.5H2zm7 3h6v2H9v-2z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className="relative border-t border-white/10 bg-[#08080f] pt-20 pb-12 overflow-hidden">
|
||||||
|
{/* Background ambient lighting */}
|
||||||
|
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-[600px] h-[300px] bg-indigo-500/10 rounded-full blur-[140px] pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
||||||
|
|
||||||
|
{/* Top Section: Newsletter Transmissions */}
|
||||||
|
<div className="glass-panel p-8 sm:p-12 rounded-3xl border border-white/10 mb-16 relative overflow-hidden">
|
||||||
|
<div className="absolute top-0 right-0 w-80 h-80 bg-cyan-500/10 rounded-full blur-[100px] pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 items-center">
|
||||||
|
<div className="lg:col-span-7 space-y-3">
|
||||||
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-cyan-500/10 border border-cyan-400/20 text-[11px] font-mono text-cyan-300">
|
||||||
|
<Radio className="w-3 h-3 text-cyan-400 animate-pulse" />
|
||||||
|
<span>FREQUENCY DISPATCH // DIRECT FEED</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl sm:text-3xl font-extrabold text-white font-mono">
|
||||||
|
Join the Aghatis Orbital Transmissions
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-slate-400 max-w-xl">
|
||||||
|
Receive confidential dispatches on original software alpha testing, closed game playtests, and deep-dive engineering devlogs. Zero marketing spam.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-5">
|
||||||
|
{subscribed ? (
|
||||||
|
<div className="p-4 rounded-2xl bg-cyan-950/40 border border-cyan-400/30 flex items-center gap-3 text-xs font-mono text-cyan-300">
|
||||||
|
<Check className="w-5 h-5 text-cyan-400" />
|
||||||
|
<span>SIGNAL LOCKED. You are registered for upcoming playtests.</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={handleSubscribe} className="flex flex-col sm:flex-row gap-3">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
placeholder="[email protected]"
|
||||||
|
required
|
||||||
|
className="flex-1 px-4 py-3 rounded-xl bg-white/5 border border-white/15 text-white placeholder-slate-500 text-xs font-mono focus:outline-none focus:border-cyan-400 transition-colors"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="px-6 py-3 rounded-xl bg-gradient-to-r from-cyan-400 to-indigo-400 text-slate-950 font-mono text-xs uppercase tracking-wider font-bold hover:opacity-90 transition-opacity flex items-center justify-center gap-2 shrink-0 cursor-pointer shadow-lg"
|
||||||
|
>
|
||||||
|
<span>Tune In</span>
|
||||||
|
<ArrowRight className="w-3.5 h-3.5 text-slate-950" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Middle Section: Studio Details & Links */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-10 pb-16 border-b border-white/10">
|
||||||
|
|
||||||
|
{/* Col 1: Studio Brand */}
|
||||||
|
<div className="lg:col-span-5 space-y-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 h-8 rounded-lg bg-gradient-to-tr from-cyan-400 to-indigo-500 flex items-center justify-center text-slate-950 font-black text-xs">
|
||||||
|
AS
|
||||||
|
</div>
|
||||||
|
<span className="font-mono font-bold tracking-widest text-lg text-white">
|
||||||
|
AGHATIS STUDIO
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-400 leading-relaxed max-w-sm">
|
||||||
|
An independent product laboratory crafting original software, atmospheric games,
|
||||||
|
and experimental systems. Dedicated exclusively to our own in-house creative universe.
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-2 text-xs font-mono text-cyan-400 pt-2">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-cyan-400 animate-ping" />
|
||||||
|
<span>ORBITAL BEACON: ACTIVE // SECTOR 01</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Col 2: Navigation */}
|
||||||
|
<div className="lg:col-span-2 space-y-3">
|
||||||
|
<h4 className="text-xs font-mono uppercase tracking-wider text-white font-bold">
|
||||||
|
NAVIGATION
|
||||||
|
</h4>
|
||||||
|
<ul className="space-y-2 text-xs font-mono text-slate-400">
|
||||||
|
<li>
|
||||||
|
<a href="#about" className="hover:text-cyan-300 transition-colors">
|
||||||
|
Studio Narrative
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#focus" className="hover:text-cyan-300 transition-colors">
|
||||||
|
Core Tracks
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#portfolio" className="hover:text-cyan-300 transition-colors">
|
||||||
|
Product Fleet
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#creed" className="hover:text-cyan-300 transition-colors">
|
||||||
|
Sovereign Creed
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/admin" className="text-cyan-400 hover:text-cyan-300 transition-colors flex items-center gap-1">
|
||||||
|
<span>Admin Cockpit</span>
|
||||||
|
<span className="text-[9px] px-1 rounded bg-cyan-500/20">DB</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Col 3: Product Tracks */}
|
||||||
|
<div className="lg:col-span-2 space-y-3">
|
||||||
|
<h4 className="text-xs font-mono uppercase tracking-wider text-white font-bold">
|
||||||
|
PORTFOLIO
|
||||||
|
</h4>
|
||||||
|
<ul className="space-y-2 text-xs font-mono text-slate-400">
|
||||||
|
<li>
|
||||||
|
<a href="#portfolio" className="hover:text-cyan-300 transition-colors">
|
||||||
|
Aetheria: Chrono Drift
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#portfolio" className="hover:text-cyan-300 transition-colors">
|
||||||
|
NebulaFlow
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#portfolio" className="hover:text-cyan-300 transition-colors">
|
||||||
|
Voidwalker OS
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#portfolio" className="hover:text-cyan-300 transition-colors">
|
||||||
|
ChromaSynth Lab
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Col 4: Transmissions & Socials */}
|
||||||
|
<div className="lg:col-span-3 space-y-3">
|
||||||
|
<h4 className="text-xs font-mono uppercase tracking-wider text-white font-bold">
|
||||||
|
CHANNELS
|
||||||
|
</h4>
|
||||||
|
<div className="flex flex-wrap gap-2 pt-1">
|
||||||
|
{socialLinks.map((link) => (
|
||||||
|
<a
|
||||||
|
key={link.name}
|
||||||
|
href={link.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="p-2.5 rounded-xl glass-card text-slate-400 hover:text-white hover:border-cyan-400/50 hover:bg-white/5 transition-all"
|
||||||
|
aria-label={link.name}
|
||||||
|
>
|
||||||
|
{link.svg}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="text-[11px] font-mono text-slate-400 pt-2">
|
||||||
|
PGP Encrypted Public Key: 0x8F4E...91A
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Bar & Implicit In-House Studio Disclaimer */}
|
||||||
|
<div className="pt-8 flex flex-col md:flex-row items-center justify-between gap-6">
|
||||||
|
|
||||||
|
{/* Studio Focus Charter */}
|
||||||
|
<div className="flex items-start sm:items-center gap-3 text-xs font-mono text-slate-400 max-w-2xl">
|
||||||
|
<Shield className="w-4 h-4 text-cyan-400 shrink-0 mt-0.5 sm:mt-0" />
|
||||||
|
<p>
|
||||||
|
<strong className="text-slate-300">STUDIO CHARTER:</strong> An independent product laboratory dedicated to engineering high-performance creative software and publishing atmospheric, narrative-driven games.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-xs font-mono text-slate-400 shrink-0">
|
||||||
|
© 2026 Aghatis Studio. All rights reserved.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { ArrowRight, Terminal, Sparkles, Orbit } from "lucide-react";
|
||||||
|
|
||||||
|
// Dynamically import 3D Hero Solar System with sleek animated fallback
|
||||||
|
const HeroSolarSystem = dynamic(
|
||||||
|
() => import("../canvas/HeroSolarSystem"),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => (
|
||||||
|
<div className="relative w-full h-[400px] sm:h-[480px] lg:h-[540px] flex items-center justify-center">
|
||||||
|
{/* Animated CSS Solar System Loading State */}
|
||||||
|
<div className="relative w-72 h-72 sm:w-96 sm:h-96 flex items-center justify-center">
|
||||||
|
{/* Central Sun Glow */}
|
||||||
|
<div className="w-16 h-16 rounded-full bg-gradient-to-tr from-cyan-400 to-indigo-500 shadow-[0_0_50px_#38bdf8] animate-pulse" />
|
||||||
|
|
||||||
|
{/* Orbit Track 1 */}
|
||||||
|
<div className="absolute w-36 h-36 rounded-full border border-cyan-500/30 animate-[spin_6s_linear_infinite]">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-amber-400 shadow-[0_0_10px_#fbbf24] -translate-x-1.5" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Orbit Track 2 */}
|
||||||
|
<div className="absolute w-56 h-56 rounded-full border border-indigo-500/30 animate-[spin_10s_linear_infinite]">
|
||||||
|
<div className="w-4 h-4 rounded-full bg-cyan-400 shadow-[0_0_12px_#38bdf8] -translate-x-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Orbit Track 3 */}
|
||||||
|
<div className="absolute w-80 h-80 rounded-full border border-purple-500/25 animate-[spin_16s_linear_infinite]">
|
||||||
|
<div className="w-5 h-5 rounded-full bg-purple-400 shadow-[0_0_15px_#c084fc] -translate-x-2.5" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute bottom-4 px-3 py-1 rounded-full glass-panel text-[11px] font-mono text-cyan-300">
|
||||||
|
CONNECTING STELLAR TELEMETRY...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function Hero() {
|
||||||
|
const telemetryStats = [
|
||||||
|
{ label: "IP Ownership", value: "100%", subtext: "In-House Creations" },
|
||||||
|
{ label: "Product Focus", value: "Pure IP", subtext: "Dedicated In-House Pipeline" },
|
||||||
|
{ label: "Active Tracks", value: "3", subtext: "Software, Games, Lab" },
|
||||||
|
{ label: "Studio State", value: "Autonomous", subtext: "Self-Sovereign Roadmap" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="relative min-h-[92vh] pt-28 sm:pt-32 pb-20 flex flex-col justify-center overflow-hidden">
|
||||||
|
{/* Decorative cosmic glow halos */}
|
||||||
|
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[400px] bg-gradient-to-tr from-indigo-500/10 via-purple-500/10 to-cyan-500/10 blur-[130px] pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 w-full">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-8 items-start">
|
||||||
|
|
||||||
|
{/* Left Column: Brand Statement & Typography */}
|
||||||
|
<div className="lg:col-span-6 space-y-6 text-center lg:text-left">
|
||||||
|
|
||||||
|
{/* Main Headline */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.7 }}
|
||||||
|
className="space-y-3"
|
||||||
|
>
|
||||||
|
<h1 className="text-4xl sm:text-5xl lg:text-6xl xl:text-7xl font-black tracking-tight leading-[1.08]">
|
||||||
|
Synthesizing <br />
|
||||||
|
<span className="text-gradient-cyan">Original Software</span> <br />
|
||||||
|
<span className="text-white">& Interactive Worlds.</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-base sm:text-lg lg:text-xl text-slate-400 font-normal max-w-2xl mx-auto lg:mx-0 leading-relaxed pt-2">
|
||||||
|
Aghatis Studio operates exclusively as an autonomous product laboratory.
|
||||||
|
We engineer bespoke software tools, forge atmospheric indie games,
|
||||||
|
and explore experimental computational frontiers with relentless dedication to craft.
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Core Capability Chips */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.7, delay: 0.15 }}
|
||||||
|
className="flex flex-wrap items-center justify-center lg:justify-start gap-2 pt-1"
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
"GPU Shaders & WebGPU",
|
||||||
|
"Kinetic Orbital Physics",
|
||||||
|
"Local-First Architectures",
|
||||||
|
"Procedural Audio Engines",
|
||||||
|
].map((chip) => (
|
||||||
|
<span
|
||||||
|
key={chip}
|
||||||
|
className="px-2.5 py-1 rounded-md text-[11px] font-mono bg-white/5 border border-white/10 text-slate-300 hover:border-cyan-400/40 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
{chip}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Call to Actions */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.7, delay: 0.2 }}
|
||||||
|
className="flex flex-col sm:flex-row items-center justify-center lg:justify-start gap-4 pt-2"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#portfolio"
|
||||||
|
className="w-full sm:w-auto px-7 py-3.5 rounded-xl font-mono text-xs uppercase tracking-widest font-bold text-slate-950 bg-gradient-to-r from-cyan-400 via-sky-300 to-indigo-300 hover:opacity-95 shadow-[0_0_30px_rgba(56,189,248,0.4)] transition-all duration-300 flex items-center justify-center gap-2 group hover:scale-[1.02] active:scale-95"
|
||||||
|
>
|
||||||
|
<Sparkles className="w-4 h-4 text-slate-950" />
|
||||||
|
<span>Explore Fleet</span>
|
||||||
|
<ArrowRight className="w-4 h-4 text-slate-950 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="#creed"
|
||||||
|
className="w-full sm:w-auto px-6 py-3.5 rounded-xl font-mono text-xs uppercase tracking-widest font-semibold text-slate-300 hover:text-white glass-panel hover:border-indigo-500/50 transition-all duration-300 flex items-center justify-center gap-2 group"
|
||||||
|
>
|
||||||
|
<Terminal className="w-4 h-4 text-indigo-400" />
|
||||||
|
<span>The Studio Creed</span>
|
||||||
|
</a>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Studio Focus Badge */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.8, delay: 0.3 }}
|
||||||
|
className="pt-2 text-xs font-mono text-slate-400 flex items-center justify-center lg:justify-start gap-2"
|
||||||
|
>
|
||||||
|
<Orbit className="w-3.5 h-3.5 text-indigo-400" />
|
||||||
|
<span>Dedicated strictly to researching, building, and publishing our own product ecosystem.</span>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column: 3D Solar System Visualization (Full Frustum Uncropped, Raised to Align with Headline) */}
|
||||||
|
<div className="lg:col-span-6 relative flex items-center justify-center lg:-mt-10 xl:-mt-14">
|
||||||
|
<HeroSolarSystem />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Telemetry Stats Bar */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.8, delay: 0.4 }}
|
||||||
|
className="mt-16 sm:mt-20 pt-8 border-t border-white/10"
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 sm:gap-8">
|
||||||
|
{telemetryStats.map((stat, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="glass-card p-5 rounded-2xl relative overflow-hidden group hover:border-cyan-500/30"
|
||||||
|
>
|
||||||
|
<div className="absolute top-0 left-0 w-12 h-1 bg-gradient-to-r from-cyan-400 to-indigo-500 group-hover:w-full transition-all duration-500" />
|
||||||
|
<div className="text-2xl sm:text-3xl font-black font-mono tracking-tight text-white group-hover:text-cyan-300 transition-colors">
|
||||||
|
{stat.value}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs font-mono uppercase tracking-wider text-slate-300 mt-1">
|
||||||
|
{stat.label}
|
||||||
|
</div>
|
||||||
|
<div className="text-[11px] font-mono text-slate-400 mt-1">
|
||||||
|
{stat.subtext}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { Sparkles, Menu, X, Compass, Layers, ShieldCheck, Box } from "lucide-react";
|
||||||
|
import CosmicAudioToggle from "../ui/CosmicAudioToggle";
|
||||||
|
|
||||||
|
export default function Navbar() {
|
||||||
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
setScrolled(window.scrollY > 20);
|
||||||
|
};
|
||||||
|
window.addEventListener("scroll", handleScroll);
|
||||||
|
return () => window.removeEventListener("scroll", handleScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const navLinks = [
|
||||||
|
{ name: "About", href: "#about", icon: Compass },
|
||||||
|
{ name: "Core Tracks", href: "#focus", icon: Layers },
|
||||||
|
{ name: "Showcase", href: "#portfolio", icon: Box },
|
||||||
|
{ name: "Studio Creed", href: "#creed", icon: ShieldCheck },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
|
||||||
|
scrolled
|
||||||
|
? "py-3 bg-[#0a0a12]/80 backdrop-blur-xl border-b border-white/10 shadow-[0_4px_30px_rgba(0,0,0,0.5)]"
|
||||||
|
: "py-5 bg-transparent"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
{/* Brand Logo */}
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="flex items-center gap-3 group focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400 rounded-lg p-1"
|
||||||
|
>
|
||||||
|
<div className="relative flex items-center justify-center w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500/20 via-purple-500/10 to-cyan-500/20 border border-white/15 group-hover:border-cyan-400/50 transition-colors shadow-inner">
|
||||||
|
<span className="w-2.5 h-2.5 rounded-full bg-cyan-400 group-hover:scale-125 transition-transform shadow-[0_0_12px_#38bdf8]" />
|
||||||
|
<div className="absolute inset-0 rounded-xl border border-indigo-500/30 group-hover:rotate-45 transition-transform duration-500 pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-extrabold tracking-widest text-lg text-white group-hover:text-cyan-200 transition-colors">
|
||||||
|
AGHATIS
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] font-mono tracking-widest uppercase px-1.5 py-0.5 rounded bg-indigo-500/15 border border-indigo-400/20 text-indigo-300">
|
||||||
|
STUDIO
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] font-mono text-slate-400 tracking-wider">
|
||||||
|
ORIGINAL IP LAB
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* Desktop Navigation Links */}
|
||||||
|
<nav className="hidden md:flex items-center gap-1 glass-panel px-4 py-1.5 rounded-full border border-white/10">
|
||||||
|
{navLinks.map((link) => (
|
||||||
|
<a
|
||||||
|
key={link.name}
|
||||||
|
href={link.href}
|
||||||
|
className="px-3.5 py-1.5 text-xs font-mono uppercase tracking-wider text-slate-300 hover:text-white rounded-full hover:bg-white/5 transition-all duration-200"
|
||||||
|
>
|
||||||
|
{link.name}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Right actions: Audio Toggle, Live Status & CTA */}
|
||||||
|
<div className="hidden sm:flex items-center gap-3">
|
||||||
|
<CosmicAudioToggle />
|
||||||
|
|
||||||
|
{/* In-house status pill */}
|
||||||
|
<div className="hidden lg:flex items-center gap-2 px-3 py-1.5 rounded-full bg-indigo-950/40 border border-indigo-500/20 text-[11px] font-mono text-indigo-200">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-indigo-400 animate-pulse" />
|
||||||
|
<span>100% PRODUCT LAB</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="#portfolio"
|
||||||
|
className="relative inline-flex items-center justify-center px-4 py-2 text-xs font-mono tracking-wider font-semibold uppercase text-slate-900 bg-gradient-to-r from-cyan-400 via-sky-300 to-indigo-300 rounded-lg shadow-[0_0_20px_rgba(56,189,248,0.35)] hover:shadow-[0_0_25px_rgba(56,189,248,0.6)] transition-all duration-300 hover:scale-[1.02] active:scale-95"
|
||||||
|
>
|
||||||
|
<Sparkles className="w-3.5 h-3.5 mr-1.5 text-slate-950" />
|
||||||
|
Explore IP
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Menu Button */}
|
||||||
|
<div className="flex sm:hidden items-center gap-2">
|
||||||
|
<CosmicAudioToggle />
|
||||||
|
<button
|
||||||
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
|
className="p-2 rounded-lg glass-panel text-slate-300 hover:text-white focus:outline-none"
|
||||||
|
aria-label="Toggle navigation menu"
|
||||||
|
>
|
||||||
|
{mobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Drawer Menu */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{mobileMenuOpen && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, height: 0 }}
|
||||||
|
animate={{ opacity: 1, height: "auto" }}
|
||||||
|
exit={{ opacity: 0, height: 0 }}
|
||||||
|
transition={{ duration: 0.25 }}
|
||||||
|
className="sm:hidden border-b border-white/10 bg-[#0a0a12]/95 backdrop-blur-2xl overflow-hidden"
|
||||||
|
>
|
||||||
|
<div className="px-6 py-6 space-y-4">
|
||||||
|
<div className="flex items-center justify-between pb-3 border-b border-white/10 text-xs font-mono text-slate-400">
|
||||||
|
<span>SECTOR // AGH-MOBILE-HUD</span>
|
||||||
|
<span className="text-cyan-400">STATUS: NOMINAL</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1">
|
||||||
|
{navLinks.map((link) => {
|
||||||
|
const Icon = link.icon;
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
key={link.name}
|
||||||
|
href={link.href}
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-mono text-slate-300 hover:text-white hover:bg-white/5 transition-colors"
|
||||||
|
>
|
||||||
|
<Icon className="w-4 h-4 text-cyan-400" />
|
||||||
|
<span>{link.name}</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<a
|
||||||
|
href="#portfolio"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
className="w-full flex items-center justify-center gap-2 px-4 py-3 text-xs font-mono uppercase tracking-wider font-semibold text-slate-950 bg-gradient-to-r from-cyan-400 to-indigo-400 rounded-lg shadow-lg"
|
||||||
|
>
|
||||||
|
<Sparkles className="w-4 h-4 text-slate-950" />
|
||||||
|
Explore Original IP
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-[11px] font-mono text-center text-slate-400 pt-2 border-t border-white/5">
|
||||||
|
Independent Product Studio • Original Software & Games
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,501 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import {
|
||||||
|
Sparkles,
|
||||||
|
ArrowUpRight,
|
||||||
|
Gamepad2,
|
||||||
|
Terminal,
|
||||||
|
FlaskConical,
|
||||||
|
X,
|
||||||
|
ExternalLink,
|
||||||
|
Cpu,
|
||||||
|
Layers,
|
||||||
|
Calendar,
|
||||||
|
ShieldCheck,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
interface Product {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
category: "software" | "games" | "lab";
|
||||||
|
categoryLabel: string;
|
||||||
|
tagline: string;
|
||||||
|
status: string;
|
||||||
|
statusType: "active" | "alpha" | "prototype" | "upcoming";
|
||||||
|
releaseTarget: string;
|
||||||
|
description: string;
|
||||||
|
longDescription: string;
|
||||||
|
features: string[];
|
||||||
|
stack: string[];
|
||||||
|
accentGradient: string;
|
||||||
|
borderGlow: string;
|
||||||
|
icon: typeof Gamepad2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Portfolio() {
|
||||||
|
const [filter, setFilter] = useState<string>("all");
|
||||||
|
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
|
||||||
|
const [productsList, setProductsList] = useState<Product[]>([]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setProductsList(initialProducts);
|
||||||
|
async function fetchFromDB() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/projects");
|
||||||
|
if (res.ok) {
|
||||||
|
const json = await res.json();
|
||||||
|
if (json.success && Array.isArray(json.data) && json.data.length > 0) {
|
||||||
|
interface ApiProject {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
category: "software" | "games" | "lab";
|
||||||
|
categoryLabel: string;
|
||||||
|
tagline: string;
|
||||||
|
status: string;
|
||||||
|
statusType: "active" | "alpha" | "prototype" | "upcoming";
|
||||||
|
releaseTarget: string;
|
||||||
|
description: string;
|
||||||
|
longDescription: string;
|
||||||
|
features: string[];
|
||||||
|
stack: string[];
|
||||||
|
accentGradient: string;
|
||||||
|
borderGlow: string;
|
||||||
|
iconName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapped: Product[] = json.data.map((p: ApiProject) => ({
|
||||||
|
...p,
|
||||||
|
icon:
|
||||||
|
p.iconName === "Gamepad2"
|
||||||
|
? Gamepad2
|
||||||
|
: p.iconName === "FlaskConical"
|
||||||
|
? FlaskConical
|
||||||
|
: Terminal,
|
||||||
|
}));
|
||||||
|
setProductsList(mapped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Fallback silently to initial products
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchFromDB();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const initialProducts: Product[] = [
|
||||||
|
{
|
||||||
|
id: "aetheria",
|
||||||
|
title: "Aetheria: Chrono Drift",
|
||||||
|
category: "games",
|
||||||
|
categoryLabel: "Tactical Flight RPG",
|
||||||
|
tagline: "Atmospheric spaceflight RPG driven by Newtonian physics & relativistic time mechanics.",
|
||||||
|
status: "In Active Dev",
|
||||||
|
statusType: "active",
|
||||||
|
releaseTarget: "Alpha 2027",
|
||||||
|
description:
|
||||||
|
"Pilot handcrafted atmospheric ships through uncharted cosmic sectors. Engage in tactical orbital maneuvers where time dilates with engine velocity.",
|
||||||
|
longDescription:
|
||||||
|
"Aetheria is our flagship narrative sci-fi title. Designed without live-service grind or predatory mechanics, it centers on contemplative space exploration, deep Newtonian flight kinematics, and an original interactive dark-ambient synth score.",
|
||||||
|
features: [
|
||||||
|
"True orbital mechanics and gravitational slingshots",
|
||||||
|
"Non-linear stellar narrative with faction autonomy",
|
||||||
|
"Zero-compromise single-player standalone artistic experience",
|
||||||
|
"Procedurally synthesized nebula volumetric rendering",
|
||||||
|
],
|
||||||
|
stack: ["Godot 4.3", "C++ GDExtension", "Bespoke GLSL", "FMOD Sound Engine"],
|
||||||
|
accentGradient: "from-cyan-500/20 via-blue-600/10 to-transparent",
|
||||||
|
borderGlow: "hover:border-cyan-400/50 hover:shadow-[0_0_30px_rgba(56,189,248,0.25)]",
|
||||||
|
icon: Gamepad2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "nebulaflow",
|
||||||
|
title: "NebulaFlow",
|
||||||
|
category: "software",
|
||||||
|
categoryLabel: "Creative Shader Pipeline",
|
||||||
|
tagline: "Node-based real-time GPU shader workbench & procedural VFX engine.",
|
||||||
|
status: "Private Alpha",
|
||||||
|
statusType: "alpha",
|
||||||
|
releaseTarget: "Beta Q3 2027",
|
||||||
|
description:
|
||||||
|
"A high-performance visual coding environment for creative coders, game devs, and technical artists compiling directly to WGSL and GLSL.",
|
||||||
|
longDescription:
|
||||||
|
"NebulaFlow re-imagines real-time shader generation. Designed with a lightweight Rust core and a distraction-free spatial interface, it compiles visual nodes directly into native shaders at 120 FPS with instantaneous hot-reloading.",
|
||||||
|
features: [
|
||||||
|
"Instant compilation to WGSL, GLSL, and Metal Shading Language",
|
||||||
|
"Deterministic local-first project workspace without cloud locks",
|
||||||
|
"Live audio-reactive parameter modulation via WebAudio / ASIO",
|
||||||
|
"Integrated raymarching and compute shader node library",
|
||||||
|
],
|
||||||
|
stack: ["Rust", "WebGPU", "Tauri", "TypeScript", "WGSL"],
|
||||||
|
accentGradient: "from-purple-500/20 via-indigo-600/10 to-transparent",
|
||||||
|
borderGlow: "hover:border-purple-400/50 hover:shadow-[0_0_30px_rgba(168,85,247,0.25)]",
|
||||||
|
icon: Terminal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "voidwalker",
|
||||||
|
title: "Voidwalker OS",
|
||||||
|
category: "software",
|
||||||
|
categoryLabel: "Spatial Workstation",
|
||||||
|
tagline: "Distraction-free spatial terminal and local knowledge workstation for intense focus.",
|
||||||
|
status: "Prototype",
|
||||||
|
statusType: "prototype",
|
||||||
|
releaseTarget: "Q4 2027",
|
||||||
|
description:
|
||||||
|
"An experimental desktop environment stripping away notification clutter in favor of an ergonomic cosmic workspace and local neural indexing.",
|
||||||
|
longDescription:
|
||||||
|
"Voidwalker replaces chaotic desktop windows with an infinite 2.5D orbital plane. Crafted for independent developers and creative writers who demand absolute cognitive quiet.",
|
||||||
|
features: [
|
||||||
|
"Local-first vector indexing over personal markdown & codebases",
|
||||||
|
"Orbital workspace clustering with keyboard-centric navigation",
|
||||||
|
"Sub-10ms input latency with custom GPU text rendering engine",
|
||||||
|
"Zero tracking, zero analytics, zero telemetry overhead",
|
||||||
|
],
|
||||||
|
stack: ["Rust", "SQLite", "Vulkan / Metal", "Tree-Sitter"],
|
||||||
|
accentGradient: "from-indigo-500/20 via-sky-600/10 to-transparent",
|
||||||
|
borderGlow: "hover:border-indigo-400/50 hover:shadow-[0_0_30px_rgba(99,102,241,0.25)]",
|
||||||
|
icon: Terminal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "singularity-pulse",
|
||||||
|
title: "Singularity Pulse",
|
||||||
|
category: "games",
|
||||||
|
categoryLabel: "Kinetic Rhythm-Strategy",
|
||||||
|
tagline: "Orbital rhythm defense game where celestial gravitational waves dictate combat tempo.",
|
||||||
|
status: "Playable Slice",
|
||||||
|
statusType: "prototype",
|
||||||
|
releaseTarget: "2027",
|
||||||
|
description:
|
||||||
|
"Defend dying binary stars from quantum anomalies by aligning resonant frequencies in time with an escalating electronic pulse.",
|
||||||
|
longDescription:
|
||||||
|
"Built at the intersection of rhythm gaming and orbital physics, Singularity Pulse challenges players to synchronize defensive shields with gravitational wave oscillations in real-time.",
|
||||||
|
features: [
|
||||||
|
"Dynamic sound-reactive procedural enemy generation",
|
||||||
|
"Precise sub-millisecond input synchronization",
|
||||||
|
"Full support for analog sticks, arcade sticks, and MIDI input",
|
||||||
|
"Minimalist wireframe vector aesthetic inspired by 80s arcade radars",
|
||||||
|
],
|
||||||
|
stack: ["Godot", "WebAudio", "Procedural GLSL"],
|
||||||
|
accentGradient: "from-pink-500/20 via-rose-600/10 to-transparent",
|
||||||
|
borderGlow: "hover:border-pink-400/50 hover:shadow-[0_0_30px_rgba(244,63,94,0.25)]",
|
||||||
|
icon: Gamepad2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "chromasynth",
|
||||||
|
title: "ChromaSynth Lab",
|
||||||
|
category: "lab",
|
||||||
|
categoryLabel: "Spatial Sound Synthesis",
|
||||||
|
tagline: "WebGPU audio-reactive 3D synthesis tool exploring geometric resonance.",
|
||||||
|
status: "Public Experiment",
|
||||||
|
statusType: "alpha",
|
||||||
|
releaseTarget: "Live Now",
|
||||||
|
description:
|
||||||
|
"A browser-based generative instrument generating real-time higher-dimensional geometric manifolds modulated by live microphone and MIDI input.",
|
||||||
|
longDescription:
|
||||||
|
"ChromaSynth began as a weekend exploration in our experimental lab. It treats polyphonic audio frequencies as physical forces that deform complex geometric meshes in real time.",
|
||||||
|
features: [
|
||||||
|
"Modular 8-oscillator additive & FM synthesis engine",
|
||||||
|
"Real-time 4D hypercube and manifold geometry projection",
|
||||||
|
"Exportable high-res WebM / MP4 video stems for VJs",
|
||||||
|
"Open-source computational code released for researchers",
|
||||||
|
],
|
||||||
|
stack: ["Three.js", "Web Audio API", "GLSL Compute", "WebAssembly"],
|
||||||
|
accentGradient: "from-emerald-500/20 via-teal-600/10 to-transparent",
|
||||||
|
borderGlow: "hover:border-emerald-400/50 hover:shadow-[0_0_30px_rgba(16,185,129,0.25)]",
|
||||||
|
icon: FlaskConical,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "event-horizon",
|
||||||
|
title: "Project Event Horizon",
|
||||||
|
category: "lab",
|
||||||
|
categoryLabel: "Procedural Cosmos R&D",
|
||||||
|
tagline: "Compute-shader planetary terrain synthesis & atmospheric Rayleigh scattering.",
|
||||||
|
status: "R&D Prototype",
|
||||||
|
statusType: "prototype",
|
||||||
|
releaseTarget: "Continuous R&D",
|
||||||
|
description:
|
||||||
|
"Algorithmic generation of continental scale geological plates, biome micro-climates, and photorealistic atmospheric scattering.",
|
||||||
|
longDescription:
|
||||||
|
"The technological foundation for our future game worlds. Event Horizon runs entirely on GPU compute shaders to generate planet-scale terrain down to individual pebbles seamlessly.",
|
||||||
|
features: [
|
||||||
|
"Seamless transition from interplanetary orbit down to surface level",
|
||||||
|
"Physical simulation of tectonic drift and erosion patterns",
|
||||||
|
"Atmospheric volumetric cloud synthesis using 3D Worley noise",
|
||||||
|
"Powers the underlying engine for upcoming Aghatis game titles",
|
||||||
|
],
|
||||||
|
stack: ["WGSL Compute", "Rust", "WebGPU"],
|
||||||
|
accentGradient: "from-amber-500/20 via-orange-600/10 to-transparent",
|
||||||
|
borderGlow: "hover:border-amber-400/50 hover:shadow-[0_0_30px_rgba(245,158,11,0.25)]",
|
||||||
|
icon: FlaskConical,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const filteredProducts =
|
||||||
|
filter === "all"
|
||||||
|
? productsList
|
||||||
|
: productsList.filter((p) => p.category === filter);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="portfolio" className="relative py-28 overflow-hidden">
|
||||||
|
{/* Background celestial glow */}
|
||||||
|
<div className="absolute top-1/2 left-1/3 w-[600px] h-[600px] bg-cyan-600/10 rounded-full blur-[170px] pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
||||||
|
|
||||||
|
{/* Section Header */}
|
||||||
|
<div className="flex flex-col items-center text-center max-w-3xl mx-auto mb-16 space-y-4">
|
||||||
|
<div className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full glass-panel border border-indigo-500/30 text-xs font-mono text-indigo-300">
|
||||||
|
<Sparkles className="w-3.5 h-3.5 text-indigo-400" />
|
||||||
|
<span>ORIGINAL IP FLEET</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-black tracking-tight text-white">
|
||||||
|
Proprietary Works & <br />
|
||||||
|
<span className="text-gradient-cyan">Upcoming Titles.</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-base sm:text-lg text-slate-400 leading-relaxed pt-2">
|
||||||
|
Every product showcased below is conceived, engineered, and published in-house
|
||||||
|
as part of our permanent creative universe and software ecosystem.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filter Controls */}
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-2 sm:gap-3 mb-14">
|
||||||
|
{[
|
||||||
|
{ id: "all", label: "All Works (06)" },
|
||||||
|
{ id: "software", label: "Software Products" },
|
||||||
|
{ id: "games", label: "Original Games" },
|
||||||
|
{ id: "lab", label: "Experimental Lab" },
|
||||||
|
].map((tab) => (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
onClick={() => setFilter(tab.id)}
|
||||||
|
className={`px-4 py-2 rounded-xl font-mono text-xs uppercase tracking-wider transition-all duration-200 cursor-pointer ${
|
||||||
|
filter === tab.id
|
||||||
|
? "bg-cyan-500/20 border border-cyan-400/50 text-cyan-300 shadow-[0_0_15px_rgba(56,189,248,0.25)]"
|
||||||
|
: "glass-card text-slate-400 hover:text-slate-200 hover:border-white/20"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Products Grid */}
|
||||||
|
<motion.div
|
||||||
|
layout
|
||||||
|
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8"
|
||||||
|
>
|
||||||
|
<AnimatePresence>
|
||||||
|
{filteredProducts.map((product) => {
|
||||||
|
const Icon = product.icon;
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
layout
|
||||||
|
key={product.id}
|
||||||
|
initial={{ opacity: 0, scale: 0.95 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.95 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className={`glass-card rounded-2xl flex flex-col justify-between overflow-hidden relative group cursor-pointer transition-all duration-300 ${product.borderGlow}`}
|
||||||
|
onClick={() => setSelectedProduct(product)}
|
||||||
|
>
|
||||||
|
{/* Visual card header area with cosmetic cosmic pattern */}
|
||||||
|
<div className={`relative h-44 w-full bg-gradient-to-b ${product.accentGradient} p-6 flex flex-col justify-between border-b border-white/5`}>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-[10px] font-mono uppercase px-2.5 py-1 rounded-full bg-black/50 backdrop-blur-md border border-white/10 text-cyan-300">
|
||||||
|
{product.categoryLabel}
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] font-mono uppercase px-2.5 py-1 rounded-full bg-indigo-950/60 border border-indigo-400/20 text-indigo-300 flex items-center gap-1.5">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-indigo-400 animate-pulse" />
|
||||||
|
{product.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Centered stylized graphic watermark */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="w-12 h-12 rounded-2xl bg-black/40 border border-white/10 flex items-center justify-center text-cyan-400 group-hover:scale-110 transition-transform shadow-lg">
|
||||||
|
<Icon className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[11px] font-mono text-slate-400">
|
||||||
|
TARGET: {product.releaseTarget}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card Body */}
|
||||||
|
<div className="p-6 space-y-4 flex-1 flex flex-col justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold font-mono text-white group-hover:text-cyan-300 transition-colors">
|
||||||
|
{product.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs font-mono text-cyan-400/80 mt-1">
|
||||||
|
{product.tagline}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-slate-400 leading-relaxed mt-3">
|
||||||
|
{product.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4 pt-2">
|
||||||
|
{/* Tech stack badges */}
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{product.stack.map((item) => (
|
||||||
|
<span
|
||||||
|
key={item}
|
||||||
|
className="text-[10px] font-mono px-2 py-0.5 rounded bg-white/5 border border-white/10 text-slate-300"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card Footer action */}
|
||||||
|
<div className="pt-3 border-t border-white/5 flex items-center justify-between text-xs font-mono text-slate-400 group-hover:text-cyan-300 transition-colors">
|
||||||
|
<span>INSPECT SPEC & ARCHITECTURE</span>
|
||||||
|
<ArrowUpRight className="w-4 h-4 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</AnimatePresence>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* In-House Notice banner */}
|
||||||
|
<div className="mt-16 p-6 rounded-2xl glass-panel border border-cyan-500/20 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<ShieldCheck className="w-5 h-5 text-cyan-400 shrink-0" />
|
||||||
|
<div className="text-xs font-mono text-slate-300">
|
||||||
|
<span className="font-bold text-white">AUTONOMOUS PRODUCT FLEET // </span>
|
||||||
|
Every title and utility is designed, developed, and maintained by Aghatis Studio from initial prototype to continuous release.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="#creed"
|
||||||
|
className="shrink-0 text-xs font-mono text-cyan-400 hover:text-cyan-300 flex items-center gap-1 underline underline-offset-4"
|
||||||
|
>
|
||||||
|
<span>Read Studio Philosophy</span>
|
||||||
|
<ArrowUpRight className="w-3.5 h-3.5" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Product Spec Detail Modal */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{selectedProduct && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6">
|
||||||
|
{/* Backdrop */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
onClick={() => setSelectedProduct(null)}
|
||||||
|
className="absolute inset-0 bg-black/80 backdrop-blur-xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Modal Dialog */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||||
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||||
|
transition={{ duration: 0.25 }}
|
||||||
|
className="relative w-full max-w-2xl bg-[#0e0e1c] border border-cyan-500/30 rounded-3xl p-6 sm:p-8 shadow-[0_0_60px_rgba(56,189,248,0.2)] max-h-[90vh] overflow-y-auto z-10 space-y-6"
|
||||||
|
>
|
||||||
|
{/* Close Button */}
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedProduct(null)}
|
||||||
|
className="absolute top-6 right-6 p-2 rounded-full glass-panel text-slate-400 hover:text-white transition-colors"
|
||||||
|
aria-label="Close modal"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div className="space-y-2 pr-8">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-[11px] font-mono tracking-widest uppercase px-2.5 py-0.5 rounded bg-cyan-500/20 border border-cyan-400/30 text-cyan-300">
|
||||||
|
{selectedProduct.categoryLabel}
|
||||||
|
</span>
|
||||||
|
<span className="text-[11px] font-mono uppercase px-2.5 py-0.5 rounded bg-indigo-500/20 border border-indigo-400/30 text-indigo-300">
|
||||||
|
{selectedProduct.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl sm:text-3xl font-extrabold text-white font-mono">
|
||||||
|
{selectedProduct.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm font-mono text-cyan-400">
|
||||||
|
{selectedProduct.tagline}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Overview & Narrative */}
|
||||||
|
<div className="space-y-3 pt-2 border-t border-white/10">
|
||||||
|
<h4 className="text-xs font-mono uppercase tracking-wider text-slate-400">
|
||||||
|
// PRODUCT MANIFESTO & PHILOSOPHY
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-slate-300 leading-relaxed">
|
||||||
|
{selectedProduct.longDescription}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Architectural Highlights */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="text-xs font-mono uppercase tracking-wider text-slate-400">
|
||||||
|
// KEY ARCHITECTURAL CAPABILITIES
|
||||||
|
</h4>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2.5">
|
||||||
|
{selectedProduct.features.map((feat, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="p-3 rounded-xl bg-white/5 border border-white/10 text-xs text-slate-300 flex items-start gap-2"
|
||||||
|
>
|
||||||
|
<Sparkles className="w-3.5 h-3.5 text-cyan-400 shrink-0 mt-0.5" />
|
||||||
|
<span>{feat}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tech Stack */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="text-xs font-mono uppercase tracking-wider text-slate-400">
|
||||||
|
// COMPILED STACK & RUNTIME
|
||||||
|
</h4>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{selectedProduct.stack.map((item) => (
|
||||||
|
<span
|
||||||
|
key={item}
|
||||||
|
className="px-3 py-1 rounded-lg text-xs font-mono bg-indigo-950/40 border border-indigo-500/30 text-indigo-200"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer Meta */}
|
||||||
|
<div className="pt-4 border-t border-white/10 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
|
<div className="flex items-center gap-2 text-xs font-mono text-slate-400">
|
||||||
|
<Calendar className="w-4 h-4 text-cyan-400" />
|
||||||
|
<span>Target Deployment: {selectedProduct.releaseTarget}</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedProduct(null)}
|
||||||
|
className="w-full sm:w-auto px-6 py-2.5 rounded-xl text-xs font-mono uppercase tracking-wider font-semibold bg-cyan-400 text-slate-950 hover:bg-cyan-300 transition-colors"
|
||||||
|
>
|
||||||
|
Close Specification
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import { Volume2, VolumeX } from "lucide-react";
|
||||||
|
|
||||||
|
export default function CosmicAudioToggle() {
|
||||||
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
|
const audioCtxRef = useRef<AudioContext | null>(null);
|
||||||
|
const masterGainRef = useRef<GainNode | null>(null);
|
||||||
|
const osc1Ref = useRef<OscillatorNode | null>(null);
|
||||||
|
const osc2Ref = useRef<OscillatorNode | null>(null);
|
||||||
|
|
||||||
|
const toggleSound = () => {
|
||||||
|
if (isPlaying) {
|
||||||
|
if (masterGainRef.current && audioCtxRef.current) {
|
||||||
|
masterGainRef.current.gain.setTargetAtTime(
|
||||||
|
0,
|
||||||
|
audioCtxRef.current.currentTime,
|
||||||
|
0.5
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsPlaying(false);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const AudioCtx =
|
||||||
|
window.AudioContext ||
|
||||||
|
(window as unknown as { webkitAudioContext: typeof AudioContext })
|
||||||
|
.webkitAudioContext;
|
||||||
|
const ctx = audioCtxRef.current || new AudioCtx();
|
||||||
|
audioCtxRef.current = ctx;
|
||||||
|
|
||||||
|
if (ctx.state === "suspended") {
|
||||||
|
ctx.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
const masterGain = ctx.createGain();
|
||||||
|
masterGain.gain.setValueAtTime(0, ctx.currentTime);
|
||||||
|
masterGain.gain.linearRampToValueAtTime(0.08, ctx.currentTime + 1.5);
|
||||||
|
masterGain.connect(ctx.destination);
|
||||||
|
masterGainRef.current = masterGain;
|
||||||
|
|
||||||
|
// Lowpass filter for deep cosmic drone
|
||||||
|
const filter = ctx.createBiquadFilter();
|
||||||
|
filter.type = "lowpass";
|
||||||
|
filter.frequency.setValueAtTime(140, ctx.currentTime);
|
||||||
|
filter.Q.setValueAtTime(2, ctx.currentTime);
|
||||||
|
filter.connect(masterGain);
|
||||||
|
|
||||||
|
// Oscillator 1: 55Hz sub drone
|
||||||
|
const osc1 = ctx.createOscillator();
|
||||||
|
osc1.type = "sine";
|
||||||
|
osc1.frequency.setValueAtTime(55, ctx.currentTime);
|
||||||
|
osc1.connect(filter);
|
||||||
|
osc1.start();
|
||||||
|
osc1Ref.current = osc1;
|
||||||
|
|
||||||
|
// Oscillator 2: 110.2Hz warm harmonic with slight detune
|
||||||
|
const osc2 = ctx.createOscillator();
|
||||||
|
osc2.type = "triangle";
|
||||||
|
osc2.frequency.setValueAtTime(110.3, ctx.currentTime);
|
||||||
|
const osc2Gain = ctx.createGain();
|
||||||
|
osc2Gain.gain.setValueAtTime(0.03, ctx.currentTime);
|
||||||
|
osc2.connect(osc2Gain);
|
||||||
|
osc2Gain.connect(filter);
|
||||||
|
osc2.start();
|
||||||
|
osc2Ref.current = osc2;
|
||||||
|
|
||||||
|
setIsPlaying(true);
|
||||||
|
} catch {
|
||||||
|
// Fallback gracefully if Web Audio is blocked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (audioCtxRef.current && audioCtxRef.current.state !== "closed") {
|
||||||
|
try {
|
||||||
|
audioCtxRef.current.close();
|
||||||
|
} catch {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={toggleSound}
|
||||||
|
title={isPlaying ? "Mute Deep Space Ambience" : "Enable Deep Space Ambience"}
|
||||||
|
className="flex items-center gap-2 px-3 py-1.5 rounded-full glass-panel hover:border-indigo-500/40 text-xs font-mono tracking-wider text-slate-300 transition-all duration-300 hover:text-white group"
|
||||||
|
aria-label="Toggle Cosmic Soundscape"
|
||||||
|
>
|
||||||
|
{isPlaying ? (
|
||||||
|
<>
|
||||||
|
<span className="relative flex h-2 w-2">
|
||||||
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75"></span>
|
||||||
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-cyan-500"></span>
|
||||||
|
</span>
|
||||||
|
<Volume2 className="w-3.5 h-3.5 text-cyan-400 group-hover:scale-110 transition-transform" />
|
||||||
|
<span className="hidden sm:inline text-[11px] text-cyan-300">AUDIO: RESONANCE ON</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<VolumeX className="w-3.5 h-3.5 text-slate-400 group-hover:scale-110 transition-transform" />
|
||||||
|
<span className="hidden sm:inline text-[11px] text-slate-400">AUDIO: VOID</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
-- ==============================================================================
|
||||||
|
-- AGHATIS STUDIO - DATABASE SCHEMA & SEED DATA
|
||||||
|
-- Database engine: PostgreSQL / MySQL / SQLite compatible (Standard ANSI SQL)
|
||||||
|
-- Description: Schema and dataset matching Aghatis Studio frontend portfolio
|
||||||
|
-- ==============================================================================
|
||||||
|
|
||||||
|
-- 1. CREATE DATABASE
|
||||||
|
-- (Jalankan perintah ini jika database belum dibuat di DBMS Anda)
|
||||||
|
-- CREATE DATABASE aghatis_studio_db;
|
||||||
|
-- \c aghatis_studio_db; -- Untuk PostgreSQL
|
||||||
|
-- USE aghatis_studio_db; -- Untuk MySQL
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
-- 2. TABLE: tracks (Operational Core Focus Tracks)
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS tracks (
|
||||||
|
id VARCHAR(50) PRIMARY KEY,
|
||||||
|
track_number VARCHAR(10) NOT NULL,
|
||||||
|
title VARCHAR(100) NOT NULL,
|
||||||
|
category VARCHAR(100) NOT NULL,
|
||||||
|
tagline VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
stack JSON NOT NULL,
|
||||||
|
principles JSON NOT NULL,
|
||||||
|
accent_color VARCHAR(100) NOT NULL,
|
||||||
|
pill_border VARCHAR(150) NOT NULL,
|
||||||
|
glow_color VARCHAR(100) NOT NULL,
|
||||||
|
order_index INT DEFAULT 0,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
-- 3. TABLE: projects (Portfolio / Original IP Fleet)
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS projects (
|
||||||
|
id VARCHAR(50) PRIMARY KEY,
|
||||||
|
title VARCHAR(150) NOT NULL,
|
||||||
|
category VARCHAR(50) NOT NULL, -- 'software', 'games', 'lab'
|
||||||
|
category_label VARCHAR(100) NOT NULL, -- e.g. 'Tactical Flight RPG', 'Creative Shader Pipeline'
|
||||||
|
tagline VARCHAR(255) NOT NULL,
|
||||||
|
status VARCHAR(50) NOT NULL, -- 'In Active Dev', 'Private Alpha', etc.
|
||||||
|
status_type VARCHAR(50) NOT NULL, -- 'active', 'alpha', 'prototype', 'upcoming'
|
||||||
|
release_target VARCHAR(100) NOT NULL, -- 'Alpha 2027', 'Beta Q3 2027', etc.
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
long_description TEXT NOT NULL,
|
||||||
|
features JSON NOT NULL, -- Array of feature strings
|
||||||
|
stack JSON NOT NULL, -- Array of technology strings
|
||||||
|
accent_gradient VARCHAR(150) NOT NULL,
|
||||||
|
border_glow VARCHAR(150) NOT NULL,
|
||||||
|
icon_name VARCHAR(50) NOT NULL, -- 'Gamepad2', 'Terminal', 'FlaskConical'
|
||||||
|
order_index INT DEFAULT 0,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
-- 4. SEED DATA: tracks
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
INSERT IGNORE INTO tracks (
|
||||||
|
id, track_number, title, category, tagline, description,
|
||||||
|
stack, principles, accent_color, pill_border, glow_color, order_index
|
||||||
|
) VALUES
|
||||||
|
(
|
||||||
|
'software',
|
||||||
|
'01',
|
||||||
|
'Software Products',
|
||||||
|
'CREATIVE & DEVELOPER TOOLS',
|
||||||
|
'High-performance utilities engineered for creators, coders, and deep-focus thinkers.',
|
||||||
|
'We build standalone software tools that respect user attention and privacy. Our software stack emphasizes local-first execution, instantaneous boot times, minimal memory footprints, and fluid spatial aesthetics.',
|
||||||
|
'["Rust", "TypeScript", "WebGPU", "Local-First SQLite", "Tauri", "Custom GLSL"]',
|
||||||
|
'["Zero telemetry bloat or intrusive subscription traps", "Deterministic local-first file system workflows", "Sub-16ms latency interface responsiveness", "Minimalist spatial design inspired by cockpit HUDs"]',
|
||||||
|
'from-cyan-500 to-blue-600',
|
||||||
|
'border-cyan-500/30 text-cyan-400 bg-cyan-500/10',
|
||||||
|
'rgba(56, 189, 248, 0.15)',
|
||||||
|
1
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'games',
|
||||||
|
'02',
|
||||||
|
'Original Games',
|
||||||
|
'ATMOSPHERIC & INDIE TITLES',
|
||||||
|
'Evocative, mechanics-first game experiences set in interstellar and surreal realities.',
|
||||||
|
'Our games emphasize tactile physics, memorable soundscapes, and evocative storytelling. We reject predatory monetization and live-service treadmills—focusing exclusively on handcrafted, complete experiences that respect the player''s time.',
|
||||||
|
'["Godot Engine", "Custom C++ GDExtension", "Bespoke Shaders", "FMOD / WebAudio", "Procreate / Blender"]',
|
||||||
|
'["Uncompromising, single-purchase complete artistic vision", "Kinetic, physics-grounded flight and orbital mechanics", "Original dynamic synthesizer soundtracks and reactive foley", "Rich speculative sci-fi lore and evocative environmental narrative"]',
|
||||||
|
'from-purple-500 to-pink-600',
|
||||||
|
'border-purple-500/30 text-purple-400 bg-purple-500/10',
|
||||||
|
'rgba(168, 85, 247, 0.15)',
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'lab',
|
||||||
|
'03',
|
||||||
|
'Experimental Lab',
|
||||||
|
'CREATIVE COMPUTING & R&D',
|
||||||
|
'Unconstrained research in procedural systems, audio synthesis, and algorithmic art.',
|
||||||
|
'The Aghatis Lab is our continuous skunkworks playground. Here we prototype procedural cosmos generators, WebAudio modular synths, chaotic attractor simulations, and emergent artificial life models that inform our core products.',
|
||||||
|
'["Three.js / R3F", "GLSL Compute", "Web Audio API", "WebAssembly", "Generative Math"]',
|
||||||
|
'["Rapid computational sketches without commercial pressure", "Algorithmic exploration of celestial mathematics & fractals", "Open prototypes released to the creative technology community", "Direct R&D incubator for upcoming software & game mechanics"]',
|
||||||
|
'from-emerald-500 to-cyan-500',
|
||||||
|
'border-emerald-500/30 text-emerald-400 bg-emerald-500/10',
|
||||||
|
'rgba(16, 185, 129, 0.15)',
|
||||||
|
3
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
-- 5. SEED DATA: projects (Matching Portfolio.tsx Data)
|
||||||
|
-- ------------------------------------------------------------------------------
|
||||||
|
INSERT IGNORE INTO projects (
|
||||||
|
id, title, category, category_label, tagline, status, status_type,
|
||||||
|
release_target, description, long_description, features, stack,
|
||||||
|
accent_gradient, border_glow, icon_name, order_index
|
||||||
|
) VALUES
|
||||||
|
(
|
||||||
|
'aetheria',
|
||||||
|
'Aetheria: Chrono Drift',
|
||||||
|
'games',
|
||||||
|
'Tactical Flight RPG',
|
||||||
|
'Atmospheric spaceflight RPG driven by Newtonian physics & relativistic time mechanics.',
|
||||||
|
'In Active Dev',
|
||||||
|
'active',
|
||||||
|
'Alpha 2027',
|
||||||
|
'Pilot handcrafted atmospheric ships through uncharted cosmic sectors. Engage in tactical orbital maneuvers where time dilates with engine velocity.',
|
||||||
|
'Aetheria is our flagship narrative sci-fi title. Designed without live-service grind or predatory mechanics, it centers on contemplative space exploration, deep Newtonian flight kinematics, and an original interactive dark-ambient synth score.',
|
||||||
|
'[
|
||||||
|
"True orbital mechanics and gravitational slingshots",
|
||||||
|
"Non-linear stellar narrative with faction autonomy",
|
||||||
|
"Zero-compromise single-player standalone artistic experience",
|
||||||
|
"Procedurally synthesized nebula volumetric rendering"
|
||||||
|
]',
|
||||||
|
'["Godot 4.3", "C++ GDExtension", "Bespoke GLSL", "FMOD Sound Engine"]',
|
||||||
|
'from-cyan-500/20 via-blue-600/10 to-transparent',
|
||||||
|
'hover:border-cyan-400/50 hover:shadow-[0_0_30px_rgba(56,189,248,0.25)]',
|
||||||
|
'Gamepad2',
|
||||||
|
1
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'nebulaflow',
|
||||||
|
'NebulaFlow',
|
||||||
|
'software',
|
||||||
|
'Creative Shader Pipeline',
|
||||||
|
'Node-based real-time GPU shader workbench & procedural VFX engine.',
|
||||||
|
'Private Alpha',
|
||||||
|
'alpha',
|
||||||
|
'Beta Q3 2027',
|
||||||
|
'A high-performance visual coding environment for creative coders, game devs, and technical artists compiling directly to WGSL and GLSL.',
|
||||||
|
'NebulaFlow re-imagines real-time shader generation. Designed with a lightweight Rust core and a distraction-free spatial interface, it compiles visual nodes directly into native shaders at 120 FPS with instantaneous hot-reloading.',
|
||||||
|
'[
|
||||||
|
"Instant compilation to WGSL, GLSL, and Metal Shading Language",
|
||||||
|
"Deterministic local-first project workspace without cloud locks",
|
||||||
|
"Live audio-reactive parameter modulation via WebAudio / ASIO",
|
||||||
|
"Integrated raymarching and compute shader node library"
|
||||||
|
]',
|
||||||
|
'["Rust", "WebGPU", "Tauri", "TypeScript", "WGSL"]',
|
||||||
|
'from-purple-500/20 via-indigo-600/10 to-transparent',
|
||||||
|
'hover:border-purple-400/50 hover:shadow-[0_0_30px_rgba(168,85,247,0.25)]',
|
||||||
|
'Terminal',
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'voidwalker',
|
||||||
|
'Voidwalker OS',
|
||||||
|
'software',
|
||||||
|
'Spatial Workstation',
|
||||||
|
'Distraction-free spatial terminal and local knowledge workstation for intense focus.',
|
||||||
|
'Prototype',
|
||||||
|
'prototype',
|
||||||
|
'Q4 2027',
|
||||||
|
'An experimental desktop environment stripping away notification clutter in favor of an ergonomic cosmic workspace and local neural indexing.',
|
||||||
|
'Voidwalker replaces chaotic desktop windows with an infinite 2.5D orbital plane. Crafted for independent developers and creative writers who demand absolute cognitive quiet.',
|
||||||
|
'[
|
||||||
|
"Local-first vector indexing over personal markdown & codebases",
|
||||||
|
"Orbital workspace clustering with keyboard-centric navigation",
|
||||||
|
"Sub-10ms input latency with custom GPU text rendering engine",
|
||||||
|
"Zero tracking, zero analytics, zero telemetry overhead"
|
||||||
|
]',
|
||||||
|
'["Rust", "SQLite", "Vulkan / Metal", "Tree-Sitter"]',
|
||||||
|
'from-indigo-500/20 via-sky-600/10 to-transparent',
|
||||||
|
'hover:border-indigo-400/50 hover:shadow-[0_0_30px_rgba(99,102,241,0.25)]',
|
||||||
|
'Terminal',
|
||||||
|
3
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'singularity-pulse',
|
||||||
|
'Singularity Pulse',
|
||||||
|
'games',
|
||||||
|
'Kinetic Rhythm-Strategy',
|
||||||
|
'Orbital rhythm defense game where celestial gravitational waves dictate combat tempo.',
|
||||||
|
'Playable Slice',
|
||||||
|
'prototype',
|
||||||
|
'2027',
|
||||||
|
'Defend dying binary stars from quantum anomalies by aligning resonant frequencies in time with an escalating electronic pulse.',
|
||||||
|
'Built at the intersection of rhythm gaming and orbital physics, Singularity Pulse challenges players to synchronize defensive shields with gravitational wave oscillations in real-time.',
|
||||||
|
'[
|
||||||
|
"Dynamic sound-reactive procedural enemy generation",
|
||||||
|
"Precise sub-millisecond input synchronization",
|
||||||
|
"Full support for analog sticks, arcade sticks, and MIDI input",
|
||||||
|
"Minimalist wireframe vector aesthetic inspired by 80s arcade radars"
|
||||||
|
]',
|
||||||
|
'["Godot", "WebAudio", "Procedural GLSL"]',
|
||||||
|
'from-pink-500/20 via-rose-600/10 to-transparent',
|
||||||
|
'hover:border-pink-400/50 hover:shadow-[0_0_30px_rgba(244,63,94,0.25)]',
|
||||||
|
'Gamepad2',
|
||||||
|
4
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'chromasynth',
|
||||||
|
'ChromaSynth Lab',
|
||||||
|
'lab',
|
||||||
|
'Spatial Sound Synthesis',
|
||||||
|
'WebGPU audio-reactive 3D synthesis tool exploring geometric resonance.',
|
||||||
|
'Public Experiment',
|
||||||
|
'alpha',
|
||||||
|
'Live Now',
|
||||||
|
'A browser-based generative instrument generating real-time higher-dimensional geometric manifolds modulated by live microphone and MIDI input.',
|
||||||
|
'ChromaSynth began as a weekend exploration in our experimental lab. It treats polyphonic audio frequencies as physical forces that deform complex geometric meshes in real time.',
|
||||||
|
'[
|
||||||
|
"Modular 8-oscillator additive & FM synthesis engine",
|
||||||
|
"Real-time 4D hypercube and manifold geometry projection",
|
||||||
|
"Exportable high-res WebM / MP4 video stems for VJs",
|
||||||
|
"Open-source computational code released for researchers"
|
||||||
|
]',
|
||||||
|
'["Three.js", "Web Audio API", "GLSL Compute", "WebAssembly"]',
|
||||||
|
'from-emerald-500/20 via-teal-600/10 to-transparent',
|
||||||
|
'hover:border-emerald-400/50 hover:shadow-[0_0_30px_rgba(16,185,129,0.25)]',
|
||||||
|
'FlaskConical',
|
||||||
|
5
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'event-horizon',
|
||||||
|
'Project Event Horizon',
|
||||||
|
'lab',
|
||||||
|
'Procedural Cosmos R&D',
|
||||||
|
'Compute-shader planetary terrain synthesis & atmospheric Rayleigh scattering.',
|
||||||
|
'R&D Prototype',
|
||||||
|
'prototype',
|
||||||
|
'Continuous R&D',
|
||||||
|
'Algorithmic generation of continental scale geological plates, biome micro-climates, and photorealistic atmospheric scattering.',
|
||||||
|
'The technological foundation for our future game worlds. Event Horizon runs entirely on GPU compute shaders to generate planet-scale terrain down to individual pebbles seamlessly.',
|
||||||
|
'[
|
||||||
|
"Seamless transition from interplanetary orbit down to surface level",
|
||||||
|
"Physical simulation of tectonic drift and erosion patterns",
|
||||||
|
"Atmospheric volumetric cloud synthesis using 3D Worley noise",
|
||||||
|
"Powers the underlying engine for upcoming Aghatis game titles"
|
||||||
|
]',
|
||||||
|
'["WGSL Compute", "Rust", "WebGPU"]',
|
||||||
|
'from-amber-500/20 via-orange-600/10 to-transparent',
|
||||||
|
'hover:border-amber-400/50 hover:shadow-[0_0_30px_rgba(245,158,11,0.25)]',
|
||||||
|
'FlaskConical',
|
||||||
|
6
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ==============================================================================
|
||||||
|
-- 3. ADMIN USERS TABLE
|
||||||
|
-- ==============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS admin_users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
full_name VARCHAR(100) DEFAULT 'Aghatis Admin',
|
||||||
|
role VARCHAR(50) DEFAULT 'admin',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Seed Initial Admin User:
|
||||||
|
-- Username: Aghatis
|
||||||
|
-- Password: @Arya654321 (stored as secure bcrypt hash)
|
||||||
|
INSERT INTO admin_users (username, password_hash, full_name, role)
|
||||||
|
VALUES (
|
||||||
|
'Aghatis',
|
||||||
|
'$2b$10$O6c/yR.Nx5sNpvBUAHsIOOR8QWsUXFxj7vlspfFaEw3Zii2iUaRMm',
|
||||||
|
'Aghatis Commander',
|
||||||
|
'superadmin'
|
||||||
|
)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
password_hash = VALUES(password_hash),
|
||||||
|
updated_at = CURRENT_TIMESTAMP;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
-- ==============================================================================
|
||||||
|
-- AGHATIS STUDIO - ADMIN USERS SCHEMA & SEED DATA
|
||||||
|
-- Database: aghatis_studio_db
|
||||||
|
-- ==============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS admin_users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
full_name VARCHAR(100) DEFAULT 'Aghatis Admin',
|
||||||
|
role VARCHAR(50) DEFAULT 'admin',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Seed Initial Admin User:
|
||||||
|
-- Username: Aghatis
|
||||||
|
-- Password: @Arya654321 (stored as secure bcrypt hash)
|
||||||
|
INSERT INTO admin_users (username, password_hash, full_name, role)
|
||||||
|
VALUES (
|
||||||
|
'Aghatis',
|
||||||
|
'$2b$10$O6c/yR.Nx5sNpvBUAHsIOOR8QWsUXFxj7vlspfFaEw3Zii2iUaRMm',
|
||||||
|
'Aghatis Commander',
|
||||||
|
'superadmin'
|
||||||
|
)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
password_hash = VALUES(password_hash),
|
||||||
|
updated_at = CURRENT_TIMESTAMP;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||||
|
import nextTs from "eslint-config-next/typescript";
|
||||||
|
|
||||||
|
const eslintConfig = defineConfig([
|
||||||
|
...nextVitals,
|
||||||
|
...nextTs,
|
||||||
|
// Override default ignores of eslint-config-next.
|
||||||
|
globalIgnores([
|
||||||
|
// Default ignores of eslint-config-next:
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
import { cookies } from "next/headers";
|
||||||
|
import bcrypt from "bcryptjs";
|
||||||
|
import { query } from "./db";
|
||||||
|
|
||||||
|
const SESSION_COOKIE_NAME = "aghatis_admin_session";
|
||||||
|
const SESSION_SECRET = process.env.ADMIN_SESSION_SECRET || "aghatis_stellar_session_secret_2026_secure_key";
|
||||||
|
|
||||||
|
interface DBAdminUser {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
password_hash: string;
|
||||||
|
full_name: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifyUserCredentials(
|
||||||
|
user: string,
|
||||||
|
pass: string
|
||||||
|
): Promise<{ success: boolean; user?: { username: string; role: string; fullName: string } }> {
|
||||||
|
try {
|
||||||
|
const rows = await query<DBAdminUser[]>(
|
||||||
|
"SELECT id, username, password_hash, full_name, role FROM admin_users WHERE username = ? LIMIT 1",
|
||||||
|
[user]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!rows || rows.length === 0) {
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const admin = rows[0];
|
||||||
|
|
||||||
|
// Check bcrypt hash or fallback to direct comparison
|
||||||
|
let isMatch = false;
|
||||||
|
if (admin.password_hash.startsWith("$2b$") || admin.password_hash.startsWith("$2a$")) {
|
||||||
|
isMatch = bcrypt.compareSync(pass, admin.password_hash);
|
||||||
|
} else {
|
||||||
|
isMatch = admin.password_hash === pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMatch) {
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
user: {
|
||||||
|
username: admin.username,
|
||||||
|
role: admin.role,
|
||||||
|
fullName: admin.full_name,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Database auth verification error:", err);
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate HMAC session token
|
||||||
|
export function createSessionToken(username: string): string {
|
||||||
|
const payload = {
|
||||||
|
username,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
};
|
||||||
|
const json = JSON.stringify(payload);
|
||||||
|
const encoded = Buffer.from(json).toString("base64url");
|
||||||
|
const signature = Buffer.from(`${encoded}:${SESSION_SECRET}`).toString("base64url");
|
||||||
|
return `${encoded}.${signature}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verifySessionToken(token: string): { username: string } | null {
|
||||||
|
try {
|
||||||
|
const [encoded, signature] = token.split(".");
|
||||||
|
if (!encoded || !signature) return null;
|
||||||
|
const expectedSig = Buffer.from(`${encoded}:${SESSION_SECRET}`).toString("base64url");
|
||||||
|
if (signature !== expectedSig) return null;
|
||||||
|
const json = Buffer.from(encoded, "base64url").toString("utf8");
|
||||||
|
const payload = JSON.parse(json);
|
||||||
|
return { username: payload.username };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isAuthenticated(): Promise<boolean> {
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
const token = cookieStore.get(SESSION_COOKIE_NAME)?.value;
|
||||||
|
if (!token) return false;
|
||||||
|
return verifySessionToken(token) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAuthenticatedUser(): Promise<{ username: string } | null> {
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
const token = cookieStore.get(SESSION_COOKIE_NAME)?.value;
|
||||||
|
if (!token) return null;
|
||||||
|
return verifySessionToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SESSION_COOKIE_NAME };
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import mysql from "mysql2/promise";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var mysqlPool: mysql.Pool | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pool =
|
||||||
|
global.mysqlPool ||
|
||||||
|
mysql.createPool({
|
||||||
|
host: process.env.DB_HOST || "103.155.224.244",
|
||||||
|
port: Number(process.env.DB_PORT) || 3306,
|
||||||
|
user: process.env.DB_USER || "root",
|
||||||
|
password: process.env.DB_PASSWORD || "hqn9OY2pJx1oYRYJoVrC0Q64sZj8k3W3ZpoVcsKAmv1DFqO9u0DIMGCkjjXEjLaB",
|
||||||
|
database: process.env.DB_NAME || "aghatis_studio_db",
|
||||||
|
waitForConnections: true,
|
||||||
|
connectionLimit: 10,
|
||||||
|
queueLimit: 0,
|
||||||
|
connectTimeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
global.mysqlPool = pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default pool;
|
||||||
|
|
||||||
|
export async function query<T = unknown>(sql: string, params?: unknown[]): Promise<T> {
|
||||||
|
const [rows] = await pool.query(sql, params);
|
||||||
|
return rows as T;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
allowedDevOrigins: ["192.168.43.89", "localhost:3000"],
|
||||||
|
experimental: {
|
||||||
|
agentFeedback: true,
|
||||||
|
},
|
||||||
|
cacheComponents: true,
|
||||||
|
partialPrefetching: true,
|
||||||
|
turbopack: {
|
||||||
|
rules: {
|
||||||
|
"*.css": {
|
||||||
|
loaders: ["@tailwindcss/turbopack"],
|
||||||
|
as: "*.css",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
Generated
+7491
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "aghatis_studio",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@react-three/drei": "^10.7.9",
|
||||||
|
"@react-three/fiber": "^9.8.2",
|
||||||
|
"@types/bcryptjs": "^2.4.6",
|
||||||
|
"@types/three": "^0.187.0",
|
||||||
|
"bcryptjs": "^3.0.3",
|
||||||
|
"framer-motion": "^14.9.0",
|
||||||
|
"lucide-react": "^1.66.0",
|
||||||
|
"mysql2": "^3.24.6",
|
||||||
|
"next": "16.4.3",
|
||||||
|
"react": "19.3.0",
|
||||||
|
"react-dom": "19.3.0",
|
||||||
|
"three": "^0.187.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/turbopack": "^4",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"eslint": "^9",
|
||||||
|
"eslint-config-next": "16.4.3",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 128 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||||
|
After Width: | Height: | Size: 385 B |
@@ -0,0 +1,39 @@
|
|||||||
|
import mysql from 'mysql2/promise';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('Migrating MySQL database aghatis_studio_db...');
|
||||||
|
const connection = await mysql.createConnection({
|
||||||
|
host: '103.155.224.244',
|
||||||
|
user: 'root',
|
||||||
|
password: 'hqn9OY2pJx1oYRYJoVrC0Q64sZj8k3W3ZpoVcsKAmv1DFqO9u0DIMGCkjjXEjLaB',
|
||||||
|
database: 'aghatis_studio_db',
|
||||||
|
multipleStatements: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sql = fs.readFileSync('database/schema.sql', 'utf8');
|
||||||
|
|
||||||
|
// Remove comment lines starting with --
|
||||||
|
const cleanSql = sql
|
||||||
|
.split('\n')
|
||||||
|
.filter(line => !line.trim().startsWith('--'))
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
console.log('Executing SQL schema and seeds...');
|
||||||
|
await connection.query(cleanSql);
|
||||||
|
console.log('Migration completed successfully!');
|
||||||
|
|
||||||
|
const [tracks] = await connection.query('SELECT id, title FROM tracks');
|
||||||
|
console.log('Tracks in DB:', tracks);
|
||||||
|
|
||||||
|
const [projects] = await connection.query('SELECT id, title, category, status FROM projects');
|
||||||
|
console.log('Projects in DB:', projects);
|
||||||
|
|
||||||
|
await connection.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error('Migration error:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import mysql from 'mysql2/promise';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('Connecting to MySQL at 103.155.224.244...');
|
||||||
|
try {
|
||||||
|
const connection = await mysql.createConnection({
|
||||||
|
host: '103.155.224.244',
|
||||||
|
user: 'root',
|
||||||
|
password: 'hqn9OY2pJx1oYRYJoVrC0Q64sZj8k3W3ZpoVcsKAmv1DFqO9u0DIMGCkjjXEjLaB',
|
||||||
|
connectTimeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Connected successfully!');
|
||||||
|
const [rows] = await connection.query('SHOW DATABASES;');
|
||||||
|
console.log('Databases:', rows);
|
||||||
|
|
||||||
|
// Create database if not exists
|
||||||
|
await connection.query('CREATE DATABASE IF NOT EXISTS aghatis_studio_db;');
|
||||||
|
console.log('Database aghatis_studio_db checked/created.');
|
||||||
|
|
||||||
|
await connection.end();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Connection failed:', err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts",
|
||||||
|
"**/*.mts"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in new issue
Block a user