diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..c394cd4 --- /dev/null +++ b/App.tsx @@ -0,0 +1,168 @@ +import React, { useState } from 'react'; +import Layout from './components/Layout'; +import Dashboard from './pages/Dashboard'; +import PlayerProfile from './pages/PlayerProfile'; +import SetupGuide from './pages/SetupGuide'; +import Projects from './pages/Projects'; +import Organizations from './pages/Organizations'; +import Cities from './pages/Cities'; +import CityProfile from './pages/CityProfile'; +import ProjectProfile from './pages/ProjectProfile'; +import DatapackGenerator from './pages/DatapackGenerator'; +import { MOCK_PLAYERS, MOCK_ORGS, MOCK_PROJECTS } from './constants'; +import { Icons } from './components/IconSet'; + +function App() { + const [activeTab, setActiveTab] = useState('dashboard'); + const [selectedPlayerId, setSelectedPlayerId] = useState(null); + const [selectedCityId, setSelectedCityId] = useState(null); + const [selectedProjectId, setSelectedProjectId] = useState(null); + const [selectedOrgId, setSelectedOrgId] = useState(null); + + const handleNavigate = (tab: string) => { + setActiveTab(tab); + if (tab !== 'players') setSelectedPlayerId(null); + if (tab !== 'cities') setSelectedCityId(null); + if (tab !== 'projects') setSelectedProjectId(null); + if (tab !== 'organizations') setSelectedOrgId(null); + }; + + // Helper to jump to a player from another view + const navigateToPlayer = (id: string) => { + setSelectedPlayerId(id); + setActiveTab('players'); + }; + + // Helper to jump to a project from another view + const navigateToProject = (id: string) => { + setSelectedProjectId(id); + setActiveTab('projects'); + }; + + // Helper to jump to an org/city + const navigateToOrg = (id: string) => { + const org = MOCK_ORGS.find(o => o.id === id); + if (org?.type === 'City') { + setSelectedCityId(id); + setActiveTab('cities'); + } else { + setSelectedOrgId(id); + setActiveTab('organizations'); + } + }; + + const renderContent = () => { + if (activeTab === 'dashboard') return ; + + if (activeTab === 'projects') { + if (selectedProjectId) { + const project = MOCK_PROJECTS.find(p => p.id === selectedProjectId); + if (project) return ( + setSelectedProjectId(null)} + onSelectPlayer={navigateToPlayer} + onSelectOrg={navigateToOrg} + /> + ); + } + return ; + } + + if (activeTab === 'organizations') { + if (selectedOrgId) { + const org = MOCK_ORGS.find(o => o.id === selectedOrgId); + if (org) return ( + setSelectedOrgId(null)} + backLabel="Zurück zum Verzeichnis" + onSelectPlayer={navigateToPlayer} + onSelectProject={navigateToProject} + /> + ); + } + return ; + } + + if (activeTab === 'setup') return ; + + if (activeTab === 'datapack') return ; + + if (activeTab === 'cities') { + if (selectedCityId) { + const city = MOCK_ORGS.find(o => o.id === selectedCityId); + if (city) return ( + setSelectedCityId(null)} + backLabel="Zurück zu Städte" + onSelectPlayer={navigateToPlayer} + onSelectProject={navigateToProject} + /> + ); + } + return ; + } + + if (activeTab === 'players') { + if (selectedPlayerId) { + const player = MOCK_PLAYERS.find(p => p.uuid === selectedPlayerId); + if (player) return setSelectedPlayerId(null)} />; + } + + return ( +
+
+

Bürgerverzeichnis

+
+ + +
+
+ +
+ {MOCK_PLAYERS.map(player => ( +
setSelectedPlayerId(player.uuid)} + className="group bg-surface border border-border p-4 rounded-xl cursor-pointer hover:border-accentInfo/50 transition-all duration-200 hover:shadow-card" + > +
+
+ +
+
+
{player.username}
+
+ {player.tags.slice(0, 2).map(t => {t})} +
+
+
+
+ ))} +
+
+ ); + } + + return ( +
+ +

Modul {activeTab} wird derzeit gewartet.

+
+ ); + }; + + return ( + + {renderContent()} + + ); +} + +export default App; \ No newline at end of file diff --git a/README.md b/README.md index 2241000..aa357e3 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,20 @@
- GHBanner - -

Built with AI Studio

- -

The fastest path from prompt to production with Gemini.

- - Start building -
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/drive/1o1Z6erDhereYS10vVy3VaRuXeUPxu-Nh + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/components/IconSet.tsx b/components/IconSet.tsx new file mode 100644 index 0000000..473f180 --- /dev/null +++ b/components/IconSet.tsx @@ -0,0 +1,91 @@ +import React from 'react'; + +// UI Icons (Lucide-style wrappers) +export const Icons = { + Home: ({ className }: { className?: string }) => ( + + ), + Users: ({ className }: { className?: string }) => ( + + ), + Map: ({ className }: { className?: string }) => ( + + ), + Layers: ({ className }: { className?: string }) => ( + + ), + Terminal: ({ className }: { className?: string }) => ( + + ), + Box: ({ className }: { className?: string }) => ( + + ), + Search: ({ className }: { className?: string }) => ( + + ), + Crown: ({ className }: { className?: string }) => ( + + ), + Shield: ({ className }: { className?: string }) => ( + + ), + Coins: ({ className }: { className?: string }) => ( + + ), + Scroll: ({ className }: { className?: string }) => ( + + ), + ShoppingBag: ({ className }: { className?: string }) => ( + + ), + Tag: ({ className }: { className?: string }) => ( + + ), + Hammer: ({ className }: { className?: string }) => ( + + ) +}; + +// Algorithmic Minecraft Item Icons (Vector) +export const ItemIcon = ({ type, className }: { type: string; className?: string }) => { + const commonClasses = `w-full h-full ${className}`; + + if (type === 'tool') { + return ( + + + + + + + ); + } + + if (type === 'block') { + return ( + + + + + + ); + } + + if (type === 'consumable') { + return ( + + + + + + ); + } + + // Misc / Default + return ( + + + + + ); +}; \ No newline at end of file diff --git a/components/InventoryGrid.tsx b/components/InventoryGrid.tsx new file mode 100644 index 0000000..299f3ed --- /dev/null +++ b/components/InventoryGrid.tsx @@ -0,0 +1,63 @@ +import React, { useState } from 'react'; +import { Item } from '../types'; +import { ItemIcon } from './IconSet'; + +interface InventoryGridProps { + items: (Item | null)[]; +} + +const InventoryGrid: React.FC = ({ items }) => { + return ( +
+
+

Inventar

+
+ {items.filter(i => i !== null).length} / {items.length} Plätze +
+
+ +
+ {items.map((item, index) => ( +
+ {item ? ( + <> +
+ +
+ + {/* Count Badge */} + {item.count > 1 && ( + + {item.count} + + )} + + {/* Tooltip */} +
+
+
+ {item.name} +
+ {item.nbtSummary && ( +
+ {item.nbtSummary} +
+ )} +
+
+ + ) : ( + // Empty slot styling +
+ )} +
+ ))} +
+
+ ); +}; + +export default InventoryGrid; \ No newline at end of file diff --git a/components/Layout.tsx b/components/Layout.tsx new file mode 100644 index 0000000..d598999 --- /dev/null +++ b/components/Layout.tsx @@ -0,0 +1,126 @@ +import React, { useState } from 'react'; +import { Icons } from './IconSet'; + +interface LayoutProps { + children: React.ReactNode; + activeTab: string; + onNavigate: (tab: string) => void; +} + +const NavItem = ({ + active, + label, + onClick +}: { + active: boolean; + label: string; + onClick: () => void; +}) => ( + +); + +const Layout: React.FC = ({ children, activeTab, onNavigate }) => { + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + + return ( +
+ {/* Top Header - Website Style */} +
+
+
+ {/* Logo */} +
onNavigate('dashboard')} + > +
+ P.V. +
+ Projekt: Vollidion +
+ + {/* Desktop Nav */} + +
+ +
+ + + + {/* Mobile Menu Toggle */} + +
+
+ + {/* Mobile Nav Dropdown */} + {mobileMenuOpen && ( +
+
{ onNavigate('dashboard'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Übersicht
+
{ onNavigate('cities'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Städte
+
{ onNavigate('players'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Bürger
+
{ onNavigate('organizations'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Organisationen
+
{ onNavigate('projects'); setMobileMenuOpen(false); }} className="block py-2 text-textMuted hover:text-textMain">Unternehmen
+
{ onNavigate('datapack'); setMobileMenuOpen(false); }} className="block py-2 text-textMain">Datapack holen
+
{ onNavigate('setup'); setMobileMenuOpen(false); }} className="block py-2 text-accentInfo font-mono text-sm border-t border-white/5 pt-4">Admin Setup >_
+
+ )} +
+ + {/* Main Content - Page Flow */} +
+
+ {children} +
+
+ + {/* Footer - Adds to the "Website" feel */} +
+
+
+
+

© 2024 Obsidian Platform

+
+
+ Dokumentation + Server Status + Datenschutz +
+
+
+
+ ); +}; + +export default Layout; \ No newline at end of file diff --git a/constants.ts b/constants.ts new file mode 100644 index 0000000..ad4a683 --- /dev/null +++ b/constants.ts @@ -0,0 +1,191 @@ +import { Player, Organization, Project } from './types'; + +export const MOCK_PLAYERS: Player[] = [ + { + uuid: '80301bff-74df-4579-bcfc-082ac8d26b5b', + username: 'kaiwastoshort', + isOnline: true, + tags: ['#Bürger', '#Händler'], + stats: { + playtimeHours: 482, + level: 45, + role: 'Bürger', + organizationId: 'org-3' // Moved to Old Haven + }, + inventory: [ + { id: 'minecraft:diamond_pickaxe', name: 'Behutsamkeit Hacke', count: 1, type: 'tool', rarity: 'epic', nbtSummary: 'Eff V, Rep.' }, + { id: 'minecraft:stone_bricks', name: 'Polierter Andesit', count: 64, type: 'block' }, + { id: 'minecraft:stone_bricks', name: 'Polierter Andesit', count: 32, type: 'block' }, + null, null, + { id: 'minecraft:cooked_beef', name: 'Steak', count: 12, type: 'consumable' }, + { id: 'minecraft:compass', name: 'Stadtkarte', count: 1, type: 'misc', nbtSummary: 'Ziel: Spawn' }, + null, null + ], + storyMarkdown: ` +# Der Bauplan von V + +> "Stein erinnert sich an das, was Eisen vergisst." + +**V** kam während der *Großen Expansion* ins Tal. Ursprünglich ein einfacher Maurer, stieg er nach dem Entwurf des **Aquädukt-Systems** schnell in den Rängen auf. + +### Bekannte Verbindungen +* Die Händlergilde +* Nördliche Entdecker + +### Aktuelles Ziel +Restaurierung des verfallenen Wachturms am östlichen Grat. + ` + }, + { + uuid: '8984c0b5-d912-4462-b189-c864fba4a1af', + username: 'DrKButz', + isOnline: false, + tags: ['#Bauunternehmer'], + stats: { + playtimeHours: 120, + level: 12, + role: 'Unternehmer', + organizationId: 'org-4' + }, + inventory: [ + { id: 'minecraft:redstone', name: 'Redstone Staub', count: 64, type: 'misc' }, + { id: 'minecraft:comparator', name: 'Komparator', count: 16, type: 'misc' }, + null, + { id: 'minecraft:book', name: 'Forschungsnotizen', count: 1, type: 'misc', nbtSummary: 'Seite 12: Logikgatter' }, + null, null, null, null, null + ], + storyMarkdown: ` +# Forschungslogbuch: + +Spezialisiert auf automatisierte Logistik. Gerüchten zufolge hat er eine Maschine gebaut, die Items schneller sortieren kann als jeder Mensch. + +* Sucht aktuell: Quarz +* Verkauft: Auto-Schmelzöfen + ` + }, + { + uuid: 'b3b84518-03a2-4b48-8551-448c3f7a7d77', + username: 'ceratic', + isOnline: true, + tags: ['#Bürger', '#Händler'], + stats: { + playtimeHours: 50, + level: 5, + role: 'Bürger', + organizationId: 'org-3' + }, + inventory: [], + storyMarkdown: 'Einfach nur eine einfache Bäckerin, die gutes Brot liebt.' + }, + { + uuid: '3c10008b-1eb2-4d89-8b8a-6d03c821eb09', + username: 'merkursun', + isOnline: true, + tags: ['#Bürger'], + stats: { + playtimeHours: 50, + level: 5, + role: 'Bürger', + organizationId: 'org-4' + }, + inventory: [], + storyMarkdown: 'Einfach nur eine einfache Bäckerin, die gutes Brot liebt.' + } +]; + +export const MOCK_ORGS: Organization[] = [ + /* { + id: 'org-2', + name: 'Butz Building GmbH', + type: 'Company', + description: 'Baugewerbe.', + memberCount: 1, + status: 'active' + },*/ + { + id: 'org-3', + name: 'Provisorium Null', + type: 'City', + description: 'Die erste Siedlung, eingebettet zwischen den Zwillingsgipfeln. Bekannt für das geschäftige Marktviertel und die alten Steinmauern.', + memberCount: 6, + status: 'active', + mayor: '', + establishedYear: 'Day 0', + bannerUrl: 'images/screenshots/2025-12-28_01.11.10.png', + gallery: [ + 'images/screenshots/2025-12-28_01.12.07.png', // Market + 'images/screenshots/2025-12-28_01.12.17.png', // Castle + 'images/screenshots/2025-12-28_01.12.24.png' // Walls + ], + cityStats: { + //taxRate: 3.5, + biome: 'Ebene / Gebirge', + //defenseRating: 8, + government: 'Feudalmonarchie', + specialty: 'Handel & Stein' + } + }, + { + id: 'org-4', + name: 'Sakura', + type: 'City', + description: 'Eine dunkle, biolumineszente Hafenstadt in den tiefen Höhlen. Heimat von Schmugglern und Händlern seltener Mineralien.', + memberCount: 2, + status: 'active', + mayor: 'Kampfzwerk', + establishedYear: 'Ära 2, Jahr 10', + bannerUrl: 'images/screenshots/2025-12-28_01.11.32.png', + gallery: [ + 'images/screenshots/2025-12-28_01.11.38.png', + 'images/screenshots/2025-12-28_01.11.44.png' + ], + cityStats: { + taxRate: 15.0, + biome: 'Deep Dark / Lush Caves', + defenseRating: 4, + government: 'Syndikat', + specialty: 'Schmuggelware & Erze' + } + }, +]; + +export const MOCK_PROJECTS: Project[] = [ + { + id: 'ven-1', + title: 'DrkButz Architektur & Mauerwerk', + description: 'Führendes Architekturbüro, spezialisiert auf gotische Strukturen und Verteidigungsmauern. Wir entwerfen dein Vermächtnis.', + category: 'Enterprise', + status: 'active', + progress: 85, // Reputation + owner: 'DrKButz', + employees: [], + hiring: false, + foundedDate: 'Zyklus 12', + associatedOrgId: 'org-4', // Old Haven + bannerUrl: 'images/screenshots/2025-12-28_01.11.49.png', + shopCatalog: [ + { id: 's2', name: 'Geländevermessung', description: '1 Stunde Beratung zur Geländevorbereitung.', price: 20, currency: 'Gold', stock: 10, type: 'service' }, + { id: 's3', name: 'Mauerbau', description: 'Wir bauen deine Außenmauern. Preis pro Chunk.', price: 100, currency: 'Gold', stock: 5, type: 'service', materialsRequired: 'Kunde stellt Steinziegel' }, + { id: 's4', name: 'Terraforming', description: 'Einebnung und Landschaftsbau.', price: 75, currency: 'Gold', stock: 2, type: 'service' } + ] + }, + { + id: 'ven-5', + title: 'Tinas Tavern', + description: 'Täglich frisches Brot zum Spawn-Markt geliefert. Lieferant der königlichen Garde.', + category: 'Enterprise', + status: 'active', + progress: 98, + owner: 'kaiwastoshort', + employees: [], + hiring: false, + associatedOrgId: 'org-3', + bannerUrl: 'images/screenshots/tinas.png', + shopCatalog: [ + { id: 'f1', name: 'Redstone Block', description: '', price: 2, currency: 'Dias', stock: 45, type: 'item' }, + { id: 'f2', name: 'Honey', description: '', price: 5, currency: 'Dias', stock: 12, type: 'item' }, + { id: 'f3', name: 'Slime', description: '', price: 10, currency: 'Dias', stock: 4, type: 'item' }, + { id: 'f4', name: 'Bier', description: '', price: 3, currency: 'Dias', stock: 20, type: 'item' } + ] + }, +]; \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..3b30ea4 --- /dev/null +++ b/index.html @@ -0,0 +1,74 @@ + + + + + + Projekt: Vollidion | Server Plattform + + + + + + + + + +
+ + \ No newline at end of file diff --git a/index.tsx b/index.tsx new file mode 100644 index 0000000..6ca5361 --- /dev/null +++ b/index.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +const rootElement = document.getElementById('root'); +if (!rootElement) { + throw new Error("Could not find root element to mount to"); +} + +const root = ReactDOM.createRoot(rootElement); +root.render( + + + +); \ No newline at end of file diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..ed7465c --- /dev/null +++ b/metadata.json @@ -0,0 +1,5 @@ +{ + "name": "Obsidian | RP Plattform", + "description": "Eine professionelle, SaaS-ähnliche Oberfläche für Minecraft-Rollenspiel-Server. Bietet Inventar-Visualisierung, Markdown-Story-Rendering und eine neutrale, hochwertige Dark-Mode-Ästhetik.", + "requestFramePermissions": [] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..f3d0435 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "obsidian-|-rp-plattform", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.3", + "react-dom": "^19.2.3" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } +} diff --git a/pages/Cities.tsx b/pages/Cities.tsx new file mode 100644 index 0000000..ab17c46 --- /dev/null +++ b/pages/Cities.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { MOCK_ORGS } from '../constants'; +import { Organization } from '../types'; +import { Icons } from '../components/IconSet'; + +interface CitiesProps { + onSelectCity: (id: string) => void; +} + +const CityCard = ({ city, onClick }: { city: Organization; onClick: () => void }) => ( +
+ {/* Background Image */} +
+ {city.name} +
+
+ + {/* Content */} +
+
+
+
{city.establishedYear || 'Unbekannte Ära'}
+

{city.name}

+

+ {city.description} +

+
+ +
+
+ + {city.memberCount} Bürger +
+
+ {city.status} +
+
+
+
+
+); + +const Cities: React.FC = ({ onSelectCity }) => { + const cities = MOCK_ORGS.filter(org => org.type === 'City'); + + return ( +
+
+

Große Siedlungen

+

Entdecke die blühenden Zentren der Zivilisation im Obsidian-Tal.

+
+ +
+ {cities.map(city => ( + onSelectCity(city.id)} /> + ))} +
+ + {cities.length === 0 && ( +
+

Noch keine Städte gegründet.

+
+ )} +
+ ); +}; + +export default Cities; \ No newline at end of file diff --git a/pages/CityProfile.tsx b/pages/CityProfile.tsx new file mode 100644 index 0000000..2b2e7b7 --- /dev/null +++ b/pages/CityProfile.tsx @@ -0,0 +1,247 @@ +import React, { useState } from 'react'; +import { Organization, Project, Player } from '../types'; +import { MOCK_PLAYERS, MOCK_PROJECTS } from '../constants'; +import { Icons } from '../components/IconSet'; + +interface CityProfileProps { + city: Organization; + onBack: () => void; + backLabel?: string; + onSelectPlayer: (id: string) => void; + onSelectProject: (id: string) => void; +} + +const CityProfile: React.FC = ({ + city, + onBack, + backLabel = 'Zurück', + onSelectPlayer, + onSelectProject +}) => { + const [activeTab, setActiveTab] = useState<'overview' | 'residents' | 'ventures'>('overview'); + + const residents = MOCK_PLAYERS.filter(p => p.stats.organizationId === city.id); + const ventures = MOCK_PROJECTS.filter(p => p.associatedOrgId === city.id); + + return ( +
+ + + {/* Hero Header */} +
+ {city.name} +
+ +
+
+
+ {city.establishedYear && ( +
+ GEGR. {city.establishedYear} +
+ )} +

{city.name}

+
+ + {city.memberCount} Mitglieder + + +
{city.status} + +
+
+ {city.mayor && ( +
+
Aktueller Anführer
+
+ + {city.mayor} +
+
+ )} +
+
+
+ + {/* Navigation Tabs */} +
+ + + +
+ + {/* Content Area */} +
+ {activeTab === 'overview' && ( +
+
+
+

+ Über {city.name} +

+

+ {city.description} +

+
+ + {city.gallery && city.gallery.length > 0 && ( +
+

Bildarchiv

+
+ {city.gallery.map((url, idx) => ( +
+ {`Gallery +
+
+ ))} +
+
+ )} +
+ + {city.cityStats && ( +
+

+ Stadt-Statistiken +

+
+ + {/* Tax Rate */} +
+
+
+ +
+ Steuersatz +
+
+ {city.cityStats.taxRate}% +
Pro Transaktion
+
+
+ +
+ + {/* Defense */} +
+
+
+
+ +
+ Verteidigungswert +
+ {city.cityStats.defenseRating}/10 +
+
+
+
+
+ +
+ + {/* Gov Type */} +
+
+
+ +
+ Regierungsform +
+ {city.cityStats.government} +
+ +
+ + {/* Biome */} +
+ Biom + {city.cityStats.biome} +
+
+ Spezialität + {city.cityStats.specialty} +
+
+
+ )} +
+ )} + + {activeTab === 'residents' && ( +
+ {residents.map(player => ( +
onSelectPlayer(player.uuid)} + className="bg-surface border border-border p-4 rounded-xl flex items-center gap-4 hover:border-accentInfo/50 transition-colors cursor-pointer group hover:shadow-card hover:bg-surfaceHighlight/30" + > +
+ {player.username.charAt(0)} +
+
+
{player.username}
+
{player.stats.role}
+
+
+ ))} + {residents.length === 0 &&
Keine Bewohner öffentlich registriert.
} +
+ )} + + {activeTab === 'ventures' && ( +
+ {ventures.map(project => ( +
onSelectProject(project.id)} + className="bg-surface border border-border rounded-xl p-5 hover:border-accentInfo/50 transition-all cursor-pointer group hover:shadow-card hover:bg-surfaceHighlight/30" + > +
+ + {project.category} + + {project.hiring && ( + STELLEN + )} +
+

{project.title}

+

{project.description}

+
+ Geführt von {project.owner} +
{project.progress}% Fortschr.
+
+
+ ))} + {ventures.length === 0 &&
Keine aktiven Unternehmen in dieser Organisation.
} +
+ )} +
+
+ ); +}; + +export default CityProfile; \ No newline at end of file diff --git a/pages/Dashboard.tsx b/pages/Dashboard.tsx new file mode 100644 index 0000000..9e2d55b --- /dev/null +++ b/pages/Dashboard.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { MOCK_PLAYERS, MOCK_PROJECTS, MOCK_ORGS } from '../constants'; +import { Icons } from '../components/IconSet'; + +const StatCard = ({ label, value, trend, icon: Icon }: any) => ( +
+
+
+ +
+ {trend && ( + 0 ? 'bg-accentSuccess/10 text-accentSuccess' : 'bg-accentWarn/10 text-accentWarn' + }`}> + {trend > 0 ? '+' : ''}{trend}% + + )} +
+
{value}
+
{label}
+
+); + +const ProjectCard = ({ project }: { project: any }) => ( +
+
+
+
{project.title}
+
Inhaber: {project.owner}
+
+
+
+
+
+
+
{project.progress}%
+
+); + +const Dashboard: React.FC = () => { + return ( +
+ {/* Intro Section */} +
+
+

Live-Telemetrie

+

Tal-Übersicht

+
+

+ Echtzeit-Datenaggregation aus dem Zentralarchiv. +

+
+ + {/* KPI Grid */} +
+ + p.status === 'active' || p.status === 'recruiting').length} trend={5} icon={Icons.Layers} /> + +
+ + {/* Content Grid */} +
+ {/* Projects List - Wider */} +
+
+

Top Unternehmen

+ +
+
+ {MOCK_PROJECTS.slice(0, 5).map(p => )} +
+
+ + {/* Decree / News - Narrower */} +
+

Offizieller Erlass

+
+ {/* Decorative background element */} +
+ +
+
+ NEU + VOR 12 MIN +
+ +
+

+ "Im Auftrag des Bürgermeisters ist jeglicher Handel innerhalb der inneren Mauern für das kommende Fest steuerbefreit." +

+

+ Bürger werden ermutigt, ihre Stände im Marktviertel aufzubauen, bevor der Mond am 14. Tag aufgeht. +

+
+
+ +
+ #Wirtschaft + #Events +
+
+
+
+
+ ); +}; + +export default Dashboard; \ No newline at end of file diff --git a/pages/DatapackGenerator.tsx b/pages/DatapackGenerator.tsx new file mode 100644 index 0000000..f2a9169 --- /dev/null +++ b/pages/DatapackGenerator.tsx @@ -0,0 +1,273 @@ +import React, { useState } from 'react'; +import { Icons } from '../components/IconSet'; + +interface FileBlock { + name: string; + path: string; + language: string; + content: string; +} + +const DatapackGenerator: React.FC = () => { + const [activeFile, setActiveFile] = useState('menu.mcfunction'); + + const files: FileBlock[] = [ + { + name: 'pack.mcmeta', + path: 'obsidian_core/pack.mcmeta', + language: 'json', + content: `{ + "pack": { + "pack_format": 15, + "description": "Obsidian Core - Verwaltung (v2.4 DE)" + } +}` + }, + { + name: 'load.json', + path: 'obsidian_core/data/minecraft/tags/functions/load.json', + language: 'json', + content: `{ + "values": [ + "obsidian:load" + ] +}` + }, + { + name: 'tick.json', + path: 'obsidian_core/data/minecraft/tags/functions/tick.json', + language: 'json', + content: `{ + "values": [ + "obsidian:system/tick" + ] +}` + }, + { + name: 'load.mcfunction', + path: 'obsidian_core/data/obsidian/functions/load.mcfunction', + language: 'mcfunction', + content: `# Initialize Scoreboards +scoreboard objectives add obs_action trigger +scoreboard objectives add obs_id dummy + +# Initialize Storage +data modify storage obsidian:main selected set value {id:"Keine", type:"None"} +tellraw @a {"text":"[Obsidian] System geladen. Trigger registriert.","color":"green"}` + }, + { + name: 'tick.mcfunction', + path: 'obsidian_core/data/obsidian/functions/system/tick.mcfunction', + language: 'mcfunction', + content: `# Enable trigger for all players +execute as @a run scoreboard players enable @s obs_action + +# Detect triggers +execute as @a[scores={obs_action=1..}] run function obsidian:system/handle_trigger` + }, + { + name: 'menu.mcfunction', + path: 'obsidian_core/data/obsidian/functions/ui/menu.mcfunction', + language: 'mcfunction', + content: `tellraw @s ["",{"text":"\\n=== OBSIDIAN MENÜ ===\\n","color":"dark_aqua","bold":true}] + +tellraw @s ["",{"text":"Neu erstellen:","color":"gray"}] +tellraw @s [{"text":"[+ Stadt] ","color":"gold","clickEvent":{"action":"run_command","value":"/trigger obs_action set 10"},"hoverEvent":{"action":"show_text","value":"Eine neue Stadt gründen"}}] +tellraw @s [{"text":" [+ Unternehmung] ","color":"green","clickEvent":{"action":"run_command","value":"/trigger obs_action set 20"},"hoverEvent":{"action":"show_text","value":"Ein neues Unternehmen registrieren"}}] + +tellraw @s ["",{"text":"\\n\\nZielauswahl:","color":"gray"}] +tellraw @s [{"text":"[Wähle per Name] ","color":"aqua","clickEvent":{"action":"run_command","value":"/trigger obs_action set 30"},"hoverEvent":{"action":"show_text","value":"Halte ein Item mit dem exakten Namen um den Eintrag zu wählen."}}] + +tellraw @s ["",{"text":"\\nAktuell gewählt: ","color":"gray"},{"nbt":"selected.id","storage":"obsidian:main","color":"light_purple"}] + +# Edit Controls +tellraw @s ["",{"text":"Bearbeiten: ","color":"gray"}] +tellraw @s [{"text":"[Umbenennen] ","color":"white","clickEvent":{"action":"run_command","value":"/trigger obs_action set 60"},"hoverEvent":{"action":"show_text","value":"Namen des Eintrags ändern"}}] +tellraw @s [{"text":"[Beschr. ändern] ","color":"white","clickEvent":{"action":"run_command","value":"/trigger obs_action set 40"},"hoverEvent":{"action":"show_text","value":"Beschreibungstext aktualisieren"}}] +tellraw @s [{"text":"[Status ändern] ","color":"white","clickEvent":{"action":"run_command","value":"/trigger obs_action set 50"},"hoverEvent":{"action":"show_text","value":"Wechseln zwischen Aktiv/Rekrutierend/Geschlossen"}}] + +tellraw @s ["",{"text":"\\n\\n[?] Hilfe","color":"yellow","clickEvent":{"action":"run_command","value":"/trigger obs_action set 9"},"hoverEvent":{"action":"show_text","value":"Spieleranleitung lesen"}}]` + }, + { + name: 'handle_trigger.mcfunction', + path: 'obsidian_core/data/obsidian/functions/system/handle_trigger.mcfunction', + language: 'mcfunction', + content: `# --- HANDLE TRIGGERS --- + +# 1: Open Menu +execute if score @s obs_action matches 1 run function obsidian:ui/menu +# 9: Help +execute if score @s obs_action matches 9 run function obsidian:ui/help + +# --- CREATION --- +# 10: Prompt City +execute if score @s obs_action matches 10 run tellraw @s ["",{"text":"[Neue Stadt] ","color":"gold"},{"text":"Item in Stadt-Name umbenennen. Halten & ","color":"gray"},{"text":"[KLICK]","color":"green","bold":true,"clickEvent":{"action":"run_command","value":"/trigger obs_action set 11"}}] +# 11: Create City +execute if score @s obs_action matches 11 run tellraw @a [{"text":"Neue Stadt gegründet: ","color":"gold"},{"nbt":"SelectedItem.tag.display.Name","entity":"@s"}] +execute if score @s obs_action matches 11 run data modify storage obsidian:db Cities append value {name:"New",status:"active"} +execute if score @s obs_action matches 11 run data modify storage obsidian:db Cities[-1].name set from entity @s SelectedItem.tag.display.Name + +# 20: Prompt Venture +execute if score @s obs_action matches 20 run tellraw @s ["",{"text":"[Neues Unternehmen] ","color":"green"},{"text":"Item in Firmen-Name umbenennen. Halten & ","color":"gray"},{"text":"[KLICK]","color":"green","bold":true,"clickEvent":{"action":"run_command","value":"/trigger obs_action set 21"}}] +# 21: Create Venture +execute if score @s obs_action matches 21 run tellraw @a [{"text":"Neues Unternehmen registriert: ","color":"green"},{"nbt":"SelectedItem.tag.display.Name","entity":"@s"}] +execute if score @s obs_action matches 21 run data modify storage obsidian:db Ventures append value {name:"New",status:"active"} +execute if score @s obs_action matches 21 run data modify storage obsidian:db Ventures[-1].name set from entity @s SelectedItem.tag.display.Name + +# --- SELECTION --- +# 30: Select Target +execute if score @s obs_action matches 30 run data modify storage obsidian:main selected.id set from entity @s SelectedItem.tag.display.Name +execute if score @s obs_action matches 30 run tellraw @s [{"text":"Ziel erfasst: ","color":"aqua"},{"nbt":"selected.id","storage":"obsidian:main"}] + +# --- EDITING --- + +# 40/41: Description +execute if score @s obs_action matches 40 run tellraw @s ["",{"text":"[Beschr. ändern] ","color":"light_purple"},{"text":"Item in neue Beschreibung umbenennen. Halten & ","color":"gray"},{"text":"[UPDATE]","color":"yellow","bold":true,"clickEvent":{"action":"run_command","value":"/trigger obs_action set 41"}}] +execute if score @s obs_action matches 41 run tellraw @a [{"text":"LOG|UPDATE_DESC|","color":"dark_gray"},{"nbt":"selected.id","storage":"obsidian:main"},{"text":"|"},{"nbt":"SelectedItem.tag.display.Name","entity":"@s"}] + +# 50: Toggle Status (Active -> Recruiting -> Closed) +execute if score @s obs_action matches 50 run tellraw @a [{"text":"LOG|CYCLE_STATUS|","color":"dark_gray"},{"nbt":"selected.id","storage":"obsidian:main"}] + +# 60/61: Rename Entry +execute if score @s obs_action matches 60 run tellraw @s ["",{"text":"[Umbenennen] ","color":"red"},{"text":"Item in NEUEN Namen umbenennen. Halten & ","color":"gray"},{"text":"[BESTÄTIGEN]","color":"red","bold":true,"clickEvent":{"action":"run_command","value":"/trigger obs_action set 61"}}] +execute if score @s obs_action matches 61 run tellraw @a [{"text":"LOG|RENAME_ENTRY|","color":"dark_gray"},{"nbt":"selected.id","storage":"obsidian:main"},{"text":"|"},{"nbt":"SelectedItem.tag.display.Name","entity":"@s"}] +execute if score @s obs_action matches 61 run data modify storage obsidian:main selected.id set from entity @s SelectedItem.tag.display.Name + +# Reset +scoreboard players set @s obs_action 0 +scoreboard players enable @s obs_action` + }, + { + name: 'help.mcfunction', + path: 'obsidian_core/data/obsidian/functions/ui/help.mcfunction', + language: 'mcfunction', + content: `tellraw @s ["",{"text":"\\n=== OBSIDIAN ANLEITUNG ===\\n","color":"dark_aqua","bold":true}] +tellraw @s [{"text":"[!] ","color":"yellow"},{"text":"Texteingabe ohne Befehle:","color":"gray"}] +tellraw @s [{"text":"\\n1. Item umbenennen: ","color":"white","bold":true},{"text":"Benenne ein Papier (oder Item) im Amboss um (Dein Text).","color":"gray"}] +tellraw @s [{"text":"\\n2. Item halten: ","color":"white","bold":true},{"text":"Halte das Item in der Haupthand.","color":"gray"}] +tellraw @s [{"text":"\\n3. Aktion klicken: ","color":"white","bold":true},{"text":"Klicke [Bestätigen] oder [Wählen] im Menü.","color":"gray"}] +tellraw @s [{"text":"\\nDas System liest den Item-Namen als Eingabe.","color":"gray","italic":true}]` + }, + { + name: 'export.mcfunction', + path: 'obsidian_core/data/obsidian/functions/system/export.mcfunction', + language: 'mcfunction', + content: `# Admin Only Export +tellraw @a {"text":"JSON_EXPORT_START|DB_DUMP|", "color":"white"} +tellraw @a {"nbt":"Cities","storage":"obsidian:db"} +tellraw @a {"nbt":"Ventures","storage":"obsidian:db"}` + } + ]; + + const currentFile = files.find(f => f.name === activeFile); + + return ( +
+ {/* Intro Sidebar */} +
+
+
+ v2.4 Non-OP (DE) + Trigger System +
+

Datapack Generator

+

+ Non-OP Management System. Nutzt Item-Umbenennung als sichere Eingabemethode für Spielertexte. +

+
+ + {/* PLAYER GUIDE SECTION */} +
+
+

+ + Spieler Anleitung +

+
+
+

+ Verteile diese Anleitung an deine Spieler. Da normale Spieler keine Befehle nutzen können, nutzt dieses Pack ein immersives "Item Key" System. +

+ +
+
+
1
+
+ Erstellen +

Klicke [+ Unternehmung], benenne ein Item zu "Mein Shop", halte es, und klicke den Bestätigungs-Link.

+
+
+
+
2
+
+ Zum Bearbeiten wählen +

Halte ein Item mit dem exakten Namen deiner Firma. Klicke [Wähle per Name]. Das Menü visiert nun diesen Eintrag an.

+
+
+
+
3
+
+ Modifizieren +

+ Nutze [Umbenennen] oder [Beschr. ändern]. Du wirst aufgefordert, ein Item zum NEUEN Wert umzubenennen und zu bestätigen. +

+
+
+
+
+
+ + {/* Admin Note */} +
+ Wichtig: + Ich habe load.json hinzugefügt, um sicherzustellen, dass das Scoreboard automatisch erstellt wird. Bitte führe /reload nach der Installation aus. +
+
+ + {/* Code Viewer */} +
+ {/* Tabs */} +
+ {files.map(file => ( + + ))} +
+ + {/* Editor Content */} +
+
+ +
+ + {currentFile && ( +
+
+ Dateipfad: {currentFile.path} +
+
+                        {currentFile.content}
+                    
+
+ )} +
+
+
+ ); +}; + +export default DatapackGenerator; \ No newline at end of file diff --git a/pages/Organizations.tsx b/pages/Organizations.tsx new file mode 100644 index 0000000..7147488 --- /dev/null +++ b/pages/Organizations.tsx @@ -0,0 +1,101 @@ +import React, { useState } from 'react'; +import { MOCK_ORGS } from '../constants'; +import { Organization } from '../types'; +import { Icons } from '../components/IconSet'; + +const OrgCard = ({ org, onClick }: { org: Organization; onClick: () => void }) => ( +
+
+
+ {org.name.charAt(0)} +
+ + {org.status} + +
+ +

+ {org.name} +

+
{org.type}
+ +

+ {org.description} +

+ +
+
+ + {org.memberCount} Mitglieder +
+
+
+); + +const Organizations: React.FC<{ onSelectOrg: (id: string) => void }> = ({ onSelectOrg }) => { + const [filter, setFilter] = useState<'all' | Organization['type']>('all'); + + const filteredOrgs = MOCK_ORGS.filter(org => + filter === 'all' ? true : org.type === filter + ); + + const tabs = [ + { id: 'all', label: 'Alle Organisationen' }, + { id: 'City', label: 'Städte' }, + { id: 'Guild', label: 'Gilden' }, + { id: 'Company', label: 'Firmen' }, + ]; + + return ( +
+
+
+

Organisationen

+

Offizielle Fraktionen, Städte und registrierte Gilden.

+
+ +
+ +
+ {tabs.map(tab => ( + + ))} +
+ +
+ {filteredOrgs.map(org => ( + onSelectOrg(org.id)} /> + ))} +
+ + {filteredOrgs.length === 0 && ( +
+

Keine Organisationen gefunden.

+
+ )} +
+ ); +}; + +export default Organizations; \ No newline at end of file diff --git a/pages/PlayerProfile.tsx b/pages/PlayerProfile.tsx new file mode 100644 index 0000000..8580280 --- /dev/null +++ b/pages/PlayerProfile.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { Player } from '../types'; +import { MOCK_ORGS } from '../constants'; +import InventoryGrid from '../components/InventoryGrid'; +import { Icons } from '../components/IconSet'; + +interface PlayerProfileProps { + player: Player; + onBack: () => void; +} + +const PlayerProfile: React.FC = ({ player, onBack }) => { + const playerOrg = MOCK_ORGS.find(o => o.id === player.stats.organizationId); + + // Simple markdown renderer replacement for demo purposes + // In production, use 'react-markdown' + const renderMarkdown = (text: string) => { + return text.split('\n').map((line, i) => { + if (line.startsWith('# ')) return

{line.replace('# ', '')}

; + if (line.startsWith('### ')) return

{line.replace('### ', '')}

; + if (line.startsWith('> ')) return
{line.replace('> ', '')}
; + if (line.startsWith('* ')) return
  • {line.replace('* ', '')}
  • ; + return

    {line}

    ; + }); + }; + + return ( +
    + + + {/* Header */} +
    +
    +
    + +
    + +
    +
    +

    {player.username}

    + {player.isOnline && ( + + + Online + + )} +
    + +
    + {player.tags.map(tag => ( + + {tag} + + ))} +
    + +
    +
    + + {player.stats.playtimeHours}h +
    +
    + + Lvl {player.stats.level} +
    +
    +
    +
    +
    + +
    + {/* Left Col: Inventory */} +
    + {/* */} + +
    +

    Zugehörigkeit

    +
    +
    + {playerOrg ? playerOrg.name.charAt(0) : } +
    +
    +
    {player.stats.role}
    +
    + {playerOrg ? ( + {playerOrg.name} + ) : ( + 'Freiberufler / Keine Zugehörigkeit' + )} +
    +
    +
    + {playerOrg && ( +
    + {playerOrg.type} • {playerOrg.description} +
    + )} +
    +
    + + {/* Right Col: Story */} +
    +
    +
    +

    + Charakter-Journal +

    + Markdown Rendered +
    +
    + {renderMarkdown(player.storyMarkdown)} +
    +
    +
    +
    +
    + ); +}; + +export default PlayerProfile; \ No newline at end of file diff --git a/pages/ProjectProfile.tsx b/pages/ProjectProfile.tsx new file mode 100644 index 0000000..589c481 --- /dev/null +++ b/pages/ProjectProfile.tsx @@ -0,0 +1,350 @@ +import React, { useState } from 'react'; +import { Project, ShopItem } from '../types'; +import { MOCK_ORGS, MOCK_PLAYERS } from '../constants'; +import { Icons, ItemIcon } from '../components/IconSet'; + +interface ProjectProfileProps { + project: Project; + onBack: () => void; + onSelectPlayer: (id: string) => void; + onSelectOrg: (id: string) => void; +} + +const ProjectProfile: React.FC = ({ + project, + onBack, + onSelectPlayer, + onSelectOrg +}) => { + const [activeTab, setActiveTab] = useState<'overview' | 'shop'>('overview'); + + const org = project.associatedOrgId ? MOCK_ORGS.find(o => o.id === project.associatedOrgId) : null; + const ownerPlayer = MOCK_PLAYERS.find(p => p.username === project.owner); + const hasShop = project.shopCatalog && project.shopCatalog.length > 0; + + // Group shop items + const services = project.shopCatalog?.filter(i => i.type === 'service') || []; + const products = project.shopCatalog?.filter(i => i.type !== 'service') || []; + + return ( +
    + + + {/* Banner Header */} +
    + {project.title} + + {/* Decorative elements for Black Market */} + {project.category === 'Black Market' && ( +
    + )} + +
    + +
    +
    +
    +
    + + {project.category} + + {project.status === 'active' && ( + + + AKTIV + + )} + {project.hiring && ( + + + STELLEN + + )} +
    +

    {project.title}

    +
    + ownerPlayer && onSelectPlayer(ownerPlayer.uuid)} + > +
    + {project.owner.charAt(0)} +
    + Inhaber: {project.owner} +
    + {project.foundedDate && ( + + Gegr. {project.foundedDate} + + )} +
    +
    + + {hasShop && ( + + )} +
    +
    +
    + + {/* Navigation */} +
    + + {hasShop && ( + + )} +
    + + {/* Content */} +
    + {activeTab === 'overview' && ( +
    +
    +
    +

    Manifest

    +

    + {project.description} +

    +
    + + {/* Project Portfolio / Gallery */} + {project.gallery && project.gallery.length > 0 && ( +
    +

    + Portfolio +

    +
    + {project.gallery.map((url, idx) => ( +
    + {`Portfolio +
    +
    + ))} +
    +
    + )} + +
    +

    + Personal +

    +
    + {project.employees.map((emp, idx) => { + const empPlayer = MOCK_PLAYERS.find(p => p.username === emp); + return ( +
    empPlayer && onSelectPlayer(empPlayer.uuid)} + className={`flex items-center gap-3 bg-surface border border-border p-3 rounded-lg ${empPlayer ? 'cursor-pointer hover:border-accentInfo/50 hover:bg-surfaceHighlight/30 transition-all group' : ''}`} + > +
    + {emp.charAt(0)} +
    + {emp} +
    + ); + })} + {project.employees.length === 0 && ( +
    Kein weiteres Personal registriert.
    + )} +
    +
    +
    + + {/* Right Column: Statistics */} +
    +
    +

    + Unternehmensdaten +

    +
    + + {/* Industry */} +
    + Branche + {project.category} +
    +
    + + {/* Founded */} +
    + Gegründet + {project.foundedDate || 'N/A'} +
    +
    + + {/* Reputation */} +
    +
    + Ruf + {project.progress}/100 +
    +
    +
    +
    +
    +
    + + {/* Workforce */} +
    + Belegschaft +
    + + {project.employees.length + 1} +
    +
    + + {/* Headquarters Link */} + {org && ( +
    + Hauptsitz +
    onSelectOrg(org.id)} + className="flex items-center gap-3 bg-surfaceHighlight/50 p-2 rounded-lg border border-white/5 cursor-pointer hover:border-accentInfo/50 hover:bg-surfaceHighlight transition-all group" + > +
    + {org.name.charAt(0)} +
    + {org.name} +
    +
    + )} + +
    +
    +
    +
    + )} + + {activeTab === 'shop' && project.shopCatalog && ( +
    + + {/* Services Section */} + {services.length > 0 && ( +
    +
    +
    + +
    +
    +

    Dienstleistungen & Verträge

    +

    Fachkräfte für Spezialaufträge anheuern.

    +
    +
    + +
    + {services.map(item => ( +
    +
    +

    {item.name}

    + + {item.price} {item.currency} + +
    +

    {item.description}

    + + {item.materialsRequired && ( +
    + Materialanforderungen: + {item.materialsRequired} +
    + )} + + +
    + ))} +
    +
    + )} + + {/* Products Section */} + {products.length > 0 && ( +
    +
    +
    + +
    +
    +

    Produktkatalog

    +

    Items, Bücher und Baupläne zur sofortigen Abholung.

    +
    +
    + +
    + {products.map(item => ( +
    +
    +
    + {item.type === 'blueprint' ? : + } +
    +
    0 ? 'bg-green-500/10 text-green-400 border-green-500/20' : 'bg-red-500/10 text-red-400 border-red-500/20' + }`}> + {item.stock > 0 ? `${item.stock} auf Lager` : 'Ausverkauft'} +
    +
    + +

    {item.name}

    +

    {item.description}

    + +
    +
    + Preis + + {item.price} {item.currency} + +
    + +
    +
    + ))} +
    +
    + )} +
    + )} +
    +
    + ); +}; + +export default ProjectProfile; \ No newline at end of file diff --git a/pages/Projects.tsx b/pages/Projects.tsx new file mode 100644 index 0000000..4b81048 --- /dev/null +++ b/pages/Projects.tsx @@ -0,0 +1,177 @@ +import React, { useState } from 'react'; +import { MOCK_PROJECTS } from '../constants'; +import { Project } from '../types'; +import { Icons } from '../components/IconSet'; + +interface ProjectsProps { + onSelectProject?: (id: string) => void; +} + +const StatusBadge = ({ status, hiring }: { status: Project['status'], hiring: boolean }) => { + const styles = { + 'active': 'bg-accentInfo/10 text-accentInfo border-accentInfo/20', + 'recruiting': 'bg-accentSuccess/10 text-accentSuccess border-accentSuccess/20', + 'private': 'bg-textMuted/10 text-textMuted border-textMuted/20', + 'completed': 'bg-textMuted/10 text-textMuted border-textMuted/20', + }; + + const labels = { + 'active': 'Aktives Geschäft', + 'recruiting': 'Offener Story-Arc', + 'private': 'Privat / Versteckt', + 'completed': 'Archiviert' + }; + + return ( +
    + + {labels[status]} + + {hiring && ( + + Stellen + + )} +
    + ); +}; + +const VentureCard = ({ project, onClick }: { project: Project, onClick?: () => void }) => ( +
    + {/* Background accent for certain types */} + {project.category === 'Black Market' && ( +
    + )} + +
    +
    + {project.category} +
    + +
    + +

    + {project.title} +

    + +
    + Inhaber + +
    + {project.owner} +
    +
    + +

    + {project.description} +

    + +
    + {/* Reputation / Progress Bar */} +
    +
    + + {project.category === 'Story Arc' ? 'Story Fortschritt' : 'Ruf'} + + {project.progress}% +
    +
    +
    +
    +
    + +
    +
    + + {project.employees.length} Mitglieder +
    +
    + {project.shopCatalog && project.shopCatalog.length > 0 && ( +
    + + Shop +
    + )} + {project.foundedDate && Gegr. {project.foundedDate}} +
    +
    +
    +
    +); + +const Projects: React.FC = ({ onSelectProject }) => { + const [filter, setFilter] = useState<'all' | Project['status']>('all'); + + const filteredProjects = MOCK_PROJECTS.filter(p => + filter === 'all' ? true : p.status === filter + ); + + const tabs = [ + { id: 'all', label: 'Alle Unternehmen' }, + { id: 'active', label: 'Aktive Firmen' }, + { id: 'recruiting', label: 'Story Arcs' }, + { id: 'private', label: 'Privat' }, + ]; + + return ( +
    +
    +
    +

    Unternehmen & Projekte

    +

    Spielergeführte Firmen, aktive Rollenspiel-Stränge und Dienstleister.

    +
    + +
    + + {/* Filter Tabs */} +
    + {tabs.map(tab => ( + + ))} +
    + + {/* Grid */} +
    + {filteredProjects.map(project => ( + onSelectProject && onSelectProject(project.id)} + /> + ))} +
    + + {filteredProjects.length === 0 && ( +
    +

    Keine Unternehmen in dieser Kategorie gefunden.

    +
    + )} +
    + ); +}; + +export default Projects; \ No newline at end of file diff --git a/pages/SetupGuide.tsx b/pages/SetupGuide.tsx new file mode 100644 index 0000000..05b28f0 --- /dev/null +++ b/pages/SetupGuide.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { Icons } from '../components/IconSet'; + +const CodeBlock = ({ label, lang, code }: { label: string, lang: string, code: string }) => ( +
    +
    + + {label} + + {lang} +
    +
    +
    +        {code}
    +      
    + +
    +
    +); + +const SetupGuide: React.FC = () => { + return ( +
    +
    +

    Installationsanleitung

    +

    + Die Obsidian-Plattform nutzt einen leichtgewichtigen, serverunabhängigen Ansatz. + Der Minecraft-Server generiert Daten über Vanilla-Befehle, und ein einfaches Shell-Skript lädt diese auf den Webserver hoch. +

    +
    + +
    + Hinweis: Kein Python oder schweres Backend erforderlich. Funktioniert unter Windows (Batch) und Linux (Bash). +
    + + + + temp_extract.txt + +while read p; do + # Extract Name and JSON data (simplified regex) + # In production, use 'jq' if available, otherwise pure bash string manipulation + PLAYER_NAME=$(echo $p | cut -d'|' -f2) + JSON_DATA=$(echo $p | cut -d'|' -f3) + + echo "{ \"username\": \"$PLAYER_NAME\", \"inventory\": $JSON_DATA }" > "$WEB_DIR/$PLAYER_NAME.json" +done < temp_extract.txt + +# 2. Upload via lftp (High performance, parallel) +lftp -u $FTP_USER,$FTP_PASS $FTP_HOST < + + %FTP_SCRIPT% +echo myuser >> %FTP_SCRIPT% +echo mypassword >> %FTP_SCRIPT% +echo cd public_html/api/players >> %FTP_SCRIPT% +echo lcd %LOCAL_DIR% >> %FTP_SCRIPT% +echo mput *.json >> %FTP_SCRIPT% +echo disconnect >> %FTP_SCRIPT% +echo quit >> %FTP_SCRIPT% + +:: Run FTP +ftp -i -s:%FTP_SCRIPT% +del %FTP_SCRIPT% + +echo Sync Complete. +`} + /> +
    + ); +}; + +export default SetupGuide; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2c6eed5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2022", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "types": [ + "node" + ], + "moduleResolution": "bundler", + "isolatedModules": true, + "moduleDetection": "force", + "allowJs": true, + "jsx": "react-jsx", + "paths": { + "@/*": [ + "./*" + ] + }, + "allowImportingTsExtensions": true, + "noEmit": true + } +} \ No newline at end of file diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..9539255 --- /dev/null +++ b/types.ts @@ -0,0 +1,77 @@ +export interface Item { + id: string; + name: string; + count: number; + type: 'tool' | 'block' | 'consumable' | 'misc'; + rarity?: 'common' | 'uncommon' | 'rare' | 'epic'; + nbtSummary?: string; +} + +export interface PlayerStats { + playtimeHours: number; + level: number; + role: string; + organizationId?: string; +} + +export interface Player { + uuid: string; + username: string; + skinUrl?: string; // Placeholder in a real app + inventory: (Item | null)[]; + stats: PlayerStats; + storyMarkdown: string; + tags: string[]; + isOnline: boolean; +} + +export interface CityStats { + taxRate: number; + biome: string; + defenseRating: number; // 0-10 + government: string; + specialty: string; +} + +export interface Organization { + id: string; + name: string; + type: 'City' | 'Guild' | 'Company'; + description: string; + memberCount: number; + status: 'active' | 'archived'; + bannerUrl?: string; + gallery?: string[]; + establishedYear?: string; + mayor?: string; + cityStats?: CityStats; +} + +export interface ShopItem { + id: string; + name: string; + description?: string; + price: number; + currency: 'Gold' | 'Credits' | 'Barter' | 'Diamonds'; + stock: number; + type: 'item' | 'service' | 'blueprint'; + imageUrl?: string; + materialsRequired?: string; // e.g. "Customer provides Stone" +} + +export interface Project { + id: string; + title: string; + description: string; + category: 'Enterprise' | 'Service' | 'Story Arc' | 'Faction' | 'Black Market'; + status: 'active' | 'recruiting' | 'private' | 'completed'; + progress: number; // For story arcs completion or company reputation + owner: string; + employees: string[]; + hiring: boolean; + foundedDate?: string; + associatedOrgId?: string; // Links this project to a city or guild + shopCatalog?: ShopItem[]; + gallery?: string[]; + bannerUrl?: string; +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..ee5fb8d --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,23 @@ +import path from 'path'; +import { defineConfig, loadEnv } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, '.', ''); + return { + server: { + port: 3000, + host: '0.0.0.0', + }, + plugins: [react()], + define: { + 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), + } + } + }; +});