File size: 22,780 Bytes
96dd062
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
---
import Live2DWidget from "@components/features/Live2DWidget.astro";
import OverlayWallpaper from "@components/features/OverlayWallpaper.astro";
import SpineModel from "@components/features/SpineModel.astro";
import Footer from "@components/layout/Footer.astro";
import Navbar from "@components/layout/Navbar.astro";
import SideBar from "@components/layout/SideBar.astro";
import type { MarkdownHeading } from "astro";
import { Icon } from "astro-icon/components";
import ImageWrapper from "@/components/common/ImageWrapper.astro";
import FloatingControls from "@/components/controls/FloatingControls.astro";
import TypewriterText from "@/components/features/TypewriterText.astro";
import {
	backgroundWallpaper,
	live2dModelConfig,
	sidebarLayoutConfig,
	siteConfig,
} from "@/config";
import {
	BANNER_HEIGHT,
	BANNER_HEIGHT_EXTEND,
	MAIN_PANEL_OVERLAPS_BANNER_HEIGHT,
} from "@/constants/constants";
import { getImageQuality } from "@/utils/image-utils";
import { getBackgroundImages, isHomePage } from "@/utils/layout-utils";
import {
	generateGridClasses,
	generateMainContentClasses,
	generateRightSidebarClasses,
	generateSidebarClasses,
	getResponsiveSidebarConfig,
	type ResponsiveSidebarConfig,
} from "@/utils/responsive-utils";
import Layout from "./Layout.astro";

interface Props {
	title?: string;
	banner?: string;
	description?: string;
	lang?: string;
	setOGTypeArticle?: boolean;
	postSlug?: string;
	headings?: MarkdownHeading[];
}

const backgroundImages = getBackgroundImages();

const {
	title,
	banner,
	description,
	lang,
	setOGTypeArticle,
	postSlug,
	headings = [],
} = Astro.props;

// 检查背景壁纸模式和是否允许切换
const isBannerMode = backgroundWallpaper.mode === "banner";
const isOverlayMode = backgroundWallpaper.mode === "overlay";
const isBackgroundEnabled = backgroundWallpaper.mode !== "none";
const isWallpaperSwitchable = backgroundWallpaper.switchable ?? true;

// 检查是否启用波浪动画效果
const wavesConfig = backgroundWallpaper.banner?.waves?.enable;
// 处理分设备控制的波浪动画
const wavesEnabledOnDesktop =
	typeof wavesConfig === "object" ? wavesConfig.desktop : wavesConfig;
const wavesEnabledOnMobile =
	typeof wavesConfig === "object" ? wavesConfig.mobile : wavesConfig;

// 处理分设备控制的横幅信贷显示
const creditEnableConfig = backgroundWallpaper.banner?.credit?.enable;
const creditEnabledOnDesktop =
	typeof creditEnableConfig === "object"
		? creditEnableConfig.desktop
		: creditEnableConfig;
const creditEnabledOnMobile =
	typeof creditEnableConfig === "object"
		? creditEnableConfig.mobile
		: creditEnableConfig;

const hasBannerCredit =
	isBannerMode && isBackgroundEnabled && creditEnableConfig;

// 处理分设备控制的横幅信贷文本和链接
const creditTextConfig = backgroundWallpaper.banner?.credit?.text;
const creditTextDesktop =
	typeof creditTextConfig === "object"
		? creditTextConfig.desktop
		: creditTextConfig;
const creditTextMobile =
	typeof creditTextConfig === "object"
		? creditTextConfig.mobile
		: creditTextConfig;

const creditUrlConfig = backgroundWallpaper.banner?.credit?.url;
const creditUrlDesktop =
	typeof creditUrlConfig === "object"
		? creditUrlConfig.desktop
		: creditUrlConfig;
const creditUrlMobile =
	typeof creditUrlConfig === "object"
		? creditUrlConfig.mobile
		: creditUrlConfig;

// 检查是否为首页
const isHomePageCheck = isHomePage(Astro.url.pathname);

// 检查是否在文章详情页
const isPostPage = !!postSlug;

// 随机选择副标题(当打字机关闭且为数组时)
const getRandomSubtitle = () => {
	const subtitle = backgroundWallpaper.banner?.homeText?.subtitle;
	if (Array.isArray(subtitle)) {
		const randomIndex = Math.floor(Math.random() * subtitle.length);
		return subtitle[randomIndex];
	}
	return subtitle;
};
const randomSubtitle = getRandomSubtitle();

// 主页横幅文本只在首页且全局开关为 true 时显示,不区分设备
const homeTextEnable = backgroundWallpaper.banner?.homeText?.enable;
const showHomeText = isBannerMode && !!homeTextEnable && isHomePageCheck;
// 手机端非首页不显示banner的CSS类
const mobileNonHomeBannerClass = !isHomePageCheck ? "mobile-hide-banner" : "";

// 计算主内容区域位置,考虑手机端非首页时banner被隐藏
const mainPanelTop =
	isBannerMode && isBackgroundEnabled
		? `calc(${BANNER_HEIGHT}vh - ${MAIN_PANEL_OVERLAPS_BANNER_HEIGHT}rem)`
		: "5.5rem";

// 当banner模式被禁用时,主内容区域应该始终从顶栏下面开始
// 非首页在小于1024px时会通过 mobile-main-no-banner CSS类覆盖top值为5.5rem
const finalMainPanelTop =
	isBannerMode && isBackgroundEnabled ? mainPanelTop : "5.5rem";

// 获取响应式侧边栏配置
const sidebarConfig = getResponsiveSidebarConfig();

// 在文章页面且启用了showRightSidebarOnPostPage时,要确保有右侧组件并参与网格计算
const shouldShowRightSidebarOnPostPage: boolean =
	isPostPage &&
	sidebarLayoutConfig.position === "left" &&
	!!sidebarLayoutConfig.showRightSidebarOnPostPage;

const effectiveIsBothSidebars: boolean =
	sidebarConfig.isBothSidebars || shouldShowRightSidebarOnPostPage;
const effectiveHasRightComponents: boolean =
	sidebarConfig.hasRightComponents ||
	(shouldShowRightSidebarOnPostPage &&
		sidebarLayoutConfig.rightComponents.some((comp) => comp.enable));

// 使用effective值重新生成网格类
const updatedGridConfig: ResponsiveSidebarConfig = {
	...sidebarConfig,
	isBothSidebars: effectiveIsBothSidebars,
	hasRightComponents: effectiveHasRightComponents,
};

const { gridCols } = generateGridClasses(updatedGridConfig);
const sidebarClass = generateSidebarClasses();
const rightSidebarClass = effectiveIsBothSidebars
	? generateRightSidebarClasses()
	: "";
const mainContentClass = generateMainContentClasses(updatedGridConfig);

const footerClass = [
	"footer",
	"col-span-1",
	"md:col-span-2",
	"onload-animation",
];

if (
	updatedGridConfig.isBothSidebars &&
	updatedGridConfig.hasLeftComponents &&
	updatedGridConfig.hasRightComponents
) {
	footerClass.push("xl:col-start-2 xl:col-span-1");
} else if (
	updatedGridConfig.hasLeftComponents &&
	!updatedGridConfig.hasRightComponents
) {
	footerClass.push("xl:col-start-2 xl:col-span-1");
} else if (
	!updatedGridConfig.hasLeftComponents &&
	updatedGridConfig.hasRightComponents
) {
	footerClass.push("xl:col-start-1 xl:col-span-1");
} else {
	footerClass.push("xl:col-start-1 xl:col-span-1");
}

const footerClassName = footerClass.join(" ");

// 检查是否应该启用半透明效果
const shouldEnableTransparency = isOverlayMode && isBackgroundEnabled;

// 为组件添加半透明效果的CSS类
const transparentClass = shouldEnableTransparency
	? "wallpaper-transparent"
	: "";

const navbarWidthFull = siteConfig.navbar.widthFull ?? false;

// 获取图片质量配置
const configQuality = getImageQuality();
const mobileQuality = Math.round(configQuality * 0.9);
---

<Layout title={title} banner={banner} description={description} lang={lang} setOGTypeArticle={setOGTypeArticle} postSlug={postSlug}>





<!-- 全屏透明覆盖壁纸 - 如果允许切换则始终渲染,否则只渲染当前模式 -->

{(isWallpaperSwitchable || isOverlayMode) && (

	<OverlayWallpaper config={{

		src: {

			desktop: typeof backgroundImages.desktop === 'string' ? backgroundImages.desktop : '',

			mobile: typeof backgroundImages.mobile === 'string' ? backgroundImages.mobile : ''

		},

		zIndex: backgroundWallpaper.overlay?.zIndex,

		opacity: backgroundWallpaper.overlay?.opacity,

		blur: backgroundWallpaper.overlay?.blur,

	}} 

	className={isWallpaperSwitchable && !isOverlayMode ? "hidden opacity-0" : undefined} />

)}



<!-- 为全屏壁纸模式添加body类 -->

{shouldEnableTransparency && (

	<script>

		document.body.classList.add('wallpaper-transparent');

	</script>

)}



<!-- Navbar -->

<slot slot="head" name="head"></slot>

<div id="top-row" class="z-50 pointer-events-none relative transition-all duration-700 mx-auto" class:list={[navbarWidthFull ? "" : "max-w-(--page-width) px-0 md:px-4"]}>

    <div id="navbar-wrapper" class="pointer-events-auto sticky top-0 transition-all">

        <Navbar></Navbar>

    </div>

</div>



<!-- Banner - 如果允许切换则始终渲染,否则只渲染当前模式 -->

{(isWallpaperSwitchable || isBannerMode) && (

<div id="banner-wrapper" class={`absolute z-10 w-full transition duration-700 overflow-hidden ${mobileNonHomeBannerClass}`} style={isWallpaperSwitchable ? `top: -${BANNER_HEIGHT_EXTEND}vh; display: none;` : `top: -${BANNER_HEIGHT_EXTEND}vh`}>

    <!-- 背景图片显示 -->

    <div class="relative h-full w-full">

        <!-- Mobile: use mobile-specific image -->

        <ImageWrapper

            alt="Mobile background image of the blog"

            class:list={["block lg:hidden object-cover h-full w-full transition duration-700 opacity-100"]}

            src={typeof backgroundImages.mobile === 'string' ? backgroundImages.mobile : (typeof backgroundImages.desktop === 'string' ? backgroundImages.desktop : '')}

            position={backgroundWallpaper.banner?.position}

            widths={[640, 750, 1080]}

            sizes="100vw"

            loading="eager"

            fetchpriority="high"

            quality={mobileQuality}

        />

        <!-- Desktop: use desktop-specific image -->

        <ImageWrapper

            id="banner"

            alt="Desktop background image of the blog"

            class:list={["hidden lg:block object-cover h-full transition duration-700 opacity-100"]}

            src={typeof backgroundImages.desktop === 'string' ? backgroundImages.desktop : (typeof backgroundImages.mobile === 'string' ? backgroundImages.mobile : '')}

            position={backgroundWallpaper.banner?.position}

            widths={[1280, 1920, 2560]}

            sizes="100vw"

            loading="eager"

            fetchpriority="high"

            quality={configQuality}

        />

    </div>

    

    <!-- Home page text overlay - 始终渲染以便切换模式时控制显示 -->

    {(isWallpaperSwitchable || showHomeText) && homeTextEnable && (

        <div class={`banner-text-overlay absolute inset-0 z-20 flex items-center justify-center ${!showHomeText ? 'hidden' : ''}`}>

            <div class="w-4/5 lg:w-3/4 text-center mb-0">

                <div class="flex flex-col">

                    {backgroundWallpaper.banner?.homeText?.title && (

                        <h1 class="banner-title font-bold text-white mb-2 lg:mb-4 leading-tight" style={{ fontSize: backgroundWallpaper.banner.homeText.titleSize || "3rem" }}>

                            {backgroundWallpaper.banner.homeText.title}

                        </h1>

                    )}

                    {backgroundWallpaper.banner?.homeText?.subtitle && (

                        <h2 id="banner-subtitle" class="banner-subtitle text-white/90 leading-snug" style={{ fontSize: backgroundWallpaper.banner.homeText.subtitleSize || "1.5rem" }} data-subtitles={JSON.stringify(backgroundWallpaper.banner.homeText.subtitle)}>

                            {backgroundWallpaper.banner.homeText.typewriter?.enable ? (

                                <TypewriterText 

                                    text={backgroundWallpaper.banner.homeText.subtitle}

                                    speed={backgroundWallpaper.banner.homeText.typewriter.speed}

                                    deleteSpeed={backgroundWallpaper.banner.homeText.typewriter.deleteSpeed}

                                    pauseTime={backgroundWallpaper.banner.homeText.typewriter.pauseTime}

                                />

                            ) : (

                                randomSubtitle

                            )}

                        </h2>

                    )}

                </div>

            </div>

        </div>

    )}

    

    <script is:inline>

        function setRandomSubtitle() {

            const subtitleElement = document.getElementById('banner-subtitle');

            if (!subtitleElement) return;

            

            const subtitlesData = subtitleElement.dataset.subtitles;

            if (!subtitlesData) return;

            

            try {

                const subtitles = JSON.parse(subtitlesData);

                // Only randomize if it's an array and typewriter is NOT enabled (check for typewriter class)

                if (Array.isArray(subtitles) && subtitles.length > 0 && !subtitleElement.querySelector('.typewriter')) {

                    // Use a global variable to persist the subtitle across Swup navigations

                    // This variable will be reset on full page reload (F5), meeting the requirement

                    if (!window.fireflyCachedSubtitle) {

                        const randomIndex = Math.floor(Math.random() * subtitles.length);

                        window.fireflyCachedSubtitle = subtitles[randomIndex];

                    }

                    subtitleElement.textContent = window.fireflyCachedSubtitle;

                }

            } catch (e) {

                console.error("Failed to parse subtitles", e);

            }

        }



        // Run on initial load

        setRandomSubtitle();

    </script>

    

    <!-- Water waves effect - 始终渲染以便动态切换 -->

    {(wavesEnabledOnDesktop || wavesEnabledOnMobile) ? (

    <div class={`waves absolute -bottom-px h-[10vh] max-h-37.5 min-h-12.5 w-full md:h-[15vh] 

        ${!wavesEnabledOnMobile ? 'hidden' : ''} ${!wavesEnabledOnDesktop ? 'md:hidden' : ''} ${wavesEnabledOnDesktop ? 'md:block' : ''}`} 

        id="header-waves" 

        style="transform: translateZ(0); will-change: fill;">

        <svg

            class="waves"

            xmlns="http://www.w3.org/2000/svg"

            xmlns:xlink="http://www.w3.org/1999/xlink"

            viewBox="0 24 150 28"

            preserveAspectRatio="none"

            shape-rendering="geometricPrecision"

        >

            <defs>

                <path

                    id="gentle-wave"

                    d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v48h-352z"

                >

                </path>

            </defs>

            <g class="parallax">

                <use

                    xlink:href="#gentle-wave"

                    x="48"

                    y="0"

                    class="opacity-25 fill-(--page-bg)"

                ></use>

                <use

                    xlink:href="#gentle-wave"

                    x="48"

                    y="3"

                    class="opacity-50 fill-(--page-bg)"

                ></use>

                <use

                    xlink:href="#gentle-wave"

                    x="48"

                    y="5"

                    class="opacity-65 fill-(--page-bg)"

                ></use>

                <use

                    xlink:href="#gentle-wave"

                    x="48"

                    y="7"

                    class=" opacity-75 fill-(--page-bg)"

                ></use>

            </g>

        </svg>

    </div>

    ) : null}

</div>

)}



<!-- Main content -->

<div class={`absolute w-full z-30 pointer-events-none ${mobileNonHomeBannerClass ? 'mobile-main-no-banner' : ''} ${!(isBannerMode && isBackgroundEnabled) ? 'no-banner-layout' : ''} ${transparentClass}`} style={`top: ${finalMainPanelTop}`}>

    <!-- The pointer-events-none here prevent blocking the click event of the TOC -->

    <div class="relative max-w-(--page-width) mx-auto pointer-events-auto">

        <div id="main-grid"

          class={`transition duration-700 w-full left-0 right-0 grid ${gridCols} grid-rows-[auto_1fr_auto] lg:grid-rows-[auto] mx-auto gap-4 px-2 md:px-4 ${!sidebarConfig.mobileShowSidebar ? 'mobile-no-sidebar' : ''}`}

          data-sidebar-position={sidebarLayoutConfig.position}

          data-show-right-sidebar-on-post={sidebarLayoutConfig.showRightSidebarOnPostPage ? "true" : "false"}

        >

            <!-- Background image credit - Desktop -->

            {hasBannerCredit && creditEnabledOnDesktop && <a 

                href={creditUrlDesktop || "#"} 

                id="banner-credit-desktop" 

                target={creditUrlDesktop ? "_blank" : "_self"}

                rel={creditUrlDesktop ? "noopener" : ""}

                aria-label="Visit image source"

                class:list={[

                    "group onload-animation transition-all absolute justify-center items-center rounded-full " +

                    "px-3 right-4 -top-13 bg-black/60 hover:bg-black/70 h-9 hidden md:flex", 

                    {"hover:pr-9 active:bg-black/80": creditUrlDesktop}

                ]}

            >

                <Icon class="text-white/75 text-[1.25rem] mr-1" name="material-symbols:copyright-outline-rounded" ></Icon>

                <div class="text-white/75 text-xs">{creditTextDesktop}</div>

                {creditUrlDesktop && <Icon class:list={["transition absolute text-[oklch(0.75_0.14_var(--hue))] right-4 text-[0.75rem] opacity-0 group-hover:opacity-100"]}

                      name="fa7-solid:arrow-up-right-from-square">

                </Icon>}

            </a>}



            <!-- Background image credit - Mobile -->

            {hasBannerCredit && creditEnabledOnMobile && <a 

                href={creditUrlMobile || "#"} 

                id="banner-credit-mobile" 

                target={creditUrlMobile ? "_blank" : "_self"}

                rel={creditUrlMobile ? "noopener" : ""}

                aria-label="Visit image source"

                class:list={[

                    "group onload-animation transition-all absolute justify-center items-center rounded-full " +

                    "px-3 right-4 -top-13 bg-black/60 hover:bg-black/70 h-9 flex md:hidden", 

                    {"hover:pr-9 active:bg-black/80": creditUrlMobile}

                ]}

            >

                <Icon class="text-white/75 text-[1.25rem] mr-1" name="material-symbols:copyright-outline-rounded" ></Icon>

                <div class="text-white/75 text-xs">{creditTextMobile}</div>

                {creditUrlMobile && <Icon class:list={["transition absolute text-[oklch(0.75_0.14_var(--hue))] right-4 text-[0.75rem] opacity-0 group-hover:opacity-100"]}

                      name="fa7-solid:arrow-up-right-from-square">

                </Icon>}

            </a>}





            {/* 渲染侧边栏 - 769px及以上显示 */}

            <div id="left-sidebar-wrapper" class="contents" style="contain: layout style paint;">

                {sidebarConfig.hasLeftComponents && (

                    <SideBar

                        side={sidebarConfig.isBothSidebars ? "left" : undefined}

                        class={`${sidebarClass} ${transparentClass}`}

                        headings={headings}

                    />

                )}

            </div>

                    

            {/* 主内容区 - 始终渲染,确保 Swup 容器存在 */}

            <main id="swup-container" class={`${mainContentClass} transition-main`}>

                {/* 携带网格布局类名,用于JS更新父容器 */}

                <div id="grid-class-carrier" data-grid-class={gridCols} class="hidden"></div>

                {/* 备用 h1 标题 - 当主页横幅文本未启用时,提供隐藏的主标题 */}

                {isHomePageCheck && !showHomeText && (

                        <h1 class="sr-only">{siteConfig.title}</h1>

                )}

                <div id="content-wrapper" class="onload-animation transition-leaving">

                    <slot></slot>



                </div>

            </main>

            

            {/* 右侧边栏 - 仅双侧边栏模式 */}

            {/*

                如果全局配置为双侧栏(position: both),则使用静态容器(不被swup替换),避免闪烁。

                如果全局配置为单侧栏(position: left),则使用动态容器(被swup替换),以便在文章页显示右侧栏。

                注意:为了满足swup要求所有containers必须存在,我们在两种模式下都必须渲染 #right-sidebar-dynamic。

                右侧边栏在1280px以下隐藏(CSS处理)。

            */}

            {sidebarLayoutConfig.position === "both" ? (

                <>

                    <div id="right-sidebar-static" class="contents">

                        {effectiveIsBothSidebars && effectiveHasRightComponents && (

                            <SideBar side="right" class={`${rightSidebarClass} ${transparentClass}`} headings={headings}></SideBar>

                        )}

                    </div>

                    <div id="right-sidebar-dynamic" class="hidden transition-swup-fade"></div>

                </>

            ) : (

                <div id="right-sidebar-dynamic" class="contents transition-swup-fade">

                    {effectiveIsBothSidebars && effectiveHasRightComponents && (

                        <SideBar side="right" class={`${rightSidebarClass} ${transparentClass}`} headings={headings}></SideBar>

                    )}

                </div>

            )}



            {/* 移动端底部组件 - 768px及以下显示 */}

            {sidebarLayoutConfig.mobileBottomComponents && sidebarLayoutConfig.mobileBottomComponents.length > 0 && (

                <div id="mobile-bottom-sidebar" class="col-span-1 block md:hidden mt-4">

                    <SideBar side="bottom" class={`${transparentClass}`} headings={headings}></SideBar>

                </div>

            )}



            <div class={footerClassName}>

                <Footer></Footer>

            </div>

        </div>



        <SpineModel></SpineModel>

        {live2dModelConfig.enable && <Live2DWidget config={live2dModelConfig} />}

    </div>

</div>

<FloatingControls headings={headings} />


</Layout>

<style is:global>

  /* 右侧栏平滑过渡 */

  #right-sidebar {

    transition: opacity 0.35s ease-in-out,

                transform 0.35s ease-in-out;

  }





  @keyframes swupFadeOut {

    from {

      opacity: 1;

    }

    to {

      opacity: 0.95;

    }

  }



  /* 横幅文字阴影 - 更柔和的扩散效果 */

  .banner-title {

    text-shadow: 0 4px 24px rgba(0, 0, 0, 0.6);

  }



  .banner-subtitle {

    text-shadow: 0 2px 16px rgba(0, 0, 0, 0.6);

  }

</style>