| --- |
| import HtmlEmbed from "./HtmlEmbed.astro"; |
|
|
| interface Props { |
| title: string; |
| titleRaw?: string; |
| description?: string; |
| authors?: string[]; |
| affiliation?: string; |
| published?: string; |
| } |
|
|
| const { title, titleRaw, description, authors = [], affiliation, published } = Astro.props as Props; |
|
|
| function stripHtml(text: string): string { |
| return String(text || '').replace(/<[^>]*>/g, ''); |
| } |
|
|
| function slugify(text: string): string { |
| return String(text || '') |
| .normalize('NFKD') |
| .replace(/\p{Diacritic}+/gu, '') |
| .toLowerCase() |
| .replace(/[^a-z0-9]+/g, '-') |
| .replace(/^-+|-+$/g, '') |
| .slice(0, 120) || 'article'; |
| } |
|
|
| const pdfBase = titleRaw ? titleRaw : stripHtml(title); |
| const pdfFilename = `${slugify(pdfBase)}.pdf`; |
| --- |
| <section class="hero"> |
| <h1 class="hero-title" set:html={title}></h1> |
| <div class="hero-banner"> |
| <HtmlEmbed src="banner.html" frameless /> |
| {description && <p class="hero-desc">{description}</p>} |
| </div> |
| </section> |
|
|
| <header class="meta"> |
| <div class="meta-container"> |
| {authors.length > 0 && ( |
| <div class="meta-container-cell"> |
| <h3>Author {authors.length > 1 ? 's' : ''}</h3> |
| <p>{authors.join(', ')}</p> |
| </div> |
| )} |
| {affiliation && ( |
| <div class="meta-container-cell"> |
| <h3>Affiliation</h3> |
| <p>{affiliation}</p> |
| </div> |
| )} |
| {published && ( |
| <div class="meta-container-cell"> |
| <h3>Published</h3> |
| <p>{published}</p> |
| </div> |
| )} |
| <div class="meta-container-cell meta-container-cell--pdf"> |
| <h3>PDF</h3> |
| <p><button id="download-pdf-btn" data-pdf-filename={pdfFilename}>Download PDF</button></p> |
| </div> |
| </div> |
| </header> |
|
|
| <script> |
| |
| (() => { |
| const ready = () => { |
| const btn = document.getElementById('download-pdf-btn'); |
| if (!btn) return; |
| btn.addEventListener('click', () => { |
| const a = document.createElement('a'); |
| const pdf = btn.getAttribute('data-pdf-filename') || 'article.pdf'; |
| a.href = `/${pdf}`; |
| a.setAttribute('download', pdf); |
| document.body.appendChild(a); |
| a.click(); |
| a.remove(); |
| }); |
| }; |
| if (document.readyState === 'loading') { |
| document.addEventListener('DOMContentLoaded', ready, { once: true }); |
| } else { ready(); } |
| })(); |
| </script> |
|
|
| <style> |
| |
| .hero { width: 100%; padding: 48px 16px 16px; text-align: center; } |
| .hero-title { font-size: clamp(28px, 4vw, 48px); font-weight: 800; line-height: 1.1; margin: 0 0 8px; max-width: 100%; margin: auto; } |
| .hero-banner { max-width: 980px; margin: 0 auto; } |
| .hero-desc { color: var(--muted-color); font-style: italic; margin: 0 0 16px 0; } |
| |
| |
| .meta { border-top: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color); padding: 1rem 0; font-size: 0.9rem; line-height: 1.8em; } |
| .meta-container { max-width: 720px; display: flex; flex-direction: row; justify-content: space-between; margin: 0 auto; gap: 8px; } |
| .meta-container-cell { display: flex; flex-direction: column; gap: 8px; } |
| .meta-container-cell h3 { margin: 0; font-size: 12px; font-weight: 400; color: var(--muted-color); text-transform: uppercase; letter-spacing: .02em; } |
| .meta-container-cell p { margin: 0; } |
| @media print { .meta-container-cell--pdf { display: none !important; } } |
| </style> |
|
|
|
|
|
|