Rebuild the web UI on the Cinder design

Replaces the card-and-chip visual language with the Cinder direction from
the design doc: editorial serif, containerless sheets divided by ash
hairlines, one 760px measure, no radii and no shadows.

The organising rule is that heat is typographic. Only a series with an
unread chapter is allowed to be crimson — title, cover foot rule, "Ch N
out", play icon — so the one thing worth acting on is the one thing that
draws the eye. Favourites get brass instead of borrowing the accent, which
is what made the old three-hue palette read as noise. Both states hang off
two classes on the article (is-new, is-dim), so a new sub-element inherits
the state rather than re-deriving it.

The action strip is flex-basis 100% inside the row, which is what lets the
same markup be a full-width six-cell strip under the row on a phone (46px
targets) and a group of 40px squares beside it on a desktop, with no
duplicate template branches. Icons move to a sprite in icons.html: cards
are swapped in by htmx and reference the page's symbols, so a row no
longer carries a screenful of inline SVG paths.

[hidden] gets a display:none override because every disclosure panel is
now a flex container and display beats the attribute. filter.js grows an
.open class alongside hidden so the strip can show which cell the open
panel belongs to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 09:33:41 +07:00
parent 4d69e54c24
commit e940b96eae
7 changed files with 723 additions and 462 deletions
+24 -12
View File
@@ -26,29 +26,41 @@ function setActiveTab(el) {
}
// The chapter-edit form and the delete confirm row are the two per-card
// disclosure panels; only one makes sense open at a time.
// disclosure panels; only one makes sense open at a time. The button that
// owns an open panel carries .open, which is how the strip shows which cell
// the panel belongs to.
function closeCardPanels(key) {
var form = document.getElementById("chapter-form-" + key);
var confirm = document.getElementById("confirm-row-" + key);
if (form) form.hidden = true;
if (confirm) confirm.hidden = true;
var card = document.getElementById("card-" + key);
if (card) {
card.querySelectorAll(".actions .open").forEach(function (b) {
b.classList.remove("open");
});
}
}
function togglePanel(key, panelId, buttonSelector) {
var panel = document.getElementById(panelId + key);
if (!panel) return null;
var opening = panel.hidden;
closeCardPanels(key);
panel.hidden = !opening;
var card = document.getElementById("card-" + key);
var button = card && card.querySelector(buttonSelector);
if (button) button.classList.toggle("open", opening);
return panel;
}
function toggleChapterForm(key) {
var form = document.getElementById("chapter-form-" + key);
if (!form) return;
var opening = form.hidden;
closeCardPanels(key);
form.hidden = !opening;
if (!form.hidden) form.querySelector("input").focus();
var form = togglePanel(key, "chapter-form-", ".actions .pencil");
if (form && !form.hidden) form.querySelector("input").focus();
}
function toggleConfirmRow(key) {
var row = document.getElementById("confirm-row-" + key);
if (!row) return;
var opening = row.hidden;
closeCardPanels(key);
row.hidden = !opening;
togglePanel(key, "confirm-row-", ".actions .remove");
}
// A failed favourite/chapter/delete request leaves the card in place (htmx