131 lines
6.4 KiB
Plaintext
131 lines
6.4 KiB
Plaintext
---
|
|
import { siteConfig } from '@/site.config';
|
|
import { slugify } from '@/lib/content';
|
|
import type { BannerTextTone } from '@/types/config';
|
|
|
|
interface Props {
|
|
article?: {
|
|
title: string;
|
|
date: { year: string; month: string; day: string };
|
|
categories: string[];
|
|
image?: string;
|
|
textTone?: BannerTextTone;
|
|
};
|
|
banner?: {
|
|
title: string;
|
|
subtitle?: string;
|
|
image?: string;
|
|
position?: string;
|
|
textTone?: BannerTextTone;
|
|
};
|
|
about?: boolean;
|
|
textTone?: BannerTextTone;
|
|
}
|
|
|
|
const { article, banner, about = false } = Astro.props;
|
|
const enabled = Boolean(about || article || banner || siteConfig.banner.enabled);
|
|
const image = banner ? banner.image : article?.image ?? (about ? undefined : siteConfig.banner.image);
|
|
const title = about ? siteConfig.site.author.name : (banner?.title ?? article?.title ?? siteConfig.banner.title);
|
|
const subtitle = banner?.subtitle ?? siteConfig.banner.subtitle;
|
|
const position = banner?.position ?? siteConfig.banner.position;
|
|
---
|
|
|
|
{enabled && <section
|
|
class:list={['relative top-0 flex min-h-50 items-center justify-center overflow-hidden bg-raised pt-16 text-center text-foreground md:pt-16', { 'about-masthead': about }]}
|
|
style={`background-position: ${position}; --mobile-height: ${siteConfig.banner.mobileHeightVh}vh; --desktop-height: ${siteConfig.banner.desktopHeightVh}vh; --overlay: ${siteConfig.banner.overlay}`}
|
|
data-banner-image={image || undefined}
|
|
data-banner-tone={image ? 'light' : undefined}
|
|
aria-labelledby="masthead-title"
|
|
data-pagefind-ignore="all"
|
|
data-banner
|
|
>
|
|
{image && <div
|
|
class="absolute inset-0 z-0 bg-cover bg-no-repeat"
|
|
style={`background-image: url("${image}"); background-position: ${position}`}
|
|
aria-hidden="true"
|
|
></div>}
|
|
{image && <div class="absolute inset-0 z-[1]" style={`background-color: rgb(0 0 0 / ${siteConfig.banner.overlay})`} aria-hidden="true"></div>}
|
|
<div class="relative z-10 flex w-[calc(100%-1.75rem)] flex-col items-center justify-center">
|
|
{about && <img
|
|
class:list={['mb-5 size-[7rem] shrink-0 rounded-full border-4 border-white/85 object-cover shadow-lg md:size-[9.375rem]', { 'about-avatar': siteConfig.site.author.rotateAvatar }]}
|
|
src={siteConfig.site.author.avatar}
|
|
alt={`${siteConfig.site.author.name} 的头像`}
|
|
width="150"
|
|
height="150"
|
|
loading="eager"
|
|
fetchpriority="high"
|
|
/>}
|
|
<h1 class:list={['m-0 text-[2rem] leading-tight font-light tracking-[0] md:text-[2.5rem]', { 'shrink-0': about }]} id="masthead-title">{title}</h1>
|
|
{article ? <p class="masthead-meta mt-3 mb-0 flex flex-wrap items-center justify-center gap-x-2 gap-y-1 text-[0.9375rem] md:text-base">
|
|
<span class="masthead-date"><span>{article.date.year}</span><strong>年</strong><span>{article.date.month}</span><strong>月</strong><span>{article.date.day}</span><strong>日</strong></span>
|
|
{article.categories.length > 0 && <span class="masthead-separator" aria-hidden="true">·</span>}
|
|
{article.categories.map((category, index) => <Fragment>
|
|
{index > 0 && <span class="masthead-separator" aria-hidden="true">/</span>}
|
|
<a class="masthead-category" data-text-link href={`/categories/${slugify(category)}/`}>{category}</a>
|
|
</Fragment>)}
|
|
</p> : !about && subtitle && <p class="mt-3 mb-0 text-[0.9375rem] md:text-base">{subtitle}</p>}
|
|
</div>
|
|
</section>}
|
|
|
|
{enabled && image && <script>
|
|
const updateBannerTone = () => {
|
|
const masthead = document.querySelector<HTMLElement>('[data-banner][data-banner-image]');
|
|
const imageUrl = masthead?.dataset.bannerImage;
|
|
if (!masthead || !imageUrl) return;
|
|
|
|
const source = new Image();
|
|
source.crossOrigin = 'anonymous';
|
|
source.onload = () => {
|
|
try {
|
|
const canvas = document.createElement('canvas');
|
|
const size = 32;
|
|
canvas.width = size;
|
|
canvas.height = size;
|
|
const context = canvas.getContext('2d', { willReadFrequently: true });
|
|
if (!context) throw new Error('Canvas context unavailable');
|
|
context.drawImage(source, 0, 0, size, size);
|
|
const pixels = context.getImageData(0, 0, size, size).data;
|
|
let luma = 0;
|
|
let weight = 0;
|
|
for (let index = 0; index < pixels.length; index += 4) {
|
|
const alpha = pixels[index + 3] / 255;
|
|
luma += (0.2126 * pixels[index] + 0.7152 * pixels[index + 1] + 0.0722 * pixels[index + 2]) * alpha;
|
|
weight += alpha;
|
|
}
|
|
masthead.dataset.bannerTone = weight && luma / weight >= 150 ? 'dark' : 'light';
|
|
} catch {
|
|
masthead.dataset.bannerTone = 'light';
|
|
}
|
|
};
|
|
source.onerror = () => { masthead.dataset.bannerTone = 'light'; };
|
|
source.src = imageUrl;
|
|
};
|
|
|
|
document.addEventListener('astro:page-load', updateBannerTone);
|
|
updateBannerTone();
|
|
</script>}
|
|
|
|
<style>
|
|
section { height: var(--mobile-height); }
|
|
.about-masthead { min-height: 15rem; }
|
|
:global(:root[data-font='serif']) section h1 { font-weight: 700; }
|
|
section[data-banner-image][data-banner-tone='dark'] h1,
|
|
section[data-banner-image][data-banner-tone='dark'] p,
|
|
section[data-banner-image][data-banner-tone='dark'] .masthead-meta { color: #201e1d; text-shadow: 0 1px 2px rgb(255 255 255 / 18%); }
|
|
section[data-banner-image][data-banner-tone='light'] h1,
|
|
section[data-banner-image][data-banner-tone='light'] p,
|
|
section[data-banner-image][data-banner-tone='light'] .masthead-meta { color: #fff; text-shadow: 0 1px 2px rgb(0 0 0 / 24%); }
|
|
section[data-banner-image][data-banner-tone='dark'] .masthead-meta a,
|
|
section[data-banner-image][data-banner-tone='light'] .masthead-meta a { color: inherit; }
|
|
.masthead-meta a { color: inherit; }
|
|
.masthead-meta a::after { border-color: currentColor; }
|
|
.masthead-date,
|
|
.masthead-separator { font-family: Consolas, Menlo, Monaco, 'lucida_console', 'Liberation_Mono', 'Courier_New', 'andale_mono', monospaceX, monospace, sans-serif; }
|
|
.masthead-date strong { font-family: var(--mirages-font-ui); font-weight: 400; }
|
|
.masthead-category { font-family: var(--mirages-font-ui); }
|
|
.about-avatar { transform: rotate(0deg); transition: transform 0.5s cubic-bezier(0.33, 1, 0.68, 1); }
|
|
.about-avatar:hover { transform: rotate(-32deg); }
|
|
@media (min-width: 48rem) { section { height: var(--desktop-height); } .about-masthead { min-height: 18rem; } }
|
|
@media (prefers-reduced-motion: reduce) { .about-avatar { transition: none; transform: none; } .about-avatar:hover { transform: none; } }
|
|
</style>
|