Refactor: replace all BEM CSS with Tailwind utility classes

Remove all BEM component classes (hero__*, card__*, issue-card__*, foot__*,
masthead__*, searchpop__*, lb__*) from CSS and templates. Replace with
Tailwind v4 utility classes inline in HTML. Create card.html partial to
avoid repeating verbose utility strings across grid templates. Rename
lightbox CSS to flat lb-* and search popup to sp-*.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 18:06:55 +02:00
parent e3e9cf6742
commit 54a87dc4ed
15 changed files with 590 additions and 604 deletions
+35 -37
View File
@@ -129,16 +129,16 @@
const cats = allCats();
const tags = allTags();
searchPop.innerHTML = `
<div class="searchpop__section">
<div class="searchpop__label">Categories <small>${cats.length}</small></div>
<div class="searchpop__chips">
${cats.map(c => `<button class="searchpop__chip" data-jump="/categories/${encodeURIComponent(c.toLowerCase().replace(/[\s,]+/g,'-').replace(/[^a-z0-9-]/g,''))}/"> ${esc(c)}</button>`).join('')}
<div class="sp-section">
<div class="sp-label">Categories <small>${cats.length}</small></div>
<div class="sp-chips">
${cats.map(c => `<button class="sp-chip" data-jump="/categories/${encodeURIComponent(c.toLowerCase().replace(/[\s,]+/g,'-').replace(/[^a-z0-9-]/g,''))}/"> ${esc(c)}</button>`).join('')}
</div>
</div>
<div class="searchpop__section">
<div class="searchpop__label">Popular tags <small>${tags.length}</small></div>
<div class="searchpop__chips">
${tags.map(t => `<button class="searchpop__chip" data-jump="/tags/${encodeURIComponent(t.toLowerCase().replace(/[\s,]+/g,'-').replace(/[^a-z0-9-]/g,''))}/"> # ${esc(t)}</button>`).join('')}
<div class="sp-section">
<div class="sp-label">Popular tags <small>${tags.length}</small></div>
<div class="sp-chips">
${tags.map(t => `<button class="sp-chip" data-jump="/tags/${encodeURIComponent(t.toLowerCase().replace(/[\s,]+/g,'-').replace(/[^a-z0-9-]/g,''))}/"> # ${esc(t)}</button>`).join('')}
</div>
</div>`;
searchPop.dataset.open = 'true';
@@ -147,17 +147,17 @@
const hits = INDEX(q) || [];
const terms = q.toLowerCase().split(/\s+/).filter(t => t.length > 1);
if (!hits.length) {
searchPop.innerHTML = `<div class="searchpop__section"><p style="color:var(--ink-soft);font-size:13px;font-style:italic">No plates match — try <em>gothic</em>, <em>warrior</em>, or <em>neon</em>.</p></div>`;
searchPop.innerHTML = `<div class="sp-section"><p style="color:var(--ink-soft);font-size:13px;font-style:italic">No plates match — try <em>gothic</em>, <em>warrior</em>, or <em>neon</em>.</p></div>`;
searchPop.dataset.open = 'true';
return;
}
const shown = hits.slice(0, 6);
searchPop.innerHTML = `
<div class="searchpop__section">
<div class="searchpop__label">Plates <small>${hits.length}</small></div>
<div class="searchpop__hits">
<div class="sp-section">
<div class="sp-label">Plates <small>${hits.length}</small></div>
<div class="sp-hits">
${shown.map(p => `
<button class="searchpop__hit" data-jump="${esc(p.url)}">
<button class="sp-hit" data-jump="${esc(p.url)}">
${p.thumb ? `<img src="${esc(p.thumb)}" alt="" loading="lazy" />` : '<div style="width:44px;height:66px;background:var(--paper-2)"></div>'}
<div>
<div class="t">${highlight(p.title, terms)}</div>
@@ -241,14 +241,13 @@
if (!lbTrack) return;
lbTrack.innerHTML = lbList.map((p, i) => {
const imgSrc = p.card || p.thumb || '';
return `<div class="lb__slide" data-i="${i}">
<div class="lb__frame">
<img class="lb__img" src="${esc(imgSrc)}" alt="${esc(p.title)}" loading="lazy" />
return `<div class="lb-slide" data-i="${i}">
<div class="lb-frame">
<img class="lb-img" src="${esc(imgSrc)}" alt="${esc(p.title)}" loading="lazy" />
</div>
</div>`;
}).join('');
lbBuilt = true;
// Preload neighbors
preloadNeighbors(lbIdx);
}
@@ -271,7 +270,7 @@
[-1, 0, 1, 2].forEach(d => {
const ni = idx + d;
if (ni < 0 || ni >= lbList.length) return;
const slide = lbTrack.querySelector(`.lb__slide[data-i="${ni}"]`);
const slide = lbTrack.querySelector(`.lb-slide[data-i="${ni}"]`);
if (!slide) return;
const img = slide.querySelector('img');
if (img && !img.src) img.src = lbList[ni].card || lbList[ni].thumb || '';
@@ -283,22 +282,22 @@
const cats = (p.categories || []).join(', ');
const tags = (p.tags || []).slice(0, 8);
lbMeta.innerHTML = `
<div class="lb__cat">${esc(cats)}</div>
<h2 class="lb__title">${esc(p.title)}</h2>
<p class="lb__desc">${esc(p.description)}</p>
<dl class="lb__factgrid">
<div class="lb__fact"><dt>Plate</dt><dd>№ ${esc(p.id)}</dd></div>
<div class="lb__fact"><dt>Issue</dt><dd><a href="/issues/${esc(p.issue || '01')}/" style="color:inherit;border-bottom:1px solid currentColor">№ ${esc(p.issue || '01')}</a></dd></div>
<div class="lb__fact"><dt>Category</dt><dd>${esc((p.categories||['—'])[0])}</dd></div>
<div class="lb-cat">${esc(cats)}</div>
<h2 class="lb-title">${esc(p.title)}</h2>
<p class="lb-desc">${esc(p.description)}</p>
<dl class="lb-facts">
<div class="lb-fact"><dt>Plate</dt><dd>№ ${esc(p.id)}</dd></div>
<div class="lb-fact"><dt>Issue</dt><dd><a href="/issues/${esc(p.issue || '01')}/" style="color:inherit;border-bottom:1px solid currentColor">№ ${esc(p.issue || '01')}</a></dd></div>
<div class="lb-fact"><dt>Category</dt><dd>${esc((p.categories||['—'])[0])}</dd></div>
</dl>
<div class="lb__tags">${tags.map(t => {
<div class="lb-tags">${tags.map(t => {
const slug = t.toLowerCase().replace(/[\s,]+/g, '-').replace(/[^a-z0-9-]/g, '');
return `<a class="lb__tag" href="/tags/${slug}/"># ${esc(t)}</a>`;
return `<a class="lb-tag" href="/tags/${slug}/"># ${esc(t)}</a>`;
}).join('')}</div>
<div class="lb__share">
<button class="lb__sh lb__sh--primary" id="lbCopy" data-url="${esc(p.url)}">
<div class="lb-share">
<button class="lb-sh lb-sh-primary" id="lbCopy" data-url="${esc(p.url)}">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 1 0-7.07-7.07l-1 1"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 1 0 7.07 7.07l1-1"/></svg>
<span class="lb__sh-l">Copy link</span>
<span class="lb-sh-l">Copy link</span>
</button>
</div>`;
@@ -306,8 +305,8 @@
const url = new URL(this.dataset.url, location.origin).href;
navigator.clipboard.writeText(url).then(() => {
this.classList.add('is-ok');
this.querySelector('.lb__sh-l').textContent = 'Copied!';
setTimeout(() => { this.classList.remove('is-ok'); this.querySelector('.lb__sh-l').textContent = 'Copy link'; }, 2000);
this.querySelector('.lb-sh-l').textContent = 'Copied!';
setTimeout(() => { this.classList.remove('is-ok'); this.querySelector('.lb-sh-l').textContent = 'Copy link'; }, 2000);
});
});
}
@@ -315,22 +314,22 @@
function lbBuildThumbs() {
if (!lbThumbs) return;
lbThumbs.innerHTML = lbList.map((p, i) =>
`<button class="lb__thumb" data-i="${i}" aria-current="${i === lbIdx}" aria-label="${esc(p.title)}">
`<button class="lb-thumb" data-i="${i}" aria-current="${i === lbIdx}" aria-label="${esc(p.title)}">
<img src="${esc(p.thumb || p.card || '')}" alt="" loading="lazy" />
</button>`
).join('');
lbThumbs.addEventListener('click', e => {
const btn = e.target.closest('.lb__thumb');
const btn = e.target.closest('.lb-thumb');
if (btn) goToSlide(parseInt(btn.dataset.i));
});
}
function syncThumbs() {
if (!lbThumbs) return;
lbThumbs.querySelectorAll('.lb__thumb').forEach((b, i) => {
lbThumbs.querySelectorAll('.lb-thumb').forEach((b, i) => {
b.setAttribute('aria-current', i === lbIdx ? 'true' : 'false');
});
const active = lbThumbs.querySelector('.lb__thumb[aria-current="true"]');
const active = lbThumbs.querySelector('.lb-thumb[aria-current="true"]');
if (active) active.scrollIntoView({ inline: 'center', behavior: 'smooth' });
}
@@ -432,7 +431,6 @@
syncTabs();
// If new page has a slug to open
const script = doc.querySelector('script[data-open-slug]');
const openSlug = window.__ROUX_OPEN_SLUG = doc.querySelector('[data-open-slug]')?.dataset.openSlug || null;
if (openSlug) {
const opened = POSTS.find(p => p.slug === openSlug);