Initial commit

This commit is contained in:
2026-02-02 22:47:52 +03:00
committed by GitHub
commit f53016aeda
239 changed files with 84360 additions and 0 deletions

522
src/layouts/base.astro Normal file
View File

@@ -0,0 +1,522 @@
---
import "katex/dist/katex.css";
import { siteConfig, profileConfig, umamiConfig, musicPlayerConfig, pioConfig } from "@/config";
import type { Favicon } from "@/types/config";
import { defaultFavicons } from "@constants/icon";
import {
BANNER_HEIGHT,
BANNER_HEIGHT_EXTEND,
BANNER_HEIGHT_HOME,
LIGHT_MODE,
DARK_MODE,
SYSTEM_MODE,
PAGE_WIDTH,
NAVBAR_HEIGHT,
} from "@constants/constants";
import { pathsEqual, url } from "@utils/url";
import FontLoader from "@components/fontLoader.astro";
import LoadingOverlay from "@components/loadingOverlay.astro";
import ConfigCarrier from "@components/configCarrier.astro";
import MusicPlayer from "@components/musicPlayer.svelte";
import Pio from "@components/pio.svelte";
import "@styles/navbar.css";
import "@styles/banner.css";
import "@styles/fancybox.css";
interface Props {
title?: string;
banner?: string;
description?: string;
lang?: string;
setOGTypeArticle?: boolean;
postSlug?: string;
}
let { title, banner, description, lang, setOGTypeArticle, postSlug } = Astro.props;
// apply a class to the body element to decide the height of the banner, only used for initial page load
const isHomePage = pathsEqual(Astro.url.pathname, url("/"));
// defines global css variables
// why doing this in BaseLayout instead of GlobalStyles: https://github.com/withastro/astro/issues/6728#issuecomment-1502203757
const configHue = siteConfig.themeColor.hue;
// 获取导航栏透明模式配置
const navbarTransparentMode =
siteConfig.wallpaper.banner?.navbar?.transparentMode || "semi";
// 判断是否应该显示顶部高光效果只在full和semifull模式下显示
const shouldShowTopHighlight =
navbarTransparentMode === "full" || navbarTransparentMode === "semifull";
// 获取默认banner图片的辅助函数
const getDefaultBanner = (): string => {
const src = siteConfig.wallpaper.src;
if (typeof src === "string") {
return src;
}
if (Array.isArray(src)) {
return src[0] || "";
}
if (src && typeof src === "object") {
// 优先使用desktop如果没有则使用mobile
const desktopSrc = src.desktop;
const mobileSrc = src.mobile;
if (typeof desktopSrc === "string") {
return desktopSrc;
}
if (Array.isArray(desktopSrc) && desktopSrc.length > 0) {
return desktopSrc[0];
}
if (typeof mobileSrc === "string") {
return mobileSrc;
}
if (Array.isArray(mobileSrc) && mobileSrc.length > 0) {
return mobileSrc[0];
}
}
return "";
};
// TODO don't use post cover as banner for now
banner = getDefaultBanner();
const enableBanner = siteConfig.wallpaper.mode === "banner";
let pageTitle: string;
if (title) {
pageTitle = `${title} - ${siteConfig.title}`;
} else {
pageTitle = siteConfig.subtitle
? `${siteConfig.title} - ${siteConfig.subtitle}`
: siteConfig.title;
}
let ogImageUrl: string | undefined;
if (siteConfig.generateOgImages && postSlug) {
ogImageUrl = new URL(`/og/${postSlug}.png`, Astro.site).toString();
}
const favicons: Favicon[] =
siteConfig.favicon.length > 0 ? siteConfig.favicon : defaultFavicons;
// const siteLang = siteConfig.lang.replace('_', '-')
if (!lang) {
lang = `${siteConfig.lang}`;
}
const siteLang = lang.replace("_", "-");
const bannerOffsetByPosition = {
top: `${BANNER_HEIGHT_EXTEND}vh`,
center: `${BANNER_HEIGHT_EXTEND / 2}vh`,
bottom: "0",
};
const bannerOffset =
bannerOffsetByPosition[siteConfig.wallpaper.position || "center"];
const umamiEnabled = umamiConfig.enabled || false;
const umamiScripts = umamiConfig.scripts || ""; // 获取Umami scripts配置
---
<!DOCTYPE html>
<html lang={siteLang} class="bg-(--page-bg) text-[14px] md:text-[16px] is-loading"
>
<head>
<meta charset="UTF-8" />
<!-- Resource Preconnect -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://code.iconify.design">
<!-- Font Loader -->
<FontLoader />
<!-- title -->
<title>{pageTitle}</title>
<!-- description -->
<meta name="description" content={description || pageTitle}>
<!-- keywords -->
{siteConfig.keywords && siteConfig.keywords.length > 0 && (
<meta name="keywords" content={siteConfig.keywords.join(', ')} />
)}
<!-- author -->
<meta name="author" content={profileConfig.name}>
<!-- Open Graph -->
<meta property="og:site_name" content={siteConfig.title}>
<meta property="og:url" content={Astro.url}>
<meta property="og:title" content={pageTitle}>
<meta property="og:description" content={description || pageTitle}>
{ogImageUrl && <meta property="og:image" content={ogImageUrl} />}
{
setOGTypeArticle ? (
<meta property="og:type" content="article" />
) : (
<meta property="og:type" content="website" />
)
}
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta property="twitter:url" content={Astro.url}>
<meta name="twitter:title" content={pageTitle}>
<meta name="twitter:description" content={description || pageTitle}>
<!-- viewport -->
<meta name="viewport" content="width=device-width" />
<!-- generator -->
<meta name="generator" content={Astro.generator} />
<!-- favicons -->
{favicons.map(favicon => (
<link rel="icon"
href={favicon.src.startsWith('/') ? url(favicon.src) : favicon.src}
sizes={favicon.sizes}
media={favicon.theme && `(prefers-color-scheme: ${favicon.theme})`}
/>
))}
<!-- Set the theme before the page is rendered to avoid a flash -->
<script is:inline define:vars={{LIGHT_MODE, DARK_MODE, SYSTEM_MODE, BANNER_HEIGHT_EXTEND, PAGE_WIDTH, NAVBAR_HEIGHT, configHue, defaultTheme: siteConfig.defaultTheme}}>
// Load the theme from local storage
const theme = localStorage.getItem('theme') || defaultTheme;
// Apply the theme to the document
let isDark = false;
switch (theme) {
case LIGHT_MODE:
document.documentElement.classList.remove('dark');
isDark = false;
break
case DARK_MODE:
document.documentElement.classList.add('dark');
isDark = true;
break
case SYSTEM_MODE:
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
isDark = true;
} else {
document.documentElement.classList.remove('dark');
isDark = false;
}
}
const expressiveTheme = isDark ? "github-dark" : "github-light";
document.documentElement.setAttribute("data-theme", expressiveTheme);
// Load the hue from local storage
const hue = localStorage.getItem('hue') || configHue;
// Apply the hue to the document
document.documentElement.style.setProperty('--hue', hue);
// calculate the --banner-height-extend, which needs to be a multiple of 4 to avoid blurry text
let offset = Math.floor(window.innerHeight * (BANNER_HEIGHT_EXTEND / 100));
offset = offset - offset % 4;
document.documentElement.style.setProperty('--banner-height-extend', `${offset}px`);
</script>
<!-- defines global css variables. This will be applied to <html> <body> and some other elements idk why -->
<style define:vars={{
configHue,
'page-width': `${PAGE_WIDTH}rem`,
}}></style>
<!-- custom head -->
<slot name="head"></slot>
<!-- Pio 看板娘样式 - 仅在启用时加载 -->
{pioConfig.enable && <link rel="stylesheet" href="/pio/static/pio.css" />}
<!-- rss feed -->
<link rel="alternate" type="application/rss+xml" title={profileConfig.name} href={`${Astro.site}rss.xml`}/>
<!-- Umami Analytics -->
{umamiEnabled && umamiScripts && <Fragment set:html={umamiScripts} />}
</head>
<body class=" min-h-screen " class:list={[{"is-home": isHomePage, "enable-banner": enableBanner}]}
>
<!-- 页面加载动画 -->
<LoadingOverlay />
<!-- 全局配置载体 -->
<ConfigCarrier />
<!-- 页面顶部渐变高光效果 - 只在full和semifull模式下显示 -->
{shouldShowTopHighlight && <div class="top-gradient-highlight"></div>}
<!-- 页面内容 -->
<slot />
<!-- Music Player - 仅在启用时加载 -->
{musicPlayerConfig.enable && <MusicPlayer client:idle />}
<!-- Pio 看板娘组件 - 如果启用了侧边栏,可能需要调整 z-index -->
{pioConfig.enable && <Pio client:idle />}
<!-- increase the page height during page transition to prevent the scrolling animation from jumping -->
<div id="page-height-extend" class="hidden h-[300vh]"></div>
</body>
</html>
<style is:global define:vars={{
bannerOffset,
'banner-height-home': `${BANNER_HEIGHT_HOME}vh`,
'banner-height': `${BANNER_HEIGHT}vh`,
}}>
@reference "../styles/main.css";
@layer components {
.enable-banner.is-home #banner-wrapper {
@apply h-(--banner-height-home) translate-y-(--banner-height-extend)
}
.enable-banner #banner-wrapper {
@apply h-(--banner-height-home)
}
.enable-banner.is-home #banner {
@apply h-(--banner-height-home) translate-y-0
}
.enable-banner #banner {
@apply h-(--banner-height-home) translate-y-(--bannerOffset)
}
.enable-banner.is-home #main-grid {
translate: 0 var(--banner-height-extend);
}
.enable-banner #top-row {
height: calc(var(--banner-height-home) - 5.5rem);
}
.enable-banner.is-home #sidebar-sticky {
top: calc(1rem - var(--banner-height-extend)) !important;
}
}
</style>
<script>
import {
NAVBAR_HEIGHT,
BANNER_HEIGHT,
BANNER_HEIGHT_HOME,
BANNER_HEIGHT_EXTEND,
} from "@/constants/constants";
import { pathsEqual, url } from "@utils/url";
import { initWallpaperMode } from "@utils/wallpaper";
import { initTheme } from "@utils/theme";
import { initHue } from "@utils/hue";
import { initMarkdownActions } from "@/utils/markdown";
import { initFancybox, cleanupFancybox } from "@utils/fancybox";
import { loadAndInitTranslate } from "@utils/language";
import { setupParticleEffects } from "@/utils/particle";
// 初始化Swup
const setup = () => {
// Swup can update the body for each page visit, but it's after the page transition, causing a delay for banner height change, so use Swup hooks instead to change the height immediately when a link is clicked
window.swup.hooks.on('link:click', () => {
// 简化 navbar 处理逻辑
const bannerEnabled = !!document.getElementById('banner-wrapper')
if (bannerEnabled) {
const navbar = document.getElementById('navbar-wrapper')
if (navbar && document.body.classList.contains('is-home')) {
const threshold = window.innerHeight * (BANNER_HEIGHT / 100) - NAVBAR_HEIGHT
if (document.documentElement.scrollTop >= threshold) {
navbar.classList.add('navbar-hidden')
}
}
}
})
window.swup.hooks.on('content:replace', () => {
// Update main grid layout class name after page transition
const gridClassCarrier = document.getElementById("grid-class-carrier");
if (gridClassCarrier) {
const gridClass = gridClassCarrier.getAttribute("data-grid-class");
const mainGrid = document.getElementById("main-grid");
if (mainGrid && gridClass) {
const currentClasses = mainGrid.className.split(" ");
const newClasses = currentClasses.filter(c => !c.startsWith("lg:grid-cols-") && !c.startsWith("md:grid-cols-") && !c.startsWith("grid-cols-"));
const gridClasses = gridClass.split(" ");
mainGrid.className = [...newClasses, ...gridClasses].join(" ");
}
}
// Reinitialize semifull mode scroll detection after page transition
const navbar = document.getElementById('navbar')
if (navbar) {
const transparentMode = navbar.getAttribute('data-transparent-mode')
if (transparentMode === 'semifull') {
// Re-call the initialization function to rebind scroll events
if (typeof (window as any).initSemifullScrollDetection === 'function') {
(window as any).initSemifullScrollDetection()
}
}
}
})
window.swup.hooks.on('visit:start', (visit: {to: {url: string}}) => {
// Disable scroll protection when a link is clicked to prevent interference with page jump scrolling to top
if ((window as any).scrollProtectionManager && typeof (window as any).scrollProtectionManager.disable === 'function') {
(window as any).scrollProtectionManager.disable();
}
// Change banner height immediately when a link is clicked
const bodyElement = document.querySelector('body')
const isHomePage = pathsEqual(visit.to.url, url('/'))
if (isHomePage) {
bodyElement!.classList.add('is-home');
} else {
bodyElement!.classList.remove('is-home');
}
// Control banner text visibility based on page
const bannerTextOverlay = document.querySelector('.banner-text-overlay')
if (bannerTextOverlay) {
if (isHomePage) {
bannerTextOverlay.classList.remove('hidden')
} else {
bannerTextOverlay.classList.add('hidden')
}
}
// Control navbar transparency based on page
const navbar = document.getElementById('navbar')
if (navbar) {
navbar.setAttribute('data-is-home', isHomePage.toString())
const transparentMode = navbar.getAttribute('data-transparent-mode')
if (transparentMode === 'semifull') {
// Re-call the initialization function to rebind scroll events
if (typeof window.initSemifullScrollDetection === 'function') {
window.initSemifullScrollDetection()
}
}
}
// increase the page height during page transition to prevent the scrolling animation from jumping
const heightExtend = document.getElementById('page-height-extend')
if (heightExtend) {
heightExtend.classList.remove('hidden')
}
});
window.swup.hooks.on('visit:end', (_visit: {to: {url: string}}) => {
setTimeout(() => {
const heightExtend = document.getElementById('page-height-extend')
if (heightExtend) {
heightExtend.classList.add('hidden')
}
}, 300)
});
window.swup.hooks.on('page:view', () => {
// Scroll to top of the page after page transition
window.scrollTo({
top: 0,
behavior: 'smooth'
});
// Check if the current page is a post page, if so, trigger a custom event to initialize the comment system
setTimeout(() => {
if (document.getElementById('tcomment')) {
const pageLoadedEvent = new CustomEvent('twilight:page:loaded', {
detail: {
path: window.location.pathname,
timestamp: Date.now()
}
});
document.dispatchEvent(pageLoadedEvent);
}
}, 300);
// Initialize Fancybox
initFancybox();
});
initFancybox();
window.swup.hooks.on("content:replace", () => {
cleanupFancybox();
},
{ before: true },
)
}
if (window?.swup?.hooks) {
setup()
} else {
document.addEventListener('swup:enable', setup)
}
// 初始化 navbar 元素,用于控制 banner 隐藏
let navbar = document.getElementById('navbar-wrapper')
// 初始化返回顶部按钮元素,用于控制返回顶部按钮的显示与隐藏
let backToTopBtn = document.getElementById('back-to-top-btn');
// 节流函数
function throttle(func: (...args: any[]) => void, limit: number) {
let inThrottle: boolean;
return function(this: any, ...args: any[]) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// 滚动事件处理函数用于控制返回顶部按钮和banner隐藏
function scrollFunction() {
const scrollTop = document.documentElement.scrollTop;
const bannerHeight = window.innerHeight * (BANNER_HEIGHT / 100);
const bannerEnabled = !!document.getElementById('banner-wrapper')
// 批量处理DOM操作
requestAnimationFrame(() => {
if (backToTopBtn) {
if (scrollTop > bannerHeight) {
backToTopBtn.classList.remove('hide')
} else {
backToTopBtn.classList.add('hide')
}
}
if (bannerEnabled && navbar) {
const isHome = document.body.classList.contains('is-home');
const currentBannerHeight = isHome ? BANNER_HEIGHT_HOME : BANNER_HEIGHT;
const threshold = window.innerHeight * (currentBannerHeight / 100) - NAVBAR_HEIGHT;
if (scrollTop >= threshold) {
navbar.classList.add('navbar-hidden')
} else {
navbar.classList.remove('navbar-hidden')
}
}
});
}
// 使用节流优化滚动性能
window.onscroll = throttle(scrollFunction, 16); // 约60fps
// 监听窗口 resize 事件,用于更新 banner 高度相关的 CSS 变量
window.onresize = () => {
// calculate the --banner-height-extend, which needs to be a multiple of 4 to avoid blurry text
let offset = Math.floor(window.innerHeight * (BANNER_HEIGHT_EXTEND / 100));
offset = offset - offset % 4;
document.documentElement.style.setProperty('--banner-height-extend', `${offset}px`);
}
// Initialize theme
initTheme();
// Initialize hue
initHue();
// Initialize wallpaper mode
initWallpaperMode();
// 初始化 Markdown 相关操作以防止因 Swup 无刷新跳转的机制限制而导致操作失效
initMarkdownActions();
// Initialize translate.js
loadAndInitTranslate();
// Initialize particle effects
setupParticleEffects();
// Remove is-loading class and trigger initial-animation after page load
if (typeof window !== 'undefined') {
window.addEventListener('load', () => {
const overlay = document.getElementById('loading-overlay');
const enableBanner = document.body.classList.contains('enable-banner');
// 触发 LoadingOverlay 淡出
if (overlay) {
overlay.classList.add('fade-out');
}
// 等待 LoadingOverlay 淡出600ms
setTimeout(() => {
if (overlay) {
overlay.style.display = 'none';
}
if (enableBanner) {
// banner 开始恢复过渡:添加 banner-restoring 类(解除 CSS 中的 height: 0
document.documentElement.classList.add('banner-restoring');
// 等待 banner 恢复过渡完成600ms
setTimeout(() => {
// 触发各容器淡入动画
document.documentElement.classList.remove('is-loading');
document.documentElement.classList.add('show-initial-animation');
setTimeout(() => {
document.documentElement.classList.remove('show-initial-animation');
document.documentElement.classList.remove('banner-restoring');
}, 1200);
}, 600);
} else {
// 非 banner 模式,直接触发淡入
document.documentElement.classList.remove('is-loading');
document.documentElement.classList.add('show-initial-animation');
setTimeout(() => {
document.documentElement.classList.remove('show-initial-animation');
}, 1200);
}
}, 600);
});
}
</script>

248
src/layouts/grid.astro Normal file
View File

@@ -0,0 +1,248 @@
---
import type { MarkdownHeading } from "astro";
import { Icon } from "astro-icon/components";
import { siteConfig } from "@/config";
import {
BANNER_HEIGHT,
MAIN_PANEL_OVERLAPS_BANNER_HEIGHT,
} from "@constants/constants";
import { widgetManager } from "@utils/widget";
import IconifyLoader from "@components/common/iconifyLoader.astro";
import Navbar from "@components/navbar.astro";
import FullscreenWallpaper from "@components/fullscreenWallpaper.astro";
import Banner from "@components/banner.astro";
import Sidebar from "@components/sidebar.astro";
import Footer from "@components/footer.astro";
import TocButton from "@components/tocButton.astro";
import BackToTopButton from "@components/back2TopButton.astro";
import BaseLayout from "./base.astro";
import "@styles/grid.css";
/**
* Wallpaper
*/
interface Props {
title?: string;
banner?: string;
description?: string;
lang?: string;
setOGTypeArticle?: boolean;
postSlug?: string;
headings?: MarkdownHeading[];
}
const {
title,
banner,
description,
lang,
setOGTypeArticle,
postSlug,
headings = [],
} = Astro.props;
const hasBannerCredit = siteConfig.wallpaper.mode === "banner" && siteConfig.wallpaper.banner?.credit?.enable;
const hasBannerLink = !!siteConfig.wallpaper.banner?.credit?.url;
// 检查是否为首页
const isHomePage = Astro.url.pathname === "/" || Astro.url.pathname === "";
// 计算主内容区域位置
const mainPanelTop = siteConfig.wallpaper.mode === "banner"
? `calc(${BANNER_HEIGHT}vh - ${MAIN_PANEL_OVERLAPS_BANNER_HEIGHT}rem)`
: "5.5rem";
// 当banner被禁用时主内容区域应该始终从顶栏下面开始
const finalMainPanelTop = siteConfig.wallpaper.mode === "banner" ? mainPanelTop : "5.5rem";
// 检查是否应该启用半透明效果
const shouldEnableTransparency = siteConfig.wallpaper.mode === "banner" || siteConfig.wallpaper.mode === "fullscreen";
// 为组件添加半透明效果的CSS类
const transparentClass = shouldEnableTransparency ? "wallpaper-transparent" : "";
/**
* Sidebar
*/
// 检查侧边栏是否启用,动态调整网格布局
const {
hasLeftSidebar,
hasRightSidebar,
hasAnyComponents,
gridCols,
leftSidebarClass,
rightSidebarClass,
mainContentClass,
mobileFooterClass,
middleSidebarClass,
} = widgetManager.getGridLayout(headings);
---
<BaseLayout
title={title}
banner={banner}
description={description}
lang={lang}
setOGTypeArticle={setOGTypeArticle}
postSlug={postSlug}
>
<!-- 全局图标加载器 -->
<IconifyLoader />
<!-- Navbar -->
<slot slot="head" name="head" />
<div
id="top-row"
class="z-50 pointer-events-none relative transition-all duration-600 max-w-(--page-width) px-0 md:px-4 mx-auto"
class:list={[""]}
>
<div
id="navbar-wrapper"
class="pointer-events-auto sticky top-0 transition-all"
>
<Navbar />
</div>
</div>
<!-- 全屏壁纸 - 总是渲染但通过CSS控制可见性 -->
<FullscreenWallpaper
config={siteConfig.wallpaper}
class={siteConfig.wallpaper.mode === "fullscreen" ? "" : "hidden"}
/>
<!-- 为全屏壁纸模式添加body类 -->
{
shouldEnableTransparency && (
<script>
document.body.classList.add('wallpaper-transparent');
</script>
)
}
<!-- Banner - 总是渲染但通过CSS控制可见性 -->
<Banner
config={siteConfig.wallpaper}
isHomePage={isHomePage}
class={siteConfig.wallpaper.mode === "banner" ? "" : "hidden"}
/>
<!-- Main content -->
<div
class={`absolute w-full z-30 pointer-events-none transition-all duration-600 ${siteConfig.wallpaper.mode !== "banner" ? "no-banner-layout" : ""} ${transparentClass}`}
style={`top: ${finalMainPanelTop}`}
>
<div
class="relative max-w-(--page-width) mx-auto pointer-events-auto"
>
<div
id="main-grid"
class={`transition-all duration-600 w-full left-0 right-0 grid ${gridCols} grid-rows-[auto] mx-auto gap-4 px-0 md:px-4 ${!hasAnyComponents ? "mobile-no-sidebar" : ""} ${(hasLeftSidebar || hasRightSidebar) ? "mobile-both-sidebar" : ""}`}
>
<!-- Banner image credit -->
{
hasBannerCredit && (
<a
href={siteConfig.wallpaper.banner?.credit?.url}
id="banner-credit"
target="_blank"
rel="noopener"
aria-label="Visit image source"
class:list={[
"group onload-animation-up transition-all absolute flex justify-center items-center rounded-full " +
"px-3 right-4 -top-13 bg-black/60 hover:bg-black/70 h-9",
{
"hover:pr-9 active:bg-black/80":
hasBannerLink,
},
]}
>
<Icon
class="text-white/75 text-[1.25rem] mr-1"
name="material-symbols:copyright-outline-rounded"
/>
<div class="text-white/75 text-xs">
{siteConfig.wallpaper.banner?.credit?.text}
</div>
<Icon
class:list={[
"transition absolute text-[oklch(0.75_0.14_var(--hue))] right-4 text-[0.75rem] opacity-0",
{
"group-hover:opacity-100":
hasBannerLink,
},
]}
name="fa6-solid:arrow-up-right-from-square"
/>
</a>
)
}
<div class="contents lg:contents md:flex md:flex-col md:col-start-1 md:row-start-1 md:gap-4">
{
hasLeftSidebar ? (
<Sidebar
id="left-sidebar"
side="left"
class={`${leftSidebarClass} ${transparentClass} onload-animation-up`}
headings={headings}
/>
) : (
<div id="left-sidebar" class="hidden"></div>
)
}
{
hasRightSidebar ? (
<Sidebar
id="right-sidebar"
side="right"
class={`${rightSidebarClass} ${transparentClass} onload-animation-up`}
headings={headings}
/>
) : (
<div id="right-sidebar" class="hidden"></div>
)
}
</div>
<main
id="swup-container"
class={`${mainContentClass} transition-swup-fade`}
>
<div id="grid-class-carrier" data-grid-class={gridCols} class="hidden"></div>
<div
id="content-wrapper"
class="onload-animation-up transition-leaving"
>
<slot />
<div
class="footer col-span-2 onload-animation-up hidden lg:block transition-swup-fade"
>
<Footer />
</div>
</div>
</main>
<div class={middleSidebarClass}>
<Sidebar
id="mobile-sidebar"
side="middle"
class={`${transparentClass} onload-animation-up`}
headings={headings}
/>
</div>
<div class={mobileFooterClass}>
<Footer />
</div>
</div>
</div>
</div>
<TocButton />
<BackToTopButton />
</BaseLayout>