Spaces:
Running
Running
thibaud frere
commited on
Commit
·
33aefb9
1
Parent(s):
c7b266f
update pdf
Browse files
app/scripts/export-pdf.mjs
CHANGED
|
@@ -239,6 +239,61 @@ async function main() {
|
|
| 239 |
}
|
| 240 |
await page.emulateMedia({ media: 'print' });
|
| 241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
// Generate OG thumbnail (1200x630)
|
| 243 |
try {
|
| 244 |
const ogW = 1200, ogH = 630;
|
|
@@ -281,13 +336,84 @@ async function main() {
|
|
| 281 |
await page.evaluate(() => { window.scrollTo(0, 0); window.dispatchEvent(new Event('resize')); });
|
| 282 |
try { await waitForD3(page, 8000); } catch {}
|
| 283 |
await waitForStableLayout(page);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
} catch {}
|
| 285 |
-
// Temporarily
|
| 286 |
let pdfCssHandle = null;
|
| 287 |
try {
|
| 288 |
pdfCssHandle = await page.addStyleTag({ content: `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
.hero .points { mix-blend-mode: normal !important; }
|
| 290 |
-
.d3-galaxy
|
|
|
|
| 291 |
` });
|
| 292 |
} catch {}
|
| 293 |
await page.pdf({
|
|
|
|
| 239 |
}
|
| 240 |
await page.emulateMedia({ media: 'print' });
|
| 241 |
|
| 242 |
+
// Enforce responsive sizing for SVG/iframes by removing hard attrs and injecting CSS (top-level and inside same-origin iframes)
|
| 243 |
+
try {
|
| 244 |
+
await page.evaluate(() => {
|
| 245 |
+
function isSmallSvg(svg){
|
| 246 |
+
try {
|
| 247 |
+
const vb = svg && svg.viewBox && svg.viewBox.baseVal ? svg.viewBox.baseVal : null;
|
| 248 |
+
if (vb && vb.width && vb.height && vb.width <= 50 && vb.height <= 50) return true;
|
| 249 |
+
const r = svg.getBoundingClientRect && svg.getBoundingClientRect();
|
| 250 |
+
if (r && r.width && r.height && r.width <= 50 && r.height <= 50) return true;
|
| 251 |
+
} catch {}
|
| 252 |
+
return false;
|
| 253 |
+
}
|
| 254 |
+
function lockSmallSvgSize(svg){
|
| 255 |
+
try {
|
| 256 |
+
const r = svg.getBoundingClientRect ? svg.getBoundingClientRect() : null;
|
| 257 |
+
const w = (r && r.width) ? Math.round(r.width) : null;
|
| 258 |
+
const h = (r && r.height) ? Math.round(r.height) : null;
|
| 259 |
+
if (w) svg.style.setProperty('width', w + 'px', 'important');
|
| 260 |
+
if (h) svg.style.setProperty('height', h + 'px', 'important');
|
| 261 |
+
svg.style.setProperty('max-width', 'none', 'important');
|
| 262 |
+
} catch {}
|
| 263 |
+
}
|
| 264 |
+
function fixSvg(svg){
|
| 265 |
+
if (!svg) return;
|
| 266 |
+
if (isSmallSvg(svg)) { lockSmallSvgSize(svg); return; }
|
| 267 |
+
try { svg.removeAttribute('width'); } catch {}
|
| 268 |
+
try { svg.removeAttribute('height'); } catch {}
|
| 269 |
+
svg.style.maxWidth = '100%';
|
| 270 |
+
svg.style.width = '100%';
|
| 271 |
+
svg.style.height = 'auto';
|
| 272 |
+
if (!svg.getAttribute('preserveAspectRatio')) svg.setAttribute('preserveAspectRatio','xMidYMid meet');
|
| 273 |
+
}
|
| 274 |
+
document.querySelectorAll('svg').forEach(fixSvg);
|
| 275 |
+
document.querySelectorAll('.mermaid, .mermaid svg').forEach((el)=>{
|
| 276 |
+
if (el.tagName && el.tagName.toLowerCase() === 'svg') fixSvg(el);
|
| 277 |
+
else { el.style.display='block'; el.style.width='100%'; el.style.maxWidth='100%'; }
|
| 278 |
+
});
|
| 279 |
+
document.querySelectorAll('iframe, embed, object').forEach((el) => {
|
| 280 |
+
el.style.width = '100%';
|
| 281 |
+
el.style.maxWidth = '100%';
|
| 282 |
+
try { el.removeAttribute('width'); } catch {}
|
| 283 |
+
// Best-effort inject into same-origin frames
|
| 284 |
+
try {
|
| 285 |
+
const doc = (el.tagName.toLowerCase()==='object' ? el.contentDocument : el.contentDocument);
|
| 286 |
+
if (doc && doc.head) {
|
| 287 |
+
const s = doc.createElement('style');
|
| 288 |
+
s.textContent = 'html,body{overflow-x:hidden;} svg,canvas,img,video{max-width:100%!important;height:auto!important;} svg[width]{width:100%!important}';
|
| 289 |
+
doc.head.appendChild(s);
|
| 290 |
+
doc.querySelectorAll('svg').forEach((svg)=>{ if (isSmallSvg(svg)) lockSmallSvgSize(svg); else fixSvg(svg); });
|
| 291 |
+
}
|
| 292 |
+
} catch (_) { /* cross-origin; ignore */ }
|
| 293 |
+
});
|
| 294 |
+
});
|
| 295 |
+
} catch {}
|
| 296 |
+
|
| 297 |
// Generate OG thumbnail (1200x630)
|
| 298 |
try {
|
| 299 |
const ogW = 1200, ogH = 630;
|
|
|
|
| 336 |
await page.evaluate(() => { window.scrollTo(0, 0); window.dispatchEvent(new Event('resize')); });
|
| 337 |
try { await waitForD3(page, 8000); } catch {}
|
| 338 |
await waitForStableLayout(page);
|
| 339 |
+
// Re-apply responsive fixes after viewport change
|
| 340 |
+
try {
|
| 341 |
+
await page.evaluate(() => {
|
| 342 |
+
function isSmallSvg(svg){
|
| 343 |
+
try {
|
| 344 |
+
const vb = svg && svg.viewBox && svg.viewBox.baseVal ? svg.viewBox.baseVal : null;
|
| 345 |
+
if (vb && vb.width && vb.height && vb.width <= 50 && vb.height <= 50) return true;
|
| 346 |
+
const r = svg.getBoundingClientRect && svg.getBoundingClientRect();
|
| 347 |
+
if (r && r.width && r.height && r.width <= 50 && r.height <= 50) return true;
|
| 348 |
+
} catch {}
|
| 349 |
+
return false;
|
| 350 |
+
}
|
| 351 |
+
function lockSmallSvgSize(svg){
|
| 352 |
+
try {
|
| 353 |
+
const r = svg.getBoundingClientRect ? svg.getBoundingClientRect() : null;
|
| 354 |
+
const w = (r && r.width) ? Math.round(r.width) : null;
|
| 355 |
+
const h = (r && r.height) ? Math.round(r.height) : null;
|
| 356 |
+
if (w) svg.style.setProperty('width', w + 'px', 'important');
|
| 357 |
+
if (h) svg.style.setProperty('height', h + 'px', 'important');
|
| 358 |
+
svg.style.setProperty('max-width', 'none', 'important');
|
| 359 |
+
} catch {}
|
| 360 |
+
}
|
| 361 |
+
function fixSvg(svg){
|
| 362 |
+
if (!svg) return;
|
| 363 |
+
if (isSmallSvg(svg)) { lockSmallSvgSize(svg); return; }
|
| 364 |
+
try { svg.removeAttribute('width'); } catch {}
|
| 365 |
+
try { svg.removeAttribute('height'); } catch {}
|
| 366 |
+
svg.style.maxWidth = '100%';
|
| 367 |
+
svg.style.width = '100%';
|
| 368 |
+
svg.style.height = 'auto';
|
| 369 |
+
if (!svg.getAttribute('preserveAspectRatio')) svg.setAttribute('preserveAspectRatio','xMidYMid meet');
|
| 370 |
+
}
|
| 371 |
+
document.querySelectorAll('svg').forEach((svg)=>{ if (isSmallSvg(svg)) lockSmallSvgSize(svg); else fixSvg(svg); });
|
| 372 |
+
document.querySelectorAll('.mermaid, .mermaid svg').forEach((el)=>{
|
| 373 |
+
if (el.tagName && el.tagName.toLowerCase() === 'svg') fixSvg(el);
|
| 374 |
+
else { el.style.display='block'; el.style.width='100%'; el.style.maxWidth='100%'; }
|
| 375 |
+
});
|
| 376 |
+
document.querySelectorAll('iframe, embed, object').forEach((el) => {
|
| 377 |
+
el.style.width = '100%';
|
| 378 |
+
el.style.maxWidth = '100%';
|
| 379 |
+
try { el.removeAttribute('width'); } catch {}
|
| 380 |
+
try {
|
| 381 |
+
const doc = (el.tagName.toLowerCase()==='object' ? el.contentDocument : el.contentDocument);
|
| 382 |
+
if (doc && doc.head) {
|
| 383 |
+
const s = doc.createElement('style');
|
| 384 |
+
s.textContent = 'html,body{overflow-x:hidden;} svg,canvas,img,video{max-width:100%!important;height:auto!important;} svg[width]{width:100%!important}';
|
| 385 |
+
doc.head.appendChild(s);
|
| 386 |
+
doc.querySelectorAll('svg').forEach((svg)=>{ if (isSmallSvg(svg)) lockSmallSvgSize(svg); else fixSvg(svg); });
|
| 387 |
+
}
|
| 388 |
+
} catch (_) {}
|
| 389 |
+
});
|
| 390 |
+
});
|
| 391 |
+
} catch {}
|
| 392 |
} catch {}
|
| 393 |
+
// Temporarily enforce print-safe responsive sizing (SVG/iframes) and improve banner visibility
|
| 394 |
let pdfCssHandle = null;
|
| 395 |
try {
|
| 396 |
pdfCssHandle = await page.addStyleTag({ content: `
|
| 397 |
+
/* General container safety */
|
| 398 |
+
html, body { overflow-x: hidden !important; }
|
| 399 |
+
|
| 400 |
+
/* Make all vector/bitmap media responsive for print */
|
| 401 |
+
svg, canvas, img, video { max-width: 100% !important; height: auto !important; }
|
| 402 |
+
/* Mermaid diagrams */
|
| 403 |
+
.mermaid, .mermaid svg { display: block; width: 100% !important; max-width: 100% !important; height: auto !important; }
|
| 404 |
+
/* Any explicit width attributes */
|
| 405 |
+
svg[width] { width: 100% !important; }
|
| 406 |
+
/* Iframes and similar embeds */
|
| 407 |
+
iframe, embed, object { width: 100% !important; max-width: 100% !important; height: auto; }
|
| 408 |
+
|
| 409 |
+
/* HtmlEmbed wrappers (defensive) */
|
| 410 |
+
.html-embed, .html-embed__card { max-width: 100% !important; width: 100% !important; }
|
| 411 |
+
.html-embed__card > div[id^="frag-"] { width: 100% !important; max-width: 100% !important; }
|
| 412 |
+
|
| 413 |
+
/* Banner centering & visibility */
|
| 414 |
.hero .points { mix-blend-mode: normal !important; }
|
| 415 |
+
.d3-galaxy { width: 100% !important; height: 300px; max-width: 980px !important; margin-left: auto !important; margin-right: auto !important; }
|
| 416 |
+
.d3-galaxy svg { background: var(--surface-bg); width: 100% !important; height: auto !important; }
|
| 417 |
` });
|
| 418 |
} catch {}
|
| 419 |
await page.pdf({
|
app/src/components/Hero.astro
CHANGED
|
@@ -57,7 +57,7 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 57 |
<p>{published}</p>
|
| 58 |
</div>
|
| 59 |
)}
|
| 60 |
-
<div class="meta-container-cell">
|
| 61 |
<h3>PDF</h3>
|
| 62 |
<p><button id="download-pdf-btn" data-pdf-filename={pdfFilename}>Download PDF</button></p>
|
| 63 |
</div>
|
|
@@ -99,6 +99,7 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 99 |
.meta-container-cell { display: flex; flex-direction: column; gap: 8px; }
|
| 100 |
.meta-container-cell h3 { margin: 0; font-size: 12px; font-weight: 400; color: var(--muted-color); text-transform: uppercase; letter-spacing: .02em; }
|
| 101 |
.meta-container-cell p { margin: 0; }
|
|
|
|
| 102 |
</style>
|
| 103 |
|
| 104 |
|
|
|
|
| 57 |
<p>{published}</p>
|
| 58 |
</div>
|
| 59 |
)}
|
| 60 |
+
<div class="meta-container-cell meta-container-cell--pdf">
|
| 61 |
<h3>PDF</h3>
|
| 62 |
<p><button id="download-pdf-btn" data-pdf-filename={pdfFilename}>Download PDF</button></p>
|
| 63 |
</div>
|
|
|
|
| 99 |
.meta-container-cell { display: flex; flex-direction: column; gap: 8px; }
|
| 100 |
.meta-container-cell h3 { margin: 0; font-size: 12px; font-weight: 400; color: var(--muted-color); text-transform: uppercase; letter-spacing: .02em; }
|
| 101 |
.meta-container-cell p { margin: 0; }
|
| 102 |
+
@media print { .meta-container-cell--pdf { display: none !important; } }
|
| 103 |
</style>
|
| 104 |
|
| 105 |
|
app/src/components/HtmlEmbed.astro
CHANGED
|
@@ -102,7 +102,20 @@ const mountId = `frag-${Math.random().toString(36).slice(2)}`;
|
|
| 102 |
.html-embed__card > div[id^="frag-"] { width: 100% !important; }
|
| 103 |
}
|
| 104 |
@media print {
|
|
|
|
| 105 |
.html-embed, .html-embed__card { break-inside: avoid; page-break-inside: avoid; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
}
|
| 107 |
</style>
|
| 108 |
|
|
|
|
| 102 |
.html-embed__card > div[id^="frag-"] { width: 100% !important; }
|
| 103 |
}
|
| 104 |
@media print {
|
| 105 |
+
/* Avoid breaks inside embeds */
|
| 106 |
.html-embed, .html-embed__card { break-inside: avoid; page-break-inside: avoid; }
|
| 107 |
+
/* Constrain width and scale inner content */
|
| 108 |
+
.html-embed, .html-embed__card { max-width: 100% !important; width: 100% !important; }
|
| 109 |
+
.html-embed__card { padding: 6px; }
|
| 110 |
+
.html-embed__card.is-frameless { padding: 0; }
|
| 111 |
+
.html-embed__card svg,
|
| 112 |
+
.html-embed__card canvas,
|
| 113 |
+
.html-embed__card img,
|
| 114 |
+
.html-embed__card video,
|
| 115 |
+
.html-embed__card iframe { max-width: 100% !important; height: auto !important; }
|
| 116 |
+
.html-embed__card > div[id^="frag-"] { width: 100% !important; max-width: 100% !important; }
|
| 117 |
+
/* Center and constrain the banner (galaxy) when printing */
|
| 118 |
+
.html-embed .d3-galaxy { width: 100% !important; max-width: 980px !important; margin-left: auto !important; margin-right: auto !important; }
|
| 119 |
}
|
| 120 |
</style>
|
| 121 |
|
app/src/content/embeds/palettes.html
CHANGED
|
@@ -28,11 +28,11 @@
|
|
| 28 |
.palettes input[type="range"]:focus { outline: none; }
|
| 29 |
/* WebKit */
|
| 30 |
.palettes input[type="range"]::-webkit-slider-runnable-track { height: 6px; background: var(--border-color); border-radius: 999px; }
|
| 31 |
-
.palettes input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; margin-top: -6px; width: 18px; height: 18px; background: var(--primary-color); border: 2px solid var(--surface-bg); border-radius: 50%;
|
| 32 |
/* Firefox */
|
| 33 |
.palettes input[type="range"]::-moz-range-track { height: 6px; background: var(--border-color); border: none; border-radius: 999px; }
|
| 34 |
.palettes input[type="range"]::-moz-range-progress { height: 6px; background: var(--primary-color); border-radius: 999px; }
|
| 35 |
-
.palettes input[type="range"]::-moz-range-thumb { width: 18px; height: 18px; background: var(--primary-color); border: 2px solid var(--surface-bg); border-radius: 50%;
|
| 36 |
/* Page-wide color vision simulation classes */
|
| 37 |
html.cb-grayscale, body.cb-grayscale { filter: grayscale(1) !important; }
|
| 38 |
html.cb-protanopia, body.cb-protanopia { filter: url(#cb-protanopia) !important; }
|
|
|
|
| 28 |
.palettes input[type="range"]:focus { outline: none; }
|
| 29 |
/* WebKit */
|
| 30 |
.palettes input[type="range"]::-webkit-slider-runnable-track { height: 6px; background: var(--border-color); border-radius: 999px; }
|
| 31 |
+
.palettes input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; margin-top: -6px; width: 18px; height: 18px; background: var(--primary-color); border: 2px solid var(--surface-bg); border-radius: 50%; }
|
| 32 |
/* Firefox */
|
| 33 |
.palettes input[type="range"]::-moz-range-track { height: 6px; background: var(--border-color); border: none; border-radius: 999px; }
|
| 34 |
.palettes input[type="range"]::-moz-range-progress { height: 6px; background: var(--primary-color); border-radius: 999px; }
|
| 35 |
+
.palettes input[type="range"]::-moz-range-thumb { width: 18px; height: 18px; background: var(--primary-color); border: 2px solid var(--surface-bg); border-radius: 50%; }
|
| 36 |
/* Page-wide color vision simulation classes */
|
| 37 |
html.cb-grayscale, body.cb-grayscale { filter: grayscale(1) !important; }
|
| 38 |
html.cb-protanopia, body.cb-protanopia { filter: url(#cb-protanopia) !important; }
|