correction config.ts et evolution

This commit is contained in:
2026-02-27 02:13:01 +04:00
parent 1fe15b5907
commit 227d5de22e
8 changed files with 532 additions and 528 deletions

8
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "ambient", "name": "ambient",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"astro": "^5.17.1" "astro": "^5.18.0"
} }
}, },
"node_modules/@astrojs/compiler": { "node_modules/@astrojs/compiler": {
@@ -1661,9 +1661,9 @@
} }
}, },
"node_modules/astro": { "node_modules/astro": {
"version": "5.17.3", "version": "5.18.0",
"resolved": "https://registry.npmjs.org/astro/-/astro-5.17.3.tgz", "resolved": "https://registry.npmjs.org/astro/-/astro-5.18.0.tgz",
"integrity": "sha512-69dcfPe8LsHzklwj+hl+vunWUbpMB6pmg35mACjetxbJeUNNys90JaBM8ZiwsPK689SAj/4Zqb1ayaANls9/MA==", "integrity": "sha512-CHiohwJIS4L0G6/IzE1Fx3dgWqXBCXus/od0eGUfxrZJD2um2pE7ehclMmgL/fXqbU7NfE1Ze2pq34h2QaA6iQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@astrojs/compiler": "^2.13.0", "@astrojs/compiler": "^2.13.0",

View File

@@ -9,6 +9,6 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"astro": "^5.17.1" "astro": "^5.18.0"
} }
} }

View File

@@ -0,0 +1,257 @@
---
// BankCard.astro
const { frontmatter, allHumans } = Astro.props;
const { name, monthly_allowance, type = "FLUX" } = frontmatter;
// --- LOGIQUE DE COULEUR SUBTILE ---
const bankConfigs = {
UpDéjeuner: { color: "#f97316", icon: "🍴" }, // Orange Up
"La Banque Postale": { color: "#137bb1", icon: "🏦" }, // Bleu BP
Sumeria: { color: "#116853", icon: "⚡" }, // Vert d'eau
};
const config = bankConfigs[name as keyof typeof bankConfigs] || {
color: "#85a9bc",
icon: "◈",
};
const color = config.color;
const now = new Date();
// Importe le type si nécessaire (Astro le génère pour toi)
// import type { CollectionEntry } from 'astro:content';
const currentMonthEntries = allHumans.filter((h: any) => {
// Remplace 'any' par 'CollectionEntry<"humans">' si tu veux être strict
const d = new Date(h.data.date);
return (
d.getMonth() === now.getMonth() && d.getFullYear() === now.getFullYear()
);
});
// Somme des flux
// BankCard.astro (Partie Logique)
const totalFlux = currentMonthEntries.reduce((acc: number, entry: any) => {
const flxManifests =
entry.data.manifestations?.filter((m: any) => {
// ON FILTRE : cercle FLX ET la source doit correspondre au nom de la carte
return m.cercle === "FLX" && m.source === name;
}) || [];
return (
acc +
flxManifests.reduce(
(sum: number, m: any) => sum + (Number(m.amount) || 0),
0,
)
);
}, 0);
const percentage = Math.max(
0,
Math.min(100, Math.round((totalFlux / monthly_allowance) * 100)),
);
// État du Hebel
let status = { label: "ABONDANCE", color: "#10b981" };
if (percentage < 20) status = { label: "ÉPUISEMENT", color: "#ef4444" };
else if (percentage < 50) status = { label: "FLUX TENDU", color: "#f59e0b" };
else if (percentage < 85) status = { label: "ÉQUILIBRE", color: "#3b82f6" };
---
<article
class="tcg-card bank-card"
style={`--accent: ${color}; --state-color: ${status.color}`}
>
<div class="card-inner">
<header class="card-header">
<span class="card-name">{name}</span>
<span class="card-cost">{config.icon}</span>
</header>
<div class="visual-container">
<div class="source-gauge">
<svg viewBox="0 0 36 36" class="circular-chart">
<path
class="circle-bg"
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
></path>
<path
class="circle"
stroke-dasharray={`${percentage}, 100`}
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
></path>
<text x="18" y="20.35" class="percentage"
>{percentage}%</text
>
</svg>
</div>
<div class="status-badge">{status.label}</div>
</div>
<div class="card-type-line">
Ressource — {type}
</div>
<div class="card-body">
<div class="flavor-text">
{
percentage > 50
? "Le souffle circule avec aisance."
: "Le flux se raréfie, prudence."
}
</div>
<div class="mechanics">
<div class="stat-row">
<span>Intégrité</span>
<div class="power-bar">
{
Array.from({ length: 5 }).map((_, i) => (
<div
class={`segment ${i < percentage / 20 ? "active" : ""}`}
/>
))
}
</div>
</div>
</div>
</div>
<footer class="card-footer">
<span class="rarity-symbol">✦✦✧</span>
<div class="reserve-level">RÉSERVE {percentage}/100</div>
</footer>
</div>
</article>
<style>
.bank-card {
background: linear-gradient(135deg, #fff 0%, #f8fafc 100%);
color: #1a202c;
border-color: var(
--accent
) !important; /* Bordure subtile de la couleur de la banque */
}
.card-inner {
height: 100%;
padding: 10px;
border-radius: 10px;
display: flex;
flex-direction: column;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #e2e8f0; /* Souligné subtil */
padding-bottom: 5px;
margin-bottom: 10px;
}
.card-name {
font-weight: bold;
font-family: "Philosopher", serif;
font-size: 1.1rem;
color: var(--accent); /* Nom coloré subtilement */
}
.visual-container {
flex: 1;
background: #f1f5f9;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
gap: 10px;
border: 1px solid rgba(0, 0, 0, 0.05);
}
.circular-chart {
max-width: 100px;
width: 100%;
}
.circle-bg {
fill: none;
stroke: rgba(0, 0, 0, 0.05);
stroke-width: 3.8;
}
.circle {
fill: none;
stroke: var(--accent); /* La jauge prend la couleur de la banque */
stroke-width: 2.8;
stroke-linecap: round;
transition: stroke-dasharray 1s ease;
}
.percentage {
fill: #1a202c;
font-family: "Inter", sans-serif;
font-size: 0.5rem;
text-anchor: middle;
font-weight: bold;
}
.status-badge {
background: var(
--state-color
); /* Garde la couleur d'état (vert/orange/rouge) pour la sécurité */
color: white;
font-size: 0.5rem;
padding: 1px 6px;
border-radius: 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.card-type-line {
font-size: 0.7rem;
font-weight: bold;
border-bottom: 1px solid #e2e8f0;
margin: 8px 0;
padding-bottom: 2px;
text-transform: uppercase;
color: #64748b;
}
.flavor-text {
font-size: 0.65rem;
font-style: italic;
color: #94a3b8;
margin-bottom: 10px;
min-height: 1.5rem;
}
.stat-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.75rem;
color: #475569;
}
.power-bar {
display: flex;
gap: 3px;
}
.segment {
width: 14px;
height: 4px;
background: #e2e8f0;
border-radius: 1px;
}
.segment.active {
background: var(--accent); /* Segments de la couleur de la banque */
}
.card-footer {
margin-top: auto;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.6rem;
color: #94a3b8;
}
</style>

View File

@@ -21,22 +21,22 @@ const hygieneScore = health?.somatic?.hygiene_level ?? 5;
// 2. RÉINTÉGRATION DES COMPTAGES // 2. RÉINTÉGRATION DES COMPTAGES
const counts = { const counts = {
PRO: manifestations?.filter((m) => m.cercle === "PRO").length || 0, PRO: manifestations?.filter((m: any) => m.cercle === "PRO").length || 0,
SAN: manifestations?.filter((m) => m.cercle === "SAN").length || 0, SAN: manifestations?.filter((m: any) => m.cercle === "SAN").length || 0,
SOC: manifestations?.filter((m) => m.cercle === "SOC").length || 0, SOC: manifestations?.filter((m: any) => m.cercle === "SOC").length || 0,
FLX: manifestations?.filter((m) => m.cercle === "FLX").length || 0, FLX: manifestations?.filter((m: any) => m.cercle === "FLX").length || 0,
HYG: manifestations?.filter((m) => m.cercle === "HYG").length || 0, HYG: manifestations?.filter((m: any) => m.cercle === "HYG").length || 0,
}; };
// 3. SCORE D'HARMONIE RAFFINÉ // 3. SCORE D'HARMONIE RAFFINÉ
const rawHarmony = const rawHarmony =
manifestations?.reduce((acc, m) => { manifestations?.reduce((acc: number, m: any) => {
if (m.type === "pos") return acc + 1; if (m.type === "pos") return acc + 1;
if (m.type === "neg") return acc - 1; if (m.type === "neg") return acc - 1;
return acc; return acc;
}, 0) || 0; }, 0) || 0;
const uniqueCercles = new Set(manifestations?.map((m) => m.cercle)).size; const uniqueCercles = new Set(manifestations?.map((m: any) => m.cercle)).size;
const hasAllCercles = uniqueCercles === 5; const hasAllCercles = uniqueCercles === 5;
const harmonyScore = rawHarmony + (hasAllCercles ? 2 : 0); const harmonyScore = rawHarmony + (hasAllCercles ? 2 : 0);
@@ -128,16 +128,16 @@ const biomes = {
weak: "OCEAN", weak: "OCEAN",
}, },
}; };
const currentBiome = biomes[autoBiome] || biomes.SAVANE; const currentBiome = biomes[autoBiome as keyof typeof biomes] || biomes.SAVANE;
const circleSummaries = Object.entries(counts) const circleSummaries = Object.entries(counts)
.filter(([_, count]) => count > 0) .filter(([_, count]: [string, number]) => count > 0)
.map(([type, count]) => { .map(([type, count]: [string, number]) => {
// Calcul du score de dominance pour ce cercle précis // Calcul du score de dominance pour ce cercle précis
const circleScore = const circleScore =
manifestations manifestations
?.filter((m) => m.cercle === type) ?.filter((m: any) => m.cercle === type)
.reduce((acc, m) => { .reduce((acc: number, m: any) => {
if (m.type === "pos") return acc + 1; if (m.type === "pos") return acc + 1;
if (m.type === "neg") return acc - 1; if (m.type === "neg") return acc - 1;
return acc; return acc;

View File

@@ -14,7 +14,6 @@ const {
image, image,
} = frontmatter; } = frontmatter;
// Définition des couleurs par catégorie
const categoryColors = { const categoryColors = {
Festival: "#f59e0b", Festival: "#f59e0b",
IRL: "#a1be18", IRL: "#a1be18",
@@ -27,274 +26,171 @@ const categoryColors = {
default: "#ec4899", default: "#ec4899",
}; };
const accentColor = categoryColors[type] || categoryColors["default"]; const accentColor =
categoryColors[type as keyof typeof categoryColors] ||
categoryColors["default"];
// --- LOGIQUE DE NOTIFICATION DE STATUT --- // --- LOGIQUE DE STATUT ---
const today = new Date(); const today = new Date();
const eventDate = new Date(date); const eventDate = new Date(date);
// Mise à zéro des heures pour comparer uniquement les jours
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
const compareDate = new Date(eventDate); const compareDate = new Date(eventDate);
compareDate.setHours(0, 0, 0, 0); compareDate.setHours(0, 0, 0, 0);
// On utilise .getTime() pour transformer les dates en nombres (ms)
const diffTime = compareDate - today; const diffTime = compareDate.getTime() - today.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
let dynamicStatus = { label: status, color: "rgba(255,255,255,0.3)", icon: "" }; let dynamicStatus = { label: status, color: "rgba(0,0,0,0.3)", icon: "" };
if (status === "Terminé") { if (status === "Terminé")
dynamicStatus = { label: "Terminé", color: "#10b981" }; dynamicStatus = { label: "Terminé", color: "#10b981", icon: "" }; // Ajoute icon: ""
} else if (status === "En cours") { else if (status === "En cours")
// Nouvelle condition pour le statut "En cours"
dynamicStatus = { label: "En cours", color: "#3b82f6", icon: "⚡" }; dynamicStatus = { label: "En cours", color: "#3b82f6", icon: "⚡" };
} else if (diffDays < 0) { else if (diffDays < 0)
dynamicStatus = { label: "En retard", color: "#ef4444" }; dynamicStatus = { label: "En retard", color: "#ef4444", icon: "" }; // Ajoute icon: ""
} else if (diffDays === 0) { else if (diffDays === 0)
dynamicStatus = { label: "Aujourd'hui", color: "#3b82f6" }; dynamicStatus = { label: "Aujourd'hui", color: "#3b82f6", icon: "" }; // Ajoute icon: ""
} else if (diffDays > 0 && diffDays <= 7) { else if (diffDays > 0 && diffDays <= 7)
dynamicStatus = { label: "Imminent", color: "#f59e0b" }; dynamicStatus = { label: "Imminent", color: "#f59e0b", icon: "" }; // Ajoute icon: ""
}
--- ---
<article <article
class={`event-card ${image ? "has-bg" : ""}`} class={`tcg-card ${image ? "is-dark has-bg" : ""}`}
style={`--accent-color: ${accentColor}; --bg-image: url('${image}'); --status-bg: ${dynamicStatus.color};`} style={`background-color: ${accentColor}; --bg-image: url('${image}');`}
> >
<div class="card-inner"> <div class="card-border">
<header class="event-header"> <header class="card-header">
<span class="event-type">{type.toUpperCase()}</span> <span class="event-type-badge">{type.toUpperCase()}</span>
<span class="status-notification"> <span
{ class="status-badge"
dynamicStatus.icon && ( style={`background: ${dynamicStatus.color}`}
<span class="status-icon">{dynamicStatus.icon}</span> >
) {dynamicStatus.icon}
}
{dynamicStatus.label} {dynamicStatus.label}
</span> </span>
</header> </header>
<div class="event-art"> <div class="card-art event-visual">
<div class="location-tag">{location}</div> <div class="location-overlay">{location}</div>
<h2 class="event-title">{title}</h2> <h2 class="event-title-main">{title}</h2>
</div> </div>
<div class="event-info-line"> <div class="card-type-line">
Prévu le : <strong Événement — {new Date(date).toLocaleDateString("fr-FR")}
>{new Date(date).toLocaleDateString("fr-FR")}</strong
>
</div> </div>
<div class="event-body"> <div class="card-body">
<p class="summary">{summary}</p> <p class="summary-text">{summary}</p>
<div class="participants-box"> <div class="participants-section">
<span class="label"> <span class="mini-label"
>{type === "Festival" ? "Artistes :" : "Membres :"}</span
>
<div class="participant-tags">
{ {
type === "Festival" participants.map((p: string) => (
? "Artistes invités :" <span class="p-tag">{p}</span>
: "Membres impliqués :" ))
} }
</span>
<div class="tags">
{participants.map((p) => <span class="p-tag">{p}</span>)}
</div> </div>
</div> </div>
</div> </div>
<footer class="event-footer"> <footer class="card-footer">
<div class="date-recorded"> <div class="footer-meta">Détecté : {target_date}</div>
Projet détecté le : <strong>{target_date}</strong> <div class="circle-badge">{circle}</div>
</div>
<div class="social-impact">{circle}</div>
</footer> </footer>
</div> </div>
</article> </article>
<style> <style>
.event-card { /* On surcharge uniquement ce qui est spécifique à l'événement */
width: 320px; .event-visual {
height: 480px;
background-color: var(--accent-color);
border-radius: 18px;
padding: 12px;
font-family: "Philosopher", serif;
color: rgba(0, 0, 0, 0.7);
transition: background 0.3s ease;
position: relative;
background-size: cover;
background-position: center;
}
.event-card.has-bg {
color: #fff;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
background-image:
linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0) 100%
),
var(--bg-image) !important;
}
.card-inner {
height: 100%;
border-radius: 10px;
display: flex;
flex-direction: column; flex-direction: column;
padding: 12px; background: rgba(0, 0, 0, 0.05);
background: linear-gradient(
rgba(20, 0, 255, 0.1) 0%,
rgba(255, 191, 0, 0.1) 70%,
rgba(255, 0, 0, 0.3) 100%
);
}
.event-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.7rem;
font-weight: bold;
letter-spacing: 1px;
}
/* Style du nouveau badge de notification */
.status-notification {
background: var(--status-bg);
color: white;
padding: 2px 10px;
border-radius: 5px;
font-size: 0.6rem;
text-transform: uppercase;
display: flex;
align-items: center;
gap: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
text-shadow: none;
}
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.event-art {
flex: 1;
border: 1px solid rgba(0, 0, 0, 0.1);
margin: 10px 0;
border-radius: 6px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center; text-align: center;
padding: 10px; padding: 10px;
} }
.event-card.has-bg .event-art { .has-bg .card-border {
border: 1px solid rgba(0, 0, 0, 0.1); background: rgba(0, 0, 0, 0.4); /* Voile noir transparent */
backdrop-filter: blur(
2px
); /* Optionnel : floute légèrement l'image derrière le texte */
color: white;
} }
.event-title { .event-title-main {
font-size: 1.4rem; font-size: 1.2rem;
margin: 5px 0; margin: 5px 0;
line-height: 1.1;
font-family: "Philosopher", serif;
font-weight: bold;
color: inherit; /* Utilise la couleur parente (blanc ou sombre) */
} }
.location-tag { .event-type-badge {
font-size: 0.7rem; font-size: 0.6rem;
opacity: 0.9; padding: 2px 6px;
} border-radius: 3px;
background: rgba(0, 0, 0, 0.2);
.event-card.has-bg .location-tag { color: white;
opacity: 1;
}
.event-info-line {
font-size: 0.8rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
padding-bottom: 5px;
margin-bottom: 10px;
}
.event-card.has-bg .event-info-line {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.event-body {
flex: 1.2;
font-size: 0.85rem;
}
.event-type {
text-transform: uppercase;
background: rgba(255, 255, 255, 0.3);
padding: 2px 8px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.event-card.has-bg .event-type {
background: rgba(0, 0, 0, 0.4);
text-shadow: none; text-shadow: none;
} }
.summary { .status-badge {
font-style: italic; font-size: 0.6rem;
line-height: 1.4; padding: 2px 8px;
margin-bottom: 10px; border-radius: 10px;
color: white;
text-transform: uppercase;
} }
.participants-box .label { .summary-text {
font-size: 0.65rem; font-size: 0.85rem;
font-style: italic;
margin-bottom: 8px;
line-height: 1.3;
opacity: 0.9;
color: inherit;
}
.mini-label {
font-size: 0.6rem;
text-transform: uppercase; text-transform: uppercase;
display: block; display: block;
margin-bottom: 5px; opacity: 0.8;
} }
.tags { .participant-tags {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 4px; gap: 4px;
margin-top: 4px;
} }
.p-tag { .p-tag {
background: rgba(255, 255, 255, 0.2); font-size: 0.65rem;
background: rgba(0, 0, 0, 0.1);
padding: 1px 5px;
border-radius: 3px;
}
.circle-badge {
background: rgba(255, 255, 255, 0.8);
color: #222;
padding: 2px 6px; padding: 2px 6px;
border-radius: 4px; border-radius: 4px;
font-size: 0.65rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.event-card.has-bg .p-tag {
background: rgba(0, 0, 0, 0.4);
text-shadow: none;
}
.event-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.7rem;
padding-top: 10px;
}
.social-impact {
background: #ffffff80;
padding: 2px 8px;
border-radius: 5px;
font-weight: bold; font-weight: bold;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); font-size: 0.7rem;
} }
.event-card.has-bg .social-impact { /* Gestion de l'image de fond si elle existe */
background: rgba(0, 0, 0, 0.4); .has-bg {
text-shadow: none; background-image:
linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
var(--bg-image) !important;
background-size: cover;
background-position: center;
} }
</style> </style>

View File

@@ -32,13 +32,13 @@ const { title } = Astro.props;
> >
<!-- Google tag (gtag.js) --> <!-- Google tag (gtag.js) -->
<script> <script is:inline>
// On déclare dataLayer pour que TS ne râle pas
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag() { function gtag() {
dataLayer.push(arguments); window.dataLayer.push(arguments);
} }
gtag("js", new Date()); gtag("js", new Date());
gtag("config", "G-5M920H5PW7"); gtag("config", "G-5M920H5PW7");
</script> </script>
@@ -49,49 +49,71 @@ const { title } = Astro.props;
<style is:global> <style is:global>
:root { :root {
--bg-gradient: radial-gradient( --card-width: 320px;
circle at 50% 50%, --card-height: 480px;
#ffffff 0%, --inner-radius: 10px;
#f1f5f9 100%
);
} }
* { /* La coquille physique de toutes les cartes */
box-sizing: border-box; .tcg-card {
margin: 0; width: var(--card-width);
padding: 0; height: var(--card-height);
} border-radius: 18px;
padding: 12px;
html,
body {
overflow-x: hidden;
height: 100%;
width: 100%;
}
body {
font-family: "Inter", sans-serif;
background-image: var(--bg-gradient);
background-attachment: fixed;
color: #1e293b;
min-height: 100vh;
display: block; /* Change de flex à block pour laisser le contrôle aux pages */
line-height: 1.5;
}
/* Style spécifique pour les titres de cartes TCG */
h1,
h2,
.name,
.trigram {
font-family: "Philosopher", serif; font-family: "Philosopher", serif;
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: column;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
color: #1e293b;
} }
/* Optimisation mobile pour ton Samsung */ /* La bordure intérieure commune */
@media (max-width: 480px) { .card-border {
body { flex: 1;
padding: 10px; border-radius: var(--inner-radius);
align-items: flex-start; /* Permet de scroller si la carte est longue */ border: 1px solid rgba(0, 0, 0, 0.1);
} display: flex;
flex-direction: column;
padding: 10px;
overflow: hidden;
background: rgba(
255,
255,
255,
0.1
); /* Transparence pour laisser voir le biome/couleur */
}
/* Les zones partagées par Card et EventCard */
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
margin-bottom: 5px;
}
.card-art {
flex: 1;
margin: 5px 0;
position: relative;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 4px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.05);
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 8px;
margin-top: auto;
} }
</style> </style>

View File

@@ -3,26 +3,35 @@ import { getCollection } from "astro:content";
import Layout from "../layouts/Layout.astro"; import Layout from "../layouts/Layout.astro";
import Card from "../components/Card.astro"; import Card from "../components/Card.astro";
import EventCard from "../components/EventCard.astro"; import EventCard from "../components/EventCard.astro";
import BankCard from "../components/BankCard.astro"; // Import du nouveau composant
const allHumans = await getCollection("humans"); const allHumans = await getCollection("humans");
const myHumans = allHumans.filter((entry: any) => entry.id.startsWith("latchimynicolas/"));
const allEvents = await getCollection("events"); const allEvents = await getCollection("events");
const allBanks = await getCollection("banks"); // Récupération des cartes banques/objets
// 1. Tri chronologique global // 1. Tri chronologique global
const sortedEntries = allHumans.sort( const sortedEntries = allHumans.sort(
(a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime(), (a: any, b: any) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime()
); );
const sortedEvents = allEvents.sort( const sortedEvents = allEvents.sort(
(a, b) => new Date(a.data.date).getTime() - new Date(b.data.date).getTime(), (a: any, b: any) => new Date(a.data.date).getTime() - new Date(b.data.date).getTime(),
); );
// 2. Groupement par utilisateur [cite: 80] const sortedBanks = allBanks.sort(
const groupedHumans = sortedEntries.reduce((acc, entry) => { (a: any, b: any) => a.data.name.localeCompare(b.data.name));
// 2. Groupement par utilisateur
const groupedHumans = sortedEntries.reduce((acc: any, entry: any) => {
const userName = entry.data.name; const userName = entry.data.name;
if (!acc[userName]) acc[userName] = []; if (!acc[userName]) acc[userName] = [];
acc[userName].push(entry); acc[userName].push(entry);
return acc; return acc;
}, {}); }, {});
--- ---
<Layout title="Collection"> <Layout title="Collection">
@@ -38,27 +47,28 @@ const groupedHumans = sortedEntries.reduce((acc, entry) => {
Object.entries(groupedHumans).map(([userName, cards]) => ( Object.entries(groupedHumans).map(([userName, cards]) => (
<section class="user-collection"> <section class="user-collection">
<h2 class="user-title"> <h2 class="user-title">
{userName} <span>({cards.length} souffles)</span> {userName}
<span>({(cards as any[]).length} souffles)</span>
</h2> </h2>
<div class="user-flex-row"> <div class="user-flex-row">
{cards.map((entry) => ( {
<a (cards as any[]).map((entry) => (
href={`/${entry.id.replace(/\.md$/, "")}`} <a
class="card-link" href={`/${entry.id.replace(/\.md$/, "")}`}
> class="card-link"
<div class="card-scaler"> >
<Card frontmatter={entry.data} /> <div class="card-scaler">
<div class="entry-meta"> <Card frontmatter={entry.data} />
<p> <div class="entry-meta">
{new Date( <p>
entry.data.date, {new Date(entry.data.date).toLocaleDateString("fr-FR")}
).toLocaleDateString("fr-FR")} </p>
</p> </div>
</div> </div>
</div> </a>
</a> ))
))} }
</div> </div>
<hr class="separator" /> <hr class="separator" />
</section> </section>
@@ -90,8 +100,28 @@ const groupedHumans = sortedEntries.reduce((acc, entry) => {
</a> </a>
))} ))}
</div> </div>
<hr class="separator" /> <hr class="separator" />
</section> </section>
<section class="user-collection">
<h2 class="user-title">Trésoreries <span>(Sources de Flux)</span></h2>
<div class="user-flex-row">
{
sortedBanks.map((bank) => (
<div class="card-link">
<div class="card-scaler">
{bank.data.sub_category === "finances" ? (
<BankCard frontmatter={bank.data} allHumans={myHumans} />
) : (
/* Autre type de stuff si nécessaire */
<div class="tcg-card">Objet: {bank.data.name}</div>
)}
</div>
</div>
))
}
</div>
</section>
) )
} }
</main> </main>
@@ -139,6 +169,7 @@ const groupedHumans = sortedEntries.reduce((acc, entry) => {
flex-wrap: wrap; flex-wrap: wrap;
gap: 45px; /* Gap simple comme demandé */ gap: 45px; /* Gap simple comme demandé */
justify-content: flex-start; justify-content: flex-start;
padding: 20px 0;
margin-bottom: 100px; margin-bottom: 100px;
} }

View File

@@ -3,16 +3,20 @@ import { getCollection } from "astro:content";
import Layout from "../layouts/Layout.astro"; import Layout from "../layouts/Layout.astro";
import Card from "../components/Card.astro"; import Card from "../components/Card.astro";
import EventCard from "../components/EventCard.astro"; import EventCard from "../components/EventCard.astro";
import BankCard from "../components/BankCard.astro"; // <-- AJOUT
// 1. Récupération des données // 1. Récupération des données
const allHumans = await getCollection("humans", ({ id }) => { const allHumans = await getCollection("humans", ({ id }) => {
return id.startsWith("latchimynicolas/"); return id.startsWith("latchimynicolas/");
}); });
const allEvents = await getCollection("events", ({ data }) => { const allEvents = await getCollection("events", ({ data }) => {
// On ne garde que les événements qui ne sont pas terminés
return data.status !== "Terminé"; return data.status !== "Terminé";
}); });
// Récupération de la banque
const allBanks = await getCollection("banks");
const upCard = allBanks.find((b) => b.id === "up" || b.id === "up.md");
// 2. Tris temporels // 2. Tris temporels
const sortedAll = allHumans.sort( const sortedAll = allHumans.sort(
(a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime(), (a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime(),
@@ -22,17 +26,18 @@ const latestEvent = allEvents.sort(
(a, b) => new Date(a.data.date).getTime() - new Date(b.data.date).getTime(), (a, b) => new Date(a.data.date).getTime() - new Date(b.data.date).getTime(),
)[0]; )[0];
// 3. Logique de Streak (Série) // 3. Logique de Streak (Série) - TON CODE ORIGINAL
const getStreak = (name, entries) => { const getStreak = (name: any, entries: any) => {
const userDates = entries const userDates = entries
.filter((e) => e.data.name === name) .filter((e: any) => e.data.name === name)
.map((e) => ({ .map((e: any) => ({
date: new Date(e.data.date).toISOString().split("T")[0], date: new Date(e.data.date).toISOString().split("T")[0],
hasHyg: hasHyg:
e.data.manifestations?.some((m) => m.cercle === "HYG") || false, e.data.manifestations?.some((m: any) => m.cercle === "HYG") ||
false,
})); }));
const uniqueDates = [...new Set(userDates.map((d) => d.date))] const uniqueDates = [...new Set(userDates.map((d: any) => d.date))]
.sort() .sort()
.reverse(); .reverse();
let streak = 0; let streak = 0;
@@ -60,7 +65,6 @@ const getStreak = (name, entries) => {
break; break;
} }
} }
const shiftDays = todaySkipped ? 1 : 0; const shiftDays = todaySkipped ? 1 : 0;
const last7Days = Array.from({ length: 7 }, (_, j) => { const last7Days = Array.from({ length: 7 }, (_, j) => {
let d = new Date( let d = new Date(
@@ -71,10 +75,9 @@ const getStreak = (name, entries) => {
}); });
const hygCount = userDates.filter( const hygCount = userDates.filter(
(e) => last7Days.includes(e.date) && e.hasHyg, (e: any) => last7Days.includes(e.date) && e.hasHyg,
).length; ).length;
if (hygCount < 3 && streak > 0) streak = Math.max(0, streak - 1); if (hygCount < 3 && streak > 0) streak = Math.max(0, streak - 1);
return streak; return streak;
}; };
@@ -87,19 +90,6 @@ const latestPerUser = Array.from(
}, new Map()) }, new Map())
.values(), .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"> <Layout title="Dashboard & Codex">
@@ -113,74 +103,7 @@ const activeSynergies = latestPerUser.map((user) => {
<main> <main>
<h1 class="main-title">Ambient</h1> <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"> <section class="hero-flex">
{ {
latestPerUser.map((entry) => { latestPerUser.map((entry) => {
@@ -214,162 +137,37 @@ const activeSynergies = latestPerUser.map((user) => {
</div> </div>
) )
} }
</section>
<hr class="separator" /> {
upCard && (
<section class="system-rules"> <div class="card-focus">
<header class="rules-header"> <BankCard
<h2>📖 Manuel de Jeu : TCG (Ruleset Complet)</h2> frontmatter={upCard.data}
<p>Traduction technique des algorithmes du moteur de jeu</p> allHumans={allHumans}
</header> />
<div class="entry-meta">
<div class="rules-layout"> <p>État du cercle FLX</p>
<div class="rule-category"> </div>
<h3>✨ Rareté & Évolution</h3> </div>
<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> </section>
</main> </main>
</Layout> </Layout>
<style> <style>
.design-intent { .hero-flex {
max-width: 1400px; /* Occupation maximale de l'espace */ display: flex;
margin: 60px auto 100px auto; flex-wrap: wrap;
padding: 40px; justify-content: center;
background: #f8fafc; gap: 40px;
border-radius: 24px; padding: 60px 20px;
border: 1px dashed #cbd5e1; /* Aspect "travail en cours" */ scroll-snap-type: x mandatory; /* Optionnel pour le swipe Samsung */
} }
.card-focus {
scroll-snap-align: center;
}
/* ... le reste de ton CSS ... */
.intent-quote { .intent-quote {
max-width: 800px; max-width: 800px;