Skip to content

Commit cf3734f

Browse files
Add gallery
1 parent 0f8f168 commit cf3734f

11 files changed

Lines changed: 265 additions & 6 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ jobs:
6464
run: |
6565
node tests/stage3-smoke.js
6666
node tests/stage4-wasm.mjs
67+
node tests/stage5-static.js
6768
6869
- name: Verifier la structure GitHub Pages
6970
run: |

.github/workflows/monitor-multilingual.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ jobs:
8787
run: |
8888
node tests/stage3-smoke.js
8989
node tests/stage4-wasm.mjs
90+
node tests/stage5-static.js
9091
9192
- name: Upload metadata artifact
9293
if: always()

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ Stage 4 adds a generated runtime bridge:
7878
node tests/stage4-wasm.mjs
7979
```
8080

81+
Stage 5 adds the preset gallery and static deployment checks:
82+
83+
```bash
84+
node tests/stage5-static.js
85+
```
86+
8187
## Attribution
8288

8389
Automaginarium is a successor to Cellcosmos and intentionally reuses its project ideas and visual direction. The initial browser metrics and rendering approach are derived from Cellcosmos concepts:

docs/gallery.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Preset Gallery
2+
3+
Stage 5 adds a browser-rendered preset gallery. Each gallery tile is generated from the same Automaginarium configuration model used by the main canvas.
4+
5+
Current preset families:
6+
7+
- elementary Wolfram rules: 30, 90, 110
8+
- binary neighborhood 5 totalistic exploration
9+
- ternary totalistic exploration
10+
- quaternary multi-channel totalistic exploration
11+
- named-symbol table rules
12+
- multi-channel color examples
13+
14+
The gallery intentionally uses real generated thumbnails rather than static images. This keeps presets honest: if an example configuration breaks, the gallery breaks visibly and the smoke tests catch the missing file.
15+
16+
The static check is:
17+
18+
```powershell
19+
node tests\stage5-static.js
20+
```

examples/quaternaire-cristal.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"nom": "Quaternaire cristal - somme modulo",
3+
"alphabet_entree": [0, 1, 2, 3],
4+
"alphabet_sortie": [0, 1, 2, 3],
5+
"taille_voisinage": 5,
6+
"nombre_canaux_sortie": 2,
7+
"mode_regle": "totalistique",
8+
"largeur": 150,
9+
"hauteur": 105,
10+
"frontiere": "circulaire",
11+
"etat_initial": {
12+
"mode": "motif",
13+
"valeurs": [3, 0, 1, 2, 1, 0, 3, 2, 2, 1, 0, 3]
14+
},
15+
"rendu": {
16+
"taille_cellule": 5,
17+
"fond": "#04080a",
18+
"afficher_zero": true,
19+
"palette": ["#04080a", "#53b0ff", "#7bd88f", "#f7d154", "#ff7d7d"]
20+
}
21+
}

examples/symboles-jardin.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"nom": "Symboles jardin - alphabet nomme",
3+
"alphabet_entree": ["sol", "graine"],
4+
"alphabet_sortie": ["sol", "graine", "tige", "fleur"],
5+
"taille_voisinage": 3,
6+
"nombre_canaux_sortie": 2,
7+
"mode_regle": "table",
8+
"table_transition": {
9+
"[\"sol\",\"sol\",\"sol\"]": ["sol", "sol"],
10+
"[\"sol\",\"sol\",\"graine\"]": ["graine", "tige"],
11+
"[\"sol\",\"graine\",\"sol\"]": ["tige", "fleur"],
12+
"[\"sol\",\"graine\",\"graine\"]": ["fleur", "fleur"],
13+
"[\"graine\",\"sol\",\"sol\"]": ["graine", "tige"],
14+
"[\"graine\",\"sol\",\"graine\"]": ["tige", "fleur"],
15+
"[\"graine\",\"graine\",\"sol\"]": ["fleur", "tige"],
16+
"[\"graine\",\"graine\",\"graine\"]": ["sol", "fleur"]
17+
},
18+
"largeur": 141,
19+
"hauteur": 100,
20+
"frontiere": "fixe",
21+
"etat_initial": {
22+
"mode": "motif",
23+
"valeurs": ["graine", "sol", "graine", "graine", "sol", "graine"]
24+
},
25+
"rendu": {
26+
"taille_cellule": 5,
27+
"fond": "#07110a",
28+
"palette": ["#07110a", "#9be564", "#53b0ff", "#ff9d4d", "#f7f1d0"]
29+
}
30+
}

examples/wolfram-110.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"nom": "Wolfram 110 - filaments universels",
3+
"alphabet_entree": [0, 1],
4+
"alphabet_sortie": [0, 1],
5+
"taille_voisinage": 3,
6+
"nombre_canaux_sortie": 1,
7+
"mode_regle": "table",
8+
"table_transition": {
9+
"[1,1,1]": [0],
10+
"[1,1,0]": [1],
11+
"[1,0,1]": [1],
12+
"[1,0,0]": [0],
13+
"[0,1,1]": [1],
14+
"[0,1,0]": [1],
15+
"[0,0,1]": [1],
16+
"[0,0,0]": [0]
17+
},
18+
"largeur": 181,
19+
"hauteur": 120,
20+
"frontiere": "fixe",
21+
"etat_initial": { "mode": "motif", "valeurs": [1, 1, 0, 1, 0, 0, 1] },
22+
"rendu": {
23+
"taille_cellule": 4,
24+
"fond": "#05070d",
25+
"palette": ["#05070d", "#f7d154", "#53b0ff"]
26+
}
27+
}

public/app.mjs

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ import { installAutomaginariumPacked } from "./generated/automate_packed_runtime
33
await installAutomaginariumPacked();
44

55
const PRESETS = [
6-
{ id: "wolfram-30", label: "Wolfram 30" },
7-
{ id: "wolfram-90", label: "Wolfram 90" },
8-
{ id: "binaire-5-sillage", label: "Binaire 5 - Sillage" },
9-
{ id: "ternaire-totalistique", label: "Ternaire totalistique" },
10-
{ id: "multi-canal-aurore", label: "Multi-canal Aurore" },
6+
{ id: "wolfram-30", label: "Wolfram 30", family: "elementaire" },
7+
{ id: "wolfram-90", label: "Wolfram 90", family: "elementaire" },
8+
{ id: "wolfram-110", label: "Wolfram 110", family: "elementaire" },
9+
{ id: "binaire-5-sillage", label: "Binaire 5 - Sillage", family: "voisinage 5" },
10+
{ id: "ternaire-totalistique", label: "Ternaire totalistique", family: "ternaire" },
11+
{ id: "quaternaire-cristal", label: "Quaternaire cristal", family: "quaternaire" },
12+
{ id: "multi-canal-aurore", label: "Multi-canal Aurore", family: "multi-canal" },
13+
{ id: "symboles-jardin", label: "Symboles jardin", family: "custom" },
1114
];
1215

1316
const state = { config: null, universe: null, lastValidConfig: null };
@@ -18,6 +21,7 @@ const title = document.querySelector("#config-title");
1821
const meta = document.querySelector("#config-meta");
1922
const statusBox = document.querySelector("#validation-status");
2023
const tableView = document.querySelector("#rule-table");
24+
const gallery = document.querySelector("#preset-gallery");
2125
const controls = {
2226
name: document.querySelector("#config-name"),
2327
alphabetInput: document.querySelector("#alphabet-input"),
@@ -37,6 +41,7 @@ const controls = {
3741
json: document.querySelector("#config-json"),
3842
importJson: document.querySelector("#import-json"),
3943
};
44+
const presetCache = new Map();
4045

4146
function parseSymbol(raw) {
4247
const trimmed = raw.trim();
@@ -175,6 +180,23 @@ function colorFor(value, config, rowIndex, colIndex, channels) {
175180
return palette[(index + drift) % palette.length] || palette[0];
176181
}
177182

183+
function drawUniverseToCanvas(targetCanvas, universe, maxWidth = 240, maxHeight = 132) {
184+
const { configuration, lignes, sorties } = universe;
185+
const context = targetCanvas.getContext("2d");
186+
const cell = Math.max(1, Math.floor(Math.min(maxWidth / configuration.largeur, maxHeight / configuration.hauteur)));
187+
targetCanvas.width = configuration.largeur * cell;
188+
targetCanvas.height = configuration.hauteur * cell;
189+
context.fillStyle = configuration.rendu.fond || "#05070d";
190+
context.fillRect(0, 0, targetCanvas.width, targetCanvas.height);
191+
lignes.forEach((ligne, y) => {
192+
ligne.forEach((value, x) => {
193+
if (String(value) === String(configuration.alphabet_entree[0]) && !configuration.rendu.afficher_zero) return;
194+
context.fillStyle = colorFor(value, configuration, y, x, sorties?.[y]?.[x]);
195+
context.fillRect(x * cell, y * cell, cell, cell);
196+
});
197+
});
198+
}
199+
178200
function resizeCanvas(config) {
179201
const cell = Number(config.rendu.taille_cellule || 5);
180202
canvas.width = config.largeur * cell;
@@ -222,9 +244,17 @@ function applyConfig(config, { updateJson = true, updateControls = true } = {})
222244
}
223245

224246
async function loadPreset(id) {
247+
const config = await fetchPreset(id);
248+
applyConfig(config);
249+
}
250+
251+
async function fetchPreset(id) {
252+
if (presetCache.has(id)) return structuredClone(presetCache.get(id));
225253
const response = await fetch(`../examples/${id}.json`);
254+
if (!response.ok) throw new Error(`Preset introuvable: ${id}`);
226255
const config = await response.json();
227-
applyConfig(config);
256+
presetCache.set(id, config);
257+
return structuredClone(config);
228258
}
229259

230260
function applyGeneratedRule() {
@@ -260,6 +290,41 @@ function downloadText(filename, text) {
260290
URL.revokeObjectURL(url);
261291
}
262292

293+
async function renderGallery() {
294+
gallery.innerHTML = "";
295+
for (const preset of PRESETS) {
296+
const config = await fetchPreset(preset.id);
297+
const previewConfig = {
298+
...config,
299+
largeur: Math.min(config.largeur || 140, 140),
300+
hauteur: Math.min(config.hauteur || 80, 80),
301+
rendu: { ...(config.rendu || {}), taille_cellule: 2 },
302+
};
303+
const universe = window.AutomaginariumCore.genererUnivers(previewConfig);
304+
const button = document.createElement("button");
305+
button.type = "button";
306+
button.className = "preset-card";
307+
button.dataset.preset = preset.id;
308+
309+
const thumbnail = document.createElement("canvas");
310+
thumbnail.className = "preset-thumb";
311+
drawUniverseToCanvas(thumbnail, universe);
312+
313+
const name = document.createElement("strong");
314+
name.textContent = preset.label;
315+
316+
const family = document.createElement("span");
317+
family.textContent = preset.family;
318+
319+
button.append(thumbnail, name, family);
320+
button.addEventListener("click", () => {
321+
presetSelect.value = preset.id;
322+
loadPreset(preset.id);
323+
});
324+
gallery.appendChild(button);
325+
}
326+
}
327+
263328
PRESETS.forEach((preset) => {
264329
const option = document.createElement("option");
265330
option.value = preset.id;
@@ -298,5 +363,7 @@ document.querySelector("#export-png").addEventListener("click", () => {
298363
link.href = canvas.toDataURL("image/png");
299364
link.click();
300365
});
366+
document.querySelector("#refresh-gallery").addEventListener("click", renderGallery);
301367

368+
await renderGallery();
302369
loadPreset("wolfram-90");

public/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ <h2 id="config-title">Chargement</h2>
3434
</aside>
3535
</section>
3636

37+
<section class="gallery-panel" aria-labelledby="gallery-title">
38+
<div class="panel-heading inline-heading">
39+
<div>
40+
<p class="eyebrow">galerie</p>
41+
<h2 id="gallery-title">Espaces preconfigures</h2>
42+
</div>
43+
<button id="refresh-gallery" type="button">Regenerer</button>
44+
</div>
45+
<div id="preset-gallery" class="preset-gallery" aria-label="Galerie de presets"></div>
46+
</section>
47+
3748
<section class="config-lab" aria-label="Configuration de l'univers">
3849
<div class="config-panel">
3950
<div class="panel-heading">

public/style.css

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,35 @@ canvas { max-width: 100%; image-rendering: pixelated; box-shadow: 0 18px 70px rg
7979
.rule-table { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 18px; max-height: 320px; overflow: auto; }
8080
.rule-table span { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 8px; color: var(--muted); overflow-wrap: anywhere; }
8181
.rule-table b { color: var(--text); }
82+
.gallery-panel { border-top: 1px solid var(--line); margin-top: 18px; padding-top: 16px; }
83+
.preset-gallery {
84+
display: grid;
85+
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
86+
gap: 12px;
87+
}
88+
.preset-card {
89+
display: grid;
90+
gap: 8px;
91+
align-content: start;
92+
text-align: left;
93+
min-height: 0;
94+
padding: 10px;
95+
background: rgba(16,25,35,0.78);
96+
}
97+
.preset-card:hover, .preset-card:focus-visible {
98+
border-color: rgba(83,176,255,0.68);
99+
outline: none;
100+
}
101+
.preset-thumb {
102+
width: 100%;
103+
aspect-ratio: 16 / 9;
104+
height: auto;
105+
background: #03060a;
106+
border: 1px solid var(--line);
107+
border-radius: 6px;
108+
}
109+
.preset-card strong { color: var(--text); font-size: 0.98rem; }
110+
.preset-card span { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; }
82111
.config-lab {
83112
display: grid;
84113
grid-template-columns: minmax(300px, 0.8fr) minmax(260px, 0.65fr) minmax(420px, 1.2fr);

0 commit comments

Comments
 (0)