Commit c474d39c by PLN (Algolia)

article css + enhance: facade styles, click-to-render wiring, wide code,…

article css + enhance: facade styles, click-to-render wiring, wide code, centered lightbox, balanced pager

Code blocks break out of the 68ch measure toward 120 mono columns on wide
screens (capped clear of the TOC rail); the lightbox reasserts margin:auto
centering a global reset was killing; pager titles get text-wrap:balance
and a wider cap instead of a 24ch squeeze.
Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
parent 8da2a826
......@@ -15,6 +15,10 @@ import { useEffect, useRef } from 'react';
* 3. Language memory: stores the last switcher choice and flags the
* reader's usual language on later posts — a nudge, never an
* auto-redirect: the reader always gets the page they asked for.
* 4. Embed facades (::youtube / ::soundcloud, built by rehypeEmbeds): the
* play button swaps the facade for the real player iframe — nothing
* remote loads until the reader asks for it. No-JS readers keep the
* facade's fallback link.
*
* Rendered as an invisible anchor inside <article>; it scopes all its DOM
* work to the closest article so unrelated pages stay untouched.
......@@ -101,6 +105,32 @@ export default function ArticleEnhance({ lang, languages }) {
}
});
// --- embed facades: click-to-render -------------------------------
article.querySelectorAll('.embed-facade').forEach((facade) => {
const play = facade.querySelector('.embed-play');
if (!play || facade.dataset.embedWired) return;
facade.dataset.embedWired = 'true';
const render = () => {
const frame = document.createElement('iframe');
frame.className = facade.classList.contains('embed-facade-youtube')
? 'embed-frame'
: 'embed-frame embed-frame-soundcloud';
frame.allow = 'autoplay; encrypted-media; picture-in-picture; fullscreen';
frame.setAttribute('allowfullscreen', '');
frame.title = facade.querySelector('.embed-fallback')?.textContent || 'Embedded media';
if (facade.classList.contains('embed-facade-youtube')) {
frame.src = `https://www.youtube-nocookie.com/embed/${facade.dataset.video}?autoplay=1`;
} else {
frame.src = `https://w.soundcloud.com/player/?url=${encodeURIComponent(
facade.dataset.embedUrl,
)}&auto_play=true`;
}
facade.replaceWith(frame);
};
play.addEventListener('click', render);
cleanups.push(() => play.removeEventListener('click', render));
});
return () => cleanups.forEach((fn) => fn());
}, [lang, languages]);
......
......@@ -391,6 +391,101 @@
margin-bottom: 0;
}
/* ::audio[...] directives (lib/markdown.ts directiveComponents) — native
player, full column width, no client JS. */
.article .prose :global(.directive-audio) {
display: block;
width: 100%;
height: 40px;
margin: 1.5rem 0;
border-radius: 10px;
}
/* ::youtube / ::soundcloud facades (lib/markdown.ts rehypeEmbeds). The
facade is what no-JS readers get too: still + play button + a real link.
ArticleEnhance swaps the whole facade for the player iframe on click. */
.article .prose :global(.embed-facade) {
position: relative;
margin: 2rem 0;
border-radius: 8px;
overflow: hidden;
background: var(--surface-raised);
border: 1px solid var(--border);
}
.article .prose :global(.embed-facade-youtube) {
aspect-ratio: 16 / 9;
}
.article .prose :global(.embed-facade-youtube img) {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
filter: saturate(0.9);
}
.article .prose :global(.embed-facade-soundcloud) {
display: flex;
align-items: center;
min-height: 110px;
padding: 1rem 4.5rem 1rem 1.25rem;
}
.article .prose :global(.embed-play) {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 3.4rem;
height: 3.4rem;
border-radius: 50%;
border: 1px solid var(--border);
background: color-mix(in srgb, var(--surface-raised) 82%, transparent);
backdrop-filter: blur(4px);
color: var(--text);
font-size: 1.1rem;
cursor: pointer;
transition: transform 120ms ease, background 120ms ease;
}
.article .prose :global(.embed-facade-soundcloud .embed-play) {
left: auto;
right: 1rem;
transform: translateY(-50%);
width: 2.8rem;
height: 2.8rem;
}
.article .prose :global(.embed-play:hover) {
transform: translate(-50%, -50%) scale(1.06);
background: var(--surface-raised);
}
.article .prose :global(.embed-facade-soundcloud .embed-play:hover) {
transform: translateY(-50%) scale(1.06);
}
.article .prose :global(.embed-fallback) {
position: relative;
font-size: 0.9rem;
color: var(--text-muted);
}
.article .prose :global(.embed-frame) {
display: block;
width: 100%;
aspect-ratio: 16 / 9;
margin: 2rem 0;
border: 1px solid var(--border);
border-radius: 8px;
}
.article .prose :global(.embed-frame-soundcloud) {
aspect-ratio: auto;
height: 166px;
}
/* ------------------------------------------------------------------ *
* Figures + lightbox
* ------------------------------------------------------------------ */
......@@ -430,6 +525,12 @@
max-width: none;
max-height: none;
overscroll-behavior: contain;
/* global resets kill the UA's margin:auto dialog centering — reassert it */
position: fixed;
inset: 0;
margin: auto;
width: fit-content;
height: fit-content;
}
.article :global(dialog.prose-lightbox)::backdrop {
......@@ -479,6 +580,26 @@
font-variant-ligatures: none;
}
/* Code blocks break out of the 68ch prose measure on wide screens: code
wants ~120 mono columns, prose wants its measure. The breakout centers on
the core (which is viewport-centered), capped so it never slides under the
TOC rail (13rem) — the 30rem reserve is rail + gaps, verified at 80rem. */
@media (min-width: 80rem) {
.article .prose :global([data-rehype-pretty-code-figure]),
.article .prose pre {
width: min(120ch, calc(100vw - 30rem));
margin-left: 50%;
transform: translateX(-50%);
}
.article .prose :global([data-rehype-pretty-code-figure]) pre {
width: 100%;
transform: none;
margin-left: 0;
}
}
.article .prose :not(pre) > code {
background: var(--surface-raised);
padding: 0.15em 0.4em;
......@@ -671,7 +792,10 @@
.pager a {
color: var(--text-primary);
text-decoration: none;
max-width: 24ch;
max-width: 34ch;
/* Long titles wrap — balance evens the lines so a 2-line title looks
set, not squeezed. */
text-wrap: balance;
}
.pager a:hover {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment