feat: platform file rows that open full width

Each file is a details whose summary is the row: name, folder when it is not the bare name, status chip. The short hash sits on one copy button beside it, since a summary is itself a control. Opened, the evidence runs across the list. A list with no folder drops the column. Code spans in the evidence are plain text.

The lists split by the share of a page they render to (elements against 10,000, bytes against 600,000) instead of Markdown length. With its parts, Batocera goes from 12 pages, 18,854 elements and 1.21 MB to 10 pages, 13,671 and 1.11 MB. A part that folds back into its platform page redirects there, from the record in website/src/data/platform-parts.json.
This commit is contained in:
Abdessamad Derraz committed 2026-10-27 21:41:00 +01:00
1 parent 20dc488e41
commit e7d33caa3f
10 files changed
+440 -192

No files matched your search

+82
View File
@@ -0,0 +1,82 @@
{
"platforms/batocera-acorn-bbc-micro.md": "platforms/batocera.md#acorn-bbc-micro",
"platforms/batocera-apple-ii.md": "platforms/batocera.md#apple-ii",
"platforms/batocera-apple-macintosh-ii.md": "platforms/batocera.md#apple-macintosh-ii",
"platforms/batocera-archimedes.md": "platforms/batocera.md#archimedes",
"platforms/batocera-atari-st.md": "platforms/batocera.md#atari-st",
"platforms/batocera-commodore-amiga.md": "platforms/batocera.md#commodore-amiga",
"platforms/batocera-elektronika-bk.md": "platforms/batocera.md#elektronika-bk",
"platforms/batocera-microsoft-msx.md": "platforms/batocera.md#microsoft-msx",
"platforms/batocera-nintendo-ds.md": "platforms/batocera.md#nintendo-ds",
"platforms/batocera-sega-dreamcast-arcade.md": "platforms/batocera.md#sega-dreamcast-arcade",
"platforms/batocera-sony-playstation.md": "platforms/batocera.md#sony-playstation",
"platforms/emuelec-microsoft-msx.md": "platforms/emuelec.md#microsoft-msx",
"platforms/knulli-acorn-bbc-micro.md": "platforms/knulli.md#acorn-bbc-micro",
"platforms/knulli-apple-macintosh-ii.md": "platforms/knulli.md#apple-macintosh-ii",
"platforms/knulli-archimedes.md": "platforms/knulli.md#archimedes",
"platforms/knulli-atari-st.md": "platforms/knulli.md#atari-st",
"platforms/knulli-commodore-amiga.md": "platforms/knulli.md#commodore-amiga",
"platforms/knulli-sega-dreamcast-arcade.md": "platforms/knulli.md#sega-dreamcast-arcade",
"platforms/lakka-arcade.md": "platforms/lakka.md#arcade",
"platforms/lakka-commodore-amiga.md": "platforms/lakka.md#commodore-amiga",
"platforms/lakka-nec-pc-98.md": "platforms/lakka.md#nec-pc-98",
"platforms/lakka-nintendo-ds.md": "platforms/lakka.md#nintendo-ds",
"platforms/lakka-nintendo-gamecube.md": "platforms/lakka.md#nintendo-gamecube",
"platforms/lakka-nintendo-sgb.md": "platforms/lakka.md#nintendo-sgb",
"platforms/lakka-nintendo-snes.md": "platforms/lakka.md#nintendo-snes",
"platforms/lakka-scummvm.md": "platforms/lakka.md#scummvm",
"platforms/lakka-sega-dreamcast-arcade.md": "platforms/lakka.md#sega-dreamcast-arcade",
"platforms/lakka-sega-saturn.md": "platforms/lakka.md#sega-saturn",
"platforms/lakka-sinclair-zx-spectrum.md": "platforms/lakka.md#sinclair-zx-spectrum",
"platforms/lakka-sony-playstation-2.md": "platforms/lakka.md#sony-playstation-2",
"platforms/lakka-sony-playstation.md": "platforms/lakka.md#sony-playstation",
"platforms/recalbox-atari-st.md": "platforms/recalbox.md#atari-st",
"platforms/recalbox-commodore-amiga.md": "platforms/recalbox.md#commodore-amiga",
"platforms/recalbox-microsoft-msx.md": "platforms/recalbox.md#microsoft-msx",
"platforms/retroarch-arcade.md": "platforms/retroarch.md#arcade",
"platforms/retroarch-commodore-amiga.md": "platforms/retroarch.md#commodore-amiga",
"platforms/retroarch-nec-pc-engine.md": "platforms/retroarch.md#nec-pc-engine",
"platforms/retroarch-nintendo-ds.md": "platforms/retroarch.md#nintendo-ds",
"platforms/retroarch-nintendo-gamecube.md": "platforms/retroarch.md#nintendo-gamecube",
"platforms/retroarch-nintendo-sgb.md": "platforms/retroarch.md#nintendo-sgb",
"platforms/retroarch-nintendo-snes.md": "platforms/retroarch.md#nintendo-snes",
"platforms/retroarch-scummvm.md": "platforms/retroarch.md#scummvm",
"platforms/retroarch-sega-dreamcast-arcade.md": "platforms/retroarch.md#sega-dreamcast-arcade",
"platforms/retroarch-sega-saturn.md": "platforms/retroarch.md#sega-saturn",
"platforms/retroarch-sinclair-zx-spectrum.md": "platforms/retroarch.md#sinclair-zx-spectrum",
"platforms/retroarch-sony-playstation-2.md": "platforms/retroarch.md#sony-playstation-2",
"platforms/retroarch-sony-playstation.md": "platforms/retroarch.md#sony-playstation",
"platforms/retrobat-atari-st.md": "platforms/retrobat.md#atari-st",
"platforms/retrobat-commodore-amiga.md": "platforms/retrobat.md#commodore-amiga",
"platforms/retrobat-microsoft-msx.md": "platforms/retrobat.md#microsoft-msx",
"platforms/retrobat-nec-pc-engine.md": "platforms/retrobat.md#nec-pc-engine",
"platforms/retrobat-nintendo-ds.md": "platforms/retrobat.md#nintendo-ds",
"platforms/retrobat-sega-saturn.md": "platforms/retrobat.md#sega-saturn",
"platforms/retrodeck-arcade-2.md": "platforms/retrodeck.md#arcade",
"platforms/retrodeck-arcade-3.md": "platforms/retrodeck.md#arcade",
"platforms/retrodeck-arcade-4.md": "platforms/retrodeck.md#arcade",
"platforms/retrodeck-arcade.md": "platforms/retrodeck.md#arcade",
"platforms/retrodeck-atari-400-800.md": "platforms/retrodeck.md#atari-400-800",
"platforms/retrodeck-microsoft-msx.md": "platforms/retrodeck.md#microsoft-msx",
"platforms/retrodeck-nintendo-snes.md": "platforms/retrodeck.md#nintendo-snes",
"platforms/retrodeck-sega-saturn.md": "platforms/retrodeck.md#sega-saturn",
"platforms/retrodeck-sinclair-zx-spectrum.md": "platforms/retrodeck.md#sinclair-zx-spectrum",
"platforms/retrodeck-sony-playstation-2.md": "platforms/retrodeck.md#sony-playstation-2",
"platforms/retrodeck-sony-playstation.md": "platforms/retrodeck.md#sony-playstation",
"platforms/retropie-arcade.md": "platforms/retropie.md#arcade",
"platforms/retropie-commodore-amiga.md": "platforms/retropie.md#commodore-amiga",
"platforms/retropie-nec-pc-98.md": "platforms/retropie.md#nec-pc-98",
"platforms/retropie-nec-pc-engine.md": "platforms/retropie.md#nec-pc-engine",
"platforms/retropie-nintendo-ds.md": "platforms/retropie.md#nintendo-ds",
"platforms/retropie-nintendo-gamecube.md": "platforms/retropie.md#nintendo-gamecube",
"platforms/retropie-nintendo-sgb.md": "platforms/retropie.md#nintendo-sgb",
"platforms/retropie-nintendo-snes.md": "platforms/retropie.md#nintendo-snes",
"platforms/retropie-scummvm.md": "platforms/retropie.md#scummvm",
"platforms/retropie-sega-dreamcast-arcade.md": "platforms/retropie.md#sega-dreamcast-arcade",
"platforms/retropie-sega-saturn.md": "platforms/retropie.md#sega-saturn",
"platforms/retropie-sinclair-zx-spectrum.md": "platforms/retropie.md#sinclair-zx-spectrum",
"platforms/retropie-snk-neogeo-cd.md": "platforms/retropie.md#snk-neogeo-cd",
"platforms/retropie-sony-playstation-2.md": "platforms/retropie.md#sony-playstation-2",
"platforms/retropie-sony-playstation.md": "platforms/retropie.md#sony-playstation",
"platforms/romm-sony-playstation-2.md": "platforms/romm.md#sony-playstation-2"
}
+33
View File
@@ -931,6 +931,38 @@ const glossaryTerms = () => {
document.addEventListener('click', close);
};
// A platform's file list (div.rb-files) is rows of a details and its copy
// button. The filter box written above a long one hides both of a row.
const filterFiles = () => {
for (const list of document.querySelectorAll<HTMLElement>('.rb-table-filter + .rb-files')) {
const controls = list.previousElementSibling!;
const input = controls.querySelector('input');
const count = controls.querySelector('.rb-table-filter-count');
if (!input || !count) continue;
const rows = [...list.querySelectorAll<HTMLElement>(':scope > details.rb-file')];
let searchable: { row: HTMLElement; text: string }[] | null = null;
input.addEventListener('input', () => {
searchable ??= rows.map((row) => ({
row,
text: `${row.querySelector('summary')?.textContent ?? ''} ${row.nextElementSibling?.matches('button.rb-copy') ? row.nextElementSibling.textContent : ''}`
.normalize('NFKD')
.toLocaleLowerCase(),
}));
const query = input.value.trim().normalize('NFKD').toLocaleLowerCase();
let visible = 0;
for (const { row, text } of searchable) {
row.hidden = !!query && !text.includes(query);
const button = row.nextElementSibling;
if (button?.matches('button.rb-copy')) (button as HTMLElement).hidden = row.hidden;
if (!row.hidden) visible += 1;
}
count.textContent = query
? `${visible.toLocaleString('en')} of ${rows.length.toLocaleString('en')} rows`
: `${rows.length.toLocaleString('en')} rows`;
});
}
};
// A hash cut short on a button (button.rb-copy): a click copies the whole
// value from data-copy, says so to a screen reader, and marks the button
// for two seconds. The full value is also in the row's details, so a
@@ -982,6 +1014,7 @@ breakPaths();
hashSearch();
glossaryTerms();
copyButtons();
filterFiles();
osTabs();
showCurrentPage();
notFound();
+62 -34
View File
@@ -551,8 +551,56 @@ body:has(.rb-sys-file, .rb-file-entry) { --sl-content-width: var(--rb-grid-width
.sl-markdown-content details.rb-details > summary::before { background-color: var(--rb-text-secondary); }
.sl-markdown-content details.rb-details[open] > summary { margin-bottom: 0.6rem; }
/* A platform file row: a short hash on a button that copies it whole
(site.ts), and the row's evidence in a details cell. */
/* A platform's file list (generate_site._platform_file_list), on the
pattern of the Tested block: each row a details whose summary is the
row (name, folder, status), its body the evidence across the whole
list. The short hash sits on its copy button (site.ts) beside the
summary, since a summary is itself a control. The details reaches under
the button column, so the row rule and an opened body run full width. */
.sl-markdown-content .rb-files {
--rb-hash-column: 6.75rem;
display: grid;
grid-template-columns: minmax(0, 1fr) var(--rb-hash-column);
margin-top: 0.75rem;
border-top: 1px solid var(--rb-border-strong);
}
.sl-markdown-content .rb-files > * { margin: 0; }
.sl-markdown-content .rb-files > details.rb-file {
grid-column: 1;
margin-right: calc(-1 * var(--rb-hash-column));
padding: 0 0 0 1.75rem;
border: 0;
border-bottom: 1px solid var(--rb-border);
font-size: 0.8125rem;
line-height: 1.5;
overflow-wrap: anywhere;
}
.sl-markdown-content .rb-files > details.rb-file[open] { padding-bottom: 0.6rem; background: var(--rb-surface); }
.sl-markdown-content .rb-files > details.rb-file > summary {
display: grid;
grid-template-columns: 1.25rem minmax(0, 1fr) 9rem 6rem;
gap: 0.5rem;
align-items: center;
min-height: 2.25rem;
margin-left: -1.75rem;
padding: 0.3rem calc(var(--rb-hash-column) + 0.5rem) 0.3rem 0;
font: 0.8125rem/1.4 var(--sl-font-mono);
cursor: pointer;
}
.sl-markdown-content .rb-files:not(.rb-files-path) > details.rb-file > summary { grid-template-columns: 1.25rem minmax(0, 1fr) 6rem; }
.sl-markdown-content .rb-files > details.rb-file > summary:hover { background: var(--rb-surface); }
.sl-markdown-content .rb-files > details.rb-file > summary::before { margin: 0; background-color: var(--rb-text-secondary); }
.sl-markdown-content .rb-files > details.rb-file > summary > code { grid-column: 3; padding: 0; background: none; color: var(--rb-text-secondary); }
.sl-markdown-content .rb-files > details.rb-file > summary > .rb-badge { grid-column: -2; justify-self: start; font-family: var(--sl-font); }
.sl-markdown-content .rb-files > button.rb-copy {
position: relative;
grid-column: 2;
align-self: start;
justify-self: start;
margin-top: 0.4rem;
}
.sl-markdown-content .rb-files > [hidden] { display: none; }
.sl-markdown-content button.rb-copy {
display: inline-flex;
align-items: center;
@@ -561,7 +609,7 @@ body:has(.rb-sys-file, .rb-file-entry) { --sl-content-width: var(--rb-grid-width
padding: 0 0.35em;
border: 1px solid var(--rb-border-strong);
border-radius: 3px;
background: var(--rb-surface);
background: var(--sl-color-bg);
color: var(--sl-color-white);
font: 0.8125rem/1.4 var(--sl-font-mono);
cursor: pointer;
@@ -579,37 +627,17 @@ body:has(.rb-sys-file, .rb-file-entry) { --sl-content-width: var(--rb-grid-width
background: var(--rb-ok-fg);
}
.sl-markdown-content button.rb-copy:hover { border-color: var(--rb-link); }
/* The file name and its destination, written as plain text to spare an
element per cell. */
.sl-markdown-content table:has(:is(button.rb-copy, details.rb-row-details)) td:is(:nth-child(1), :nth-child(2)) {
font-family: var(--sl-font-mono);
font-size: 0.8125rem;
overflow-wrap: anywhere;
}
.sl-markdown-content details.rb-row-details {
margin: 0;
padding: 0;
border: 0;
}
.sl-markdown-content details.rb-row-details > summary {
min-height: 24px;
color: var(--rb-link);
font-size: 0.875rem;
cursor: pointer;
}
.sl-markdown-content details.rb-row-details[open] {
font-size: 0.8125rem;
line-height: 1.5;
overflow-wrap: anywhere;
}
/* Open beside four narrow columns, the evidence keeps a readable width:
the other cells give up their side padding for it. */
@media (min-width: 50rem) {
.sl-markdown-content table:has(details.rb-row-details) :is(th, td) { padding-inline: 0.5rem; }
/* A long name or path wraps before the table outgrows the column. */
.sl-markdown-content table:has(details.rb-row-details) td:first-child { min-width: 9rem; }
.sl-markdown-content table:has(details.rb-row-details) td:nth-child(2) { min-width: 4rem; }
.sl-markdown-content details.rb-row-details[open] { min-width: 15rem; }
@media (max-width: 50rem) {
/* On a phone the folder goes under the name, the status stays on the
first line. A row with no folder prints nothing for it. */
.sl-markdown-content .rb-files > details.rb-file > summary,
.sl-markdown-content .rb-files:not(.rb-files-path) > details.rb-file > summary {
grid-template-columns: 1.25rem minmax(0, 1fr) auto;
row-gap: 0.1rem;
}
.sl-markdown-content .rb-files > details.rb-file > summary::before { grid-row: 1; }
.sl-markdown-content .rb-files > details.rb-file > summary > code { grid-column: 2; grid-row: 2; }
.sl-markdown-content .rb-files > details.rb-file > summary > .rb-badge { grid-column: 3; grid-row: 1; }
}
/* Asides: a neutral box for notes and tips, no side stripe, no icon.