This commit is contained in:
2026-02-09 14:12:44 +04:00
parent 6727d7391f
commit 431fe2dd55
13 changed files with 375 additions and 166 deletions

View File

@@ -7,7 +7,7 @@ interface Props {
}
const { posts, tags, basePath } = Astro.props;
// Groupement des tags par catégorie
// Groupement des tags par catégorie (ton code actuel)
const groupedTags: Record<string, string[]> = { Général: [] };
tags.forEach((tag) => {
if (tag.includes(":")) {
@@ -67,7 +67,11 @@ tags.forEach((tag) => {
<div id="search-results" class="card-grid">
{
posts.map((post) => (
<a href={`/${basePath}/${post.id}/`} class="gold-button">
<a
href={`/${basePath}/${post.id}/`}
class="gold-button"
data-era={post.data.era}
>
<div class="title">{post.data.title}</div>
<p class="body">
Publié le{" "}
@@ -98,7 +102,10 @@ tags.forEach((tag) => {
if (!bridge || !input || !container) return;
const posts = JSON.parse(bridge.getAttribute("data-posts") || "[]");
// --- ÉTATS DES FILTRES ---
let activeTag = "all";
let activeEra = "all";
function update() {
const query = input.value
@@ -106,18 +113,18 @@ tags.forEach((tag) => {
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "");
const filtered = posts.filter((p) => {
const filtered = posts.filter((p: any) => {
const mQuery = p.data.title.toLowerCase().includes(query);
const mTag =
activeTag === "all" || p.data.tags?.includes(activeTag);
return mQuery && mTag;
const mEra = activeEra === "all" || p.data.era === activeEra;
return mQuery && mTag && mEra;
});
// Reconstruction propre du HTML des cartes
container.innerHTML = filtered
.map(
(p) => `
<a href="/journal/${p.id}/" class="gold-button">
(p: any) => `
<a href="/journal/${p.id}/" class="gold-button" data-era="${p.data.era || ""}">
<div class="title">${p.data.title}</div>
<p class="body">Publié le ${new Date(p.data.publishDate).toLocaleDateString("fr-FR")}</p>
</a>
@@ -126,22 +133,39 @@ tags.forEach((tag) => {
.join("");
}
// --- ÉCOUTEUR POUR LA TIMELINE (INDEX) ---
// On écoute les clics sur les chips de la Timeline qui sont hors du composant
document.addEventListener("click", (e) => {
const target = e.target as HTMLElement;
const eraBtn = target.closest(".era-chip");
if (eraBtn) {
activeEra = eraBtn.getAttribute("data-era") || "all";
// On gère la classe active visuellement
document
.querySelectorAll(".era-chip")
.forEach((btn) => btn.classList.remove("active"));
eraBtn.classList.add("active");
update();
}
});
// --- TES ÉCOUTEURS ACTUELS ---
input.addEventListener("input", update);
// Gestion de la navigation par catégories
document.querySelectorAll(".cat-nav-btn").forEach((btn) => {
btn.addEventListener("click", () => {
document
.querySelectorAll(".cat-nav-btn")
.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
const target = btn.getAttribute("data-target");
document.querySelectorAll(".tag-subgroup").forEach((g) => {
(g as HTMLElement).style.display =
g.id === `group-${target}` ? "block" : "none";
});
if (target === "all") {
activeTag = "all";
document
@@ -152,7 +176,6 @@ tags.forEach((tag) => {
});
});
// Gestion des filtres par tags
document.querySelectorAll(".js-tag-filter").forEach((t) => {
t.addEventListener("click", () => {
document
@@ -165,7 +188,6 @@ tags.forEach((tag) => {
});
}
// Ré-initialisation lors du changement de page (Astro View Transitions)
document.addEventListener("astro:page-load", init);
</script>

104
src/components/Footer.astro Normal file
View File

@@ -0,0 +1,104 @@
---
// On remonte de src/components vers la racine pour trouver le package.json
import packageInfo from "../../package.json" with { type: "json" };
const authorInfo = {
pseudo: "G'Mas",
role: "Artisan du Sable & Aventurier",
repoName: "sweet",
repoUrl: "https://git.liestral.io/chim1chim2/sweet",
};
const currentYear = new Date().getFullYear();
---
<footer>
<div class="footer-content">
<div class="metadata">
<p>
<strong>{authorInfo.pseudo}</strong><br /><br />{
authorInfo.role
}
</p>
<div class="specs">
<span>Version <code>v{packageInfo.version}</code></span>
<span class="separator">|</span>
<span
>Dépôt : <a
href={authorInfo.repoUrl}
target="_blank"
rel="noopener noreferrer"
><em>{authorInfo.repoName}</em></a
></span
>
<span class="separator">|</span>
<span>© {currentYear}</span>
</div>
</div>
</div>
</footer>
<style>
footer {
margin-top: 5rem;
padding: 3rem 1rem;
/* On utilise l'encre de ton thème pour la bordure */
border-top: 1px solid rgba(74, 65, 48, 0.1);
font-family: "Courier New", Courier, monospace;
color: var(--color-ink); /* Utilisation de ta variable globale */
font-size: 0.85rem;
opacity: 0.8;
}
.footer-content {
max-width: 800px;
margin: 0 auto;
text-align: center;
}
.metadata p {
margin-bottom: 0.5rem;
}
.specs {
display: flex;
justify-content: center;
gap: 0.75rem;
flex-wrap: wrap;
align-items: center;
}
code {
/* Fond sombre pour le code sur papier clair */
background: rgba(74, 65, 48, 0.05);
padding: 2px 6px;
border-radius: 4px;
color: var(--color-ink);
}
a {
color: var(--color-gold-dark);
text-decoration: none;
transition: all 0.2s;
border-bottom: 1px dashed transparent;
}
a:hover {
color: var(--color-gold);
border-bottom-color: var(--color-gold);
}
.separator {
opacity: 0.3;
}
@media (max-width: 600px) {
.separator {
display: none;
}
.specs {
flex-direction: column;
gap: 0.5rem;
}
}
</style>

View File

@@ -0,0 +1,89 @@
---
// src/components/Timeline.astro
interface Props {
eras: {
id: string;
title: string;
}[];
}
const { eras } = Astro.props;
---
<div class="timeline-ribbon">
<div class="scroll-container">
<button class="era-chip active" data-era="all">
Toutes les Ères
</button>
{
eras.map((era) => (
<button class="era-chip" data-era={era.id}>
<span class="scribe-icon">◈</span> {era.title}
</button>
))
}
</div>
</div>
<style>
.timeline-ribbon {
margin: 2rem 0;
padding: 1rem 0;
border-top: 1px solid rgba(200, 155, 60, 0.2);
border-bottom: 1px solid rgba(200, 155, 60, 0.2);
}
.scroll-container {
display: flex;
gap: 1rem;
overflow-x: auto;
padding: 0.5rem;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE */
}
.scroll-container::-webkit-scrollbar {
display: none; /* Chrome/Safari */
}
.era-chip {
flex: 0 0 auto;
background: white;
border: 1px solid #dcd0b9;
padding: 0.6rem 1.2rem;
border-radius: 4px;
font-family: "Cinzel", serif;
font-size: 0.85rem;
color: var(--color-ink);
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
}
.era-chip:hover {
border-color: var(--color-gold);
background: #fdfaf3;
}
.era-chip.active {
background: var(--color-gold);
color: white;
border-color: var(--color-gold);
box-shadow: 0 2px 8px rgba(200, 155, 60, 0.3);
}
.scribe-icon {
font-size: 0.9rem;
opacity: 0.8;
}
@media (max-width: 640px) {
.era-chip {
font-size: 0.75rem;
padding: 0.5rem 1rem;
}
}
</style>

View File

@@ -1,118 +0,0 @@
---
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

@@ -1,14 +0,0 @@
---
interface Props {
href: string;
title: string;
body: string;
}
const { href, title, body } = Astro.props;
---
<a href={href} class="gold-button">
<div class="title">{title}</div>
<p class="body">{body}</p>
</a>

View File

@@ -35,6 +35,7 @@ tags: [
"lore:Legendes-de-Kouel",
"lore:Bataille-du-Grand-Chêne"
]
era: "initiation"
---
### Chroniques de Yeuze-sur-Chenarde
@@ -65,6 +66,7 @@ A l'intérieur, l'obscurité y était totale, une gueule noire dévorant la lumi
Cest ainsi que **Gurdil**, désormais baptisé **"Cul Brillant"**, est devenu notre phare, notre cap, que dis-je notre firmament. Et c'est guidés par son postérieur rayonnant que nous nous sommes enfoncés dans les entrailles de la terre, prêts à affronter l'obscurité.
---
### Aventuriers
@@ -76,6 +78,8 @@ Cest ainsi que **Gurdil**, désormais baptisé **"Cul Brillant"**, est devenu
| **Gurdil** | Nain | Désormais surnommé "Cul Brillant" à cause du sort de Bulle. |
| **Jinn** | Genasi | Musicien du groupe ; a payé sa chambre d'auberge en jouant. |
---
### Personnages
| **Nom** | **Race / Rôle** | **Relation / Détails** |
@@ -96,6 +100,8 @@ Cest ainsi que **Gurdil**, désormais baptisé **"Cul Brillant"**, est devenu
| **Ran & Zas** | Gobelins | Enfants de Tritte et Iben. |
| **Alvin** | Gnome | Enfant du village. |
---
### Les légendes de Kouel le Sang-dragon
Kouel semble être une source d'informations aussi riche qu'incertaine. Voici les détails de ses récits :

View File

@@ -0,0 +1,5 @@
---
title: "inconnu"
publishDate: 2026-02-08
isEra: true
---

View File

@@ -0,0 +1,5 @@
---
title: "initiation"
publishDate: 2026-01-10
isEra: true
---

View File

@@ -19,6 +19,7 @@ tags: [
"plot:Cul-Brillant",
"event:Combat-Mine"
]
era: "initiation"
---
### Les Griffes de l'Obscurité
@@ -37,6 +38,8 @@ Le groupe réagit alors avec une coordination héroïque. **Gurdil** pulvérisa
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).

View File

@@ -21,6 +21,7 @@ tags: [
"event:Combat-Vampire",
"event:Level-Up"
]
era: "initiation"
---
## L'Ombre de Constantia
@@ -47,6 +48,8 @@ Nous sommes sortis de la mine en héros, accueillis par le village entier. Les e
Le village a célébré notre victoire par un festin, mais le repos fut de courte durée. Tandis que Gurdil se noyait dans la bière et que **Kouel** s'appropriait nos exploits à l'auberge, le sommeil loin d'être léger m'accueilli en son ceint pour me délivrer un message nocturne en rêve. La fée du **Grand Chêne** m'a appelé à l'aide : Ce souffle de vie si cher à mon cœur, est menacé. Dans cette vision, Nyrae et Bulle apparaissaient comme les messagères les plus aptes à communiquer avec cette force ancienne.
---
### L'Entité : Constantia Denney
- **Identité :** Vampire et Chevalier de la Couronne d'Argent.
@@ -56,6 +59,7 @@ Le village a célébré notre victoire par un festin, mais le repos fut de court
- **Drain de vie :** Recouvre sa puissance en s'abreuvant de sang frais (victime : Nyrae).
- **Statut :** En fuite via un portail dimensionnel.
---
### Mécanismes & Mystères
@@ -63,14 +67,20 @@ Le village a célébré notre victoire par un festin, mais le repos fut de court
- **L'Incident Diplomatique :** Gurdil a rompu l'hypnose de Zajic par un choc d'éveil, créant une tension avec la Bourgmestre Maielan.
- **Le Paradoxe :** Bien que Constantia prétende que son apparence féerique était fortuite, l'appel de détresse du **Grand Chêne** a suivi immédiatement sa libération.
---
### Le Souffle de Vie
- **Localisation :** Le cœur du Grand Chêne, l'arbre millénaire du village.
- **État :** Menacé par une force obscure. La fée protectrice de l'arbre a envoyé un appel au secours onirique.
- **Émissaires :** Nyrae et Bulle sont identifiées comme les vecteurs de communication prioritaires avec l'essence de l'Arbre.
---
### Évolution des Aventuriers (Niveau 2)
---
|**Nom**|**Action Marquante**|**État**|
|---|---|---|
|**G'mas**|A tenté la diplomatie ; charmé par la vampire.|Troublé, en quête de réponses oniriques.|
@@ -79,6 +89,8 @@ Le village a célébré notre victoire par un festin, mais le repos fut de court
|**Bulle**|Diagnostic magique et éclair dévastateur.|Prête pour la mission féerique.|
|**Jinn**|Soutien au combat malgré la régénération de l'ennemi.|Prêt pour la suite.|
---
### Métadonnées
- **Lieu :** Conduit Nord-Est, Secteur Maçonné (facture humaine médiocre).

View File

@@ -1,8 +1,11 @@
---
import { ClientRouter } from "astro:transitions";
import Footer from "../components/Footer.astro";
interface Props {
title: string;
}
const { title } = Astro.props;
---
@@ -18,10 +21,12 @@ const { title } = Astro.props;
<main>
<slot />
</main>
<Footer />
</body>
</html>
<style is:global>
@import url("https://fonts.googleapis.com/css2?family=Mrs+Eaves&family=Playfair+Display:ital,wght@0,700;1,700&family=Roboto+Condensed:wght@700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Cinzel:wght@700&display=swap");
:root {
@@ -29,6 +34,10 @@ const { title } = Astro.props;
--color-ink: #4a4130;
--color-gold: #c89b3c;
--color-gold-dark: #b8860b;
--color-ancient-blue: #3f51b5; /* Le bleu dominant */
--color-ancient-purple: #673ab7; /* Les ombres de l'illustration */
--color-ancient-gold: #f4d03f; /* Les touches de lumière ocre */
--color-bg-canvas: #fdf6e8; /* Ton parchemin actuel */
}
/* Reset de base */
@@ -43,6 +52,18 @@ const { title } = Astro.props;
overflow-x: hidden;
}
hr {
border: 0;
height: 1px;
background-image: linear-gradient(
to right,
transparent,
#dcd0b9,
transparent
);
margin: 2.5rem 0;
}
/* LE FIX DES MARGES : C'est ce bloc qui centre tout ton site */
main {
width: 100%;
@@ -169,11 +190,10 @@ const { title } = Astro.props;
/* LE STYLE DES CARTES (FORCE LE LOOK D'INDEX) */
.gold-button {
flex: 1 1 300px;
max-width: 400px;
min-height: 120px;
background: white !important;
border: 2px solid var(--color-gold) !important;
border: 1px solid var(--color-gold) !important;
border-radius: 12px !important;
padding: 20px !important;
text-decoration: none !important;

View File

@@ -1,25 +1,96 @@
---
import { getCollection } from "astro:content";
import GameLayout from "../layouts/GameLayout.astro";
import Timeline from "../components/Timeline.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(
// On récupère les Ères
const eras = allPosts
.filter((p) => p.data.isEra)
.sort((a, b) => a.data.publishDate.valueOf() - b.data.publishDate.valueOf())
.map((p) => ({ id: p.id, title: p.data.title }));
const chroniclesOnly = allPosts.filter((post) => !post.data.isEra);
const sortedPosts = chroniclesOnly.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 allTags = [
...new Set(chroniclesOnly.flatMap((post) => post.data.tags || [])),
];
---
<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.
<div class="index-container">
<header class="dnd-header">
<h1 class="dnd-title">Sweeties Journey</h1>
<div class="dnd-bar"></div>
<p class="dnd-intro">
Chroniques des aventures d'une portion de nouilles de la
communauté, idée originale de Orson Pattes Givrées
</p>
</header>
<Timeline eras={eras} />
<!-- 2. On passe tous les articles au composant client -->
<ContentSearch posts={sortedPosts} tags={allTags} basePath="journal" />
</div>
</GameLayout>
<style is:global>
.index-container {
max-width: 1000px;
margin: 0 auto;
padding: 0 1rem;
}
/* Le style du titre DnD reste le même que précédemment */
.dnd-header {
margin: 2rem 0;
}
.dnd-title {
font-family: "Playfair Display", serif;
font-size: 3.5rem;
color: #c89b3c;
margin: 0;
}
.dnd-bar {
height: 4px;
background: linear-gradient(to right, #c89b3c 0%, transparent 100%);
margin-top: 4px;
}
.dnd-intro {
font-family: "Cinzel", serif;
font-size: 0.9rem;
margin-top: 1rem;
opacity: 0.8;
}
</style>
<script>
function setupEraFiltering() {
const eraButtons = document.querySelectorAll(".era-chip");
const posts = document.querySelectorAll(".gold-button");
eraButtons.forEach((button) => {
button.addEventListener("click", () => {
const selectedEra = button.getAttribute("data-era");
// Toggle active class
eraButtons.forEach((btn) => btn.classList.remove("active"));
button.classList.add("active");
posts.forEach((post) => {
const postEra = post.getAttribute("data-era");
if (selectedEra === "all" || postEra === selectedEra) {
(post as HTMLElement).style.display = "block";
} else {
(post as HTMLElement).style.display = "none";
}
});
});
});
}
document.addEventListener("astro:page-load", setupEraFiltering);
</script>

View File

@@ -2,7 +2,6 @@
// [tag].astro
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");
@@ -37,11 +36,15 @@ const { posts, originalTag } = Astro.props;
<div class="card-grid">
{
posts.map((post) => (
<GoldButton
href={`/journal/${post.id}/`}
title={post.data.title}
body={`Chronique du ${new Date(post.data.publishDate).toLocaleDateString("fr-FR")}`}
/>
<a href={`/journal/${post.id}/`} class="gold-button">
<div class="title">{post.data.title}</div>
<p class="body">
Chronique du{" "}
{new Date(post.data.publishDate).toLocaleDateString(
"fr-FR",
)}
</p>
</a>
))
}
</div>
@@ -49,6 +52,7 @@ const { posts, originalTag } = Astro.props;
</GameLayout>
<style>
/* Tes styles spécifiques au conteneur restent inchangés */
.tag-page-container {
max-width: 1000px;
margin: 0 auto;
@@ -61,7 +65,7 @@ const { posts, originalTag } = Astro.props;
font-size: 0.9rem;
}
.tag-header h1 {
font-size: 1.8rem; /* Titre réduit */
font-size: 1.8rem;
margin: 1.5rem 0 3rem;
}
.tag-header span {