From 68b0ceb59378fe51469b716ea5493994b30f817c Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Mon, 27 Apr 2026 16:27:08 +0200 Subject: [PATCH] feat: add localized docs pages --- package-lock.json | 10 + package.json | 1 + src/index.css | 250 ++++++++++++++----- src/pages/DocsPage.tsx | 66 ----- src/pages/docs/DocsDocument.tsx | 51 ++++ src/pages/docs/DocsLanguageProvider.tsx | 25 ++ src/pages/docs/DocsLayout.tsx | 47 ++++ src/pages/docs/architecture/page.tsx | 14 ++ src/pages/docs/docsLanguageContext.ts | 11 + src/pages/docs/docsSections.ts | 33 +++ src/pages/docs/docsTranslations.ts | 258 ++++++++++++++++++++ src/pages/docs/features/page.tsx | 14 ++ src/pages/docs/page.tsx | 14 ++ src/pages/docs/target-architecture/page.tsx | 14 ++ src/pages/docs/useDocsLanguage.ts | 12 + src/router.tsx | 42 +++- 16 files changed, 735 insertions(+), 127 deletions(-) delete mode 100644 src/pages/DocsPage.tsx create mode 100644 src/pages/docs/DocsDocument.tsx create mode 100644 src/pages/docs/DocsLanguageProvider.tsx create mode 100644 src/pages/docs/DocsLayout.tsx create mode 100644 src/pages/docs/architecture/page.tsx create mode 100644 src/pages/docs/docsLanguageContext.ts create mode 100644 src/pages/docs/docsSections.ts create mode 100644 src/pages/docs/docsTranslations.ts create mode 100644 src/pages/docs/features/page.tsx create mode 100644 src/pages/docs/page.tsx create mode 100644 src/pages/docs/target-architecture/page.tsx create mode 100644 src/pages/docs/useDocsLanguage.ts diff --git a/package-lock.json b/package-lock.json index c566598..780e0ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@tanstack/react-router": "^1.168.25", "gsap": "^3.15.0", "lil-gui": "^0.21.0", + "lucide-react": "^1.11.0", "r3f-perf": "^7.2.3", "react": "^19.2.4", "react-dom": "^19.2.4", @@ -3331,6 +3332,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.11.0.tgz", + "integrity": "sha512-UOhjdztXCgdBReRcIhsvz2siIBogfv/lhJEIViCpLt924dO+GDms9T7DNoucI23s6kEPpe988m5N0D2ajnzb2g==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/maath": { "version": "0.10.8", "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz", diff --git a/package.json b/package.json index 0e98ff5..125d289 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@tanstack/react-router": "^1.168.25", "gsap": "^3.15.0", "lil-gui": "^0.21.0", + "lucide-react": "^1.11.0", "r3f-perf": "^7.2.3", "react": "^19.2.4", "react-dom": "^19.2.4", diff --git a/src/index.css b/src/index.css index 4ae30e8..98f7bec 100644 --- a/src/index.css +++ b/src/index.css @@ -1,6 +1,8 @@ +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); + :root { color-scheme: dark; - font-family: Inter; + font-family: "Helvetica Neue", Helvetica, Inter, Arial, sans-serif; } html, @@ -29,131 +31,257 @@ canvas { .docs-page { display: grid; - grid-template-columns: minmax(220px, 280px) minmax(0, 1fr); + grid-template-columns: 300px minmax(0, 1fr); width: 100vw; height: 100vh; overflow: hidden; - background: - radial-gradient( - circle at top left, - rgba(77, 146, 217, 0.18), - transparent 34rem - ), - #071019; - color: rgba(255, 255, 255, 0.88); + background: #050505; + color: #f4efe7; + font-family: "Helvetica Neue", Helvetica, Inter, Arial, sans-serif; } .docs-sidebar { - padding: 32px 24px; - border-right: 1px solid rgba(255, 255, 255, 0.12); - background: rgba(4, 7, 13, 0.72); + border-right: 2px solid #d8d0c4; + background: #050505; overflow-y: auto; } -.docs-sidebar h1 { - margin: 24px 0 18px; - font-size: 26px; - line-height: 1.1; +.docs-sidebar__header, +.docs-content__header { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 78px; + padding: 0 18px; + border-bottom: 2px solid #d8d0c4; +} + +.docs-sidebar__header h1, +.docs-content__header span { + margin: 0; + color: #f4efe7; + font-size: 21px; + font-weight: 700; + letter-spacing: -0.04em; } .docs-sidebar nav { display: grid; - gap: 10px; } -.docs-sidebar a, -.docs-back-link { - color: rgba(255, 255, 255, 0.78); +.docs-sidebar a { + color: #f4efe7; text-decoration: none; } -.docs-sidebar nav a { - padding: 10px 12px; - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 10px; - background: rgba(255, 255, 255, 0.04); +.docs-nav-item { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 46px; + padding: 0 16px; + border-bottom: 2px solid #d8d0c4; + color: #f4efe7; + transition: + background 160ms ease, + color 160ms ease; +} + +.docs-home-link { + display: grid; + place-items: center; + width: 34px; + height: 34px; + border: 2px solid currentColor; + border-radius: 999px; + transition: + background 160ms ease, + color 160ms ease; +} + +.docs-nav-item span:first-child { + display: grid; + gap: 2px; +} + +.docs-nav-item strong { + font-size: 13px; + font-weight: 700; + letter-spacing: -0.03em; +} + +.docs-nav-item small, +.docs-nav-item__meta { + color: #a9a196; + font-size: 11px; + font-weight: 600; + letter-spacing: -0.01em; } .docs-sidebar a:hover, -.docs-sidebar a:focus-visible { - color: white; - border-color: rgba(255, 255, 255, 0.24); +.docs-sidebar a:focus-visible, +.docs-nav-item--active { + background: #f4efe7; + color: #050505; + outline: none; } -.docs-back-link { - display: inline-flex; - font-size: 14px; +.docs-sidebar a:hover small, +.docs-sidebar a:hover .docs-nav-item__meta, +.docs-sidebar a:focus-visible small, +.docs-sidebar a:focus-visible .docs-nav-item__meta, +.docs-nav-item--active small, +.docs-nav-item--active .docs-nav-item__meta { + color: #050505; } .docs-content { overflow-y: auto; scroll-behavior: smooth; - padding: 48px clamp(20px, 5vw, 72px); + background: #050505; +} + +.docs-content__header { + position: sticky; + top: 0; + z-index: 2; + background: #050505; +} + +.docs-language-toggle { + display: inline-flex; + align-items: center; + gap: 0; + padding: 2px; + border: 2px solid #d8d0c4; + border-radius: 999px; + background: transparent; + color: #f4efe7; + cursor: pointer; +} + +.docs-language-toggle span { + display: grid; + place-items: center; + min-width: 36px; + min-height: 26px; + border-radius: 999px; + color: #a9a196; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.04em; +} + +.docs-language-toggle .is-active { + background: #f4efe7; + color: #050505; +} + +.docs-language-toggle:hover, +.docs-language-toggle:focus-visible { + outline: none; + background: rgba(216, 208, 196, 0.1); } .docs-section { - max-width: 980px; - margin: 0 auto 48px; - padding: clamp(24px, 4vw, 44px); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 24px; - background: rgba(255, 255, 255, 0.055); - box-shadow: 0 24px 80px rgba(0, 0, 0, 0.26); + max-width: 920px; + margin: 0 auto; + padding: 34px clamp(18px, 4vw, 56px) 48px; +} + +.docs-section__eyebrow { + display: flex; + justify-content: space-between; + margin-bottom: 22px; + color: #a9a196; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; } .docs-section h1, .docs-section h2, .docs-section h3 { - color: white; - line-height: 1.15; + color: #f4efe7; + letter-spacing: -0.06em; + line-height: 1.05; } .docs-section h1 { margin-top: 0; - font-size: clamp(32px, 5vw, 56px); + margin-bottom: 20px; + font-size: clamp(46px, 7vw, 88px); + font-weight: 700; } .docs-section h2 { - margin-top: 34px; - font-size: clamp(24px, 3vw, 34px); + margin-top: 44px; + margin-bottom: 12px; + padding-bottom: 10px; + border-bottom: 2px solid #d8d0c4; + font-size: clamp(28px, 4vw, 44px); + font-weight: 700; } .docs-section h3 { - margin-top: 26px; - font-size: 20px; + margin-top: 30px; + margin-bottom: 10px; + font-size: 18px; + font-weight: 700; + letter-spacing: -0.03em; } .docs-section p, .docs-section li { + color: #d8d0c4; + font-family: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 15px; line-height: 1.75; } +.docs-section ul, +.docs-section ol { + padding-left: 24px; +} + .docs-section a { - color: #8bd3ff; + color: #f4efe7; + text-underline-offset: 4px; } .docs-section code { - border-radius: 6px; - padding: 2px 6px; - background: rgba(255, 255, 255, 0.12); - color: #d7f2ff; + border: 0; + border-radius: 2px; + padding: 2px 5px; + background: rgba(216, 208, 196, 0.22); + color: #f4efe7; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; + font-size: 0.92em; } .docs-section pre { overflow-x: auto; padding: 18px; - border-radius: 16px; - background: rgba(0, 0, 0, 0.34); + border: 0; + border-radius: 0; + background: #0d0d0d; } .docs-section pre code { + display: block; padding: 0; + border: 0; background: transparent; + color: #f4efe7; + line-height: 1.45; + white-space: pre; } .docs-section table { display: block; width: 100%; + margin: 20px 0; overflow-x: auto; border-collapse: collapse; } @@ -161,15 +289,21 @@ canvas { .docs-section th, .docs-section td { padding: 10px 12px; - border: 1px solid rgba(255, 255, 255, 0.16); + border: 2px solid #d8d0c4; text-align: left; } +.docs-section th { + background: #111; + color: #f4efe7; + font-weight: 700; +} + .docs-section blockquote { margin-left: 0; padding-left: 18px; - border-left: 3px solid rgba(139, 211, 255, 0.72); - color: rgba(255, 255, 255, 0.72); + border-left: 2px solid #d8d0c4; + color: #a9a196; } @media (max-width: 760px) { @@ -180,7 +314,7 @@ canvas { .docs-sidebar { border-right: 0; - border-bottom: 1px solid rgba(255, 255, 255, 0.12); + border-bottom: 2px solid #d8d0c4; } .docs-content { diff --git a/src/pages/DocsPage.tsx b/src/pages/DocsPage.tsx deleted file mode 100644 index db05161..0000000 --- a/src/pages/DocsPage.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Link } from "@tanstack/react-router"; -import ReactMarkdown from "react-markdown"; -import remarkGfm from "remark-gfm"; -import readme from "../../README.md?raw"; -import architecture from "../../docs/technical/architecture.md?raw"; -import targetArchitecture from "../../docs/technical/target-architecture.md?raw"; -import features from "../../docs/user/features.md?raw"; - -interface DocSection { - id: string; - title: string; - content: string; -} - -const docSections: DocSection[] = [ - { - id: "readme", - title: "README", - content: readme, - }, - { - id: "architecture", - title: "Architecture actuelle", - content: architecture, - }, - { - id: "target-architecture", - title: "Architecture cible", - content: targetArchitecture, - }, - { - id: "features", - title: "Fonctionnalites", - content: features, - }, -]; - -export function DocsPage(): React.JSX.Element { - return ( -
- - -
- {docSections.map((section) => ( -
- - {section.content} - -
- ))} -
-
- ); -} diff --git a/src/pages/docs/DocsDocument.tsx b/src/pages/docs/DocsDocument.tsx new file mode 100644 index 0000000..b1940ca --- /dev/null +++ b/src/pages/docs/DocsDocument.tsx @@ -0,0 +1,51 @@ +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { useDocsLanguage } from "@/pages/docs/useDocsLanguage"; + +interface DocsDocumentProps { + title: string; + meta: string; + content: string; + frContent: string; +} + +export function DocsDocument({ + title, + meta, + content, + frContent, +}: DocsDocumentProps): React.JSX.Element { + const { language, toggleLanguage } = useDocsLanguage(); + const translatedContent = language === "fr" ? frContent : content; + + return ( +
+
+ {title} + +
+ +
+
+ {title} + {meta} +
+ + {translatedContent} + +
+
+ ); +} diff --git a/src/pages/docs/DocsLanguageProvider.tsx b/src/pages/docs/DocsLanguageProvider.tsx new file mode 100644 index 0000000..e4c5903 --- /dev/null +++ b/src/pages/docs/DocsLanguageProvider.tsx @@ -0,0 +1,25 @@ +import { useState } from "react"; +import { + DocsLanguageContext, + type DocsLanguage, +} from "@/pages/docs/docsLanguageContext"; + +interface DocsLanguageProviderProps { + children: React.ReactNode; +} + +export function DocsLanguageProvider({ + children, +}: DocsLanguageProviderProps): React.JSX.Element { + const [language, setLanguage] = useState("en"); + + function toggleLanguage(): void { + setLanguage((currentLanguage) => (currentLanguage === "en" ? "fr" : "en")); + } + + return ( + + {children} + + ); +} diff --git a/src/pages/docs/DocsLayout.tsx b/src/pages/docs/DocsLayout.tsx new file mode 100644 index 0000000..9c8bce6 --- /dev/null +++ b/src/pages/docs/DocsLayout.tsx @@ -0,0 +1,47 @@ +import { Link, Outlet } from "@tanstack/react-router"; +import { Home } from "lucide-react"; +import { DocsLanguageProvider } from "@/pages/docs/DocsLanguageProvider"; +import { docSections } from "@/pages/docs/docsSections"; + +export function DocsLayout(): React.JSX.Element { + return ( + +
+ + + +
+
+ ); +} diff --git a/src/pages/docs/architecture/page.tsx b/src/pages/docs/architecture/page.tsx new file mode 100644 index 0000000..89f2cc9 --- /dev/null +++ b/src/pages/docs/architecture/page.tsx @@ -0,0 +1,14 @@ +import architecture from "../../../../docs/technical/architecture.md?raw"; +import { DocsDocument } from "@/pages/docs/DocsDocument"; +import { architectureFr } from "@/pages/docs/docsTranslations"; + +export function DocsArchitecturePage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/docs/docsLanguageContext.ts b/src/pages/docs/docsLanguageContext.ts new file mode 100644 index 0000000..295bb7f --- /dev/null +++ b/src/pages/docs/docsLanguageContext.ts @@ -0,0 +1,11 @@ +import { createContext } from "react"; + +export type DocsLanguage = "en" | "fr"; + +export interface DocsLanguageContextValue { + language: DocsLanguage; + toggleLanguage: () => void; +} + +export const DocsLanguageContext = + createContext(null); diff --git a/src/pages/docs/docsSections.ts b/src/pages/docs/docsSections.ts new file mode 100644 index 0000000..3bbc201 --- /dev/null +++ b/src/pages/docs/docsSections.ts @@ -0,0 +1,33 @@ +export interface DocSection { + path: string; + title: string; + subtitle: string; + meta: string; +} + +export const docSections: DocSection[] = [ + { + path: "/docs", + title: "README", + subtitle: "Project overview", + meta: "01", + }, + { + path: "/docs/architecture", + title: "Architecture actuelle", + subtitle: "Runtime structure", + meta: "02", + }, + { + path: "/docs/target-architecture", + title: "Architecture cible", + subtitle: "Next direction", + meta: "03", + }, + { + path: "/docs/features", + title: "Fonctionnalites", + subtitle: "Implemented scope", + meta: "04", + }, +]; diff --git a/src/pages/docs/docsTranslations.ts b/src/pages/docs/docsTranslations.ts new file mode 100644 index 0000000..7f89281 --- /dev/null +++ b/src/pages/docs/docsTranslations.ts @@ -0,0 +1,258 @@ +export const readmeFr = `# La-Fabrik + +Une expérience web 3D interactive pour La Fabrik Durable, un service low-tech de réparation et de transformation situé à Altera, une ville post-capitaliste reconstruite en 2039. Les joueurs incarnent un technicien fraîchement intégré et vivent une journée de service : réparer un vélo électrique, remettre en état un réseau d'énergie et améliorer le système d'irrigation d'une ferme verticale. + +Construit avec React, Three.js et Vite. Fonctionne dans le navigateur, sans installation côté utilisateur. + +## Stack technique + +### Build et langage + +| Package | +| -------------------------------------------------- | +| [TypeScript](https://www.typescriptlang.org/docs/) | +| [React](https://react.dev/learn) | +| [Vite](https://vite.dev/guide/) | +| [ESLint](https://eslint.org/docs/latest/) | +| [Prettier](https://prettier.io/docs/) | + +### Moteur 3D + +| Package | +| ----------------------------------------------------------------------------------------- | +| [Three.js](https://threejs.org/docs/) | +| [@react-three/fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction) | +| [@react-three/drei](https://pmndrs.github.io/drei) | +| [@react-three/rapier](https://rapier.rs/docs/) | +| [@react-three/postprocessing](https://github.com/pmndrs/postprocessing) | +| [GSAP](https://gsap.com/docs/v3/Installation/) | + +### Performance et effets + +| Package | +| --------------------------------------------------------------------------- | +| [r3f-perf](https://github.com/utsuboco/r3f-perf) | +| [AnimationMixer](https://threejs.org/docs/#api/en/animation/AnimationMixer) | + +## Structure du projet + +\`\`\` +la-fabrik/ +├── public/ +│ ├── models/ +│ │ ├── map/ # Carte de base, chargée au démarrage +│ │ ├── workshop/ +│ │ ├── powerGrid/ +│ │ └── farm/ +│ ├── textures/ +│ └── sounds/ +│ +└── src/ + ├── world/ # Monde 3D persistant + │ ├── World.tsx # Composition principale de la scène + │ ├── Map.tsx # Carte de base, toujours montée + │ ├── Lighting.tsx # Lumières ambiante, directionnelle et ponctuelles + │ ├── Environment.tsx # HDRI, brouillard, ciel + │ ├── PostFX.tsx # Bloom, SSAO, aberration chromatique + │ ├── zones/ # Zones spatiales, LOD par zone + │ └── player/ # Contrôleur joueur et caméra + │ + ├── components/ + │ ├── 3d/ # Éléments 3D réutilisables + │ └── ui/ # Overlays HTML hors Canvas + │ + ├── stateManager/ # Logique, état et orchestration + ├── hooks/ # Hooks React autour des managers + ├── data/ # Configuration statique + ├── shaders/ # Shaders GLSL + └── utils/ # Utilitaires partagés et debug +\`\`\` + +## Démarrage + +\`\`\`bash +git clone https://github.com/La-Fabrik-Durable/La-Fabrik.git +cd La-Fabrik +npm install +npm run dev +\`\`\` + +- application : \`http://localhost:5173\` +- mode debug : \`http://localhost:5173?debug\` + +## Licence + +Voir le fichier [LICENSE](./LICENSE). +`; + +export const architectureFr = `# Architecture actuelle + +Ce document décrit le code réellement présent aujourd'hui dans le dépôt. + +## Structure runtime + +- \`src/App.tsx\` monte le \`Canvas\`, le \`World\` 3D, l'overlay de performance debug et les overlays HTML. +- \`src/world/World.tsx\` compose la scène active avec : + - l'environnement et l'éclairage + - les helpers debug et le mode caméra debug + - soit la carte principale, soit la scène de test physique debug + - le rig joueur quand le mode caméra actif est \`player\` +- \`src/world/Map.tsx\` charge le modèle principal de la carte et construit l'octree de collision. +- \`src/world/debug/TestScene.tsx\` fournit une scène orientée debug pour les interactions et la physique. +- \`src/world/player/PlayerComponent.tsx\` monte la caméra et le contrôleur. +- \`src/world/player/PlayerController.tsx\` gère le mouvement pointer lock, le saut et les inputs d'interaction. + +## Modèle d'interaction + +- \`src/stateManager/InteractionManager.ts\` est la source d'état actuelle des interactions. +- \`src/components/3d/InteractableObject.tsx\` gère la détection de focus par distance et raycasting. +- \`src/components/3d/TriggerObject.tsx\` implémente les interactions de type trigger. +- \`src/components/3d/GrabbableObject.tsx\` implémente les interactions saisir / relâcher. +- \`src/hooks/useInteraction.ts\` expose un snapshot d'interaction à l'UI React. +- \`src/components/ui/InteractPrompt.tsx\` affiche le prompt \`E\` pour les interactions trigger. + +## Audio + +- \`src/stateManager/AudioManager.ts\` fournit actuellement une lecture de sons one-shot avec pool. +- Les interactions trigger peuvent lancer directement un son via \`AudioManager\`. + +## Système debug + +- Le mode debug est activé avec \`?debug\`. +- \`src/utils/debug/Debug.ts\` possède l'instance \`lil-gui\` et les contrôles debug. +- \`src/hooks/debug/useCameraMode.ts\` et \`src/hooks/debug/useSceneMode.ts\` s'abonnent à l'état debug. +- \`src/utils/debug/DebugPerf.tsx\` monte \`r3f-perf\` en lazy uniquement en mode debug. +- \`src/utils/debug/scene/DebugHelpers.tsx\` monte les helpers debug. +- \`src/utils/debug/scene/DebugCameraControls.tsx\` monte la caméra libre debug. + +## Limites actuelles + +- Le dépôt est encore un prototype, pas le runtime complet du jeu. +- \`src/world/debug/TestScene.tsx\` fait encore partie de la composition active. +- Il n'existe pas encore d'orchestrateur gameplay central comme \`GameManager\`. +- Les systèmes de missions, zones, cinématiques et dialogues ne sont pas implémentés. +- Le joueur utilise une collision octree et des règles simples, pas une pile physique gameplay complète. +`; + +export const targetArchitectureFr = `# Architecture cible + +Ce document décrit l'architecture visée à moyen terme pour le projet. + +## Relation avec le code actuel + +- \`docs/technical/architecture.md\` reste la source de vérité de ce qui existe maintenant. +- Ce document est volontairement aspirational. +- Si ce document contredit l'implémentation actuelle, l'implémentation actuelle gagne. + +## Objectifs + +- Garder \`App.tsx\` petit et centré sur l'orchestration. +- Séparer le code de production du monde des chemins runtime uniquement debug. +- Garder une source de vérité claire par responsabilité. +- Faire grandir les systèmes gameplay progressivement, sans préconstruire une architecture vide. + +## Couches prévues + +### Couche App + +- \`App.tsx\` monte la scène canvas et les overlays HTML de premier niveau. +- Il doit rester fin et éviter la logique gameplay. + +### Couche World + +- \`src/world/\` doit contenir la composition de scène de production et les objets de scène de production. +- Responsabilités attendues : + - composition du monde + - carte, environnement, éclairage + - contrôleur joueur + - ancres d'interaction de production + - post-processing de production si nécessaire + +### Couche Debug + +- Les scènes et outils uniquement debug doivent être isolés du chemin de production. +- Responsabilités attendues : + - \`lil-gui\` + - overlay de performance + - helpers de scène + - caméra libre et contrôles de calibration + - scènes temporaires de test utilisées pendant le développement + +### Couche UI + +- \`src/components/ui/\` doit contenir les overlays HTML visibles par le joueur. +- Exemples futurs : + - crosshair + - flow de chargement + - HUD de mission + - overlays narratifs + +### Couche Gameplay + +- À mesure que le projet grandit, l'état gameplay peut évoluer vers une couche d'orchestration plus claire. +- Sujets probables : + - missions + - zones + - cinématiques + - dialogues + - audio + - interactions + +## Règles + +- Préférer du code direct et fonctionnel plutôt qu'un échafaudage spéculatif. +- Les types partagés doivent rester proches de leur domaine jusqu'à avoir plusieurs vrais consommateurs. +- Éviter de créer de nouveaux managers ou services sans besoin runtime actif. +- Les chemins runtime uniquement debug doivent être clairement marqués et faciles à retirer plus tard. +`; + +export const featuresFr = `# Fonctionnalités implémentées + +Ce document liste les fonctionnalités présentes dans le code actuel. + +## Scène + +- Scène React Three Fiber plein écran +- Carte principale chargée depuis \`public/models/map/model.gltf\` +- Scène de test physique debug sélectionnable depuis le panneau debug +- Éclairage ambiant et directionnel +- Configuration de l'environnement de fond + +## Joueur + +- Mode caméra joueur +- Orientation souris avec pointer lock +- Déplacement avec \`ZQSD\` +- Saut +- Collision basée sur une octree contre la carte chargée + +## Interactions + +- Détection de focus par distance et raycast +- Interactions trigger activées avec \`E\` +- Interactions grab activées avec le bouton principal de la souris +- Prompt d'interaction affiché pour les interactions trigger + +## Audio + +- Lecture de sons one-shot pour les interactions trigger +- Pool simple par son via \`AudioManager\` + +## Outils debug + +- Le paramètre \`?debug\` active le panneau debug +- Contrôles \`lil-gui\` pour le mode caméra, le mode scène et les sphères d'interaction +- Helpers de scène debug +- Caméra libre debug +- Overlay \`r3f-perf\` + +## Pas encore implémenté + +- système de missions +- système de zones +- système de cinématiques +- système de dialogues +- flow de chargement +- minimap et HUD de mission +- séparation complète production / debug pour les scènes gameplay +`; diff --git a/src/pages/docs/features/page.tsx b/src/pages/docs/features/page.tsx new file mode 100644 index 0000000..e1a510f --- /dev/null +++ b/src/pages/docs/features/page.tsx @@ -0,0 +1,14 @@ +import features from "../../../../docs/user/features.md?raw"; +import { DocsDocument } from "@/pages/docs/DocsDocument"; +import { featuresFr } from "@/pages/docs/docsTranslations"; + +export function DocsFeaturesPage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/docs/page.tsx b/src/pages/docs/page.tsx new file mode 100644 index 0000000..2fce4b6 --- /dev/null +++ b/src/pages/docs/page.tsx @@ -0,0 +1,14 @@ +import readme from "../../../README.md?raw"; +import { DocsDocument } from "@/pages/docs/DocsDocument"; +import { readmeFr } from "@/pages/docs/docsTranslations"; + +export function DocsReadmePage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/docs/target-architecture/page.tsx b/src/pages/docs/target-architecture/page.tsx new file mode 100644 index 0000000..f5b9b8b --- /dev/null +++ b/src/pages/docs/target-architecture/page.tsx @@ -0,0 +1,14 @@ +import targetArchitecture from "../../../../docs/technical/target-architecture.md?raw"; +import { DocsDocument } from "@/pages/docs/DocsDocument"; +import { targetArchitectureFr } from "@/pages/docs/docsTranslations"; + +export function DocsTargetArchitecturePage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/docs/useDocsLanguage.ts b/src/pages/docs/useDocsLanguage.ts new file mode 100644 index 0000000..d65a481 --- /dev/null +++ b/src/pages/docs/useDocsLanguage.ts @@ -0,0 +1,12 @@ +import { useContext } from "react"; +import { DocsLanguageContext } from "@/pages/docs/docsLanguageContext"; + +export function useDocsLanguage() { + const context = useContext(DocsLanguageContext); + + if (!context) { + throw new Error("useDocsLanguage must be used inside DocsLanguageProvider"); + } + + return context; +} diff --git a/src/router.tsx b/src/router.tsx index 2549a14..f507a2f 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -4,8 +4,12 @@ import { createRoute, createRouter, } from "@tanstack/react-router"; -import { DocsPage } from "@/pages/DocsPage"; import { HomePage } from "@/pages/HomePage"; +import { DocsArchitecturePage } from "@/pages/docs/architecture/page"; +import { DocsLayout } from "@/pages/docs/DocsLayout"; +import { DocsFeaturesPage } from "@/pages/docs/features/page"; +import { DocsReadmePage } from "@/pages/docs/page"; +import { DocsTargetArchitecturePage } from "@/pages/docs/target-architecture/page"; const rootRoute = createRootRoute({ component: Outlet, @@ -20,10 +24,42 @@ const indexRoute = createRoute({ const docsRoute = createRoute({ getParentRoute: () => rootRoute, path: "/docs", - component: DocsPage, + component: DocsLayout, }); -const routeTree = rootRoute.addChildren([indexRoute, docsRoute]); +const docsIndexRoute = createRoute({ + getParentRoute: () => docsRoute, + path: "/", + component: DocsReadmePage, +}); + +const docsArchitectureRoute = createRoute({ + getParentRoute: () => docsRoute, + path: "/architecture", + component: DocsArchitecturePage, +}); + +const docsTargetArchitectureRoute = createRoute({ + getParentRoute: () => docsRoute, + path: "/target-architecture", + component: DocsTargetArchitecturePage, +}); + +const docsFeaturesRoute = createRoute({ + getParentRoute: () => docsRoute, + path: "/features", + component: DocsFeaturesPage, +}); + +const routeTree = rootRoute.addChildren([ + indexRoute, + docsRoute.addChildren([ + docsIndexRoute, + docsArchitectureRoute, + docsTargetArchitectureRoute, + docsFeaturesRoute, + ]), +]); export const router = createRouter({ routeTree });