ne pas se reposer sur ces acquis

This commit is contained in:
2026-01-14 00:05:28 +04:00
parent d2607859bd
commit b45c19687c
27 changed files with 942 additions and 335 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@@ -1,66 +0,0 @@
---
export interface Props {
title: string;
body: string;
href: string;
}
const { href, title, body } = Astro.props;
---
<li class="link-card">
<a href={href}>
<h2>
{title}
<span>&rarr;</span>
</h2>
<p>
{body}
</p>
</a>
</li>
<style>
.link-card {
list-style: none;
display: flex;
padding: 1px;
background-color: #f0f0f0;
background-image: none;
background-size: 400%;
border-radius: 7px;
background-position: 100%;
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.link-card > a {
width: 100%;
text-decoration: none;
line-height: 1.4;
padding: calc(1.5rem - 1px);
border-radius: 8px;
color: rgb(49, 49, 49);
opacity: 0.8;
}
h2 {
margin: 0;
font-size: 1.25rem;
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
p {
margin-top: 0.5rem;
margin-bottom: 0;
}
h2 span {
display: inline-block;
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.link-card:is(:hover, :focus-within) {
background-position: 0;
background-image: var(--accent-gradient);
}
.link-card:is(:hover, :focus-within) h2 {
color: rgb(var(--accent-light));
}
.link-card:is(:hover, :focus-within) h2 span {
transform: translateX(4px);
}
</style>

View File

@@ -0,0 +1,163 @@
---
import type { CollectionEntry } from 'astro:content';
import GoldButton from './ui/GoldButton.astro';
interface Props {
posts: CollectionEntry<'journal' | 'logs'>[];
tags: string[];
basePath: 'journal' | 'logs';
}
const { posts, tags, basePath } = Astro.props;
---
<div id="journal-search-component">
<div class="search-wrapper">
<input type="search" id="search-input" placeholder="Rechercher..." />
</div>
<div id="tag-filters" class="tags-container">
<button class="tag-btn active" data-tag="all">Toutes</button>
{
tags.map((tag) => (
<button class="tag-btn" data-tag={tag}>{tag}</button>
))
}
</div>
<div id="search-results" class="card-grid">
{
posts.map((post) => (
<GoldButton
href={`/${basePath}/${post.slug}/`}
title={post.data.title}
body={`Publié le ${post.data.publishDate.toLocaleDateString('fr-FR')}`}
/>
))
}
</div>
<p id="no-results" class="no-results-message" style="display: none;">
Aucun document ne correspond à votre recherche.
</p>
</div>
<script define:vars={{ posts, tags, basePath }}>
const searchInput = document.getElementById('search-input');
const resultsContainer = document.getElementById('search-results');
const noResultsMessage = document.getElementById('no-results');
const tagFiltersContainer = document.getElementById('tag-filters');
let currentQuery = '';
let activeTag = 'all';
// --- Fonctions Utilitaires ---
// Fonction pour normaliser le texte (enlever les accents, mettre en minuscule)
function normalizeText(text) {
return text
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '');
}
// 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));
}
// 2. Filtrer par recherche textuelle
if (currentQuery) {
filteredPosts = filteredPosts.filter(post => normalizeText(post.data.title).includes(currentQuery));
}
// 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('');
}
// --- Écouteurs d'événements ---
searchInput.addEventListener('input', (e) => {
currentQuery = normalizeText(e.target.value);
renderResults();
});
tagFiltersContainer.addEventListener('click', (e) => {
if (!e.target.matches('.tag-btn')) return;
// Mettre à jour le style du bouton actif
tagFiltersContainer.querySelector('.active').classList.remove('active');
e.target.classList.add('active');
activeTag = e.target.dataset.tag;
renderResults();
});
</script>
<style>
/* Styles pour les filtres de balises */
.tags-container {
display: flex;
justify-content: center;
gap: 0.75rem;
flex-wrap: wrap;
margin-bottom: 3rem;
}
.search-wrapper {
margin-bottom: 3rem;
text-align: center;
}
#search-input {
width: 100%;
max-width: 400px;
padding: 0.75rem 1rem;
font-size: 1.1rem;
font-family: 'EB Garamond', serif;
border: 2px solid #dcd0b9;
border-radius: 8px;
background-color: rgba(253, 246, 232, 0.8);
color: #4a4130;
}
#search-input:focus {
outline: none;
border-color: #c89b3c;
box-shadow: 0 0 10px rgba(200, 155, 60, 0.4);
}
.no-results-message {
text-align: center;
font-style: italic;
font-size: 1.2rem;
color: #4a4130;
}
.tag-btn {
background-color: transparent;
color: #b8860b;
padding: 0.4rem 1rem;
border-radius: 15px;
font-size: 0.9rem;
font-family: 'Cinzel', serif;
border: 1px solid rgba(200, 155, 60, 0.5);
cursor: pointer;
transition: all 0.2s ease;
}
.tag-btn:hover {
background-color: rgba(200, 155, 60, 0.2);
border-color: #c89b3c;
}
.tag-btn.active {
background-color: #c89b3c;
color: #2c2a24;
border-color: #c89b3c;
}
</style>

View File

@@ -0,0 +1,10 @@
---
// Ce composant accepte les props SVG standards comme `class`.
const { ...props } = Astro.props;
---
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M8 21h12a2 2 0 0 0 2-2v-2H10v2a2 2 0 0 1-2 2Z"></path>
<path d="M4 21a2 2 0 0 1-2-2v-2h10v4H4Z"></path>
<path d="M4 5v12h16V5a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2Z"></path>
</svg>

View File

@@ -0,0 +1,87 @@
---
const { pathname } = Astro.url;
// Dans une application réelle, ces liens pourraient provenir d'un fichier de configuration.
const navItems = [
{ href: '/', label: 'Accueil' },
{ href: '/atelier', label: 'Atelier' },
{ href: '/observatoire', label: 'Observatoire' },
{ href: '/boussole', label: 'Boussole' },
{ href: '/journal', label: 'Journal' },
{ href: '/auteur', label: 'Auteur' },
{ href: '/logs', label: 'Logs' },
];
---
<nav class="game-nav">
<div id="nav-container" class="nav-container">
{
navItems.map((item) => {
// Met en surbrillance si le chemin commence par l'URL du lien (ex: /journal/2 commence par /journal)
const isActive = item.href === '/' ? pathname === '/' : pathname.startsWith(item.href) && item.href.length > 1;
return (
<a href={item.href} class:list={['nav-item', { active: isActive }]} title={item.label}>
{item.label}
</a>
);
})
}
</div>
</nav>
<style>
/* --- Styles de la barre de navigation --- */
.game-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: auto; /* La hauteur s'adapte au contenu */
background: rgba(253, 246, 232, 0.85); /* Fond parchemin semi-transparent */
border-top: 2px solid #c89b3c; /* Bordure dorée */
box-shadow: 0 -5px 20px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(8px);
z-index: 1000;
padding: 0.5rem 1rem; /* Un peu d'espace intérieur */
}
/* --- Styles du conteneur des liens --- */
.nav-container {
display: flex;
flex-wrap: wrap; /* Permet aux éléments de passer à la ligne */
justify-content: center;
align-items: center;
height: inherit;
gap: 0.5rem 1.5rem; /* Espace vertical et horizontal entre les liens */
}
.nav-item {
text-decoration: none;
color: #4a4130; /* Couleur texte sombre pour le contraste */
font-family: 'Cinzel', serif;
font-size: 0.9rem;
transition: all 0.2s ease-in-out;
padding: 0.25rem 0.5rem;
}
.nav-item.active {
color: #b8860b; /* Couleur dorée pour l'élément actif */
transform: scale(1.05);
}
.nav-item:hover {
color: #b8860b; /* Couleur dorée au survol */
transform: scale(1.1);
}
/* --- Styles pour les écrans larges (Desktop) --- */
@media (min-width: 768px) {
.game-nav {
height: 55px; /* Hauteur fixe pour le bureau */
padding: 0 1rem;
}
.nav-container {
flex-wrap: nowrap; /* Empêche le retour à la ligne sur les grands écrans */
}
}
</style>

View File

@@ -0,0 +1,17 @@
---
interface Props {
href: string;
title: string;
body: string;
}
const { href, title, body } = Astro.props;
---
<style>
/* Les styles ont été déplacés dans GameLayout.astro pour être globaux */
</style>
<a href={href} class="gold-button">
<div class="title">{title}</div>
<p class="body">{body}</p>
</a>

View File

@@ -0,0 +1,20 @@
---
// Ce composant est un simple conteneur stylisé.
// Il n'a pas besoin de props, il affiche simplement le contenu passé à l'intérieur.
---
<div class="parchment-card">
<slot />
</div>
<style>
.parchment-card {
background-color: rgba(253, 246, 232, 0.8); /* Fond parchemin légèrement transparent */
border: 1px solid #dcd0b9;
border-radius: 8px;
padding: 2rem;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(4px); /* Effet de verre dépoli pour voir la texture du fond */
margin-bottom: 2rem;
}
</style>

View File

@@ -1,31 +1,27 @@
// 1. Importer les utilitaires de `astro:content`
import { z, defineCollection } from 'astro:content';
import { defineCollection, z } from 'astro:content';
// 2. Définir une collection pour le journal d'aventure
// Collection pour les articles du journal d'aventure
const journalCollection = defineCollection({
type: 'content', // 'content' pour les fichiers .md ou .mdx
type: 'content',
schema: z.object({
title: z.string(),
author: z.string(),
publishDate: z.date(),
image: z.object({
src: z.string(),
alt: z.string(),
}).optional(),
tags: z.array(z.string()).optional(), // Ajout des balises (optionnel)
}),
});
// 3. Définir une collection pour les logs de construction
// Collection pour les logs de construction
const logsCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
publishDate: z.date(),
tags: z.array(z.string()).optional(),
}),
});
// 4. Exporter les collections pour les enregistrer
export const collections = {
'journal': journalCollection,
'logs': logsCollection,
journal: journalCollection,
logs: logsCollection,
};

View File

@@ -2,11 +2,12 @@
title: "L'Éveil du Cul Brillant"
author: "G'Mas"
publishDate: 2026-01-10
tags: ["Yeuze-sur-Chenarde", "Exploration", "Gurdil", "Bulle", "Social", "Mystère"]
---
### Chroniques de Yeuze-sur-Chenarde
L'arrivée à **Yeuze-sur-Chenarde** restera gravée dans ma mémoire, non pas par le sang, mais par la glace. Nous sommes arrivés en début d'après-midi, et le froid m'a immédiatement saisi les os, moi qui suis si sensible à ce "souffle de vie" qui anime le monde. Accompagné de la fée Bulle, je ne pensais qu'à une chose : le **Grand Chêne**. Cet arbre colossal, mon but, mon phare dans cette immensité blanche qui se dresse dorénavant devant nous
L'arrivée à **Yeuze-sur-Chenarde** restera gravée dans ma mémoire, non pas par le sang, mais par la glace. Nous sommes arrivés en début d'après-midi, et le froid m'a immédiatement saisi les os, moi qui suis si sensible à ce "souffle de vie" qui anime le monde. Accompagné de la fée Bulle, je ne pensais qu'à une chose : le **Grand Chêne**. Cet arbre colossal, mon but, mon phare dans cette immensité blanche qui se dresse dorénavant devant nous.
Soudain, le chaos. Un projectile percute l'arrière de mon crâne. Gurdil le nain, plus chanceux ou plus bas, l'évite de justesse. Nyrae, notre Tabaxi, n'a pas cette chance, bien qu'elle ait aperçu nos assaillants avant l'impact. Ce n'était pas une embuscade de brigands, mais une armée de gamins du village armés de boules de neige ! Frigorifié, incapable de réagir, je me suis laissé "exploser" par leurs tirs. Une défaite tactique cuisante qui nous a forcés à nous réfugier, tels des vaincus, dans la chaleur de l'auberge.
@@ -76,4 +77,3 @@ Kwel semble être une source d'informations aussi riche qu'incertaine. Voici les
**Paradoxe Temporel :** Il affirme avoir fait partie d'aventuriers il y a un millénaire, tout en disant qu'il a lui-même moins de 1000 ans (alors que le village, lui, dépasse cet âge).
**Connaissance des Grungs :** Il a mentionné avoir déjà croisé des membres de votre espèce (Grung) au cours de sa vie.

View File

@@ -0,0 +1,35 @@
---
title: "Acquis et Paradigmes d'Astro"
publishDate: 2026-01-13
tags: ["Astro", "Architecture", "Paradigmes", "Composants", "Routage", "Contenu"]
---
Ce log est une halte. Un moment pour observer le chemin parcouru, non pas comme une liste de compétences acquises, mais comme la compréhension des fondations sur lesquelles nous nous tenons. Cette version du site est une étape nécessaire : l'apprentissage des langages et des structures du web "plat", pour mieux en déceler les limites et préparer le véritable saut dans l'immersion.
### 1. L'Apprentissage du Langage des Bâtisseurs
Avant de pouvoir façonner le sable en mondes, il fallait apprendre à tailler la pierre. L'architecture actuelle repose sur les paradigmes d'Astro, qui sont ceux des bâtisseurs de la toile moderne.
* **L'Art de la Brique (`Composants Astro`)** : Nous avons appris à créer des "briques" réutilisables (`GoldButton`, `ParchmentCard`). C'est un art de l'efficacité, de la modularité. Chaque brique est autonome, avec ses propres styles. C'est puissant, mais cela nous enferme dans une logique de "boîtes" empilées les unes sur les autres.
* **Le Grimoire Ordonné (`Content Collections`)** : Nous avons appris à cataloguer nos récits et nos logs. Chaque parchemin est validé, chaque métadonnée est à sa place. C'est la sagesse de l'archiviste, qui assure l'ordre et la cohérence. Mais un livre, même magique, reste une succession de pages linéaires.
* **Les Sentiers Tracés (`Routage Dynamique`)** : Nous avons appris à tracer les chemins qui relient nos pages. De l'index du journal à un article, d'un article à sa balise. Ces chemins sont clairs, rapides, générés à l'avance. Mais ce sont des sentiers balisés sur une carte en deux dimensions, pas une exploration libre dans un monde vivant.
### 2. La Conscience des Murs Invisibles
Cette architecture modulaire, si prisée, est aussi une cage dorée. Elle nous a permis de construire vite et bien, mais elle nous a aussi forcés à penser en "pages", en "blocs", en "liens". L'immersion que permet Three.js ne peut se contenter de cela.
Le paradigme actuel est celui du **document**. On navigue d'un document à l'autre. L'ambition est de passer au paradigme de l'**espace**. Un monde unique et persistant dans lequel le contenu n'est pas une page que l'on charge, mais un objet avec lequel on interagit.
C'est ici que des logiques ont été abandonnées, non par erreur, mais par nécessité. Le menu hamburger, par exemple, est une convention du web 2D. Dans un monde 3D, la navigation pourrait être un objet dans la scène, une carte que l'on déplie, un chemin de lumière que l'on suit.
### 3. Le Prochain Pas : Façonner le Sable
Cette fondation n'est pas vaine. Elle est le sol stable sur lequel nous allons maintenant ériger un sanctuaire en trois dimensions. La prochaine grande étape n'est pas d'ajouter une fonctionnalité, mais de changer de regard.
* **Comprendre chaque grain** : Il s'agira de déconstruire. Comment une scène Three.js peut-elle devenir le "layout" principal ? Comment faire en sorte que le routage d'Astro ne recharge pas une page, mais déclenche une animation dans la scène 3D ?
* **Construire avec intention** : Chaque choix devra être conscient. Nous n'appliquerons pas des solutions toutes faites, mais nous chercherons à comprendre les principes fondamentaux de la 3D sur le web pour créer une expérience qui a du sens. Le but n'est pas de "faire" un site 3D, mais de **comprendre** comment le faire naître.
Cette étape est terminée. Le pèlerin a appris les cartes du vieux monde. Il est temps, maintenant, de les brûler pour dessiner la sienne.

View File

@@ -1,6 +1,7 @@
---
title: "Initialisation du projet"
publishDate: 2026-01-09
tags: ["Setup", "Infrastructure", "Docker", "Raspberry Pi", "Astro"]
---
Le projet a été initialisé avec Astro. La structure de base est en place.

View File

@@ -1,7 +1,7 @@
---
title: "Ajout de la matière du passé"
publishDate: 2026-01-12
description: "Création du contenu narratif principal du site à travers la voix de G'Mas."
tags: ["Contenu", "Design", "Narratif", "Composants"]
---
Le site a été enrichi avec le contenu principal, donnant vie à l'univers narratif du projet. L'ensemble du site adopte désormais la voix et la perspective de G'Mas, le pèlerin Grung.

View File

@@ -0,0 +1,252 @@
---
import { ViewTransitions } from 'astro:transitions';
import GameNav from '../components/ui/GameNav.astro';
interface Props {
title: string;
}
const { title } = Astro.props;
---
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="description" content="Clone de AFK Journey avec Astro et Three.js" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<ViewTransitions />
</head>
<body>
<main>
<slot />
</main>
<GameNav />
</body>
</html>
<style is:global>
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@700&family=EB+Garamond&display=swap');
html {
background-color: #fdf6e8; /* Ton crème/parchemin */
}
body {
/* La couleur de fond est maintenant sur l'élément HTML */
background-color: transparent;
color: #4a4a4a;
font-family: 'EB Garamond', serif;
margin: 0;
/* Ajout d'un padding en bas pour ne pas que la nav masque le contenu */
padding: 2rem 2rem 100px 2rem;
position: relative;
}
/* Applique une texture de grain de papier en arrière-plan */
body::before {
content: '';
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
/* Assurez-vous d'avoir une image de texture dans public/textures/ */
background-image: url('/textures/paper-grain.png');
opacity: 0.5; /* J'augmente l'opacité pour qu'elle soit bien visible */
pointer-events: none;
z-index: -1;
}
/* --- Styles des Titres --- */
h1, h2 {
font-family: 'Cinzel', serif;
text-align: center;
color: #3a352a; /* Une couleur de texte sombre pour un bon contraste */
margin-bottom: 2rem; /* Espace après l'ornement */
}
/* Ajoute un ornement doré sous les titres */
h1::after, h2::after {
content: '';
display: block;
width: 100px; /* Largeur de l'ornement */
height: 2px;
background: linear-gradient(90deg, transparent, #c89b3c, transparent); /* Dégradé doré */
margin: 0.75rem auto 0; /* Espace entre le texte et l'ornement */
opacity: 0.8;
/* Animation pour "dessiner" la ligne */
transform: scaleX(0);
animation: drawLine 1s cubic-bezier(0.22, 1, 0.36, 1) 0.3s forwards;
}
/* On peut différencier légèrement le h2 */
h2 {
font-size: 1.8rem;
color: #4a4130;
}
/* --- Styles pour le contenu Markdown (.prose) --- */
.prose h3 {
font-family: 'Cinzel', serif;
text-align: left;
font-size: 1.5rem;
border-bottom: 1px solid #dcd0b9;
padding-bottom: 0.5rem;
margin-top: 2.5rem;
}
/* On retire l'ornement pour les h3 */
.prose h3::after {
content: none;
}
.prose p {
line-height: 1.7;
font-size: 1.1rem;
}
.parchment-card a {
color: #c89b3c; /* Or vif */
text-decoration: none;
font-weight: bold;
transition: color 0.2s;
}
.parchment-card a:hover {
filter: brightness(1.2);
}
.prose ul {
list-style: none;
padding-left: 1rem;
}
.prose ul li::before {
content: '•';
color: #c89b3c;
font-weight: bold;
display: inline-block;
width: 1em;
margin-left: -1em;
}
.prose hr {
border: 0;
height: 2px;
background: linear-gradient(90deg, transparent, #c89b3c, transparent);
margin: 3rem 0;
}
.prose table {
width: 100%;
border-collapse: collapse;
margin: 2rem 0;
}
.prose th, .prose td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #dcd0b9; /* Ligne de séparation parchemin */
}
.prose th {
font-family: 'Cinzel', serif;
background-color: rgba(253, 246, 232, 0.5); /* Fond de cellule plus clair */
}
/* --- Styles pour GoldButton --- */
.gold-button {
background: linear-gradient(145deg, #fefbf3, #f8f1e4); /* Dégradé parchemin clair */
border: 2px solid #c89b3c;
border-radius: 15px;
color: #4a4130; /* Texte sombre pour le contraste */
padding: 1.5rem;
text-align: center;
text-decoration: none;
transition: all 0.3s ease;
display: block;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1), inset 0 0 5px rgba(200, 155, 60, 0.1);
}
.gold-button:hover {
transform: translateY(-5px);
filter: brightness(1.05);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15), inset 0 0 10px rgba(200, 155, 60, 0.2);
border-color: #f0e6d2;
}
.gold-button .title { /* Ciblage plus spécifique */
font-family: 'Cinzel', serif;
font-size: 1.5rem;
color: #b8860b; /* Titre en couleur or */
margin-bottom: 0.5rem;
}
.gold-button .body { /* Ciblage plus spécifique */
font-family: 'EB Garamond', serif;
font-size: 1rem;
color: #4a4130; /* Texte sombre pour le contraste */
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
gap: 2rem;
padding: 0;
}
/* --- Styles pour les Tags --- */
.tags-container {
display: flex;
justify-content: center;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 2rem;
}
.tag {
background-color: rgba(200, 155, 60, 0.2);
color: #b8860b;
padding: 0.25rem 0.75rem;
border-radius: 15px;
font-size: 0.8rem;
font-family: 'Cinzel', serif;
border: 1px solid rgba(200, 155, 60, 0.3);
text-decoration: none;
transition: all 0.2s ease;
}
.tag:hover {
background-color: #c89b3c;
color: #2c2a24;
}
/* Keyframes pour l'animation de l'ornement */
@keyframes drawLine {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
/* Animation pour la nouvelle page (le "pop-up") */
@keyframes slide-in {
from {
transform: translateY(15%) scale(0.95);
opacity: 0;
}
to {
transform: translateY(0) scale(1);
opacity: 1;
}
}
/* Animation pour l'ancienne page qui disparaît */
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
/* On applique nos animations aux pseudo-éléments de View Transitions */
::view-transition-new(root) {
animation: 0.4s cubic-bezier(0.22, 1, 0.36, 1) 0s 1 normal both running slide-in;
}
::view-transition-old(root) {
animation: 0.2s ease-out 0s 1 normal both running fade-out;
}
</style>

View File

@@ -1,50 +0,0 @@
---
interface Props {
title: string;
}
const { title } = Astro.props;
---
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="description" content="Astro description" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<style is:global>
table {
width: 100%;
border-collapse: collapse;
margin-block: 1rem;
}
th,
td {
border: 1px solid #ccc;
padding: 0.5rem;
text-align: left;
}
th {
background-color: #f2f2f2;
}
</style>
</head>
<body>
<header>
<nav>
<a href="/">Accueil</a> |
<a href="/journal">Journal</a> |
<a href="/auteur">Auteur</a> |
<a href="/logs">Logs de Construction</a>
</nav>
</header>
<main>
<slot />
</main>
</body>
</html>

View File

@@ -1,7 +1,9 @@
---
import Layout from '../layouts/Layout.astro';
import GameLayout from '../layouts/GameLayout.astro';
import ParchmentCard from '../components/ui/ParchmentCard.astro';
---
<Layout title="Le Sanctuaire des Grains - Sweet Journey">
<GameLayout title="Le Sanctuaire des Grains - Sweet Journey">
<ParchmentCard>
<h1>🏜️ Le Sanctuaire des Grains</h1>
<p>
Entrez dans mon lieu de méditation. D'où je viens, la création n'est pas une affaire de métal et de feu, mais de patience, de concentration et de sable. Chaque grain est une possibilité, chaque ligne de code un chemin tracé dans les dunes de l'esprit. Cette section dévoile les arts que j'emploie pour donner forme à ce monde.
@@ -42,4 +44,5 @@ import Layout from '../layouts/Layout.astro';
</ul>
</dd>
</dl>
</Layout>
</ParchmentCard>
</GameLayout>

View File

@@ -1,8 +1,10 @@
---
import Layout from '../layouts/Layout.astro';
import GameLayout from '../layouts/GameLayout.astro';
import ParchmentCard from '../components/ui/ParchmentCard.astro';
---
<Layout title="L'Artisan des Rêves - Nicolas Latchimy">
<GameLayout title="L'Artisan des Rêves - Nicolas Latchimy">
<ParchmentCard>
<h1>🐸 L'Artisan derrière le Pèlerin</h1>
<p>
Je suis G'Mas, un pèlerin fait de sable et de songes. Mais qui est l'artisan qui me rêve ? Qui est celui dont les mains façonnent mon voyage ? Laissez-moi vous parler de lui, car son histoire est la source de la mienne.
@@ -38,5 +40,7 @@ import Layout from '../layouts/Layout.astro';
<li><strong>Instagram :</strong> <a href="https://www.instagram.com/mosaic_chantilly/" target="_blank" rel="noopener noreferrer">@mosaic_chantilly</a></li>
<li><strong>CV en ligne :</strong> <a href="https://nohay.github.io/" target="_blank" rel="noopener noreferrer">Découvrir son parcours</a></li>
</ul>
</ParchmentCard>
</GameLayout>
</Layout>

View File

@@ -1,8 +1,10 @@
---
import Layout from '../layouts/Layout.astro';
import GameLayout from '../layouts/GameLayout.astro';
import ParchmentCard from '../components/ui/ParchmentCard.astro';
---
<Layout title="La Boussole Éthique - Sweet Journey">
<GameLayout title="La Boussole Éthique - Sweet Journey">
<ParchmentCard>
<h1>⚖️ La Boussole Éthique</h1>
<p>
Mon voyage a commencé à cause d'un fléau : un sable sombre, dépourvu de vie, qui corrompt mes terres et empoisonne les rêves de mon peuple. Cette quête m'a enseigné à distinguer le sable pur du sable vicié. Il en va de même pour les créations des mortels. Certaines sont conçues pour apporter un repos réparateur, d'autres sont des pièges, aussi subtils que le poison sur ma peau.
@@ -13,5 +15,5 @@ import Layout from '../layouts/Layout.astro';
<h2>Les Marécages de Sable Noir : Les Chemins à Éviter</h2>
<p>Ici se trouvent les sables mouvants. Je perçois dans le monde une tendance à la démesure, une soif de pouvoir qui oublie l'équilibre. On y cherche à dominer plutôt qu'à harmoniser, à prendre plutôt qu'à partager. Cette philosophie ressemble au sable noir : elle crée des systèmes où quelques-uns s'enrichissent en asséchant les rêves de la multitude. Mon art se refuse à emprunter cette voie.</p>
</Layout>
</ParchmentCard>
</GameLayout>

View File

@@ -1,17 +1,9 @@
---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
import GameLayout from '../layouts/GameLayout.astro';
import GoldButton from '../components/ui/GoldButton.astro';
---
<style>
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
gap: 2rem;
padding: 0;
}
</style>
<Layout title="Bienvenue sur JDRSweetJourney">
<GameLayout title="Bienvenue sur JDRSweetJourney">
<h1>Bienvenue sur le projet JDR Sweet Journey !</h1>
<h2>L'Ambition</h2>
<p>L'objectif est de créer un clone entièrement web de l'application mobile <strong>AFK Journey</strong>. Ce projet sera une vitrine technologique utilisant :</p>
@@ -20,10 +12,10 @@ import Card from '../components/Card.astro';
<li>La retranscription de ces éléments dans le navigateur grâce à <strong>Three.js</strong>.</li>
</ul>
<ul class="card-grid">
<Card
<GoldButton
href="https://threejs-journey.com"
title="🎓 Formation : Three.js Journey"
body="Pour garantir une base technique solide, ce projet s'appuie sur les enseignements de Three.js Journey, une formation de référence pour maîtriser Three.js."
body="Ce projet s'appuie sur les enseignements de Three.js Journey pour garantir une base technique solide."
/>
</ul>
<p>Si en plus, cette plateforme peut servir de point de repère pour nos aventures de jeu de rôle, c'est une pierre deux coups !</p>
@@ -32,17 +24,17 @@ import Card from '../components/Card.astro';
<p>Ce site est aussi un levier pour maîtriser les paradigmes technologiques de demain. Explorez le <strong>journal d'aventure</strong> pour suivre la quête, ou consultez les <strong>logs de construction</strong> pour voir les coulisses techniques.</p>
<ul class="card-grid">
<Card
<GoldButton
href="/atelier"
title="🏜️ Le Sanctuaire des Grains"
body="Découvrez les arts et les outils du pèlerin pour façonner le sable et les rêves."
/>
<Card
<GoldButton
href="/observatoire"
title="🎨 L'Observatoire"
body="Explorez les sources d'inspiration : artistes, développeurs, podcasts et jeux qui nourrissent ce projet."
/>
<Card
<GoldButton
href="/boussole"
title="⚖️ La Boussole Éthique"
body="Une réflexion sur les modèles économiques vertueux et ceux à éviter dans le monde du jeu vidéo."
@@ -50,4 +42,4 @@ import Card from '../components/Card.astro';
</ul>
<p>Quelle ambition, jeune batracien...</p>
</Layout>
</GameLayout>

View File

@@ -0,0 +1,45 @@
---
import { getCollection } from 'astro:content';
import GameLayout from '../../layouts/GameLayout.astro';
import ParchmentCard from '../../components/ui/ParchmentCard.astro';
// 1. Génère une page statique pour chaque article de la collection
export async function getStaticPaths() {
const posts = await getCollection('journal');
return posts.map((post) => ({
params: { slug: post.slug },
props: post,
}));
}
// 2. Récupère les props de l'article correspondant
const post = Astro.props;
const { Content } = await post.render();
---
<GameLayout title={post.data.title}>
<ParchmentCard>
<h1>{post.data.title}</h1>
<div class="meta">
Par {post.data.author} &bull; Le {post.data.publishDate.toLocaleDateString('fr-FR')}
</div>
{post.data.tags && (
<div class="tags-container">
{post.data.tags.map((tag) => (
<a href={`/journal/tags/${tag}`} class="tag">{tag}</a>
))}
</div>
)}
<article class="prose">
<Content />
</article>
</ParchmentCard>
</GameLayout>
<style>
.meta {
text-align: center;
font-style: italic;
margin-bottom: 2rem;
}
</style>

View File

@@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
// 1. Génère une page pour chaque entrée de la collection 'journal'
export async function getStaticPaths() {
const journalEntries = await getCollection('journal');
return journalEntries.map(entry => ({
params: { slug: entry.slug },
props: { entry },
}));
}
// 2. Récupère les props pour la page actuelle
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<Layout title={entry.data.title}>
<h1>{entry.data.title}</h1>
<p>Par {entry.data.author}, le {entry.data.publishDate.toLocaleDateString('fr-FR')}</p>
<hr>
<Content />
</Layout>

View File

@@ -1,18 +1,26 @@
---
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
import GameLayout from '../../layouts/GameLayout.astro';
import ContentSearch from '../../components/ContentSearch.astro';
// 1. Récupère TOUS les articles et les trie.
const allPosts = await getCollection('journal');
const sortedPosts = allPosts.sort(
(a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
);
// 2. Extrait toutes les balises uniques
const allTags = [...new Set(allPosts.flatMap(post => post.data.tags || []))];
const allJournalEntries = await getCollection('journal');
---
<Layout title="Journal d'Aventure">
<h1>Journal d'Aventure</h1>
<ul>
{allJournalEntries.map(entry => (
<li>
<a href={`/journal/${entry.slug}`}>{entry.data.title}</a>
<p>Publié le: {entry.data.publishDate.toLocaleDateString('fr-FR')}</p>
</li>
))}
</ul>
</Layout>
<GameLayout title="Journal d'Aventure">
<h1>📖 Journal d'Aventure</h1>
<p style="text-align: center; margin-bottom: 3rem;">
Les chroniques de nos voyages. Utilisez la barre de recherche pour trouver un récit.
</p>
<!-- 2. On passe tous les articles au composant client -->
<ContentSearch posts={sortedPosts} tags={allTags} basePath="journal" />
</GameLayout>

View File

@@ -0,0 +1,39 @@
---
import { getCollection } from 'astro:content';
import GameLayout from '../../../layouts/GameLayout.astro';
import GoldButton from '../../../components/ui/GoldButton.astro';
export async function getStaticPaths() {
const allPosts = await getCollection('journal');
const allTags = [...new Set(allPosts.flatMap((post) => post.data.tags || []))];
return allTags.map((tag) => {
const filteredPosts = allPosts.filter((post) => post.data.tags?.includes(tag));
return {
params: { tag },
props: { posts: filteredPosts },
};
});
}
const { tag } = Astro.params;
const { posts } = Astro.props;
---
<GameLayout title={`Récits: ${tag}`}>
<h1>Récits avec la balise : <span class="tag-title">{tag}</span></h1>
<div class="card-grid">
{
posts.map((post) => (
<GoldButton href={`/journal/${post.slug}/`} title={post.data.title} body={`Publié le ${post.data.publishDate.toLocaleDateString('fr-FR')}`} />
))
}
</div>
</GameLayout>
<style>
.tag-title {
color: #c89b3c;
}
</style>

View File

@@ -0,0 +1,47 @@
---
import { getCollection } from 'astro:content';
import GameLayout from '../../layouts/GameLayout.astro';
import ParchmentCard from '../../components/ui/ParchmentCard.astro';
// 1. Génère une page pour chaque entrée de la collection 'logs'
export async function getStaticPaths() {
const logEntries = await getCollection('logs');
return logEntries.map((entry) => ({
params: { slug: entry.slug },
props: entry,
}));
}
// 2. Récupère les props pour la page actuelle
const post = Astro.props;
const { Content } = await post.render();
---
<GameLayout title={post.data.title}>
<ParchmentCard>
<h1>{post.data.title}</h1>
<div class="meta">
Publié le {post.data.publishDate.toLocaleDateString('fr-FR')}
</div>
{post.data.tags && (
<div class="tags-container">
{post.data.tags.map((tag) => (
<a href={`/logs/tags/${tag}`} class="tag">{tag}</a>
))}
</div>
)}
<article class="prose">
<Content />
</article>
</ParchmentCard>
</GameLayout>
<style>
/* Les styles des tags sont maintenant globaux dans GameLayout.astro */
.meta {
text-align: center;
font-style: italic;
margin-bottom: 2rem;
}
</style>

View File

@@ -1,24 +0,0 @@
---
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
// 1. Génère une page pour chaque entrée de la collection 'logs'
export async function getStaticPaths() {
const journalEntries = await getCollection('logs');
return journalEntries.map(entry => ({
params: { slug: entry.slug },
props: { entry },
}));
}
// 2. Récupère les props pour la page actuelle
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<Layout title={entry.data.title}>
<h1>{entry.data.title}</h1>
<p>Publié le {entry.data.publishDate.toLocaleDateString('fr-FR')}</p>
<hr>
<Content />
</Layout>

View File

@@ -1,18 +1,25 @@
---
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
import GameLayout from '../../layouts/GameLayout.astro';
import ContentSearch from '../../components/ContentSearch.astro';
const allLogEntries = await getCollection('logs');
// 1. Récupère TOUS les logs et les trie.
const allPosts = await getCollection('logs');
const sortedPosts = allPosts.sort(
(a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
);
// 2. Extrait toutes les balises uniques
const allTags = [...new Set(allPosts.flatMap(post => post.data.tags || []))];
---
<Layout title="Logs de Construction">
<h1>Logs de Construction</h1>
<ul>
{allLogEntries.map(entry => (
<li>
<a href={`/logs/${entry.slug}`}>{entry.data.title}</a>
<p>Publié le: {entry.data.publishDate.toLocaleDateString('fr-FR')}</p>
</li>
))}
</ul>
</Layout>
<GameLayout title="Logs de Construction">
<h1>⚙️ Logs de Construction</h1>
<p style="text-align: center; margin-bottom: 3rem;">
Les coulisses techniques du projet. Utilisez la barre de recherche pour trouver un log.
</p>
<!-- 3. On utilise le composant de recherche pour les logs -->
<ContentSearch posts={sortedPosts} tags={allTags} basePath="logs" />
</GameLayout>

View File

@@ -0,0 +1,39 @@
---
import { getCollection } from 'astro:content';
import GameLayout from '../../../layouts/GameLayout.astro';
import GoldButton from '../../../components/ui/GoldButton.astro';
export async function getStaticPaths() {
const allPosts = await getCollection('logs');
const allTags = [...new Set(allPosts.flatMap((post) => post.data.tags || []))];
return allTags.map((tag) => {
const filteredPosts = allPosts.filter((post) => post.data.tags?.includes(tag));
return {
params: { tag },
props: { posts: filteredPosts },
};
});
}
const { tag } = Astro.params;
const { posts } = Astro.props;
---
<GameLayout title={`Logs: ${tag}`}>
<h1>Logs avec la balise : <span class="tag-title">{tag}</span></h1>
<div class="card-grid">
{
posts.map((post) => (
<GoldButton href={`/logs/${post.slug}/`} title={post.data.title} body={`Publié le ${post.data.publishDate.toLocaleDateString('fr-FR')}`} />
))
}
</div>
</GameLayout>
<style>
.tag-title {
color: #c89b3c;
}
</style>

View File

@@ -1,8 +1,10 @@
---
import Layout from '../layouts/Layout.astro';
import GameLayout from '../layouts/GameLayout.astro';
import ParchmentCard from '../components/ui/ParchmentCard.astro';
---
<Layout title="L'Observatoire des Songes - Sweet Journey">
<GameLayout title="L'Observatoire des Songes - Sweet Journey">
<ParchmentCard>
<h1>🔭 L'Observatoire des Songes</h1>
<p>
Un pèlerin, même dans les mers de sable, doit lever les yeux vers les constellations pour ne pas perdre son chemin. Mon voyage n'est pas solitaire ; il est guidé par les échos d'autres mondes, les murmures d'autres créateurs. Cet observatoire est ma carte du ciel, où chaque étoile est une âme dont la lumière m'inspire.
@@ -24,5 +26,7 @@ import Layout from '../layouts/Layout.astro';
<h2>Les Murmures du Vent</h2>
<h2>Les Chroniques d'Autres Mondes</h2>
</ParchmentCard>
</GameLayout>
</Layout>