Files
ambient/src/pages/index.astro
2026-02-24 18:12:52 +04:00

562 lines
18 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 &ge; 3 + 3 cercles uniques.
</li>
<li>
<strong>LÉGENDAIRE :</strong> Streak &ge; 10 + Harmonie
&ge; 4.
</li>
<li>
<strong>MYTHIQUE :</strong> Streak &ge; 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
&ge; 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 &ge; 7</code>, <code
>Hallu &ge; 2</code
> ou <code>Harmonie &le; -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 &gt; 2.
</li>
<li>
<strong>FOURNAISE (La Forge) :</strong> S'active par dominance
technique (PRO &ge; 2 et PRO &ge; 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) &lt; 6h, ta DEF baisse de 2.
<em>(Le malus travail s'active si PRO &ge; 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>