deuxième aventure

This commit is contained in:
2026-01-18 16:11:38 +04:00
parent 7009b7a541
commit 0e3a15a482
20 changed files with 729 additions and 20 deletions

43
config.ts Normal file
View File

@@ -0,0 +1,43 @@
import { defineCollection, z } from "astro:content";
// Collection pour le Journal de bord de la campagne JDR
const journalCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
author: z.string(),
publishDate: z.date(),
tags: z.array(z.string()),
}),
});
// Collection pour les logs de développement et de réflexion
const logsCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
publishDate: z.date(),
tags: z.array(z.string()),
}),
});
// Collection pour le "Codex de la Substance" (apprentissage)
const codexCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
subtitle: z.string(),
publishDate: z.date(),
tags: z.array(z.string()),
concept2D: z.string(),
mecanique: z.string(),
vision3D: z.string(),
status: z.enum(["Acquis", "En cours", "À explorer"]),
}),
});
export const collections = {
journal: journalCollection,
logs: logsCollection,
codex: codexCollection,
};

View File

@@ -1,17 +1,18 @@
---
import type { CollectionEntry } from 'astro:content';
import GoldButton from './ui/GoldButton.astro';
import CodexCard from './ui/CodexCard.astro';
interface Props {
posts: CollectionEntry<'journal' | 'logs'>[];
posts: CollectionEntry<'journal' | 'logs' | 'codex'>[];
tags: string[];
basePath: 'journal' | 'logs';
basePath: 'journal' | 'logs' | 'codex';
}
const { posts, tags, basePath } = Astro.props;
---
<div id="journal-search-component">
<div id="content-search-component">
<div class="search-wrapper">
<input type="search" id="search-input" placeholder="Rechercher..." />
</div>
@@ -26,14 +27,19 @@ const { posts, tags, basePath } = Astro.props;
</div>
<div id="search-results" class="card-grid">
{
{basePath === 'codex' ? (
posts.map((post) => (
<CodexCard entry={post as CollectionEntry<'codex'>} />
))
) : (
posts.map((post) => (
<GoldButton
href={`/${basePath}/${post.slug}/`}
title={post.data.title}
body={`Publié le ${post.data.publishDate.toLocaleDateString('fr-FR')}`}
body={`Publié le ${new Date(post.data.publishDate).toLocaleDateString('fr-FR')}`}
/>
))
)
}
</div>
<p id="no-results" class="no-results-message" style="display: none;">
@@ -59,13 +65,51 @@ const { posts, tags, basePath } = Astro.props;
.replace(/[\u0300-\u036f]/g, '');
}
// Fonction pour créer le HTML d'une carte Codex
function createCodexCardHTML(post) {
const statusClass = post.data.status.toLowerCase().replace(' ', '-');
return `
<a href="/codex/${post.slug}" class="codex-card">
<div class="card-header">
<div class="card-title">${post.data.title}</div>
<div class="card-subtitle">${post.data.subtitle}</div>
<span class="status status-${statusClass}">${post.data.status}</span>
</div>
<div class="skill-section">
<div class="skill-header">Compétence</div>
<p class="skill-content">${post.data.mecanique}</p>
<div class="skill-header">Application Concrète</div>
<p class="skill-content">${post.data.vision3D}</p>
</div>
</a>
`;
}
// Fonction pour créer le HTML d'un GoldButton
function createGoldButtonHTML(post) {
return `
<a href="/${basePath}/${post.slug}/" class="gold-button">
<div class="title">${post.data.title}</div>
<p class="body">Publié le ${new Date(post.data.publishDate).toLocaleDateString('fr-FR')}</p>
</a>
`;
}
// Fonction pour mettre à jour l'affichage
function renderResults() {
let filteredPosts = posts;
// 1. Filtrer par balise active
if (activeTag !== 'all') {
filteredPosts = filteredPosts.filter(post => post.data.tags?.includes(activeTag));
// Pour le codex, on filtre par 'domain', sinon par 'tags'
const filterKey = basePath === 'codex' ? 'domain' : 'tags';
filteredPosts = filteredPosts.filter(post => {
const values = post.data[filterKey];
if (Array.isArray(values)) {
return values.includes(activeTag);
}
return values === activeTag; // Gère le cas où 'domain' est une simple chaîne
});
}
// 2. Filtrer par recherche textuelle
@@ -76,15 +120,10 @@ const { posts, tags, basePath } = Astro.props;
// 3. Mettre à jour le DOM
noResultsMessage.style.display = filteredPosts.length === 0 ? 'block' : 'none';
resultsContainer.innerHTML = filteredPosts
.map(
(post) =>
`<a href="/${basePath}/${post.slug}/" class="gold-button">
<div class="title">${post.data.title}</div>
<p class="body">Publié le ${new Date(post.data.publishDate).toLocaleDateString('fr-FR')}</p>
</a>`
)
.join('');
const resultsHTML = filteredPosts.map(post =>
basePath === 'codex' ? createCodexCardHTML(post) : createGoldButtonHTML(post)
).join('');
resultsContainer.innerHTML = resultsHTML;
}
// --- Écouteurs d'événements ---

View File

@@ -0,0 +1,104 @@
---
import type { CollectionEntry } from "astro:content";
interface Props {
entry: CollectionEntry<"codex">;
}
const { entry } = Astro.props;
---
<a href={`/codex/${entry.slug}`} class="codex-card">
<div class="card-header">
<div class="card-title">{entry.data.title}</div>
<div class="card-subtitle">{entry.data.subtitle}</div>
<span class={`status status-${entry.data.status.toLowerCase().replace(' ', '-')}`}>
{entry.data.status}
</span>
</div>
<div class="skill-section">
<div class="skill-header">Compétence</div>
<p class="skill-content">{entry.data.mecanique}</p>
<div class="skill-header">Application Concrète</div>
<p class="skill-content">{entry.data.vision3D}</p>
</div>
</a>
<style>
.codex-card {
display: block;
text-decoration: none;
color: inherit;
background: linear-gradient(145deg, #fefbf3, #f8f1e4);
border: 2px solid #dcd0b9;
border-radius: 15px;
padding: 1.5rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
width: 100%;
max-width: 450px; /* Largeur max pour une carte */
}
.codex-card:hover {
transform: translateY(-5px) scale(1.02);
border-color: #c89b3c;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.card-header {
text-align: center;
margin-bottom: 1rem;
position: relative;
}
.card-title {
font-family: 'Cinzel', serif;
font-size: 1.3rem;
color: #b8860b;
}
.card-subtitle {
font-family: 'EB Garamond', serif;
font-style: italic;
font-size: 1rem;
opacity: 0.8;
}
.status {
position: absolute;
top: -5px;
right: -5px;
font-family: 'Cinzel', serif;
font-size: 0.7rem;
padding: 0.2rem 0.5rem;
border-radius: 10px;
border: 1px solid;
}
.status-acquis { background-color: rgba(60, 150, 60, 0.1); border-color: rgba(60, 150, 60, 0.4); color: #3c963c; }
.status-en-cours { background-color: rgba(200, 155, 60, 0.15); border-color: rgba(200, 155, 60, 0.4); color: #b8860b; }
.status-à-explorer { background-color: rgba(100, 100, 100, 0.1); border-color: rgba(100, 100, 100, 0.3); color: #646464; }
.skill-section {
margin-top: 1rem;
font-size: 0.9rem;
background-color: rgba(253, 246, 232, 0.5);
border: 1px solid #dcd0b9;
border-radius: 8px;
padding: 0.75rem 1rem;
}
.skill-header {
font-family: 'Cinzel', serif;
font-weight: bold;
color: #4a4130;
font-size: 0.8rem;
margin-bottom: 0.25rem;
}
.skill-content {
margin: 0 0 0.75rem 0;
padding-left: 0.5rem;
border-left: 2px solid rgba(200, 155, 60, 0.3);
}
</style>

View File

@@ -4,6 +4,7 @@ const { pathname } = Astro.url;
// Dans une application réelle, ces liens pourraient provenir d'un fichier de configuration.
const navItems = [
{ href: '/', label: 'Accueil' },
{ href: '/codex', label: 'Codex' },
{ href: '/atelier', label: 'Atelier' },
{ href: '/observatoire', label: 'Observatoire' },
{ href: '/boussole', label: 'Boussole' },

View File

@@ -0,0 +1,29 @@
---
title: "L'Anatomie du Mouvement"
subtitle: "Animer pour le Web"
publishDate: 2026-01-14
tags: ["Blender", "Pipeline", "Animation", "Rigging"]
domain: "3D"
concept2D: "Créer un GIF animé à partir d'une séquence d'images."
mecanique: "Stocker plusieurs animations dans l'Éditeur d'actions non linéaires (NLA) pour les exporter en une seule fois."
vision3D: "Une statue qui bouge n'est pas vivante. Une créature vivante exprime une intention dans chaque mouvement. Le NLA Editor est le grimoire où l'on écrit les différents 'états d'âme' de l'avatar (marche, course, attente) pour pouvoir les invoquer à volonté dans le code."
status: "À explorer"
---
Le mouvement est la manifestation la plus pure du souffle de vie. Pour qu'il s'exprime dans Three.js, il doit être correctement catalogué dans Blender.
### La Check-list de l'Animateur
1. **Une Armature, Un Corps :**
* **Mécanique :** Le maillage de votre personnage (`Mesh`) doit être parenté à un seul objet `Armature`. Évitez les armatures multiples.
* **Vision :** Le squelette est unique. Il est le conduit par lequel le souffle se propage dans tout le corps.
2. **Le Grimoire des Actions (NLA Editor) :**
* **Mécanique :** Ne laissez pas vos animations en vrac. Pour chaque animation (ex: `marche`, `course`), allez dans l'éditeur d'actions, créez une nouvelle action, puis cliquez sur le bouton "Stocker l'action" (Push Down). Cela l'ajoute comme une "piste" dans l'éditeur NLA.
* **Vision :** Chaque piste est un chapitre de la vie de votre personnage. En les nommant correctement, vous pourrez dire à votre code : "Maintenant, lis le chapitre de la course" ou "Joue l'incantation de l'attente".
3. **Exporter les Actions :**
* **Mécanique :** Dans les paramètres d'export GLB, sous l'onglet `Animation`, assurez-vous que "Animer les actions" est coché et que "Exporter les actions NLA" l'est aussi.
* **Vision :** C'est le sceau final qui lie tous les chapitres de votre grimoire d'animations au cristal `.glb` avant son grand voyage.
En suivant ces préceptes, vous pourrez, dans Three.js, charger votre modèle et accéder à un tableau de toutes ses animations par leur nom, prêt à insuffler le véritable Hebel à votre création.

View File

@@ -0,0 +1,34 @@
---
title: "Le Sceau de Transmutation"
subtitle: "Exporter en GLB/GLTF pour le Web"
publishDate: 2026-01-14
tags: ["Blender", "Pipeline", "GLTF", "Export"]
domain: "3D"
concept2D: "Enregistrer un fichier en 'JPEG pour le Web' avec les bons réglages de compression."
mecanique: "Configurer les paramètres de l'exportateur GLTF 2.0 pour optimiser la taille et la compatibilité."
vision3D: "L'export n'est pas une simple sauvegarde. C'est un rituel qui enferme l'essence du modèle dans un cristal (le fichier .glb) pour son voyage vers le monde du navigateur. Un rituel mal exécuté peut briser le cristal ou en altérer le contenu."
status: "À explorer"
---
Le format `.glb` (GLTF Binaire) est le vaisseau qui transportera votre création. Le configurer correctement, c'est s'assurer que le voyage se fasse sans perte et que le modèle arrive intact et léger.
### La Check-list du Rituel d'Exportation (`Fichier > Exporter > gLTF 2.0`)
1. **Format :**
* Choisissez **`gLTF Binaire (.glb)`**. Il contient tout (modèle, textures, animations) en un seul fichier, plus simple à gérer dans le code.
2. **Onglet `Inclure` :**
* **`Objets sélectionnés` :** Cochez **toujours** cette case. Vous ne voulez exporter que votre personnage, pas les lumières ou caméras de votre scène de travail.
* **`Propriétés personnalisées` :** Utile pour passer des données de Blender au code.
* **`Caméras` / `Lumières directionnelles` :** Décochez-les. La lumière et la caméra doivent être gérées par Three.js pour un contrôle total.
3. **Onglet `Transformation` :**
* **`+Y vers le Haut` :** Toujours coché. C'est la convention de Three.js.
4. **Onglet `Géométrie` :**
* **`Appliquer les modificateurs` :** Cochez cette case, sauf si vous utilisez des Shape Keys (morphings) pour la déformation.
* **`Compression` :** **Activez-la !** C'est le secret pour des modèles légers qui se chargent vite. L'algorithme Draco de Google est extrêmement efficace.
5. **Onglet `Animation` :**
* **`Animer les actions` :** Cochez cette case.
* **`Élaguer les animations` :** Cochez pour enlever les images-clés inutiles qui alourdissent le fichier.

View File

@@ -0,0 +1,35 @@
---
title: "La Discipline de la Forme"
subtitle: "Préparer le Modèle pour le Souffle"
publishDate: 2026-01-14
tags: ["Blender", "Pipeline", "Optimisation"]
domain: "3D"
concept2D: "Nettoyer un fichier SVG avant de l'importer"
mecanique: "Appliquer les transformations, vérifier l'orientation et nettoyer la géométrie d'un modèle 3D."
vision3D: "Le corps de l'avatar n'est pas qu'une forme, c'est un réceptacle. S'il est impur ou mal orienté, le souffle de vie (l'animation) sera corrompu ou s'exprimera de travers. C'est l'art de préparer un golem parfait avant le rituel d'animation."
status: "À explorer"
---
Avant même de murmurer la formule d'exportation, le pèlerin doit s'assurer que son réceptacle est pur. Un modèle mal préparé dans Blender se manifestera dans Three.js comme une entité tordue, décalée ou gigantesque.
### La Check-list des Fondations
1. **Le Centre du Monde :**
* **Mécanique :** Assurez-vous que votre personnage est au centre de la scène (Origine du Monde : 0, 0, 0). Son origine (le petit point orange) doit être idéalement entre ses pieds.
* **Vision :** L'avatar doit être ancré dans la réalité de votre monde. S'il naît décalé, il sera toujours un étranger.
2. **L'Échelle de la Réalité :**
* **Mécanique :** Appliquez toutes les transformations avec `Ctrl + A` > `Toutes les transformations`. L'échelle de votre objet doit être de `(1, 1, 1)`.
* **Vision :** Un avatar dont l'échelle n'est pas appliquée peut apparaître 100 fois trop grand ou trop petit dans la scène Three.js. C'est la différence entre un dieu et un insecte.
3. **Le Regard vers l'Avant :**
* **Mécanique :** Votre modèle doit faire face à l'axe **-Y** (Avant). C'est la convention pour que la commande `lookAt()` de Three.js fonctionne intuitivement.
* **Vision :** Le regard de votre création doit être aligné avec son intention. S'il regarde de côté, il avancera en crabe, son corps trahissant son âme.
4. **La Pureté de la Géométrie :**
* **Mécanique :** En mode `Édition`, sélectionnez tout (`A`) et utilisez `M` > `Par distance` pour fusionner les sommets en double. Recalculez les normales avec `Shift + N`.
* **Vision :** Les sommets dupliqués sont des fractures dans l'âme du modèle. Les normales inversées sont des morceaux de peau retournés. Ces impuretés créent des artefacts visuels, des cicatrices sombres là où la lumière devrait être.
5. **Nommer, c'est Invoquer :**
* **Mécanique :** Donnez des noms clairs à vos objets (`Corps`, `Yeux`, `Armature`) et à vos matériaux.
* **Vision :** Un nom est une incantation. Il vous permettra, dans le code, d'invoquer une partie précise du corps pour la modifier, comme si vous appeliez son véritable nom.

View File

@@ -0,0 +1,21 @@
---
title: "L'Alignement des Souffles"
subtitle: "CSS Flexbox"
publishDate: 2026-01-14
tags: ["CSS", "Layout", "Flexbox"]
domain: "Web Dev"
concept2D: "Centrer un texte verticalement dans un bloc, avec des astuces."
mecanique: "Un modèle de boîte unidimensionnel qui permet de distribuer l'espace et d'aligner des éléments le long d'un axe principal (horizontal ou vertical)."
vision3D: "Imaginez des âmes (des objets) flottant en ligne. Flexbox est le vent qui les pousse pour les regrouper, les espacer ou les centrer. C'est un souffle directionnel, parfait pour organiser des éléments qui se suivent, comme une barre de navigation ou une liste d'attributs."
status: "Acquis"
---
Flexbox (Flexible Box Layout) a été une révolution pour les développeurs web. Il a rendu simple ce qui était auparavant complexe : l'alignement et la distribution d'éléments dans un conteneur.
### La Philosophie de l'Axe
Flexbox est **unidimensionnel**. Il ne raisonne que sur un seul axe à la fois :
* **L'axe principal (`justify-content`) :** Comment les éléments se répartissent-ils le long de la direction principale (par défaut, de gauche à droite) ? Sont-ils au début, à la fin, au centre, ou avec un espace égal entre eux ?
* **L'axe secondaire (`align-items`) :** Comment les éléments s'alignent-ils sur l'axe perpendiculaire ? Sont-ils en haut, en bas, au centre, ou étirés pour remplir l'espace ?
C'est l'outil idéal pour les composants d'interface : barres de navigation, en-têtes de cartes, listes... partout où des éléments doivent être organisés en ligne ou en colonne.

View File

@@ -0,0 +1,21 @@
---
title: "La Trame du Monde"
subtitle: "CSS Grid"
publishDate: 2026-01-14
tags: ["CSS", "Layout", "Grid"]
domain: "Web Dev"
concept2D: "Utiliser des `<table>` pour la mise en page (l'ancienne méthode, rigide)."
mecanique: "Un modèle de boîte bidimensionnel qui permet de créer des mises en page complexes en définissant des lignes et des colonnes explicites."
vision3D: "Si Flexbox est un souffle, Grid est la main de l'architecte qui dessine la trame invisible du monde. On ne pousse pas les objets, on leur assigne une place précise dans une structure. C'est le pouvoir de dire : 'Toi, tu iras ici, de cette colonne à celle-ci', créant des espaces complexes et asymétriques."
status: "Acquis"
---
CSS Grid a complété Flexbox en offrant un contrôle sur les deux dimensions simultanément. C'est l'outil de choix pour la mise en page globale d'une page ou d'un composant complexe.
### La Philosophie de la Grille
Grid est **bidimensionnel**. Il raisonne en termes de grille, avec des lignes et des colonnes :
* **Définition de la trame (`grid-template-columns` / `rows`) :** Vous définissez la structure de votre grille à l'avance. Combien de colonnes ? De quelle taille ?
* **Placement des éléments (`grid-column` / `grid-row`) :** Vous pouvez ensuite placer chaque élément précisément à l'intérieur de cette grille, en lui disant de s'étendre sur une ou plusieurs cellules.
C'est l'outil parfait pour les mises en page de magazines, les tableaux de bord, et toute interface où les éléments ne se suivent pas simplement mais occupent des zones spécifiques de l'écran.

View File

@@ -0,0 +1,23 @@
---
title: "Le Portail Diégétique"
subtitle: "La Question du Menu de Création"
publishDate: 2026-01-14
tags: ["UI", "UX", "Game Design", "Interface"]
domain: "Game Design"
concept2D: "Un formulaire web bien désigné."
mecanique: "L'Interface Utilisateur (UI) qui permet au joueur de faire des choix pour personnaliser son avatar."
vision3D: "Le menu est la membrane entre le monde du joueur et le monde du jeu. S'il est une simple liste de boutons, il est une barrière à l'immersion. S'il est intégré à l'univers (un miroir, un rêve, une table d'alchimiste), il devient le premier rituel, le premier acte de création magique."
status: "À explorer"
---
Le mot "menu" peut sembler froid et technique, mais il représente un défi de design crucial : comment donner au joueur les outils de la création sans briser l'illusion ?
### Les Approches du Menu de Création
1. **Le Menu "Catalogue" (Classique) :** Une série de listes et de curseurs. Efficace, précis, mais souvent peu immersif. C'est la boîte à outils posée à côté de la sculpture.
2. **Le Menu "Diégétique" (Moderne) :** L'interface fait partie du monde du jeu.
* **Exemple :** Le personnage se regarde dans un miroir magique et le joueur modifie son reflet (*Dragon's Dogma 2*).
* **Exemple :** Le joueur manipule des orbes d'énergie ou des fragments de cristal qui flottent autour de l'avatar pour en changer les aspects.
Dans un projet Low Poly novateur, le menu lui-même peut devenir une expérience artistique. Puisque le personnage est simple, l'interface peut être riche et animée, utilisant des formes géométriques et des effets de lumière qui répondent en temps réel aux choix du joueur, transformant un formulaire en un véritable acte de magie.

View File

@@ -0,0 +1,21 @@
---
title: "Le Regard et le Toucher"
subtitle: "Raycasting"
publishDate: 2026-01-15
tags: ["Physique du Sable", "Interaction"]
domain: "3D"
concept2D: "États :hover et :active des boutons"
mecanique: "Lancer un rayon depuis la caméra pour détecter les intersections avec les objets de la scène."
vision3D: "Le regard qui devient une main. Le simple fait de poser les yeux sur un objet peut altérer sa matière (changement de shader), déclencher un son, ou le faire léviter. C'est le premier pas vers une interaction sans interface visible."
status: "Acquis"
---
### La Dissection du Grain
Le Raycasting est le pont entre le monde 2D de l'écran et l'espace 3D de la scène. En projetant un rayon invisible depuis la position de la souris (ou du centre de la caméra), nous pouvons lister tous les objets qu'il traverse.
C'est le mécanisme fondamental qui nous permet de répondre à la question : "Qu'est-ce que le visiteur est en train de regarder ?". Il remplace la logique du DOM où l'on attache des `event listeners` à des boîtes HTML. Ici, l'événement est attaché à la direction du regard lui-même. C'est la mécanique derrière le "regard" ou la "main" du visiteur dans notre univers.
### Le Journal de Transmutation
Le passage du `:hover` CSS au Raycasting est un saut paradigmatique. On ne décore plus une boîte, on interroge l'espace. Le feedback n'est plus un simple changement de couleur, mais peut devenir une réaction physique de l'objet lui-même, lui donnant l'illusion d'être conscient de notre présence.

View File

@@ -0,0 +1,23 @@
---
title: "La Pureté Géométrique"
subtitle: "Le Style Low Poly & Voxel"
publishDate: 2026-01-14
tags: ["Low Poly", "Voxel", "Direction Artistique", "Style"]
domain: "Game Design"
concept2D: "Le Pixel Art, où chaque carré compte."
mecanique: "Utiliser un nombre minimal de polygones (Low Poly) ou de cubes (Voxel) pour représenter des formes, en se concentrant sur la silhouette, la couleur et la lumière plutôt que sur le détail réaliste."
vision3D: "Le 'souffle de vie' n'a pas besoin des pores de la peau pour exister. En réduisant la forme à son essence, on laisse plus de place à l'imagination et à l'énergie pure. Le personnage n'est plus une imitation du réel, mais un symbole, une idée en mouvement."
status: "À explorer"
---
Choisir le Low Poly en 2026 n'est plus une contrainte technique, mais un choix esthétique radical. C'est l'art de la **"Modernité Simple"**. On utilise la puissance des moteurs modernes non pas pour imiter le réel, mais pour sublimer la simplicité.
### Le Low Poly "Haute-Fidélité"
Pour qu'un style simple ne paraisse pas "vieux jeu", on le marie à des technologies de pointe :
* **La Lumière sur la Forme :** On applique des éclairages et des ombres ultra-réalistes (calculés par des shaders complexes) sur des formes géométriques pures. Le contraste entre la simplicité du modèle et la complexité de la lumière crée un rendu unique, à la fois lisible et magnifique. C'est le style de jeux comme *Tunic* ou *Sable*.
* **La Matière avant la Texture :** Au lieu de peindre des détails, on simule des matériaux. Le personnage peut ressembler à une figurine en plastique brillant, en verre dépoli ou en métal brossé. La lumière interagit avec ces matériaux de façon crédible, donnant au modèle un aspect "premium" et tangible.
* **L'Animation comme Âme :** La fluidité du mouvement devient primordiale. Un personnage en 100 polygones peut sembler plus vivant qu'un modèle photoréaliste s'il bouge avec une grâce et une intention parfaites. Le "Hebel" se déplace du détail de la surface vers la pureté du mouvement.

View File

@@ -0,0 +1,21 @@
---
title: "L'Architecture de l'Âme"
subtitle: "L'Arbre de Talents"
publishDate: 2026-01-14
tags: ["Game Design", "Systèmes", "RPG", "Progression"]
domain: "Game Design"
concept2D: "Un organigramme ou une carte mentale."
mecanique: "Un système de progression visuel où le joueur dépense des points pour débloquer des compétences et des bonus interconnectés, créant un 'build' (une construction) unique."
vision3D: "Le 'Hebel' n'est pas que visuel. C'est aussi la structure invisible de la puissance du personnage. L'arbre de talents est la constellation de son âme guerrière, où chaque choix est une étoile qui définit qui il est au combat, bien plus que la couleur de ses yeux."
status: "À explorer"
---
La personnalisation n'est pas qu'une affaire d'apparence. Un arbre de talents est une forme de création de personnage tout aussi profonde, mais qui se joue au niveau des systèmes.
### La Personnalisation Systémique
* **L'Expression par le Gameplay :** Dans des jeux comme *Path of Exile* ou *Divinity: Original Sin*, l'identité du personnage se définit par ses actions : la manière dont il combine ses sorts, la vitesse de ses attaques, sa capacité à survivre. L'arbre de talents est le lieu où ces choix sont architecturés.
* **Le Plaisir Cérébral :** "Jouer dans les menus" est une forme de plaisir à part entière. Le joueur devient un théoricien, un architecte de builds, cherchant la synergie parfaite entre des dizaines de compétences.
* **Une Constellation de Pouvoirs :** Visuellement, un arbre de talents peut lui-même devenir une œuvre d'art. Une carte céleste où chaque compétence est une étoile, et les liens entre elles forment les constellations du pouvoir que le joueur dessine pour son avatar.

View File

@@ -1,17 +1,17 @@
import { defineCollection, z } from 'astro:content';
// Collection pour les articles du journal d'aventure
// Collection pour le Journal de bord de la campagne JDR
const journalCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
author: z.string(),
publishDate: z.date(),
tags: z.array(z.string()).optional(), // Ajout des balises (optionnel)
tags: z.array(z.string()).optional(),
}),
});
// Collection pour les logs de construction
// Collection pour les logs de développement et de réflexion
const logsCollection = defineCollection({
type: 'content',
schema: z.object({
@@ -21,7 +21,24 @@ const logsCollection = defineCollection({
}),
});
// Collection pour le "Codex de la Substance" (apprentissage)
const codexCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
subtitle: z.string(),
publishDate: z.date(),
tags: z.array(z.string()),
domain: z.string(), // Le nouveau champ pour le domaine d'étude
concept2D: z.string(),
mecanique: z.string(),
vision3D: z.string(),
status: z.enum(["Acquis", "En cours", "À explorer"]),
}),
});
export const collections = {
journal: journalCollection,
logs: logsCollection,
codex: codexCollection,
};

View File

@@ -0,0 +1,32 @@
---
title: "Le Murmure des Rouages"
author: "G'Mas"
publishDate: 2026-01-18
tags: ["Mine", "Combat", "Krutik", "Nyrae", "Jinn", "Vase Grise"]
---
### Les Griffes de l'Obscurité
Notre progression dans les entrailles de la terre ne fut pas une ligne droite. Dès l'entrée, nous avons croisé un **premier embranchement** menant à une salle abritant une porte massive. Sur le moment, elle nous parut sans intérêt : une simple barrière de pierre et de fer parmi tant d'autres. Nous ignorions encore que ce mécanisme, que nous aurions dû étudier de plus près, nous donnerait tant de fil à retordre quelques minutes plus tard.
S'enfonçant plus profondément dans les galeries désaffectées, le groupe atteignit un **second embranchement**. L'instinct de **Nyrae** fut formel : alors que les traces de Gablo revenaient sur leurs pas, la brigade de chenapans s'était dirigée vers la gauche. Un silence de mort régnait dans ce boyau, contrastant avec l'incroyable spectacle visuel qui nous entourait. Malgré l'angoisse de la mission, il était difficile de ne pas être hypnotisé par la "Lumière Constellaire" émanant du postérieur de **Gurdil**. Sous l'effet du sort de **Bulle**, les parois s'animaient de flammes dansantes et de reflets spectaculaires, transformant la roche brute en un palais de lumière mouvante. Mais le "souffle de vie" me rappelait à l'ordre : nous n'étions pas là pour nous extasier devant ce firmament improvisé sur un caleçon de nain. Chaque seconde perdue à admirer les jeux d'ombres nous rapprochait d'un drame.
Nous fîmes finalement face à la fameuse porte dans la troisième salle. Les traces des enfants y menaient directement, mais le passage était condamné : le mécanisme était grippé et le gond droit était sorti de son axe, rendant les manivelles inutiles. Tandis que Gurdil soulevait la porte utilisant sa force innée, Bulle et la Bourgmestre s'escrimaient sur les chaînes, Nyrae restait à l'écart, observant la scène avec une ironie féline, comme si nous déployions beaucoup d'énergie pour peu de résultats. C'est alors que je repérais un **cliquet de sûreté** bloqué. En glissant ma main dans les rouages pour libérer un engrenage et agiter quelques ressorts, je parvins à réenclencher la sécurité. La porte reprit son axe, les chaînes se tendirent, et le panneau de fer monta jusqu'à son sommet, maintenu désormais par la sécurité que j'avais restaurée.
Derrière la porte, des bruits de mastication écœurants rompirent le silence. Quatre **Krutiks** — des insectes de la taille d'un chien — dévoraient un rat géant. Trop occupés par leur festin, ils ne nous virent pas venir, et nous passâmes à l'attaque. **Gurdil** ouvrit le bal avec ses hachettes volantes, fauchant le premier adversaire. De mon côté, je déchaînai mon souffle à travers mon **souffle-quart** — ce bâton qui me sert tour à tour de guide, de boussole et de sarbacane — touchant un ennemi de plein fouet. Malheureusement, ces bestioles semblaient immunisées à mes toxines. **Bulle** acheva la première bête d'une flèche précise et de la vitesse d'un éclair, tandis que **Jinn** et **Nyrae** tentaient de contenir le reste de la meute.
Cependant, la chance tourna. Nos coups commencèrent à rater, comme si nous étions nous-mêmes perturbés par quelque chose. Gurdil, une goutte de sueur perlant sur son front, fixait une paroi rocheuse avec insistance, sentant un danger tapis. Soudain, un Krutik nous prit à revers tandis que le rocher surveillé par le nain se liquéfiait en une **Vase Grise**. Ce fléau, capable de dissoudre l'acier, constituait un danger certain malgré sa lenteur. Le combat devint chaotique : **Jinn**, submergé, s'effondra inconscient sous les assauts des deux derniers Krutiks.
Le groupe réagit alors avec une coordination héroïque. **Gurdil** pulvérisa un Krutik d'un coup de marteau dévastateur. **Nyrae** créa une ouverture en "tenaille", me permettant de fracasser une autre créature avec mon **bâton au quart d'un souffle**. **Bulle** marqua la Vase d'un _Éclair Traçant_, et **Nyrae** acheva la masse gélatineuse d'un coup de fouet salvateur avant qu'elle ne nous dissolve.
Le calme revenu, nous avons secouru **Jinn**. Stabilisé par Bulle et soigné par Maielan, le Génasi reprit connaissance. Nyrae se contenta d'un simple "Mouais..." laconique en croisant son regard, soulignant le poids de l'épreuve. Après s'être lui même soigné, Jinn retrouva assez de forces pour continuer. Nous avons repris la marche jusqu'au bout du couloir. Là, un **mur maçonné**, étrangement propre et hors de place, nous barrait la route. Une partie de son centre est effondrée. C'est de l'autre côté, dans ce vide inconnu, que nous entendons enfin les voix des enfants. Qu'est-ce qui a bien pu être enfermé ici ?
---
### État du Groupe
- **Jinn :** Affaibli mais conscient (récupération en cours).
- **G'mas (moi) :** En pleine forme, baigné par l'éclat de la grotte.
- **Gurdil :** Prêt au combat, hachette aux dents, hache à deux mains prête.
- **Bulle & Nyrae :** Indemnes.
- **Tableau de chasse :** 5 Krutiks et 1 Vase Grise éliminés.

View File

@@ -0,0 +1,25 @@
---
title: "Le Piège du Contenu Instantané : Une Réflexion sur l'IA"
publishDate: 2026-01-14
tags: ["IA", "Création", "Philosophie", "Codex"]
---
Ce log est une correction de trajectoire, une prise de conscience née d'une expérience aussi fascinante que décevante. Dans ma quête pour bâtir le "Codex de la Substance", j'ai cédé à la tentation de la vitesse. J'ai utilisé une IA pour générer le contenu de nombreuses cartes, pensant gagner du temps et peupler rapidement mon grimoire de savoirs.
Le résultat fut immédiat, et en surface, impressionnant. Des dizaines de compétences, de mécaniques et de visions du monde, toutes parfaitement formulées, sont apparues en un claquement de doigts. Mon codex était plein.
Mais il était vide.
### La Perte du Souffle de Vie
En parcourant ces cartes générées, je me suis senti étranger à mon propre projet. Les mots étaient là, corrects, mais ils ne portaient aucune trace du chemin, aucune cicatrice de l'apprentissage, aucune lueur de la découverte. C'était un savoir sans âme, un corps sans "Hebel". Je me suis plus perdu en lisant ces textes que je ne me suis retrouvé en les créant.
L'IA, dans son efficacité redoutable, avait court-circuité le processus même que le codex était censé documenter : le pèlerinage de l'apprentissage. Elle m'a donné la destination sans le voyage.
### L'IA : Outil d'Émancipation, non de Servitude
Cette expérience m'a enseigné une leçon cruciale. L'intelligence artificielle est un outil d'une puissance inouïe. Mais comme tout outil, son usage définit son essence. Utilisée pour produire en masse, elle nous emprisonne dans un carcan de contenu sans saveur, nous rendant dépendants d'une facilité qui atrophie notre propre muscle créatif.
Je choisis de voir l'IA différemment : non pas comme une usine à contenu, mais comme un partenaire de réflexion. Un miroir qui peut reformuler une idée, un expert qui peut débroussailler un sujet complexe, un assistant qui peut automatiser une tâche rébarbative. Son rôle doit être de nous aider à nous émanciper, à aller plus vite dans notre propre exploration, pas de nous en dispenser.
Le Codex ne sera pas rempli par une machine. Chaque carte sera le fruit d'un effort, d'une recherche, d'un "Eurêka !". Le chemin sera plus long, mais il sera mien. Et le savoir qu'il contiendra aura le poids de l'expérience vécue.

View File

@@ -183,10 +183,12 @@ const { title } = Astro.props;
color: #4a4130; /* Texte sombre pour le contraste */
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 2rem;
padding: 0;
margin: 0;
}
/* --- Styles pour les Tags --- */

View File

@@ -0,0 +1,152 @@
---
import { getCollection, type CollectionEntry } from "astro:content";
import GameLayout from "../../layouts/GameLayout.astro";
export async function getStaticPaths() {
const codexEntries = await getCollection("codex");
return codexEntries.map((entry) => ({
params: { slug: entry.slug },
}));
}
const { slug } = Astro.params;
const codexEntries = await getCollection("codex");
const entry = codexEntries.find((entry) => entry.slug === slug);
if (!entry) {
return Astro.redirect("/404");
}
const { Content } = await entry.render();
---
<GameLayout title={entry.data.title}>
<main class="codex-entry-container">
<article class="parchment-card">
<div class="header">
<h1>{entry.data.title}</h1>
<h2>{entry.data.subtitle}</h2>
<p class="publish-date">
Savoir acquis le : {
entry.data.publishDate.toLocaleDateString("fr-FR", {
year: "numeric",
month: "long",
day: "numeric",
})
}
</p>
</div>
<div class="transmutation-table">
<div class="table-row header">
<div class="col-title">Ancien Monde (2D)</div>
<div class="col-title">Mécanique</div>
<div class="col-title">Monde de Demain (3D)</div>
</div>
<div class="table-row">
<div>{entry.data.concept2D}</div>
<div>{entry.data.mecanique}</div>
<div>{entry.data.vision3D}</div>
</div>
</div>
{entry.data.tags && (
<div class="tags-container">
{entry.data.tags.map((tag) => (
<a href={`/codex/tags/${tag.toLowerCase()}`} class="tag">{tag}</a>
))}
</div>
)}
<div class="prose">
<Content />
</div>
<a href="/codex" class="back-link"> &larr; Retourner au Codex </a>
</article>
</main>
</GameLayout>
<style>
.codex-entry-container {
max-width: 900px;
margin: 0 auto;
padding: 2rem 1rem;
}
.parchment-card {
background: linear-gradient(145deg, #fefbf3, #f8f1e4);
border: 2px solid #dcd0b9;
border-radius: 15px;
padding: 2rem 3rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
}
.header {
text-align: center;
margin-bottom: 2rem;
}
.header h1 {
margin-bottom: 0.5rem;
}
.header h2 {
font-family: "EB Garamond", serif;
font-style: italic;
font-size: 1.4rem;
color: #4a4130;
margin-top: 0;
}
/* On retire l'ornement pour le h2 de la carte */
.header h2::after {
content: none;
}
.publish-date {
font-style: italic;
opacity: 0.7;
}
.transmutation-table {
margin: 2rem 0;
font-size: 0.9rem;
background-color: rgba(253, 246, 232, 0.5);
border: 1px solid #dcd0b9;
border-radius: 8px;
overflow: hidden;
}
.table-row {
display: grid;
grid-template-columns: 1fr 1.5fr 1.5fr;
gap: 1rem;
padding: 0.75rem 1rem;
}
.table-row.header {
background-color: rgba(220, 208, 185, 0.4);
}
.table-row:not(.header) {
border-top: 1px solid #dcd0b9;
}
.col-title {
font-family: "Cinzel", serif;
font-weight: bold;
color: #4a4130;
}
.back-link {
display: inline-block;
margin-top: 3rem;
font-family: "Cinzel", serif;
color: #b8860b;
text-decoration: none;
transition: all 0.2s ease;
}
.back-link:hover {
transform: translateX(-5px);
}
</style>

View File

@@ -0,0 +1,28 @@
---
import { getCollection } from "astro:content";
import GameLayout from "../../layouts/GameLayout.astro";
import ContentSearch from "../../components/ContentSearch.astro";
// 1. Récupérer toutes les entrées du codex
const codexEntries = await getCollection("codex");
// 2. Trier les entrées par date de publication
const sortedEntries = codexEntries.sort(
(a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
);
// 3. Extraire tous les domaines uniques pour les catégories
const allDomains = [...new Set(codexEntries.map(entry => entry.data.domain))];
---
<GameLayout title="Codex de la Substance">
<h1>Le Codex de la Substance</h1>
<p style="text-align: center; margin-bottom: 3rem;">
Le laboratoire où le pèlerin dissèque les mécanismes du réel (le code)
pour insuffler le "souffle de vie" (Hebel) à ses créations.
</p>
<!-- On passe les entrées, les domaines (en tant que tags) et le chemin de base au composant de recherche -->
<ContentSearch posts={sortedEntries} tags={allDomains} basePath="codex" />
</GameLayout>

View File

@@ -0,0 +1,38 @@
---
import { getCollection } from "astro:content";
import GameLayout from "../../../layouts/GameLayout.astro";
import CodexCard from "../../../components/ui/CodexCard.astro";
export async function getStaticPaths() {
const allCodexEntries = await getCollection("codex");
const allTags = allCodexEntries.flatMap((entry) => entry.data.tags);
const uniqueTags = [...new Set(allTags)];
return uniqueTags.map((tag) => {
return {
params: { tag: tag.toLowerCase() },
props: { tag },
};
});
}
const { tag } = Astro.props;
const allCodexEntries = await getCollection("codex");
const filteredEntries = allCodexEntries.filter((entry) =>
entry.data.tags.map(t => t.toLowerCase()).includes(Astro.params.tag)
);
---
<GameLayout title={`Domaine : ${tag}`}>
<main class="codex-container">
<h1>Domaine : {tag}</h1>
<p>
Liste de tous les savoirs et mécaniques liés au domaine "{tag}".
</p>
<div class="card-grid">
{filteredEntries.map((entry) => <CodexCard entry={entry} />)}
</div>
</main>
</GameLayout>