562 lines
18 KiB
Plaintext
562 lines
18 KiB
Plaintext
---
|
||
import { getCollection } from "astro:content";
|
||
import Layout from "../layouts/Layout.astro";
|
||
import Card from "../components/Card.astro";
|
||
import EventCard from "../components/EventCard.astro";
|
||
|
||
// 1. Récupération des données
|
||
const allHumans = await getCollection("humans", ({ id }) => {
|
||
return id.startsWith("latchimynicolas/");
|
||
});
|
||
const allEvents = await getCollection("events");
|
||
|
||
// 2. Tris temporels
|
||
const sortedAll = allHumans.sort(
|
||
(a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime(),
|
||
);
|
||
|
||
const latestEvent = allEvents.sort(
|
||
(a, b) => new Date(a.data.date).getTime() - new Date(b.data.date).getTime(),
|
||
)[0];
|
||
|
||
// 3. Logique de Streak (Série)
|
||
const getStreak = (name, entries) => {
|
||
const userDates = entries
|
||
.filter((e) => e.data.name === name)
|
||
.map((e) => ({
|
||
date: new Date(e.data.date).toISOString().split("T")[0],
|
||
hasHyg:
|
||
e.data.manifestations?.some((m) => m.cercle === "HYG") || false,
|
||
}));
|
||
|
||
const uniqueDates = [...new Set(userDates.map((d) => d.date))]
|
||
.sort()
|
||
.reverse();
|
||
let streak = 0;
|
||
let today = new Date();
|
||
let checkDate = new Date(
|
||
Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()),
|
||
);
|
||
let todaySkipped = false;
|
||
|
||
for (let i = 0; i < 31; i++) {
|
||
const dateStr = checkDate.toISOString().split("T")[0];
|
||
if (uniqueDates.includes(dateStr)) {
|
||
streak++;
|
||
checkDate.setUTCDate(checkDate.getUTCDate() - 1);
|
||
} else {
|
||
if (i === 0) {
|
||
checkDate.setUTCDate(checkDate.getUTCDate() - 1);
|
||
if (
|
||
uniqueDates.includes(checkDate.toISOString().split("T")[0])
|
||
) {
|
||
todaySkipped = true;
|
||
continue;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
const shiftDays = todaySkipped ? 1 : 0;
|
||
const last7Days = Array.from({ length: 7 }, (_, j) => {
|
||
let d = new Date(
|
||
Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()),
|
||
);
|
||
d.setUTCDate(d.getUTCDate() - (j + shiftDays));
|
||
return d.toISOString().split("T")[0];
|
||
});
|
||
|
||
const hygCount = userDates.filter(
|
||
(e) => last7Days.includes(e.date) && e.hasHyg,
|
||
).length;
|
||
if (hygCount < 3 && streak > 0) streak = Math.max(0, streak - 1);
|
||
|
||
return streak;
|
||
};
|
||
|
||
// 4. Sélection de la carte principale
|
||
const latestPerUser = Array.from(
|
||
sortedAll
|
||
.reduce((map, obj) => {
|
||
if (!map.has(obj.data.name)) map.set(obj.data.name, obj);
|
||
return map;
|
||
}, new Map())
|
||
.values(),
|
||
);
|
||
|
||
// 5. CALCUL DE SYNERGIE
|
||
const activeSynergies = latestPerUser.map((user) => {
|
||
const matchingManifestation = user.data.manifestations?.find(
|
||
(m) => m.cercle === latestEvent?.data.circle,
|
||
);
|
||
return {
|
||
userName: user.data.name,
|
||
hasSynergy: !!matchingManifestation,
|
||
cercle: latestEvent?.data.circle,
|
||
type: matchingManifestation?.type,
|
||
};
|
||
});
|
||
---
|
||
|
||
<Layout title="Dashboard & Codex">
|
||
<header class="minimal-header">
|
||
<nav>
|
||
<a href="/collection" class="menu-link">
|
||
<span class="icon">🎴</span> Collection
|
||
</a>
|
||
</nav>
|
||
</header>
|
||
|
||
<main>
|
||
<h1 class="main-title">Ambient</h1>
|
||
<section class="design-intent">
|
||
<header class="rules-header">
|
||
<h2>🛠️ Chantier Ergonomique (en cours)</h2>
|
||
<p class="intent-quote">
|
||
<em
|
||
>"Repenser la visualisation des cartes pour un parcours
|
||
utilisateur en totale concordance avec les besoins du
|
||
type d'utilisateur."</em
|
||
>
|
||
</p>
|
||
</header>
|
||
|
||
<div class="work-todo-grid">
|
||
<div class="todo-column">
|
||
<h3>🎴 Ergonomie des Cartes</h3>
|
||
<ul>
|
||
<li>
|
||
Optimiser le ratio d'aspect pour une lecture
|
||
mobile-first sur Samsung/Android.
|
||
</li>
|
||
<li>
|
||
Intégrer les indicateurs de "Synergie" directement
|
||
sur le visuel de la carte.
|
||
</li>
|
||
<li>
|
||
Refondre le système de rareté (Shiny/Légendaire)
|
||
avec des effets CSS avancés.
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="todo-column">
|
||
<h3>🛤️ Parcours Utilisateur</h3>
|
||
<ul>
|
||
<li>
|
||
Simplifier la transition entre la vue "Dashboard" et
|
||
la "Collection" complète.
|
||
</li>
|
||
<li>
|
||
Créer des points d'entrée contextuels selon le
|
||
cercle dominant (PRO, SAN, etc.).
|
||
</li>
|
||
<li>
|
||
Améliorer la narration visuelle entre le manuel de
|
||
jeu et les logs d'événements.
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="todo-column">
|
||
<h3>⚙️ Technique & Backlog</h3>
|
||
<ul>
|
||
<li>
|
||
Automatiser la mise à jour des versions (Changelog)
|
||
via les fichiers Markdown.
|
||
</li>
|
||
<li>
|
||
Améliorer la performance du rendu Astro sur le
|
||
serveur Raspberry Pi 4.
|
||
</li>
|
||
<li>
|
||
Finaliser les composants EventCard pour les
|
||
correctifs (Fixes).
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="hero-flex">
|
||
{
|
||
latestPerUser.map((entry) => {
|
||
const currentStreak = getStreak(entry.data.name, allHumans);
|
||
return (
|
||
<div class="card-focus">
|
||
<Card
|
||
frontmatter={entry.data}
|
||
streakCount={currentStreak}
|
||
/>
|
||
<div class="entry-meta">
|
||
<p>
|
||
Dernier souffle :{" "}
|
||
{new Date(
|
||
entry.data.date,
|
||
).toLocaleDateString("fr-FR")}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
})
|
||
}
|
||
|
||
{
|
||
latestEvent && (
|
||
<div class="card-focus">
|
||
<EventCard frontmatter={latestEvent.data} />
|
||
<div class="entry-meta">
|
||
<p>Projet Actif : {latestEvent.data.type}</p>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
</section>
|
||
|
||
<hr class="separator" />
|
||
|
||
<section class="system-rules">
|
||
<header class="rules-header">
|
||
<h2>📖 Manuel de Jeu : TCG (Ruleset Complet)</h2>
|
||
<p>Traduction technique des algorithmes du moteur de jeu</p>
|
||
</header>
|
||
|
||
<div class="rules-layout">
|
||
<div class="rule-category">
|
||
<h3>✨ Rareté & Évolution</h3>
|
||
<ul>
|
||
<li>
|
||
<strong>SHINY :</strong> Streak ≥ 3 + 3 cercles uniques.
|
||
</li>
|
||
<li>
|
||
<strong>LÉGENDAIRE :</strong> Streak ≥ 10 + Harmonie
|
||
≥ 4.
|
||
</li>
|
||
<li>
|
||
<strong>MYTHIQUE :</strong> Streak ≥ 20 + 0 Hallucinations.
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="rule-category">
|
||
<h3>⚔️ Système de Combat</h3>
|
||
<ul>
|
||
<li>
|
||
<strong>Puissance (ATK) :</strong>
|
||
<code
|
||
>(PRO × 2) + (Motiv/4) + (Énergie/3) + Bonus
|
||
Harmonie + StreakBonus</code
|
||
>.
|
||
<em
|
||
>Le travail (PRO) est ton multiplicateur
|
||
principal.</em
|
||
>
|
||
</li>
|
||
<li>
|
||
<strong>Résilience (DEF) :</strong>
|
||
<code
|
||
>10 + (Hygiène/2) - Stress - Hallu - Pénalité
|
||
Sommeil + StreakBonus</code
|
||
>.
|
||
<em
|
||
>L'hygiène est ton armure, le stress ta faille.</em
|
||
>
|
||
</li>
|
||
<li>
|
||
<strong>Coût d'Invocation :</strong> Nombre total de manifestations
|
||
divisé par 2 (arrondi au supérieur).
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="rule-category">
|
||
<h3>📈 Séries & Rareté (Streak)</h3>
|
||
<ul>
|
||
<li>
|
||
<strong>Streak Bonus :</strong> +1 ATK/DEF tous les 7
|
||
jours de série consécutifs.
|
||
</li>
|
||
<li>
|
||
<strong>Éveil Shiny :</strong> Activé dès 3 jours de série
|
||
+ 3 cercles différents activés.
|
||
</li>
|
||
<li>
|
||
<strong>Échelle de Rareté :</strong>
|
||
<br />• <strong>PEU COMMUNE :</strong> 3 jours de série.
|
||
<br />• <strong>RARE :</strong> 6 jours de série.
|
||
<br />• <strong>LÉGENDAIRE :</strong> 10 jours + Harmonie
|
||
≥ 4.
|
||
<br />• <strong>MYTHIQUE :</strong> 20 jours + 0 Hallucinations.
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="rule-category">
|
||
<h3>🌍 Influence des Biomes</h3>
|
||
<ul>
|
||
<li>
|
||
<strong>ÉTANG (L'Inerte) :</strong> Priorité Somatique.
|
||
S'active si <code>Stress ≥ 7</code>, <code
|
||
>Hallu ≥ 2</code
|
||
> ou <code>Harmonie ≤ -3</code>.
|
||
</li>
|
||
<li>
|
||
<strong>BELOUVE (La Racine) :</strong> État d'Harmonie.
|
||
S'active si 3+ cercles sont présents avec au moins un
|
||
log <strong>SAN</strong>.
|
||
</li>
|
||
<li>
|
||
<strong>OCEAN (Le Flux) :</strong> S'active si le Social
|
||
(SOC) domine le Pro ou si le Flux (FLX) est > 2.
|
||
</li>
|
||
<li>
|
||
<strong>FOURNAISE (La Forge) :</strong> S'active par dominance
|
||
technique (PRO ≥ 2 et PRO ≥ SOC).
|
||
</li>
|
||
<li>
|
||
<strong>SAVANE (La Clarté) :</strong> Biome d'équilibre
|
||
par défaut.
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="rule-category">
|
||
<h3>🔧 Maintenance & Malus</h3>
|
||
<ul>
|
||
<li>
|
||
<strong>Pénalité Sommeil :</strong> Si (Sommeil - Malus
|
||
Travail) < 6h, ta DEF baisse de 2.
|
||
<em>(Le malus travail s'active si PRO ≥ 5).</em>
|
||
</li>
|
||
<li>
|
||
<strong>Harmonie des Cercles :</strong> Posséder les 5
|
||
cercles (SAN, FLX, PRO, SOC, HYG) sur un log octroie un
|
||
bonus massif de **+2** au Score d'Harmonie.
|
||
</li>
|
||
<li>
|
||
<strong>Règle HYG (Prévention) :</strong> Si moins de
|
||
3 logs "HYG" sont détectés sur les 7 derniers jours, la
|
||
série (Streak) est amputée d'un point de pénalité.
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="rule-category">
|
||
<h3>🎭 Objets & Équipements</h3>
|
||
<ul>
|
||
<li>
|
||
<strong>VÉLO :</strong> Convertit 2 points de Stress en
|
||
1 point d'Énergie au petit matin.
|
||
</li>
|
||
<li>
|
||
<strong>ARIPIPRAZOLE :</strong> Fixe Hallucinations à
|
||
0. Ajoute le trait "Stabilité".
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
</Layout>
|
||
<style>
|
||
.design-intent {
|
||
max-width: 1400px; /* Occupation maximale de l'espace */
|
||
margin: 60px auto 100px auto;
|
||
padding: 40px;
|
||
background: #f8fafc;
|
||
border-radius: 24px;
|
||
border: 1px dashed #cbd5e1; /* Aspect "travail en cours" */
|
||
}
|
||
|
||
.intent-quote {
|
||
max-width: 800px;
|
||
margin: 20px auto;
|
||
font-size: 1.1rem;
|
||
color: #475569;
|
||
text-align: center;
|
||
}
|
||
|
||
.work-todo-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 30px;
|
||
margin-top: 40px;
|
||
}
|
||
|
||
.todo-column {
|
||
background: white;
|
||
padding: 25px;
|
||
border-radius: 16px;
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.02);
|
||
}
|
||
|
||
.todo-column h3 {
|
||
font-family: "Philosopher", serif;
|
||
font-size: 1.2rem;
|
||
margin-bottom: 20px;
|
||
color: #1e293b;
|
||
border-bottom: 2px solid #e2e8f0;
|
||
padding-bottom: 10px;
|
||
}
|
||
|
||
.todo-column ul {
|
||
list-style: none;
|
||
padding: 0;
|
||
}
|
||
|
||
.todo-column li {
|
||
margin-bottom: 15px;
|
||
font-size: 0.95rem;
|
||
line-height: 1.5;
|
||
color: #64748b;
|
||
position: relative;
|
||
padding-left: 20px;
|
||
}
|
||
|
||
.todo-column li::before {
|
||
content: "→";
|
||
position: absolute;
|
||
left: 0;
|
||
color: #3b82f6;
|
||
font-weight: bold;
|
||
}
|
||
|
||
@media (max-width: 1024px) {
|
||
.work-todo-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
.main-title {
|
||
text-align: center;
|
||
font-family: "Philosopher", serif;
|
||
font-size: 2.5rem;
|
||
margin-top: 40px;
|
||
color: #1e293b;
|
||
}
|
||
|
||
.minimal-header {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
z-index: 100;
|
||
}
|
||
|
||
.menu-link {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
text-decoration: none;
|
||
color: #1e293b;
|
||
font-family: "Philosopher", serif;
|
||
font-weight: bold;
|
||
background: rgba(255, 255, 255, 0.8);
|
||
padding: 10px 20px;
|
||
border-radius: 50px;
|
||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
||
backdrop-filter: blur(5px);
|
||
}
|
||
|
||
.hero-flex {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
gap: 40px;
|
||
padding: 60px 20px;
|
||
min-height: 60vh;
|
||
}
|
||
|
||
.card-focus {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 15px;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.entry-meta {
|
||
font-family: "Philosopher", serif;
|
||
font-size: 0.9rem;
|
||
color: #64748b;
|
||
}
|
||
|
||
.separator {
|
||
border: 0;
|
||
height: 1px;
|
||
background-image: linear-gradient(
|
||
to right,
|
||
transparent,
|
||
rgba(0, 0, 0, 0.1),
|
||
transparent
|
||
);
|
||
margin: 40px 0;
|
||
}
|
||
|
||
/* Codex Style */
|
||
.codex {
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
padding: 40px;
|
||
background: #f8fafc;
|
||
border-radius: 24px;
|
||
border: 1px solid #e2e8f0;
|
||
}
|
||
|
||
.codex-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||
gap: 20px;
|
||
}
|
||
|
||
.codex-box {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 16px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
|
||
}
|
||
|
||
.synergy-active {
|
||
border: 2px solid #8b5cf6;
|
||
}
|
||
.status-ok {
|
||
color: #10b981;
|
||
font-weight: bold;
|
||
}
|
||
.status-ko {
|
||
color: #94a3b8;
|
||
}
|
||
|
||
/* System Rules Style */
|
||
.system-rules {
|
||
max-width: 1100px;
|
||
margin: 40px auto 100px auto;
|
||
padding: 20px;
|
||
font-family: "Philosopher", serif;
|
||
}
|
||
|
||
.rules-layout {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||
gap: 30px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.rule-category h3 {
|
||
border-bottom: 1px solid #cbd5e1;
|
||
padding-bottom: 5px;
|
||
margin-bottom: 15px;
|
||
color: #334155;
|
||
}
|
||
|
||
.rule-category ul {
|
||
list-style: none;
|
||
padding: 0;
|
||
}
|
||
|
||
.rule-category li {
|
||
margin-bottom: 12px;
|
||
font-size: 0.95rem;
|
||
line-height: 1.4;
|
||
}
|
||
</style>
|