| --- |
| import HtmlEmbed from "./HtmlEmbed.astro"; |
| import { |
| type HeroProps, |
| type Author, |
| normalizeAuthors, |
| stripHtml, |
| slugify, |
| } from "./heroUtils"; |
|
|
| const { |
| title, |
| titleRaw, |
| description, |
| authors = [], |
| affiliations = [], |
| affiliation, |
| published, |
| doi, |
| pdfProOnly = false, |
| showPdf = true, |
| } = Astro.props as HeroProps; |
|
|
| const normalizedAuthors: Author[] = normalizeAuthors(authors as any); |
|
|
| const authorAffiliationIndexSet = new Set<number>(); |
| for (const author of normalizedAuthors) { |
| const indices = Array.isArray(author.affiliationIndices) |
| ? author.affiliationIndices |
| : []; |
| for (const idx of indices) { |
| if (typeof idx === "number") authorAffiliationIndexSet.add(idx); |
| } |
| } |
| const shouldShowAffiliationSupers = authorAffiliationIndexSet.size > 1; |
| const hasMultipleAffiliations = |
| Array.isArray(affiliations) && affiliations.length > 1; |
|
|
| const pdfBase = titleRaw ? stripHtml(titleRaw) : stripHtml(title); |
| const pdfFilename = `${slugify(pdfBase)}.pdf`; |
| --- |
|
|
| <section class="hero"> |
| <h1 class="hero-title" set:html={title} /> |
| <div class="hero-banner"> |
| <HtmlEmbed src="banner.html" frameless /> |
| </div> |
| {description && <p class="hero-desc">{description}</p>} |
| </section> |
|
|
| <script is:inline> |
| (() => { |
| const el = document.querySelector(".hero-title"); |
| if (!el) return; |
| const len = (el.textContent || "").length; |
| if (len > 100) el.dataset.titleSize = "sm"; |
| else if (len > 60) el.dataset.titleSize = "md"; |
| })(); |
| </script> |
|
|
| <header class="meta" aria-label="Article meta information"> |
| <div class="meta-container"> |
| { |
| normalizedAuthors.length > 0 && ( |
| <div class="meta-container-cell"> |
| <h3>Author{normalizedAuthors.length > 1 ? "s" : ""}</h3> |
| <ul class="authors"> |
| {normalizedAuthors.map((a, i) => { |
| const supers = |
| shouldShowAffiliationSupers && |
| Array.isArray(a.affiliationIndices) && |
| a.affiliationIndices.length ? ( |
| <sup>{a.affiliationIndices.join(", ")}</sup> |
| ) : null; |
| return ( |
| <li> |
| {a.url ? <a href={a.url}>{a.name}</a> : a.name}{supers}{i < normalizedAuthors.length - 1 && <span set:html=", " />} |
| </li> |
| ); |
| })} |
| </ul> |
| </div> |
| ) |
| } |
| { |
| Array.isArray(affiliations) && affiliations.length > 0 && ( |
| <div class="meta-container-cell meta-container-cell--affiliations"> |
| <h3>Affiliation{affiliations.length > 1 ? "s" : ""}</h3> |
| {hasMultipleAffiliations ? ( |
| <ol class="affiliations"> |
| {affiliations.map((af) => ( |
| <li value={af.id}> |
| {af.url ? ( |
| <a href={af.url} target="_blank" rel="noopener noreferrer"> |
| {af.name} |
| </a> |
| ) : ( |
| af.name |
| )} |
| </li> |
| ))} |
| </ol> |
| ) : ( |
| <p> |
| {affiliations[0]?.url ? ( |
| <a |
| href={affiliations[0].url} |
| target="_blank" |
| rel="noopener noreferrer" |
| > |
| {affiliations[0].name} |
| </a> |
| ) : ( |
| affiliations[0]?.name |
| )} |
| </p> |
| )} |
| </div> |
| ) |
| } |
| { |
| (!affiliations || affiliations.length === 0) && affiliation && ( |
| <div class="meta-container-cell meta-container-cell--affiliations"> |
| <h3>Affiliation</h3> |
| <p>{affiliation}</p> |
| </div> |
| ) |
| } |
| { |
| published && ( |
| <div class="meta-container-cell meta-container-cell--published"> |
| <h3>Published</h3> |
| <p>{published}</p> |
| </div> |
| ) |
| } |
| {doi && ( |
| <div class="meta-container-cell meta-container-cell--doi"> |
| <h3>DOI</h3> |
| <p><a href={`https://doi.org/${doi}`} target="_blank" rel="noopener noreferrer">{doi}</a></p> |
| </div> |
| )} |
| {showPdf && ( |
| <div class="meta-container-cell meta-container-cell--pdf"> |
| <div class="pdf-header-wrapper"> |
| <h3>PDF</h3> |
| <span class="pro-badge-wrapper" style="display: none;"> |
| <span class="pro-badge-prefix">- you are</span> |
| <span class="pro-badge">PRO</span> |
| </span> |
| <span class="pro-only-label" style="display: none;"> |
| <span class="pro-only-dash">-</span> |
| <span class="pro-only-text">pro only</span> |
| <svg class="pro-only-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect> |
| <path d="M7 11V7a5 5 0 0 1 10 0v4"></path> |
| </svg> |
| </span> |
| </div> |
| <div id="pdf-download-container" data-pdf-pro-only={pdfProOnly.toString()}> |
| <p class="pdf-loading">Checking access...</p> |
| <p class="pdf-pro-only" style="display: none;"> |
| <a |
| class="button" |
| href={`/${pdfFilename}`} |
| download={pdfFilename} |
| aria-label={`Download PDF ${pdfFilename}`} |
| > |
| Download PDF |
| </a> |
| </p> |
| <div class="pdf-locked" style="display: none;"> |
| <a |
| class="button button-locked" |
| href="https://huggingface.co/subscribe/pro" |
| target="_blank" |
| rel="noopener noreferrer" |
| > |
| <svg class="lock-icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" viewBox="0 0 12 12" fill="none"> |
| <path d="M6.48 1.26c0 1.55.67 2.58 1.5 3.24.86.68 1.9 1 2.58 1.07v.86A5.3 5.3 0 0 0 7.99 7.5a3.95 3.95 0 0 0-1.51 3.24h-.96c0-1.55-.67-2.58-1.5-3.24a5.3 5.3 0 0 0-2.58-1.07v-.86A5.3 5.3 0 0 0 4.01 4.5a3.95 3.95 0 0 0 1.51-3.24h.96Z" fill="currentColor"></path> |
| </svg> |
| <span class="locked-title">Subscribe to Pro</span> |
| </a> |
| </div> |
| </div> |
| </div> |
| )} |
| </div> |
| </header> |
|
|
| {showPdf && ( |
| <script is:inline> |
| |
| const LOCAL_IS_PRO = true; |
| const FALLBACK_TIMEOUT_MS = 3000; |
| const MAX_RETRY_ATTEMPTS = 100; |
| let userPlanChecked = false; |
| let initialized = false; |
| |
| function isProUser(plan) { |
| if (!plan) return false; |
| return plan.user === "pro"; |
| } |
| |
| function updatePdfAccess(isPro, pdfProOnly) { |
| const loadingEl = document.querySelector(".pdf-loading"); |
| const proOnlyEl = document.querySelector(".pdf-pro-only"); |
| const lockedEl = document.querySelector(".pdf-locked"); |
| const proOnlyLabel = document.querySelector(".pro-only-label"); |
| const proBadgeWrapper = document.querySelector(".pro-badge-wrapper"); |
| if (loadingEl) loadingEl.style.display = "none"; |
| if (!pdfProOnly) { |
| if (proOnlyEl) proOnlyEl.style.display = "block"; |
| if (proOnlyLabel) proOnlyLabel.style.display = "none"; |
| if (lockedEl) lockedEl.style.display = "none"; |
| if (proBadgeWrapper) proBadgeWrapper.style.display = "none"; |
| return; |
| } |
| if (isPro) { |
| if (proOnlyEl) proOnlyEl.style.display = "block"; |
| if (proOnlyLabel) proOnlyLabel.style.display = "none"; |
| if (lockedEl) lockedEl.style.display = "none"; |
| if (proBadgeWrapper) proBadgeWrapper.style.display = "inline-flex"; |
| } else { |
| if (proOnlyEl) proOnlyEl.style.display = "none"; |
| if (proOnlyLabel) proOnlyLabel.style.display = "inline-flex"; |
| if (lockedEl) lockedEl.style.display = "block"; |
| if (proBadgeWrapper) proBadgeWrapper.style.display = "none"; |
| } |
| } |
| |
| function handleUserPlan(plan, pdfProOnly) { |
| userPlanChecked = true; |
| updatePdfAccess(isProUser(plan), pdfProOnly); |
| } |
| |
| function handleFallback(pdfProOnly) { |
| handleUserPlan(LOCAL_IS_PRO ? { user: "pro" } : { user: "free" }, pdfProOnly); |
| } |
| |
| function initPdfAccess(retryCount = 0) { |
| if (initialized) return; |
| const pdfContainer = document.querySelector("#pdf-download-container"); |
| if (!pdfContainer) { |
| if (retryCount < MAX_RETRY_ATTEMPTS) { |
| setTimeout(() => initPdfAccess(retryCount + 1), 10); |
| } else { |
| setTimeout(() => { |
| const container = document.querySelector("#pdf-download-container"); |
| if (container && !initialized) { |
| initialized = true; |
| const pdfProOnly = container.getAttribute("data-pdf-pro-only") === "true"; |
| updatePdfAccess(!pdfProOnly, pdfProOnly); |
| } |
| }, 100); |
| } |
| return; |
| } |
| initialized = true; |
| const pdfProOnly = pdfContainer.getAttribute("data-pdf-pro-only") === "true"; |
| if (!pdfProOnly) { |
| updatePdfAccess(true, pdfProOnly); |
| } else { |
| window.addEventListener("message", (event) => { |
| if (event.data.type === "USER_PLAN") handleUserPlan(event.data.plan, pdfProOnly); |
| }); |
| if (window.parent && window.parent !== window) { |
| window.parent.postMessage({ type: "USER_PLAN_REQUEST" }, "*"); |
| setTimeout(() => { if (!userPlanChecked) handleFallback(pdfProOnly); }, FALLBACK_TIMEOUT_MS); |
| } else { |
| handleFallback(pdfProOnly); |
| } |
| } |
| } |
| |
| (function() { |
| if (document.readyState === "loading") { |
| document.addEventListener("DOMContentLoaded", () => setTimeout(() => initPdfAccess(), 0)); |
| } else { |
| setTimeout(() => initPdfAccess(), 0); |
| } |
| })(); |
| </script> |
| )} |
|
|
| <style> |
| .hero { |
| position: relative; |
| width: 100%; |
| min-height: 100svh; |
| padding: clamp(56px, 9vh, 96px) 16px clamp(32px, 7vh, 72px); |
| text-align: center; |
| display: flex; |
| flex-direction: column; |
| justify-content: center; |
| gap: 16px; |
| overflow: hidden; |
| isolation: isolate; |
| } |
| .hero-title { |
| position: relative; |
| z-index: 2; |
| font-size: clamp(34px, 5vw, 54px); |
| font-weight: 800; |
| line-height: 1.12; |
| letter-spacing: 0; |
| margin: 0 auto; |
| max-width: 780px; |
| text-wrap: balance; |
| text-shadow: 0 2px 28px color-mix(in srgb, var(--page-bg) 92%, transparent); |
| } |
| .hero-title[data-title-size="md"] { |
| font-size: clamp(28px, 4vw, 44px); |
| } |
| .hero-title[data-title-size="sm"] { |
| font-size: clamp(24px, 3.2vw, 38px); |
| } |
| .hero-banner { |
| position: absolute; |
| inset: 0; |
| width: 100%; |
| height: 100%; |
| overflow: hidden; |
| z-index: 0; |
| } |
| .hero-banner::after { |
| content: ""; |
| position: absolute; |
| inset: 0; |
| pointer-events: none; |
| z-index: 1; |
| background: |
| radial-gradient(ellipse at center, color-mix(in srgb, var(--page-bg) 70%, transparent) 0%, color-mix(in srgb, var(--page-bg) 36%, transparent) 28%, transparent 54%), |
| linear-gradient(to bottom, color-mix(in srgb, var(--page-bg) 30%, transparent) 0%, transparent 34%), |
| linear-gradient(to top, var(--page-bg) 0%, transparent 22%); |
| } |
| .hero-banner :global(.html-embed) { |
| height: 100%; |
| } |
| .hero-banner :global(.html-embed__card) { |
| height: 100%; |
| } |
| .hero-desc { |
| position: relative; |
| z-index: 2; |
| color: var(--muted-color); |
| font-style: italic; |
| margin: 0 auto; |
| max-width: min(680px, 90%); |
| text-shadow: 0 1px 18px color-mix(in srgb, var(--page-bg) 96%, transparent); |
| } |
|
|
| @media (max-width: 768px) { |
| .hero { |
| min-height: 100svh; |
| padding-top: clamp(48px, 9vh, 72px); |
| } |
| } |
|
|
| |
| .meta { |
| border-top: 1px solid var(--border-color); |
| border-bottom: 1px solid var(--border-color); |
| padding: 1rem 0; |
| font-size: 0.9rem; |
| } |
| .meta-container { |
| max-width: 980px; |
| display: flex; |
| flex-direction: row; |
| justify-content: space-between; |
| margin: 0 auto; |
| padding: 0 var(--content-padding-x); |
| gap: 8px; |
| flex-wrap: wrap; |
| row-gap: 12px; |
| } |
| .meta-container a:not(.button) { |
| color: var(--primary-color); |
| text-decoration: underline; |
| text-underline-offset: 2px; |
| text-decoration-thickness: 0.06em; |
| text-decoration-color: var(--link-underline); |
| transition: text-decoration-color 0.15s ease-in-out; |
| } |
| .meta-container a:hover { |
| text-decoration-color: var(--link-underline-hover); |
| } |
| .meta-container a.button, |
| .meta-container .button { |
| text-decoration: none; |
| } |
| .meta-container-cell { |
| display: flex; |
| flex-direction: column; |
| gap: 8px; |
| max-width: 400px; |
| } |
| .meta-container-cell h3 { |
| margin: 0; |
| font-size: 12px; |
| font-weight: 400; |
| color: var(--muted-color); |
| text-transform: uppercase; |
| letter-spacing: 0.02em; |
| } |
| .meta-container-cell p { |
| margin: 0; |
| } |
| .authors { |
| margin: 0; |
| list-style-type: none; |
| padding-left: 0; |
| display: flex; |
| flex-wrap: wrap; |
| } |
| .authors li { |
| white-space: nowrap; |
| padding: 0; |
| } |
| .affiliations { |
| margin: 0; |
| padding-left: 1.25em; |
| } |
| .affiliations li { |
| margin: 0; |
| } |
|
|
| @media (max-width: 768px) { |
| .meta-container-cell:nth-child(even) { text-align: right; } |
| .meta-container-cell:last-child:nth-child(odd) { |
| flex-grow: 0; |
| flex-basis: auto; |
| margin-left: auto; |
| text-align: right; |
| } |
| } |
|
|
| @media print { |
| .meta-container-cell--pdf { display: none !important; } |
| } |
|
|
| |
| .pdf-header-wrapper { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| line-height: 1; |
| } |
| .pdf-header-wrapper h3 { line-height: 1; } |
| #pdf-download-container { |
| min-height: calc(var(--button-padding-y) * 2 + var(--button-font-size) + 2px); |
| } |
| .pdf-loading { |
| color: var(--muted-color); |
| font-size: var(--button-font-size); |
| line-height: 1; |
| padding: var(--button-padding-y) var(--button-padding-x); |
| margin: 0; |
| display: inline-block; |
| box-sizing: border-box; |
| border: 1px solid transparent; |
| height: calc(var(--button-padding-y) * 2 + var(--button-font-size) + 2px); |
| vertical-align: top; |
| } |
| .pdf-pro-only { margin: 0; line-height: 0; } |
| .pdf-pro-only .button { margin: 0; } |
| .pro-badge-wrapper { |
| display: inline-flex; |
| align-items: center; |
| gap: 5px; |
| font-style: normal; |
| } |
| .pro-badge-prefix { |
| font-size: 0.85em; |
| opacity: 0.5; |
| font-weight: 400; |
| font-style: normal; |
| } |
| .pro-badge { |
| display: inline-block; |
| border: 1px solid rgba(0, 0, 0, 0.025); |
| background: linear-gradient(to bottom right, #f9a8d4, #86efac, #fde047); |
| color: black; |
| padding: 1px 5px; |
| border-radius: 3px; |
| font-size: 0.5rem; |
| font-weight: 700; |
| font-style: normal; |
| letter-spacing: 0.025em; |
| text-transform: uppercase; |
| } |
| :global(.dark) .pro-badge, |
| :global([data-theme="dark"]) .pro-badge { |
| background: linear-gradient(to bottom right, #ec4899, #22c55e, #eab308); |
| border-color: rgba(255, 255, 255, 0.15); |
| } |
| .pro-only-label { |
| display: inline-flex; |
| flex-direction: row; |
| align-items: center; |
| gap: 5px; |
| font-size: 0.85em; |
| opacity: 0.5; |
| font-weight: 400; |
| line-height: 1; |
| } |
| .pro-only-dash { display: inline-flex; align-items: center; line-height: 1; } |
| .pro-only-icon { |
| width: 11px; |
| height: 11px; |
| flex-shrink: 0; |
| display: inline-flex; |
| align-items: center; |
| } |
| .pro-only-text { display: inline-flex; align-items: center; line-height: 1; } |
| .pdf-locked { display: block; } |
| .button-locked { |
| display: inline-flex; |
| align-items: center; |
| gap: 6px; |
| background: linear-gradient(135deg, |
| var(--primary-color) 0%, |
| oklch(from var(--primary-color) calc(l - 0.1) calc(c + 0.05) calc(h - 60)) 100%); |
| border-radius: var(--button-radius); |
| padding: var(--button-padding-y) var(--button-padding-x); |
| font-size: var(--button-font-size); |
| line-height: 1; |
| color: var(--on-primary); |
| position: relative; |
| overflow: hidden; |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| font-weight: normal; |
| border-color: rgba(0, 0, 0, 0.15); |
| } |
| .button-locked:active { transform: translateY(0); } |
| .lock-icon { font-size: 1em; flex-shrink: 0; position: relative; z-index: 1; } |
| .locked-title { position: relative; z-index: 1; } |
|
|
| @media (max-width: 768px) { |
| .meta-container-cell--pdf { |
| display: flex; |
| flex-direction: column; |
| align-items: flex-end; |
| } |
| } |
| </style> |
|
|