first commit
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
|
||||
const x = ref(0);
|
||||
const y = ref(0);
|
||||
const visible = ref(false);
|
||||
|
||||
function handleMouseMove(e: MouseEvent) {
|
||||
x.value = e.clientX;
|
||||
y.value = e.clientY;
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseleave", handleMouseLeave);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseleave", handleMouseLeave);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="wi-cursor-glow"
|
||||
:class="{ 'wi-cursor-glow--visible': visible }"
|
||||
:style="{ transform: `translate(calc(${x}px - 50%), calc(${y}px - 50%))` }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wi-cursor-glow {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(212, 118, 78, 0.06) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.wi-cursor-glow--visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
html.dark .wi-cursor-glow {
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(232, 149, 95, 0.04) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,178 @@
|
||||
---
|
||||
---
|
||||
|
||||
<section
|
||||
class="wi-section"
|
||||
th:if="${theme.config?.home?.home_featured_enabled != false}"
|
||||
th:with="featuredPosts = ${postFinder.list({page: 1, size: theme.config?.home?.home_featured_count ?: 3})}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_featured_title ?: '精选'}">精选</h2>
|
||||
</div>
|
||||
<div class="wi-featured">
|
||||
<a
|
||||
th:each="post,iterStat : ${featuredPosts.items}"
|
||||
th:href="@{${post.status.permalink}}"
|
||||
class="wi-featured__card"
|
||||
th:classappend="${iterStat.index == 0} ? 'wi-featured__card--hero' : ''"
|
||||
>
|
||||
<div
|
||||
class="wi-featured__cover"
|
||||
th:if="${!#strings.isEmpty(post.spec.cover)}"
|
||||
>
|
||||
<img th:src="${post.spec.cover}" th:alt="${post.spec.title}" class="wi-featured__image" />
|
||||
</div>
|
||||
<div class="wi-featured__body">
|
||||
<time
|
||||
class="wi-featured__date"
|
||||
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
|
||||
></time>
|
||||
<h3
|
||||
class="wi-featured__title"
|
||||
th:text="${post.spec.title}"
|
||||
></h3>
|
||||
<p
|
||||
class="wi-featured__excerpt"
|
||||
th:if="${post.status.excerpt}"
|
||||
th:text="${post.status.excerpt}"
|
||||
></p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.wi-section__header {
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.wi-section__title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.wi-section__title::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wi-featured {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-featured__card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background: var(--bg-raised);
|
||||
box-shadow: var(--shadow-sm);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition:
|
||||
transform var(--duration-normal) var(--ease-out-expo),
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-featured__card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.wi-featured__card--hero {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.wi-featured__card--hero .wi-featured__cover {
|
||||
aspect-ratio: 21 / 9;
|
||||
}
|
||||
|
||||
.wi-featured__card--hero .wi-featured__title {
|
||||
font-size: var(--text-2xl);
|
||||
}
|
||||
|
||||
.wi-featured__cover {
|
||||
aspect-ratio: 16 / 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wi-featured__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.wi-featured__card:hover .wi-featured__image {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.wi-featured__body {
|
||||
padding: var(--space-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.wi-featured__date {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wi-featured__title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-xl);
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-featured__card:hover .wi-featured__title {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-featured__excerpt {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-relaxed);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-featured {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.wi-featured__card--hero .wi-featured__cover {
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
.wi-featured__card--hero .wi-featured__title {
|
||||
font-size: var(--text-xl);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+144
-12
@@ -1,33 +1,165 @@
|
||||
---
|
||||
// TODO: Only execute when compiling Astro templates, so it will not be dynamically updated
|
||||
const THEME_VERSION = "1.0.0";
|
||||
const today = new Date();
|
||||
---
|
||||
|
||||
<footer>
|
||||
<footer th:with="showPowered=${theme.config?.footer?.footer_show_powered != false}, showTheme=${theme.config?.footer?.footer_show_theme != false}, hideRight=${showPowered == false and showTheme == false}">
|
||||
<div class="footer-inner">
|
||||
<span>
|
||||
© {today.getFullYear()}
|
||||
<th:block th:text="${site.title}"></th:block>.
|
||||
</span>
|
||||
<div class="footer-bottom" th:classappend="${hideRight} ? 'footer-bottom--center' : ''">
|
||||
<div class="footer-bottom__left">
|
||||
<span class="footer-copyright" th:if="${theme.config?.footer?.footer_copyright}" th:text="${theme.config?.footer?.footer_copyright}"></span>
|
||||
<span class="footer-copyright" th:unless="${theme.config?.footer?.footer_copyright}">
|
||||
© {today.getFullYear()}
|
||||
<th:block th:text="${site.title}"></th:block>
|
||||
</span>
|
||||
<span class="footer-icp" th:if="${theme.config?.footer?.footer_icp}">
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer" th:text="${theme.config?.footer?.footer_icp}"></a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="footer-socials" th:if="${theme.config?.footer?.footer_socials}">
|
||||
<a
|
||||
th:each="social : ${theme.config?.footer?.footer_socials}"
|
||||
th:href="${social.url}"
|
||||
th:title="${social.platform}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<i th:if="${social.icon}" th:class="${social.icon}"></i>
|
||||
<span th:if="${#strings.isEmpty(social.icon)}" th:text="${social.platform}"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="footer-bottom__right" th:if="${showPowered or showTheme}">
|
||||
<a th:if="${showPowered}" href="https://halo.run" target="_blank" rel="noopener noreferrer">Powered by Halo</a>
|
||||
<span class="footer-separator" th:if="${showPowered and showTheme}">·</span>
|
||||
<span th:if="${showTheme}">WarmIsland v{THEME_VERSION}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div th:if="${theme.config?.footer?.footer_custom_html}" th:utext="${theme.config?.footer?.footer_custom_html}" class="footer-custom"></div>
|
||||
<halo:footer />
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
footer {
|
||||
border-top: 1px solid var(--rule);
|
||||
background: var(--bg);
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--bg),
|
||||
color-mix(in srgb, var(--bg) 97%, var(--accent) 3%)
|
||||
);
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0 1.25rem;
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
width: 820px;
|
||||
max-width: calc(100% - 2.5rem);
|
||||
margin: 0 auto;
|
||||
padding: 1.5rem 0 2.5rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--ink-3);
|
||||
font-size: 0.88rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-bottom--center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.footer-bottom--center .footer-bottom__left {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.footer-bottom__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.footer-copyright {
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.footer-icp a {
|
||||
color: var(--ink-3);
|
||||
text-decoration: none;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.footer-icp a:hover {
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.footer-socials {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.footer-socials a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--ink-3);
|
||||
text-decoration: none;
|
||||
font-size: 1.05rem;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.footer-socials a:hover {
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.footer-bottom__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.footer-bottom__right a {
|
||||
color: var(--ink-3);
|
||||
text-decoration: none;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.footer-bottom__right a:hover {
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.footer-separator {
|
||||
color: var(--ink-3);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.footer-custom {
|
||||
margin-left: auto;
|
||||
font-size: 0.85rem;
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.footer-bottom {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-bottom__left {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.footer-bottom__right {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
---
|
||||
<section
|
||||
class="wi-section wi-friends"
|
||||
th:if="${theme.config?.home?.home_friends_enabled == true and pluginFinder.available('PluginLinks')}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_friends_title ?: '友链'}">友链</h2>
|
||||
<span class="wi-section__accent"></span>
|
||||
</div>
|
||||
|
||||
<div class="wi-friends__grid">
|
||||
<a
|
||||
th:each="link : ${linkFinder.list()}"
|
||||
th:href="${link.spec.url}"
|
||||
th:title="${link.spec.displayName}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="wi-friends__card"
|
||||
>
|
||||
<img
|
||||
th:if="${link.spec.logo}"
|
||||
th:src="${link.spec.logo}"
|
||||
th:alt="${link.spec.displayName}"
|
||||
class="wi-friends__avatar"
|
||||
/>
|
||||
<div
|
||||
th:unless="${link.spec.logo}"
|
||||
class="wi-friends__avatar wi-friends__avatar--placeholder"
|
||||
>
|
||||
<span th:text="${#strings.substring(link.spec.displayName, 0, 1)}"></span>
|
||||
</div>
|
||||
<div class="wi-friends__info">
|
||||
<span class="wi-friends__name" th:text="${link.spec.displayName}"></span>
|
||||
<span
|
||||
class="wi-friends__desc"
|
||||
th:if="${link.spec.description}"
|
||||
th:text="${link.spec.description}"
|
||||
></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.wi-friends__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-friends__card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-lg);
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition:
|
||||
transform var(--duration-normal) var(--ease-out-expo),
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
border-color var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-friends__card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
border-color: var(--accent);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.wi-friends__avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 9999px;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wi-friends__avatar--placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--accent-bg);
|
||||
color: var(--accent);
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.wi-friends__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wi-friends__name {
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wi-friends__desc {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
line-height: var(--leading-snug);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.wi-friends__grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,172 +0,0 @@
|
||||
---
|
||||
import ThemeSwitcher from "./ThemeSwitcher.vue";
|
||||
import UserButton from "./UserButton.vue";
|
||||
---
|
||||
|
||||
<header>
|
||||
<div class="header-inner">
|
||||
<a class="brand" href="/" th:text="${site.title}"></a>
|
||||
<nav
|
||||
th:with="menu = ${menuFinder.getPrimary()}"
|
||||
aria-label="Main navigation"
|
||||
>
|
||||
<a
|
||||
th:each="menuItem : ${menu.menuItems}"
|
||||
th:href="@{${menuItem.status.href}}"
|
||||
th:target="${menuItem.spec.target?.value}"
|
||||
th:text="${menuItem.status.displayName}"
|
||||
>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="header-actions">
|
||||
<ThemeSwitcher client:load />
|
||||
<UserButton client:load>
|
||||
<div slot="fallback" class="user-button-fallback" aria-hidden="true">
|
||||
<span class="user-button-fallback__avatar"></span>
|
||||
<span class="user-button-fallback__text"></span>
|
||||
</div>
|
||||
</UserButton>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
background: var(--bg);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 2rem;
|
||||
width: 820px;
|
||||
max-width: calc(100% - 2.5rem);
|
||||
margin: 0 auto;
|
||||
height: 58px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
letter-spacing: -0.02em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.brand:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.user-button-fallback {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
width: 112px;
|
||||
height: 34px;
|
||||
padding: 0 0.7rem;
|
||||
border-radius: 8px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.user-button-fallback__avatar,
|
||||
.user-button-fallback__text {
|
||||
display: block;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bg-raised) 0%,
|
||||
color-mix(in srgb, var(--bg-raised) 78%, var(--ink) 22%) 50%,
|
||||
var(--bg-raised) 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: user-button-fallback-shimmer 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.user-button-fallback__avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 999px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-button-fallback__text {
|
||||
width: 100%;
|
||||
height: 0.72rem;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
nav :global(a) {
|
||||
display: inline-block;
|
||||
padding: 0.3rem 0.65rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--ink-2);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
color 0.15s ease,
|
||||
background 0.15s ease;
|
||||
}
|
||||
|
||||
nav :global(a:hover) {
|
||||
color: var(--ink);
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
|
||||
nav :global(a.active) {
|
||||
color: var(--ink);
|
||||
background: var(--bg-raised);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.header-inner {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
height: auto;
|
||||
padding: 1rem 0;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.1rem;
|
||||
margin-left: -0.65rem;
|
||||
flex: initial;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes user-button-fallback-shimmer {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,303 @@
|
||||
---
|
||||
---
|
||||
|
||||
<section
|
||||
class="hero"
|
||||
th:if="${theme.config?.hero?.hero_enabled != false}"
|
||||
th:classappend="${theme.config?.animation?.animation_breath == false} ? 'hero--no-breath'"
|
||||
>
|
||||
<div class="hero__bg">
|
||||
<div class="hero__orb hero__orb--1"></div>
|
||||
<div class="hero__orb hero__orb--2"></div>
|
||||
<div class="hero__orb hero__orb--3"></div>
|
||||
<div
|
||||
class="hero__bg-image"
|
||||
th:if="${theme.config?.hero?.hero_background_image}"
|
||||
th:style="'background-image: url(' + ${theme.config?.hero?.hero_background_image} + ')'"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="hero__content">
|
||||
<h1
|
||||
class="hero__title"
|
||||
th:text="${theme.config?.hero?.hero_title != null and !#strings.isEmpty(theme.config?.hero?.hero_title) ? theme.config?.hero?.hero_title : site.title}"
|
||||
>
|
||||
暖屿
|
||||
</h1>
|
||||
<p
|
||||
class="hero__subtitle"
|
||||
th:text="${theme.config?.hero?.hero_subtitle != null and !#strings.isEmpty(theme.config?.hero?.hero_subtitle) ? theme.config?.hero?.hero_subtitle : site.subtitle}"
|
||||
>
|
||||
深夜里温暖的小岛
|
||||
</p>
|
||||
<p
|
||||
class="hero__description"
|
||||
th:if="${theme.config?.hero?.hero_description_mode == 'hitokoto'}"
|
||||
id="wi-hitokoto"
|
||||
th:attr="data-api=${theme.config?.hero?.hero_hitokoto_api ?: 'https://v1.hitokoto.cn/'},data-categories=${theme.config?.hero?.hero_hitokoto_categories}"
|
||||
></p>
|
||||
<p
|
||||
class="hero__description"
|
||||
th:if="${theme.config?.hero?.hero_description_mode == 'custom'}"
|
||||
th:text="${theme.config?.hero?.hero_custom_description}"
|
||||
></p>
|
||||
</div>
|
||||
|
||||
<div class="hero__scroll-indicator" aria-hidden="true">
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
var el = document.getElementById("wi-hitokoto");
|
||||
if (!el) return;
|
||||
|
||||
var api = el.getAttribute("data-api") || "https://v1.hitokoto.cn/";
|
||||
var categories = el.getAttribute("data-categories");
|
||||
var url = api + "?encode=json";
|
||||
|
||||
if (categories) {
|
||||
try {
|
||||
var cats = JSON.parse(categories);
|
||||
if (Array.isArray(cats)) {
|
||||
cats.forEach(function (c) {
|
||||
url += "&c=" + encodeURIComponent(c);
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
el.style.opacity = "0.5";
|
||||
el.textContent = "\u2026";
|
||||
|
||||
fetch(url)
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
return res.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
el.style.opacity = "1";
|
||||
var text = data.hitokoto || "";
|
||||
var from = data.from || data.from_who || "";
|
||||
if (from) {
|
||||
text += " \u2014\u2014 " + from;
|
||||
}
|
||||
el.textContent = text;
|
||||
})
|
||||
.catch(function () {
|
||||
el.style.opacity = "1";
|
||||
el.textContent = "";
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
position: relative;
|
||||
height: calc(100vh + 80px);
|
||||
height: calc(100dvh + 80px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(
|
||||
160deg,
|
||||
var(--bg) 0%,
|
||||
color-mix(in srgb, var(--bg) 85%, var(--accent) 15%) 40%,
|
||||
color-mix(in srgb, var(--bg) 90%, var(--mist-pink) 10%) 70%,
|
||||
var(--bg) 100%
|
||||
);
|
||||
margin-top: -80px;
|
||||
padding-top: 80px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hero__bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hero__orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
animation: breathe 5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.hero__orb--1 {
|
||||
width: 420px;
|
||||
height: 420px;
|
||||
top: -10%;
|
||||
right: -5%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(212, 118, 78, 0.4) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
animation-delay: 0s;
|
||||
animation-duration: 5s;
|
||||
}
|
||||
|
||||
.hero__orb--2 {
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
bottom: -8%;
|
||||
left: -5%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(220, 140, 160, 0.3) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
animation-delay: -1.8s;
|
||||
animation-duration: 6s;
|
||||
}
|
||||
|
||||
.hero__orb--3 {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(180, 150, 200, 0.2) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
animation-delay: -3.2s;
|
||||
animation-duration: 4.5s;
|
||||
}
|
||||
|
||||
.hero__bg-image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.hero__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: var(--space-xl);
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.hero__title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: clamp(3rem, 8vw, 6rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--ink);
|
||||
line-height: var(--leading-tight);
|
||||
}
|
||||
|
||||
.hero__subtitle {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-xl);
|
||||
color: var(--ink-2);
|
||||
letter-spacing: 0.05em;
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
.hero__description {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-relaxed);
|
||||
max-width: 560px;
|
||||
text-align: center;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.hero__scroll-indicator {
|
||||
position: absolute;
|
||||
bottom: 2rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: var(--ink-3);
|
||||
animation: scrollHint 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.hero--no-breath .hero__orb {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
html.dark .hero {
|
||||
background: linear-gradient(
|
||||
160deg,
|
||||
var(--bg) 0%,
|
||||
color-mix(in srgb, var(--bg) 85%, var(--accent) 12%) 40%,
|
||||
var(--bg) 100%
|
||||
);
|
||||
}
|
||||
|
||||
html.dark .hero__orb--1 {
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(232, 149, 95, 0.25) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
|
||||
html.dark .hero__orb--2 {
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(200, 120, 140, 0.18) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
|
||||
html.dark .hero__orb--3 {
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(160, 130, 180, 0.12) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero__title {
|
||||
font-size: clamp(2.25rem, 10vw, 3.5rem);
|
||||
}
|
||||
|
||||
.hero__orb--1 {
|
||||
width: 260px;
|
||||
height: 260px;
|
||||
}
|
||||
|
||||
.hero__orb--2 {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.hero__orb--3 {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.hero__content {
|
||||
padding: var(--space-lg);
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.hero__description {
|
||||
max-width: 90vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,307 @@
|
||||
---
|
||||
---
|
||||
|
||||
<section
|
||||
class="wi-section"
|
||||
th:if="${theme.config?.home?.home_latest_enabled != false}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_latest_title ?: '最新文章'}">最新文章</h2>
|
||||
</div>
|
||||
<div
|
||||
class="wi-latest"
|
||||
th:classappend="|wi-latest--${theme.config?.home?.home_latest_style ?: 'magazine'}|"
|
||||
>
|
||||
<a
|
||||
th:each="post,iterStat : ${posts.items}"
|
||||
th:href="@{${post.status.permalink}}"
|
||||
class="wi-latest__card"
|
||||
th:classappend="${iterStat.index lt 2} ? 'wi-latest__card--hero' : ''"
|
||||
>
|
||||
<div
|
||||
class="wi-latest__cover"
|
||||
th:if="${!#strings.isEmpty(post.spec.cover)}"
|
||||
>
|
||||
<img th:src="${post.spec.cover}" th:alt="${post.spec.title}" class="wi-latest__image" />
|
||||
</div>
|
||||
<div class="wi-latest__body">
|
||||
<div class="wi-latest__meta">
|
||||
<span
|
||||
class="wi-latest__category"
|
||||
th:if="${!#lists.isEmpty(post.categories)}"
|
||||
th:text="${post.categories[0].spec.displayName}"
|
||||
></span>
|
||||
<time
|
||||
class="wi-latest__date"
|
||||
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
|
||||
></time>
|
||||
</div>
|
||||
<h3
|
||||
class="wi-latest__title"
|
||||
th:text="${post.spec.title}"
|
||||
></h3>
|
||||
<p
|
||||
class="wi-latest__excerpt"
|
||||
th:if="${post.status.excerpt}"
|
||||
th:text="${post.status.excerpt}"
|
||||
></p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<nav
|
||||
th:if="${posts.totalPages gt 1}"
|
||||
class="wi-pagination"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<a
|
||||
th:if="${posts.hasPrevious()}"
|
||||
th:href="@{${posts.prevUrl}}"
|
||||
class="wi-pagination__prev"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||
>← 较新</a>
|
||||
<span
|
||||
class="wi-pagination__info"
|
||||
th:text="|${posts.page} / ${posts.totalPages}|"
|
||||
></span>
|
||||
<a
|
||||
th:if="${posts.hasNext()}"
|
||||
th:href="@{${posts.nextUrl}}"
|
||||
class="wi-pagination__next"
|
||||
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||
>较旧 →</a>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.wi-section__header {
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.wi-section__title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.wi-section__title::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wi-latest {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-latest--magazine {
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
|
||||
.wi-latest--magazine .wi-latest__card {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.wi-latest--magazine .wi-latest__card--hero {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
.wi-latest--magazine .wi-latest__card--hero .wi-latest__cover {
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
.wi-latest--magazine .wi-latest__card--hero .wi-latest__title {
|
||||
font-size: var(--text-xl);
|
||||
}
|
||||
|
||||
.wi-latest--grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.wi-latest--list .wi-latest__card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.wi-latest--list .wi-latest__cover {
|
||||
width: 240px;
|
||||
flex-shrink: 0;
|
||||
aspect-ratio: 4 / 3;
|
||||
}
|
||||
|
||||
.wi-latest--list .wi-latest__body {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wi-latest__card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background: var(--bg-raised);
|
||||
box-shadow: var(--shadow-sm);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition:
|
||||
transform var(--duration-normal) var(--ease-out-expo),
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-latest__card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.wi-latest__cover {
|
||||
aspect-ratio: 4 / 3;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wi-latest__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.wi-latest__card:hover .wi-latest__image {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.wi-latest__body {
|
||||
padding: var(--space-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.wi-latest__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.wi-latest__category {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--accent);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wi-latest__category::after {
|
||||
content: '·';
|
||||
margin-left: var(--space-sm);
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.wi-latest__date {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wi-latest__title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-latest__card:hover .wi-latest__title {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-latest__excerpt {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-relaxed);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wi-pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding-top: var(--space-xl);
|
||||
margin-top: var(--space-xl);
|
||||
border-top: 1px solid var(--rule);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.wi-pagination__prev,
|
||||
.wi-pagination__next {
|
||||
color: var(--ink-2);
|
||||
text-decoration: none;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-pagination__prev:hover,
|
||||
.wi-pagination__next:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-pagination__info {
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.wi-latest--magazine {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.wi-latest--magazine .wi-latest__card {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.wi-latest--magazine .wi-latest__card--hero {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.wi-latest--grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-latest--magazine,
|
||||
.wi-latest--grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.wi-latest--magazine .wi-latest__card--hero {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.wi-latest--list .wi-latest__card {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wi-latest--list .wi-latest__cover {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,255 @@
|
||||
---
|
||||
---
|
||||
|
||||
<script>
|
||||
import 'lightgallery/css/lightgallery-bundle.css';
|
||||
import lightGallery from 'lightgallery';
|
||||
import lgZoom from 'lightgallery/plugins/zoom';
|
||||
import lgThumbnail from 'lightgallery/plugins/thumbnail';
|
||||
import lgFullscreen from 'lightgallery/plugins/fullscreen';
|
||||
import lgRotate from 'lightgallery/plugins/rotate';
|
||||
import lgAutoplay from 'lightgallery/plugins/autoplay';
|
||||
|
||||
var _initialized = new WeakSet();
|
||||
|
||||
function isInitialized(el) {
|
||||
if (_initialized.has(el)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function markInitialized(el) {
|
||||
_initialized.add(el);
|
||||
}
|
||||
|
||||
function buildExifHtml(exifStr, caption) {
|
||||
if (!exifStr) return caption || '';
|
||||
var parts = exifStr.split('|');
|
||||
if (parts.length < 8) return caption || '';
|
||||
var make = parts[0], model = parts[1], lensModel = parts[2];
|
||||
var fNumber = parts[3], exposureTime = parts[4], iso = parts[5];
|
||||
var focalLength = parts[6], focalLengthIn35mm = parts[7];
|
||||
var hasAnyExif = make || model || lensModel || fNumber || exposureTime || iso || focalLength;
|
||||
if (!hasAnyExif) return caption || '';
|
||||
var html = '<div class="wi-lg-caption">' + (caption || '') + '</div>';
|
||||
html += '<div class="wi-lg-exif">';
|
||||
if (make || model) {
|
||||
html += '<span class="wi-lg-exif__item wi-lg-exif__camera">' + (make ? make + ' ' : '') + model + '</span>';
|
||||
}
|
||||
if (lensModel) {
|
||||
html += '<span class="wi-lg-exif__item wi-lg-exif__lens">' + lensModel + '</span>';
|
||||
}
|
||||
html += '<div class="wi-lg-exif__params">';
|
||||
if (fNumber) html += '<span class="wi-lg-exif__item wi-lg-exif__aperture">f/' + fNumber + '</span>';
|
||||
if (exposureTime) html += '<span class="wi-lg-exif__item wi-lg-exif__shutter">' + exposureTime + 's</span>';
|
||||
if (iso) html += '<span class="wi-lg-exif__item wi-lg-exif__iso">ISO ' + iso + '</span>';
|
||||
if (focalLength) {
|
||||
var focalText = focalLength + 'mm';
|
||||
if (focalLengthIn35mm && focalLengthIn35mm !== focalLength) {
|
||||
focalText += ' (eq. ' + focalLengthIn35mm + 'mm)';
|
||||
}
|
||||
html += '<span class="wi-lg-exif__item wi-lg-exif__focal">' + focalText + '</span>';
|
||||
}
|
||||
html += '</div></div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function preprocessExif(el) {
|
||||
var items = el.querySelectorAll('.wi-lightgallery-item[data-exif]');
|
||||
items.forEach(function(item) {
|
||||
var exifStr = item.getAttribute('data-exif');
|
||||
if (!exifStr) return;
|
||||
var caption = item.getAttribute('data-sub-html') || '';
|
||||
var html = buildExifHtml(exifStr, caption);
|
||||
if (html && html !== caption) {
|
||||
item.setAttribute('data-sub-html', html);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createLgConfig() {
|
||||
return {
|
||||
selector: '.wi-lightgallery-item',
|
||||
plugins: [lgZoom, lgThumbnail, lgFullscreen, lgRotate, lgAutoplay],
|
||||
speed: 280,
|
||||
mode: 'lg-fade',
|
||||
licenseKey: '0000-0000-0000-0000',
|
||||
download: false,
|
||||
counter: true,
|
||||
preload: 2,
|
||||
thumbWidth: 80,
|
||||
thumbHeight: '60px',
|
||||
thumbMargin: 4,
|
||||
zoomFromOrigin: true,
|
||||
actualSize: false,
|
||||
showZoomInOutIcons: true,
|
||||
toggleThumb: true,
|
||||
allowMediaOverlap: true,
|
||||
autoplay: false,
|
||||
slideDelay: 5000,
|
||||
progressBar: true,
|
||||
rotate: true,
|
||||
flipHorizontal: false,
|
||||
flipVertical: false,
|
||||
mobileSettings: {
|
||||
controls: true,
|
||||
showCloseIcon: true,
|
||||
download: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function initLg(el) {
|
||||
if (isInitialized(el)) return;
|
||||
var items = el.querySelectorAll('.wi-lightgallery-item');
|
||||
if (items.length === 0) return;
|
||||
preprocessExif(el);
|
||||
lightGallery(el, createLgConfig());
|
||||
markInitialized(el);
|
||||
}
|
||||
|
||||
function initLightGallery() {
|
||||
initContentPages();
|
||||
initPhotosPage();
|
||||
initMomentsPage();
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
var toggleBtn = e.target.closest('.lg-toggle-thumb');
|
||||
if (toggleBtn) {
|
||||
e.stopPropagation();
|
||||
var lgOuter = toggleBtn.closest('.lg-outer');
|
||||
if (lgOuter) {
|
||||
lgOuter.classList.toggle('wi-thumb-shown');
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
function initContentPages() {
|
||||
var selectors = ['.wi-post__body', '.wi-page__body'];
|
||||
selectors.forEach(function(sel) {
|
||||
var els = document.querySelectorAll(sel);
|
||||
els.forEach(function(el) {
|
||||
processContentImages(el);
|
||||
initLg(el);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initPhotosPage() {
|
||||
var allGroups = document.querySelector('.wi-photos-page__all-groups');
|
||||
if (allGroups) {
|
||||
var links = allGroups.querySelectorAll('.wi-photos-page__link');
|
||||
links.forEach(function(link) {
|
||||
link.classList.add('wi-lightgallery-item');
|
||||
});
|
||||
initLg(allGroups);
|
||||
return;
|
||||
}
|
||||
|
||||
var singleGrid = document.querySelector('.wi-photos-page__grid:not(.wi-photos-page__all-groups .wi-photos-page__grid)');
|
||||
if (singleGrid) {
|
||||
var links2 = singleGrid.querySelectorAll('.wi-photos-page__link');
|
||||
links2.forEach(function(link) {
|
||||
link.classList.add('wi-lightgallery-item');
|
||||
});
|
||||
initLg(singleGrid);
|
||||
return;
|
||||
}
|
||||
|
||||
var grids = document.querySelectorAll('.wi-photos-page__grid');
|
||||
grids.forEach(function(el) {
|
||||
processPhotosGrid(el);
|
||||
initLg(el);
|
||||
});
|
||||
}
|
||||
|
||||
function initMomentsPage() {
|
||||
var selectors = ['.wi-moments-page__media', '.wi-moments-page__card-media', '.wi-moments-page__masonry-media'];
|
||||
selectors.forEach(function(sel) {
|
||||
var els = document.querySelectorAll(sel);
|
||||
els.forEach(function(el) {
|
||||
processMomentsMedia(el);
|
||||
initLg(el);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function processContentImages(container) {
|
||||
var images = container.querySelectorAll('img');
|
||||
images.forEach(function(img) {
|
||||
var src = img.getAttribute('src');
|
||||
if (!src) return;
|
||||
|
||||
var w = img.getAttribute('width');
|
||||
var h = img.getAttribute('height');
|
||||
if ((w && parseInt(w) < 50) || (h && parseInt(h) < 50)) return;
|
||||
|
||||
var className = (img.getAttribute('class') || '').toLowerCase();
|
||||
if (/emoji|icon|avatar|logo|badge|smiley/.test(className)) return;
|
||||
|
||||
if (img.getAttribute('role') === 'presentation') return;
|
||||
if (img.getAttribute('aria-hidden') === 'true') return;
|
||||
|
||||
var parentA = img.closest('a');
|
||||
if (parentA) {
|
||||
if (parentA.getAttribute('target') === '_blank') return;
|
||||
parentA.classList.add('wi-lightgallery-item');
|
||||
if (!parentA.getAttribute('data-src')) {
|
||||
var href = parentA.getAttribute('href') || src;
|
||||
parentA.setAttribute('data-src', href);
|
||||
}
|
||||
var alt = img.getAttribute('alt') || '';
|
||||
if (alt && !parentA.getAttribute('data-sub-html')) {
|
||||
parentA.setAttribute('data-sub-html', alt);
|
||||
}
|
||||
} else {
|
||||
var a = document.createElement('a');
|
||||
a.setAttribute('href', src);
|
||||
a.setAttribute('data-src', src);
|
||||
a.classList.add('wi-lightgallery-item');
|
||||
var alt2 = img.getAttribute('alt') || '';
|
||||
if (alt2) {
|
||||
a.setAttribute('data-sub-html', alt2);
|
||||
}
|
||||
img.parentNode.insertBefore(a, img);
|
||||
a.appendChild(img);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processPhotosGrid(container) {
|
||||
var links = container.querySelectorAll('.wi-photos-page__link');
|
||||
links.forEach(function(link) {
|
||||
link.classList.add('wi-lightgallery-item');
|
||||
});
|
||||
}
|
||||
|
||||
function processMomentsMedia(container) {
|
||||
var images = container.querySelectorAll('img');
|
||||
images.forEach(function(img) {
|
||||
var src = img.getAttribute('src');
|
||||
if (!src) return;
|
||||
|
||||
var parentA = img.closest('a');
|
||||
if (parentA) {
|
||||
parentA.classList.add('wi-lightgallery-item');
|
||||
if (!parentA.getAttribute('data-src')) {
|
||||
parentA.setAttribute('data-src', src);
|
||||
}
|
||||
} else {
|
||||
var a = document.createElement('a');
|
||||
a.setAttribute('href', src);
|
||||
a.setAttribute('data-src', src);
|
||||
a.classList.add('wi-lightgallery-item');
|
||||
img.parentNode.insertBefore(a, img);
|
||||
a.appendChild(img);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initLightGallery);
|
||||
} else {
|
||||
initLightGallery();
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,153 @@
|
||||
---
|
||||
---
|
||||
<section
|
||||
class="wi-section wi-message-wall"
|
||||
th:if="${theme.config?.home?.home_message_wall_enabled == true}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_message_wall_title ?: '留言墙'}">留言墙</h2>
|
||||
<span class="wi-section__accent"></span>
|
||||
</div>
|
||||
|
||||
<div class="wi-message-wall__grid">
|
||||
<div
|
||||
class="wi-message-wall__card"
|
||||
th:each="comment : ${commentFinder.list()}"
|
||||
>
|
||||
<div class="wi-message-wall__head">
|
||||
<img
|
||||
th:if="${comment.spec.owner.avatar}"
|
||||
th:src="${comment.spec.owner.avatar}"
|
||||
th:alt="${comment.spec.owner.displayName}"
|
||||
class="wi-message-wall__avatar"
|
||||
/>
|
||||
<div
|
||||
th:unless="${comment.spec.owner.avatar}"
|
||||
class="wi-message-wall__avatar wi-message-wall__avatar--placeholder"
|
||||
>
|
||||
<span th:text="${#strings.substring(comment.spec.owner.displayName, 0, 1)}"></span>
|
||||
</div>
|
||||
<div class="wi-message-wall__meta">
|
||||
<span class="wi-message-wall__name" th:text="${comment.spec.owner.displayName}"></span>
|
||||
<time
|
||||
class="wi-message-wall__date"
|
||||
th:text="${#dates.format(comment.metadata.creationTimestamp, 'yyyy-MM-dd')}"
|
||||
></time>
|
||||
</div>
|
||||
</div>
|
||||
<p class="wi-message-wall__text" th:text="${comment.spec.content}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.wi-message-wall__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-message-wall__card {
|
||||
padding: var(--space-lg);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--rule);
|
||||
transition:
|
||||
transform var(--duration-normal) var(--ease-out-expo),
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-message-wall__card:nth-child(6n + 1) {
|
||||
background: var(--accent-bg);
|
||||
}
|
||||
|
||||
.wi-message-wall__card:nth-child(6n + 2) {
|
||||
background: var(--mist-pink);
|
||||
}
|
||||
|
||||
.wi-message-wall__card:nth-child(6n + 3) {
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
|
||||
.wi-message-wall__card:nth-child(6n + 4) {
|
||||
background: color-mix(in srgb, var(--bg) 85%, var(--accent) 15%);
|
||||
}
|
||||
|
||||
.wi-message-wall__card:nth-child(6n + 5) {
|
||||
background: color-mix(in srgb, var(--bg) 90%, var(--caramel) 10%);
|
||||
}
|
||||
|
||||
.wi-message-wall__card:nth-child(6n + 6) {
|
||||
background: var(--sea-salt);
|
||||
}
|
||||
|
||||
.wi-message-wall__card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.wi-message-wall__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-message-wall__avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 9999px;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wi-message-wall__avatar--placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--accent-bg);
|
||||
color: var(--accent);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.wi-message-wall__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wi-message-wall__name {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wi-message-wall__date {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
.wi-message-wall__text {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-relaxed);
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.wi-message-wall__grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,274 @@
|
||||
---
|
||||
---
|
||||
<div class="wi-mobile-menu" id="wi-mobile-menu" aria-hidden="true">
|
||||
<div class="wi-mobile-menu__backdrop"></div>
|
||||
<div class="wi-mobile-menu__panel">
|
||||
<div class="wi-mobile-menu__header">
|
||||
<button class="wi-mobile-menu__close" type="button" aria-label="Close menu">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="wi-mobile-menu__nav" aria-label="Mobile navigation">
|
||||
<th:block th:if="${menuFinder != null}">
|
||||
<th:block th:with="menu = ${menuFinder.getPrimary()}">
|
||||
<th:block th:if="${menu != null and menu.menuItems != null and not #lists.isEmpty(menu.menuItems)}">
|
||||
<a
|
||||
th:each="menuItem : ${menu.menuItems}"
|
||||
th:href="@{${menuItem.status.href}}"
|
||||
th:target="${menuItem.spec.target}"
|
||||
th:text="${menuItem.status.displayName}"
|
||||
class="wi-mobile-menu__link"
|
||||
th:data-href="${menuItem.status.href}"
|
||||
></a>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</nav>
|
||||
|
||||
<div class="wi-mobile-menu__footer">
|
||||
<button
|
||||
th:if="${pluginFinder.available('PluginSearchWidget')}"
|
||||
class="wi-mobile-menu__action-btn"
|
||||
type="button"
|
||||
aria-label="Search"
|
||||
onclick="SearchWidget.open()"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
<span th:text="${theme.config?.basic?.label_search ?: '搜索'}">搜索</span>
|
||||
</button>
|
||||
|
||||
<button class="wi-mobile-menu__action-btn wi-mobile-menu__theme-btn" type="button" aria-label="Toggle theme">
|
||||
<svg class="wi-mobile-menu__icon-moon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
|
||||
<svg class="wi-mobile-menu__icon-sun" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
|
||||
<span class="wi-mobile-menu__theme-label" th:text="${theme.config?.basic?.label_theme_switch ?: '切换主题'}">切换主题</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script is:inline>
|
||||
const menu = document.getElementById("wi-mobile-menu");
|
||||
const backdrop = menu?.querySelector(".wi-mobile-menu__backdrop");
|
||||
const closeBtn = menu?.querySelector(".wi-mobile-menu__close");
|
||||
const themeBtn = menu?.querySelector(".wi-mobile-menu__theme-btn");
|
||||
let isOpen = false;
|
||||
|
||||
function openMenu() {
|
||||
isOpen = true;
|
||||
menu?.classList.add("wi-mobile-menu--open");
|
||||
menu?.setAttribute("aria-hidden", "false");
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
isOpen = false;
|
||||
menu?.classList.remove("wi-mobile-menu--open");
|
||||
menu?.setAttribute("aria-hidden", "true");
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
|
||||
window.addEventListener("wi:toggle-mobile-menu", () => {
|
||||
if (isOpen) {
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu();
|
||||
}
|
||||
});
|
||||
|
||||
closeBtn?.addEventListener("click", closeMenu);
|
||||
backdrop?.addEventListener("click", closeMenu);
|
||||
|
||||
themeBtn?.addEventListener("click", () => {
|
||||
document.documentElement.classList.add("wi-theme-transition");
|
||||
const isDark = document.documentElement.classList.toggle("dark");
|
||||
localStorage.setItem("wi-theme", isDark ? "dark" : "light");
|
||||
document.documentElement.setAttribute("data-color-scheme", isDark ? "dark" : "light");
|
||||
setTimeout(() => {
|
||||
document.documentElement.classList.remove("wi-theme-transition");
|
||||
}, 300);
|
||||
});
|
||||
|
||||
menu?.querySelectorAll(".wi-mobile-menu__link").forEach((link) => {
|
||||
link.addEventListener("click", closeMenu);
|
||||
});
|
||||
|
||||
menu?.querySelectorAll('.wi-mobile-menu__link[data-href]').forEach(function(link) {
|
||||
try {
|
||||
if (new URL(link.href).pathname === window.location.pathname) {
|
||||
link.classList.add('wi-mobile-menu__link--active');
|
||||
}
|
||||
} catch(e) {}
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && isOpen) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.wi-mobile-menu {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 200;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.wi-mobile-menu--open {
|
||||
pointer-events: auto;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.wi-mobile-menu__backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
opacity: 0;
|
||||
transition: opacity var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-mobile-menu--open .wi-mobile-menu__backdrop {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.wi-mobile-menu__panel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: min(85vw, 360px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(24px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
||||
border-left: 1px solid var(--glass-border);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateX(100%);
|
||||
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-mobile-menu--open .wi-mobile-menu__panel {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.wi-mobile-menu__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 1rem 1.25rem;
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.wi-mobile-menu__close {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
background: transparent;
|
||||
color: var(--ink-2);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background var(--duration-fast) var(--ease-out-quart),
|
||||
color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-mobile-menu__close:hover {
|
||||
background: var(--bg-raised);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.wi-mobile-menu__close:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.wi-mobile-menu__nav {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1rem 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.wi-mobile-menu__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 500;
|
||||
color: var(--ink-2);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
color var(--duration-fast) var(--ease-out-quart),
|
||||
background var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-mobile-menu__link:hover {
|
||||
color: var(--accent);
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
|
||||
.wi-mobile-menu__link--active {
|
||||
color: var(--accent);
|
||||
background: var(--bg-raised);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wi-mobile-menu__footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem 1.25rem;
|
||||
border-top: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.wi-mobile-menu__action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: transparent;
|
||||
color: var(--ink-2);
|
||||
font-size: var(--text-md);
|
||||
font-family: var(--font-sans);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
color var(--duration-fast) var(--ease-out-quart),
|
||||
background var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-mobile-menu__action-btn:hover {
|
||||
color: var(--ink);
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
|
||||
.wi-mobile-menu__action-btn:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.wi-mobile-menu__icon-sun {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:global(.dark) .wi-mobile-menu__icon-moon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:global(.dark) .wi-mobile-menu__icon-sun {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,156 @@
|
||||
---
|
||||
import { findPageBySlug } from "../lib/pages";
|
||||
|
||||
const pageConfig = findPageBySlug("moments");
|
||||
|
||||
---
|
||||
|
||||
<Fragment>
|
||||
<th:block th:if="${pluginFinder.available('PluginMoments')}">
|
||||
<section class="wi-moments-section">
|
||||
<div class="wi-container">
|
||||
<h2 class="wi-moments-section__title" th:text="${pageConfig?.title ?: theme.config?.moments?.moments_title ?: '瞬间'}">瞬间</h2>
|
||||
<div
|
||||
class="wi-moments-section__list"
|
||||
th:attr="data-style=${theme.config?.moments?.moments_style ?: 'timeline'}"
|
||||
>
|
||||
<th:block th:with="limit = ${theme.config?.moments?.moments_count ?: 5}, momentsResult = ${momentFinder.list(1, limit)}">
|
||||
<div
|
||||
class="wi-moments-section__item"
|
||||
th:each="moment : ${momentsResult.items}"
|
||||
>
|
||||
<div class="wi-moments-section__dot" th:if="${theme.config?.moments?.moments_style != 'cards'}"></div>
|
||||
<div class="wi-moments-section__content" th:utext="${moment.spec?.content?.html ?: moment.spec?.content?.raw ?: moment.spec?.content}"></div>
|
||||
<time
|
||||
class="wi-moments-section__date"
|
||||
th:text="${#dates.format(moment.spec?.releaseTime ?: moment.metadata?.creationTimestamp, 'yyyy-MM-dd HH:mm')}"
|
||||
></time>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
<a th:href="@{'/moments'}" class="wi-moments-section__more">
|
||||
<span th:text="${theme.config?.home?.home_label_view_all ?: '查看全部'}">查看全部</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</th:block>
|
||||
</Fragment>
|
||||
|
||||
<style>
|
||||
.wi-moments-section {
|
||||
padding-block: var(--section-spacing);
|
||||
}
|
||||
|
||||
.wi-moments-section__title {
|
||||
font-size: var(--text-2xl);
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-2xl);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wi-moments-section__title::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin-inline: auto;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-moments-section__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-xl);
|
||||
}
|
||||
|
||||
.wi-moments-section__list[data-style="timeline"] {
|
||||
padding-left: var(--space-2xl);
|
||||
}
|
||||
|
||||
.wi-moments-section__item {
|
||||
position: relative;
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 16px;
|
||||
padding: var(--space-xl);
|
||||
transition:
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
transform var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-moments-section__item:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.wi-moments-section__list[data-style="timeline"] .wi-moments-section__item {
|
||||
margin-left: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-moments-section__list[data-style="timeline"] .wi-moments-section__dot {
|
||||
position: absolute;
|
||||
left: calc(-1 * var(--space-2xl) - 5px);
|
||||
top: 28px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--bg), 0 0 0 4px var(--accent);
|
||||
}
|
||||
|
||||
.wi-moments-section__content {
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-relaxed);
|
||||
color: var(--ink);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-moments-section__date {
|
||||
display: block;
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
.wi-moments-section__more {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
margin-top: var(--space-xl);
|
||||
margin-inline: auto;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
transition: color var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-moments-section__more:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-moments-section__more svg {
|
||||
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-moments-section__more:hover svg {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-moments-section__list[data-style="timeline"] {
|
||||
padding-left: var(--space-xl);
|
||||
}
|
||||
|
||||
.wi-moments-section__list[data-style="timeline"] .wi-moments-section__item {
|
||||
margin-left: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-moments-section__list[data-style="timeline"] .wi-moments-section__dot {
|
||||
left: calc(-1 * var(--space-xl) - 5px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,324 @@
|
||||
---
|
||||
import ThemeSwitcher from "./ThemeSwitcher.vue";
|
||||
---
|
||||
|
||||
<header class="wi-navbar-wrapper" id="wi-navbar" th:classappend="${theme.config?.navbar?.navbar_style == 'minimal'} ? 'wi-navbar--minimal' : (${theme.config?.navbar?.navbar_style == 'float'} ? 'wi-navbar--float' : 'wi-navbar--glass')">
|
||||
<div class="wi-navbar__capsule">
|
||||
<a class="wi-navbar__brand" th:href="@{/}">
|
||||
<img
|
||||
th:if="${theme.config?.basic?.logo}"
|
||||
th:src="${theme.config?.basic?.logo}"
|
||||
alt=""
|
||||
class="wi-navbar__logo"
|
||||
/>
|
||||
<span
|
||||
th:if="${#strings.isEmpty(theme.config?.basic?.logo)}"
|
||||
class="wi-navbar__title"
|
||||
th:text="${site.title}"
|
||||
></span>
|
||||
</a>
|
||||
|
||||
<nav
|
||||
class="wi-navbar__nav"
|
||||
aria-label="Main navigation"
|
||||
>
|
||||
<th:block th:if="${menuFinder != null}">
|
||||
<th:block th:with="menu = ${menuFinder.getPrimary()}">
|
||||
<th:block th:if="${menu != null and menu.menuItems != null and not #lists.isEmpty(menu.menuItems)}">
|
||||
<a
|
||||
th:each="menuItem : ${menu.menuItems}"
|
||||
th:href="@{${menuItem.status.href}}"
|
||||
th:target="${menuItem.spec.target}"
|
||||
th:text="${menuItem.status.displayName}"
|
||||
class="wi-navbar__link"
|
||||
th:data-href="${menuItem.status.href}"
|
||||
>
|
||||
</a>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</nav>
|
||||
|
||||
<div class="wi-navbar__actions">
|
||||
<button
|
||||
th:if="${pluginFinder.available('PluginSearchWidget') and theme.config?.navbar?.navbar_show_search != false}"
|
||||
class="wi-navbar__action-btn"
|
||||
type="button"
|
||||
aria-label="Search"
|
||||
onclick="SearchWidget.open()"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
</button>
|
||||
|
||||
<th:block th:if="${theme.config?.navbar?.navbar_show_theme_switch != false}">
|
||||
<ThemeSwitcher client:load />
|
||||
</th:block>
|
||||
|
||||
<button
|
||||
class="wi-navbar__action-btn wi-navbar__menu-btn"
|
||||
type="button"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<script is:inline>
|
||||
const navbar = document.getElementById("wi-navbar");
|
||||
let lastScrollY = window.scrollY;
|
||||
let ticking = false;
|
||||
|
||||
function onScroll() {
|
||||
if (!ticking) {
|
||||
requestAnimationFrame(() => {
|
||||
const currentScrollY = window.scrollY;
|
||||
|
||||
if (currentScrollY > 20) {
|
||||
navbar?.classList.add("wi-navbar--scrolled");
|
||||
} else {
|
||||
navbar?.classList.remove("wi-navbar--scrolled");
|
||||
}
|
||||
|
||||
if (currentScrollY > lastScrollY && currentScrollY > 80) {
|
||||
navbar?.classList.add("wi-navbar--hidden");
|
||||
} else if (currentScrollY < lastScrollY) {
|
||||
navbar?.classList.remove("wi-navbar--hidden");
|
||||
}
|
||||
|
||||
lastScrollY = currentScrollY;
|
||||
ticking = false;
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
|
||||
document.querySelectorAll('.wi-navbar__link[data-href]').forEach(function(link) {
|
||||
try {
|
||||
if (new URL(link.href).pathname === window.location.pathname) {
|
||||
link.classList.add('wi-navbar__link--active');
|
||||
}
|
||||
} catch(e) {}
|
||||
});
|
||||
|
||||
document.querySelector(".wi-navbar__menu-btn")?.addEventListener("click", () => {
|
||||
window.dispatchEvent(new CustomEvent("wi:toggle-mobile-menu"));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.wi-navbar-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0.75rem 1rem;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
transform 0.35s cubic-bezier(0.16, 1, 0.3, 1),
|
||||
padding var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-navbar--hidden {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.wi-navbar__capsule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
padding: 0 1.5rem;
|
||||
margin: 0 auto;
|
||||
pointer-events: auto;
|
||||
transition:
|
||||
background var(--duration-normal) var(--ease-out-expo),
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
transform var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-navbar--glass .wi-navbar__capsule {
|
||||
border-radius: 9999px;
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||||
border: 1px solid var(--glass-border);
|
||||
box-shadow: var(--shadow-md);
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.wi-navbar--glass.wi-navbar--scrolled .wi-navbar__capsule {
|
||||
background: var(--bg-overlay);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.wi-navbar--minimal {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.wi-navbar--minimal .wi-navbar__capsule {
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
border: none;
|
||||
border-bottom: 1px solid transparent;
|
||||
box-shadow: none;
|
||||
max-width: 100%;
|
||||
padding: 0 2rem;
|
||||
transition: background 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.wi-navbar--minimal.wi-navbar--scrolled .wi-navbar__capsule {
|
||||
background: var(--bg);
|
||||
border-bottom-color: var(--rule);
|
||||
}
|
||||
|
||||
.wi-navbar--float .wi-navbar__capsule {
|
||||
border-radius: 16px;
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(24px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
||||
border: 1px solid var(--glass-border);
|
||||
box-shadow: var(--shadow-lg);
|
||||
max-width: 800px;
|
||||
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), background 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.wi-navbar--float.wi-navbar--scrolled .wi-navbar__capsule {
|
||||
transform: scale(0.96);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.wi-navbar__brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
text-decoration: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wi-navbar__logo {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
border-radius: 8px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.wi-navbar__title {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 700;
|
||||
font-size: var(--text-md);
|
||||
color: var(--ink);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
white-space: nowrap;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-navbar__brand:hover .wi-navbar__title {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-navbar__nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wi-navbar__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.3rem 0.7rem;
|
||||
border-radius: 9999px;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
color var(--duration-fast) var(--ease-out-quart),
|
||||
background var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-navbar__link:hover {
|
||||
color: var(--accent);
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
|
||||
.wi-navbar__link--active {
|
||||
color: var(--ink);
|
||||
background: var(--bg-raised);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wi-navbar__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.wi-navbar__action-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
background: transparent;
|
||||
color: var(--ink-2);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background var(--duration-fast) var(--ease-out-quart),
|
||||
color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-navbar__action-btn:hover {
|
||||
background: var(--bg-raised);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.wi-navbar__action-btn:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.wi-navbar__menu-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-navbar-wrapper {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
.wi-navbar__capsule {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.wi-navbar__nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wi-navbar__action-btn[aria-label="Search"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wi-navbar__menu-btn {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,170 @@
|
||||
---
|
||||
import { findPageBySlug } from "../lib/pages";
|
||||
|
||||
const pageConfig = findPageBySlug("photos");
|
||||
|
||||
---
|
||||
|
||||
<Fragment>
|
||||
<th:block th:if="${pluginFinder.available('PluginPhotos')}">
|
||||
<section class="wi-photos-section">
|
||||
<div class="wi-container">
|
||||
<h2 class="wi-photos-section__title" th:text="${pageConfig?.title ?: theme.config?.photos?.photos_title ?: '图库'}">图库</h2>
|
||||
<div
|
||||
class="wi-photos-section__grid"
|
||||
th:attr="data-style=${theme.config?.photos?.photos_style ?: 'masonry'}, data-columns=${theme.config?.photos?.photos_columns ?: 3}"
|
||||
>
|
||||
<th:block th:with="limit = ${theme.config?.photos?.photos_count ?: 6}">
|
||||
<th:block th:each="group : ${photoFinder.groupBy()}">
|
||||
<div
|
||||
class="wi-photos-section__item"
|
||||
th:each="photo, stat : ${group.photos}"
|
||||
th:if="${stat.index < limit}"
|
||||
>
|
||||
<a th:href="@{'/photos'}" class="wi-photos-section__link">
|
||||
<div class="wi-photos-section__wrap">
|
||||
<img
|
||||
th:src="${photo.spec.url}"
|
||||
th:alt="${photo.spec.displayName ?: ''}"
|
||||
class="wi-photos-section__image"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</div>
|
||||
<a th:href="@{'/photos'}" class="wi-photos-section__more">
|
||||
<span th:text="${theme.config?.home?.home_label_view_all ?: '查看全部'}">查看全部</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</th:block>
|
||||
</Fragment>
|
||||
|
||||
<style>
|
||||
.wi-photos-section {
|
||||
padding-block: var(--section-spacing);
|
||||
}
|
||||
|
||||
.wi-photos-section__title {
|
||||
font-size: var(--text-2xl);
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-2xl);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wi-photos-section__title::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin-inline: auto;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-photos-section__grid[data-style="masonry"] {
|
||||
columns: 3;
|
||||
column-gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-photos-section__grid[data-style="masonry"][data-columns="2"] {
|
||||
columns: 2;
|
||||
}
|
||||
|
||||
.wi-photos-section__grid[data-style="grid"] {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-photos-section__grid[data-style="grid"][data-columns="2"] {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.wi-photos-section__grid[data-style="masonry"] .wi-photos-section__item {
|
||||
break-inside: avoid;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-photos-section__wrap {
|
||||
position: relative;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition:
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
transform var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-photos-section__wrap:hover {
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.wi-photos-section__link {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.wi-photos-section__image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 0;
|
||||
transition: transform var(--duration-slow) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-photos-section__grid[data-style="grid"] .wi-photos-section__image {
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.wi-photos-section__wrap:hover .wi-photos-section__image {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
|
||||
.wi-photos-section__more {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
margin-top: var(--space-xl);
|
||||
margin-inline: auto;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
transition: color var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-photos-section__more:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-photos-section__more svg {
|
||||
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-photos-section__more:hover svg {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-photos-section__grid[data-style="masonry"] {
|
||||
columns: 2;
|
||||
column-gap: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-photos-section__grid[data-style="masonry"] .wi-photos-section__item {
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-photos-section__grid[data-style="grid"] {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--space-md);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,248 @@
|
||||
---
|
||||
interface Props {
|
||||
variant?: "default" | "featured" | "compact";
|
||||
}
|
||||
|
||||
const { variant = "default" } = Astro.props;
|
||||
---
|
||||
|
||||
<a
|
||||
class:list={["post-card", `post-card--${variant}`]}
|
||||
th:href="@{${post.status.permalink}}"
|
||||
>
|
||||
<div
|
||||
th:unless="${#strings.isEmpty(post.spec.cover)}"
|
||||
class="post-card__image-wrap"
|
||||
>
|
||||
<img
|
||||
class="post-card__image"
|
||||
th:src="${post.spec.cover}"
|
||||
th:srcset="|${thumbnail.gen(post.spec.cover, 's')} 400w,
|
||||
${thumbnail.gen(post.spec.cover, 'm')} 800w,
|
||||
${thumbnail.gen(post.spec.cover, 'l')} 1200w|"
|
||||
th:alt="${post.spec.title}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<div class="post-card__overlay"></div>
|
||||
</div>
|
||||
|
||||
<div class="post-card__content">
|
||||
<time
|
||||
class="post-card__date"
|
||||
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
|
||||
></time>
|
||||
<h3 class="post-card__title" th:text="${post.spec.title}"></h3>
|
||||
<p
|
||||
th:if="${variant != 'compact'}"
|
||||
class="post-card__excerpt"
|
||||
th:text="${post.status.excerpt}"
|
||||
></p>
|
||||
<div
|
||||
th:if="${variant != 'compact' and !#lists.isEmpty(post.tags)}"
|
||||
class="post-card__tags"
|
||||
>
|
||||
<span
|
||||
th:each="tag : ${post.tags}"
|
||||
class="post-card__tag"
|
||||
th:text="${tag.spec.displayName}"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
.post-card {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: var(--space-lg);
|
||||
background: var(--bg-raised);
|
||||
border-radius: var(--border-radius-lg);
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition:
|
||||
transform 400ms var(--ease-out-expo),
|
||||
box-shadow 400ms var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.post-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.post-card__image-wrap {
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
border-radius: var(--border-radius-lg);
|
||||
}
|
||||
|
||||
.post-card__image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 400ms var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.post-card:hover .post-card__image {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.post-card__overlay {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post-card__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-lg);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.post-card__date {
|
||||
font-size: var(--text-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wider);
|
||||
color: var(--ink-3);
|
||||
line-height: var(--leading-none);
|
||||
}
|
||||
|
||||
.post-card__title {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 700;
|
||||
font-size: clamp(1.1rem, 2vw, 1.5rem);
|
||||
line-height: var(--leading-tight);
|
||||
color: var(--ink);
|
||||
margin: 0;
|
||||
transition: color 400ms var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.post-card:hover .post-card__title {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.post-card__excerpt {
|
||||
color: var(--ink-2);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--leading-normal);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.post-card__tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-xs);
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.post-card__tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.2em 0.6em;
|
||||
border-radius: var(--border-radius-sm);
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-none);
|
||||
}
|
||||
|
||||
.post-card--default {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.post-card--default .post-card__image-wrap {
|
||||
width: 40%;
|
||||
aspect-ratio: 4 / 3;
|
||||
}
|
||||
|
||||
.post-card--featured {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.post-card--featured .post-card__image-wrap {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.post-card--featured .post-card__overlay {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 50%;
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
var(--glass-bg),
|
||||
transparent
|
||||
);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.post-card--featured .post-card__content {
|
||||
padding: var(--space-xl);
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.post-card--featured .post-card__title {
|
||||
font-size: clamp(1.5rem, 3vw, 2rem);
|
||||
}
|
||||
|
||||
.post-card--featured .post-card__excerpt {
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
.post-card--compact {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.post-card--compact:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.post-card--compact .post-card__image-wrap {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
flex-shrink: 0;
|
||||
border-radius: var(--border-radius-md);
|
||||
}
|
||||
|
||||
.post-card--compact .post-card__image-wrap img {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
.post-card--compact .post-card__content {
|
||||
padding: var(--space-sm) 0;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.post-card--compact .post-card__title {
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.post-card--default {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.post-card--default .post-card__image-wrap {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
---
|
||||
<section
|
||||
class="wi-section wi-quote"
|
||||
th:if="${theme.config?.home?.home_quote_enabled == true and theme.config?.home?.home_quote_content}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-quote__inner">
|
||||
<span class="wi-quote__mark wi-quote__mark--open" aria-hidden="true">"</span>
|
||||
<blockquote
|
||||
class="wi-quote__text"
|
||||
th:text="${theme.config?.home?.home_quote_content}"
|
||||
></blockquote>
|
||||
<span class="wi-quote__mark wi-quote__mark--close" aria-hidden="true">"</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.wi-quote__inner {
|
||||
position: relative;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-2xl) var(--space-xl);
|
||||
background: var(--mist-pink);
|
||||
border-radius: 16px;
|
||||
text-align: center;
|
||||
border: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-quote__mark {
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 5rem;
|
||||
line-height: 1;
|
||||
color: var(--accent);
|
||||
opacity: 0.25;
|
||||
position: absolute;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.wi-quote__mark--open {
|
||||
top: -0.1em;
|
||||
left: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-quote__mark--close {
|
||||
bottom: -0.5em;
|
||||
right: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-quote__text {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-xl);
|
||||
font-style: italic;
|
||||
line-height: var(--leading-relaxed);
|
||||
color: var(--ink);
|
||||
letter-spacing: var(--tracking-normal);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.wi-quote__inner {
|
||||
padding: var(--space-xl) var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-quote__mark {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
|
||||
.wi-quote__mark--open {
|
||||
left: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-quote__mark--close {
|
||||
right: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-quote__text {
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
|
||||
const observer = ref<IntersectionObserver | null>(null);
|
||||
|
||||
onMounted(() => {
|
||||
const animationEnabled = !document.documentElement.classList.contains(
|
||||
"wi-no-animations"
|
||||
);
|
||||
if (!animationEnabled) return;
|
||||
|
||||
observer.value = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add("wi-revealed");
|
||||
observer.value?.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.1, rootMargin: "0px 0px -40px 0px" }
|
||||
);
|
||||
|
||||
document.querySelectorAll(".wi-reveal").forEach((el) => {
|
||||
observer.value?.observe(el);
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
observer.value?.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot />
|
||||
</template>
|
||||
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted } from "vue";
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
||||
e.preventDefault();
|
||||
const searchWidget = (window as any).SearchWidget;
|
||||
if (searchWidget && typeof searchWidget.open === "function") {
|
||||
searchWidget.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("keydown", handleKeydown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("keydown", handleKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="wi-search-hint" aria-hidden="true">
|
||||
<kbd class="wi-search-hint__key">⌘</kbd>
|
||||
<kbd class="wi-search-hint__key">K</kbd>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wi-search-hint {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.wi-search-hint__key {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 22px;
|
||||
height: 22px;
|
||||
padding: 0 5px;
|
||||
font-family: var(--font-body);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: var(--ink-3);
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 0 var(--rule);
|
||||
}
|
||||
</style>
|
||||
@@ -10,9 +10,13 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
function toggle() {
|
||||
document.documentElement.classList.add("wi-theme-transition");
|
||||
isDark.value = !isDark.value;
|
||||
document.documentElement.classList.toggle("dark", isDark.value);
|
||||
localStorage.setItem("theme", isDark.value ? "dark" : "light");
|
||||
localStorage.setItem("wi-theme", isDark.value ? "dark" : "light");
|
||||
setTimeout(() => {
|
||||
document.documentElement.classList.remove("wi-theme-transition");
|
||||
}, 300);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
---
|
||||
<section
|
||||
class="wi-section wi-timeline"
|
||||
th:if="${theme.config?.home?.home_timeline_enabled == true}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_timeline_title ?: '时间线'}">时间线</h2>
|
||||
<span class="wi-section__accent"></span>
|
||||
</div>
|
||||
|
||||
<div class="wi-timeline__body">
|
||||
<th:block th:with="postsResult = ${postFinder.list({page: 1, size: 50})}">
|
||||
<th:block th:each="post : ${postsResult.items}">
|
||||
<div class="wi-timeline__year" th:text="${#dates.format(post.spec.publishTime, 'yyyy')}"></div>
|
||||
<div class="wi-timeline__entries">
|
||||
<a
|
||||
th:href="@{${post.status.permalink}}"
|
||||
class="wi-timeline__entry"
|
||||
>
|
||||
<span class="wi-timeline__dot"></span>
|
||||
<span class="wi-timeline__line"></span>
|
||||
<time
|
||||
class="wi-timeline__date"
|
||||
th:text="${#dates.format(post.spec.publishTime, 'MM-dd')}"
|
||||
></time>
|
||||
<div class="wi-timeline__content">
|
||||
<span class="wi-timeline__title" th:text="${post.spec.title}"></span>
|
||||
<span
|
||||
class="wi-timeline__excerpt"
|
||||
th:if="${post.status.excerpt}"
|
||||
th:text="${post.status.excerpt}"
|
||||
></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.wi-timeline__body {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2xl);
|
||||
}
|
||||
|
||||
.wi-timeline__year {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-xl);
|
||||
font-weight: 700;
|
||||
color: var(--ink-2);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
padding-bottom: var(--space-sm);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-timeline__entries {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wi-timeline__entry {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 48px 56px minmax(0, 1fr);
|
||||
gap: var(--space-sm);
|
||||
align-items: baseline;
|
||||
padding: var(--space-sm) 0;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-timeline__entry:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-timeline__dot {
|
||||
position: absolute;
|
||||
left: 19px;
|
||||
top: 10px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.wi-timeline__line {
|
||||
position: absolute;
|
||||
left: 22px;
|
||||
top: 18px;
|
||||
bottom: calc(-1 * var(--space-sm));
|
||||
width: 1px;
|
||||
background: var(--rule);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.wi-timeline__entry:last-child .wi-timeline__line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wi-timeline__date {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.wi-timeline__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wi-timeline__title {
|
||||
font-size: var(--text-base);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
line-height: var(--leading-snug);
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-timeline__entry:hover .wi-timeline__title {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-timeline__excerpt {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
line-height: var(--leading-normal);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.wi-timeline__entry {
|
||||
grid-template-columns: 36px 48px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.wi-timeline__dot {
|
||||
left: 13px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.wi-timeline__line {
|
||||
left: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,185 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { DetailedUser } from "@halo-dev/api-client";
|
||||
import ky from "ky";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
|
||||
const user = ref<DetailedUser | null>(null);
|
||||
const isLoading = ref(true);
|
||||
|
||||
const isAnonymous = computed(() => {
|
||||
return user.value?.user.metadata.name === "anonymousUser";
|
||||
});
|
||||
|
||||
const displayName = computed(() => {
|
||||
return (
|
||||
user.value?.user.spec.displayName ||
|
||||
user.value?.user.metadata.name ||
|
||||
"Account"
|
||||
);
|
||||
});
|
||||
|
||||
const avatarSrc = computed(() => {
|
||||
return user.value?.user.spec.avatar || "";
|
||||
});
|
||||
|
||||
const avatarLabel = computed(() => {
|
||||
if (isAnonymous.value) {
|
||||
return "?";
|
||||
}
|
||||
|
||||
return displayName.value.charAt(0).toUpperCase();
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
user.value = await ky
|
||||
.get<DetailedUser>(`/apis/api.console.halo.run/v1alpha1/users/-`)
|
||||
.json();
|
||||
} catch {
|
||||
user.value = null;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="user-button-shell user-button-shell--loading"
|
||||
aria-busy="true"
|
||||
aria-live="polite"
|
||||
>
|
||||
<span
|
||||
class="user-button-avatar user-button-avatar--skeleton"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span class="user-button-text-skeleton" aria-hidden="true"></span>
|
||||
<span class="sr-only">Loading user state</span>
|
||||
</div>
|
||||
<a
|
||||
href="/uc"
|
||||
v-else-if="user && !isAnonymous"
|
||||
class="user-button-shell"
|
||||
:title="displayName"
|
||||
>
|
||||
<span class="user-button-avatar" aria-hidden="true">
|
||||
<img
|
||||
v-if="avatarSrc"
|
||||
class="user-button-avatar__image"
|
||||
:src="avatarSrc"
|
||||
:alt="displayName"
|
||||
/>
|
||||
<span v-else>{{ avatarLabel }}</span>
|
||||
</span>
|
||||
<span class="user-button-text">{{ displayName }}</span>
|
||||
</a>
|
||||
<a v-else class="user-button-shell user-button-shell--link" href="/login">
|
||||
<span class="user-button-avatar" aria-hidden="true">{{ avatarLabel }}</span>
|
||||
<span class="user-button-text">Login</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.user-button-shell {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
width: 112px;
|
||||
height: 34px;
|
||||
padding: 0 0.7rem;
|
||||
border-radius: 8px;
|
||||
color: var(--ink-2);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.user-button-shell--link {
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background 0.15s ease,
|
||||
color 0.15s ease;
|
||||
}
|
||||
|
||||
.user-button-shell--link:hover {
|
||||
background: var(--bg-raised);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.user-button-shell--loading {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.user-button-avatar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 999px;
|
||||
background: var(--bg-raised);
|
||||
color: var(--ink);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-button-avatar__image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-button-avatar--skeleton,
|
||||
.user-button-text-skeleton {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bg-raised) 0%,
|
||||
color-mix(in srgb, var(--bg-raised) 78%, var(--ink) 22%) 50%,
|
||||
var(--bg-raised) 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.user-button-text {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.user-button-text-skeleton {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 0.72rem;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+94
-26
@@ -1,52 +1,120 @@
|
||||
---
|
||||
import "../styles/global.css";
|
||||
import "../styles/main.scss";
|
||||
import Navbar from "../components/Navbar.astro";
|
||||
import Footer from "../components/Footer.astro";
|
||||
import Header from "../components/Header.astro";
|
||||
import ScrollReveal from "../components/ScrollReveal.vue";
|
||||
import CursorGlow from "../components/CursorGlow.vue";
|
||||
import MobileMenu from "../components/MobileMenu.astro";
|
||||
|
||||
interface Props {
|
||||
pageTitle?: string;
|
||||
contentClass?: string;
|
||||
wide?: boolean;
|
||||
home?: boolean;
|
||||
}
|
||||
|
||||
const { pageTitle, contentClass } = Astro.props;
|
||||
|
||||
const hasHeading = Boolean(pageTitle);
|
||||
const { pageTitle, contentClass, wide, home } = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh" th:lang="${theme.config?.basic?.language ?: #locale.language}" th:classappend="${theme.config?.animation?.animation_enabled == false} ? 'wi-no-animations'">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<script is:inline>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script is:inline th:attr="data-scheme=${theme.config?.style?.color_scheme}">
|
||||
(function () {
|
||||
var stored = localStorage.getItem("theme");
|
||||
var prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
).matches;
|
||||
if (stored === "dark" || (!stored && prefersDark)) {
|
||||
var stored = localStorage.getItem("wi-theme");
|
||||
var prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
var scheme = null;
|
||||
if (document.currentScript && document.currentScript.getAttribute("data-scheme")) {
|
||||
scheme = document.currentScript.getAttribute("data-scheme");
|
||||
}
|
||||
var isDark = false;
|
||||
if (stored === "dark") {
|
||||
isDark = true;
|
||||
} else if (stored === "light") {
|
||||
isDark = false;
|
||||
} else if (scheme === "dark") {
|
||||
isDark = true;
|
||||
} else if (scheme === "light") {
|
||||
isDark = false;
|
||||
} else if (!stored && prefersDark) {
|
||||
isDark = true;
|
||||
}
|
||||
if (isDark) {
|
||||
document.documentElement.classList.add("dark");
|
||||
document.documentElement.setAttribute("data-color-scheme", "dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
document.documentElement.setAttribute("data-color-scheme", "light");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<link
|
||||
rel="icon"
|
||||
th:href="${theme.config?.basic?.favicon ?: '/favicon.ico'}"
|
||||
/>
|
||||
<link rel="alternate" type="application/rss+xml" th:title="${site.title}" th:href="@{/feed.xml}" />
|
||||
<style th:if="${theme.config?.style?.accent_color}" th:utext="${':root { --accent: ' + theme.config?.style?.accent_color + '; --accent-hover: ' + theme.config?.style?.accent_color + '; }'}"></style>
|
||||
<style th:if="${theme.config?.style?.custom_css}" th:utext="${theme.config?.style?.custom_css}"></style>
|
||||
<slot name="head" />
|
||||
</head>
|
||||
<body>
|
||||
<Header />
|
||||
<main class="layout-main">
|
||||
<article class="page-shell">
|
||||
{
|
||||
hasHeading && (
|
||||
<header class="page-heading">
|
||||
{pageTitle && <h1>{pageTitle}</h1>}
|
||||
</header>
|
||||
)
|
||||
}
|
||||
<div class:list={["page-content", contentClass]}>
|
||||
<body class="wi-body">
|
||||
<Navbar />
|
||||
<main class:list={["wi-main", { "wi-main--wide": wide || home, "wi-main--home": home }]}>
|
||||
{home ? (
|
||||
<slot />
|
||||
) : (
|
||||
<div class:list={["wi-content-wrap", { "wi-content-wrap--wide": wide }]}>
|
||||
<slot />
|
||||
</div>
|
||||
</article>
|
||||
)}
|
||||
</main>
|
||||
<Footer />
|
||||
<MobileMenu />
|
||||
<ScrollReveal client:load />
|
||||
<CursorGlow th:if="${theme.config?.animation?.animation_cursor_glow}" client:load />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style is:global>
|
||||
html {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
html.wi-theme-transition,
|
||||
html.wi-theme-transition *,
|
||||
html.wi-theme-transition *::before,
|
||||
html.wi-theme-transition *::after {
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease, fill 0.3s ease, stroke 0.3s ease !important;
|
||||
}
|
||||
|
||||
.wi-body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wi-main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.wi-reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition:
|
||||
opacity var(--duration-normal, 300ms) var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)),
|
||||
transform var(--duration-normal, 300ms) var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
|
||||
}
|
||||
|
||||
.wi-revealed {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
html.wi-no-animations .wi-reveal {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
transition: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Astro 构建时辅助函数,提供页面配置信息
|
||||
// 运行时由 Thymeleaf 模板中的 theme.config 覆盖
|
||||
|
||||
interface PageConfig {
|
||||
title: string
|
||||
slug: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
const pages: Record<string, PageConfig> = {
|
||||
moments: {
|
||||
title: '瞬间',
|
||||
slug: 'moments',
|
||||
enabled: true,
|
||||
},
|
||||
photos: {
|
||||
title: '图库',
|
||||
slug: 'photos',
|
||||
enabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
export function findPageBySlug(slug: string): PageConfig | undefined {
|
||||
return pages[slug]
|
||||
}
|
||||
+245
-48
@@ -2,64 +2,261 @@
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout pageTitle="归档">
|
||||
<Layout>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|归档 - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<section class="archive-list" aria-label="Archive list">
|
||||
<th:block th:each="archive : ${archives.items}">
|
||||
<th:block th:each="month : ${archive.months}">
|
||||
<div class="archive-group">
|
||||
<h2
|
||||
class="archive-group__year"
|
||||
th:text="|${archive.year} · ${month.month}|"
|
||||
>
|
||||
</h2>
|
||||
<ul class="archive-group__items">
|
||||
<li th:each="post : ${month.posts}" class="archive-entry">
|
||||
<div class="wi-archives">
|
||||
<header class="wi-archives__header">
|
||||
<h1 class="wi-archives__title">归档</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p class="wi-archives__subtitle" th:text="${theme.config?.home?.home_label_post_count ?: '共 {total} 篇文章}'.replace('{total}', archives.total)}"></p>
|
||||
</header>
|
||||
|
||||
<section class="wi-archives__timeline" aria-label="Archive timeline">
|
||||
<th:block th:each="archive : ${archives.items}">
|
||||
<th:block th:each="month : ${archive.months}">
|
||||
<div class="wi-archives__group">
|
||||
<div class="wi-archives__group-label">
|
||||
<span
|
||||
class="archive-entry__date"
|
||||
th:text="${#dates.format(post.spec.publishTime, 'MM/dd')}"
|
||||
class="wi-archives__dot"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<div>
|
||||
<h3 class="archive-entry__title">
|
||||
<h2
|
||||
class="wi-archives__year-month"
|
||||
th:text="|${archive.year} · ${month.month}|"
|
||||
></h2>
|
||||
</div>
|
||||
<ul class="wi-archives__items">
|
||||
<li
|
||||
th:each="post : ${month.posts}"
|
||||
class="wi-archives__entry"
|
||||
>
|
||||
<span
|
||||
class="wi-archives__entry-date"
|
||||
th:text="${#dates.format(post.spec.publishTime, 'MM/dd')}"
|
||||
></span>
|
||||
<div class="wi-archives__entry-body">
|
||||
<a
|
||||
class="wi-archives__entry-title"
|
||||
th:href="@{${post.status.permalink}}"
|
||||
th:text="${post.spec.title}"></a>
|
||||
</h3>
|
||||
<p
|
||||
class="archive-entry__summary"
|
||||
th:text="${post.status.excerpt}"
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
th:text="${post.spec.title}"
|
||||
></a>
|
||||
<p
|
||||
class="wi-archives__entry-excerpt"
|
||||
th:text="${post.status.excerpt}"
|
||||
></p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<div th:if="${archives.total == 0}" class="archive-empty">暂无文章。</div>
|
||||
</section>
|
||||
<div
|
||||
th:if="${archives.total == 0}"
|
||||
class="wi-archives__empty"
|
||||
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章。'}"
|
||||
>暂无文章。</div>
|
||||
</section>
|
||||
|
||||
<nav
|
||||
th:if="${archives.totalPages gt 1}"
|
||||
class="pagination"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<a
|
||||
th:if="${archives.hasPrevious()}"
|
||||
th:href="@{${archives.prevUrl}}"
|
||||
class="pagination__prev">← 较新</a
|
||||
<nav
|
||||
th:if="${archives.totalPages gt 1}"
|
||||
class="wi-archives__pagination"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<span
|
||||
class="pagination__info"
|
||||
th:text="|${archives.page} / ${archives.totalPages}|"></span>
|
||||
<a
|
||||
th:if="${archives.hasNext()}"
|
||||
th:href="@{${archives.nextUrl}}"
|
||||
class="pagination__next">较旧 →</a
|
||||
>
|
||||
</nav>
|
||||
<a
|
||||
th:if="${archives.hasPrevious()}"
|
||||
th:href="@{${archives.prevUrl}}"
|
||||
class="wi-archives__page-link wi-archives__page-link--prev"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||
>← 较新</a>
|
||||
<span
|
||||
class="wi-archives__page-info"
|
||||
th:text="|${archives.page} / ${archives.totalPages}|"
|
||||
></span>
|
||||
<a
|
||||
th:if="${archives.hasNext()}"
|
||||
th:href="@{${archives.nextUrl}}"
|
||||
class="wi-archives__page-link wi-archives__page-link--next"
|
||||
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||
>较旧 →</a>
|
||||
</nav>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-archives {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-archives__header {
|
||||
text-align: center;
|
||||
padding-bottom: var(--space-lg);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-archives__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-sm) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-archives__subtitle {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wi-archives__timeline {
|
||||
display: grid;
|
||||
gap: var(--space-3xl);
|
||||
}
|
||||
|
||||
.wi-archives__group {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-archives__group-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-archives__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wi-archives__year-month {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 700;
|
||||
color: var(--ink-2);
|
||||
letter-spacing: -0.01em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wi-archives__items {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-left: var(--space-lg);
|
||||
border-left: 2px solid var(--rule);
|
||||
margin-left: 3px;
|
||||
display: grid;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.wi-archives__entry {
|
||||
display: grid;
|
||||
grid-template-columns: 64px minmax(0, 1fr);
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-md) 0;
|
||||
border-bottom: 1px dashed var(--rule);
|
||||
transition: background var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-archives__entry:hover {
|
||||
background: var(--bg-raised);
|
||||
margin: 0 calc(-1 * var(--space-md));
|
||||
padding-left: var(--space-md);
|
||||
padding-right: var(--space-md);
|
||||
border-radius: $border-radius-sm;
|
||||
}
|
||||
|
||||
.wi-archives__entry:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.wi-archives__entry-date {
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-sm);
|
||||
padding-top: 0.15rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.wi-archives__entry-body {
|
||||
display: grid;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.wi-archives__entry-title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
line-height: var(--leading-snug);
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-archives__entry-title:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-archives__entry-excerpt {
|
||||
margin: 0;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
.wi-archives__empty {
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-md);
|
||||
padding: var(--space-2xl) 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wi-archives__pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-md);
|
||||
padding-top: var(--space-xl);
|
||||
border-top: 1px solid var(--rule);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.wi-archives__page-link {
|
||||
color: var(--ink-2);
|
||||
text-decoration: none;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-archives__page-link:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-archives__page-info {
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-archives__entry {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.wi-archives__items {
|
||||
padding-left: var(--space-md);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+147
-9
@@ -2,21 +2,159 @@
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout pageTitle="分类">
|
||||
<Layout>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|分类 - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<nav class="taxonomy-list" aria-label="Category list">
|
||||
<div th:each="category : ${categories}" class="taxonomy-list__item">
|
||||
<div class="wi-categories">
|
||||
<header class="wi-categories__header">
|
||||
<h1 class="wi-categories__title">分类</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p class="wi-categories__subtitle" th:text="|共 ${categories.size} 个分类|"></p>
|
||||
</header>
|
||||
|
||||
<nav class="wi-categories__list" aria-label="Category list">
|
||||
<a
|
||||
class="taxonomy-list__link"
|
||||
th:each="category : ${categories}"
|
||||
class="wi-categories__item"
|
||||
th:href="@{${category.status.permalink}}"
|
||||
th:text="${category.spec.displayName}"
|
||||
>
|
||||
<div class="wi-categories__item-content">
|
||||
<span
|
||||
class="wi-categories__name"
|
||||
th:text="${category.spec.displayName}"
|
||||
></span>
|
||||
<p
|
||||
th:if="${not #strings.isEmpty(category.spec.description)}"
|
||||
class="wi-categories__desc"
|
||||
th:text="${category.spec.description}"
|
||||
></p>
|
||||
</div>
|
||||
<span
|
||||
class="wi-categories__count"
|
||||
th:text="${category.postCount}"
|
||||
></span>
|
||||
</a>
|
||||
<span class="taxonomy-list__count" th:text="|${category.postCount} 篇|">
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-categories {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-categories__header {
|
||||
text-align: center;
|
||||
padding-bottom: var(--space-lg);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-categories__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-sm) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-categories__subtitle {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wi-categories__list {
|
||||
display: grid;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-categories__item {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: var(--space-lg);
|
||||
padding: var(--space-lg);
|
||||
border-radius: $border-radius-sm;
|
||||
border: 1px solid var(--rule);
|
||||
background: var(--bg);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
align-items: start;
|
||||
transition: all var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-categories__item:hover {
|
||||
border-color: var(--accent);
|
||||
background: var(--bg-raised);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.wi-categories__item-content {
|
||||
display: grid;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.wi-categories__name {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
line-height: var(--leading-snug);
|
||||
}
|
||||
|
||||
.wi-categories__desc {
|
||||
margin: 0;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-normal);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wi-categories__count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0 0.5rem;
|
||||
border-radius: 999px;
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wi-categories__item:hover .wi-categories__count {
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-categories__item {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-categories__count {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+119
-29
@@ -7,34 +7,124 @@ import Layout from "../layouts/Layout.astro";
|
||||
<title th:text="|${category.spec.displayName} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<div class="page-heading">
|
||||
<h1 th:text="${category.spec.displayName}"></h1>
|
||||
<p class="page-meta">
|
||||
<span th:text="|${posts.total} 篇文章|"></span>
|
||||
</p>
|
||||
<div class="wi-category">
|
||||
<header class="wi-category__header">
|
||||
<h1
|
||||
class="wi-category__title"
|
||||
th:text="${category.spec.displayName}"
|
||||
></h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p class="wi-category__subtitle" th:text="${theme.config?.home?.home_label_post_count ?: '共 {total} 篇文章}'.replace('{total}', posts.total)}"></p>
|
||||
<p
|
||||
th:if="${not #strings.isEmpty(category.spec.description)}"
|
||||
class="wi-category__description"
|
||||
th:text="${category.spec.description}"
|
||||
></p>
|
||||
</header>
|
||||
|
||||
<th:block th:replace="~{fragments/post-list}"></th:block>
|
||||
|
||||
<div
|
||||
th:if="${posts.total == 0}"
|
||||
class="wi-category__empty"
|
||||
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章。'}"
|
||||
>暂无文章。</div>
|
||||
|
||||
<nav
|
||||
th:if="${posts.totalPages gt 1}"
|
||||
class="wi-category__pagination"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<a
|
||||
th:if="${posts.hasPrevious()}"
|
||||
th:href="@{${posts.prevUrl}}"
|
||||
class="wi-category__page-link wi-category__page-link--prev"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||
>← 较新</a>
|
||||
<span
|
||||
class="wi-category__page-info"
|
||||
th:text="|${posts.page} / ${posts.totalPages}|"
|
||||
></span>
|
||||
<a
|
||||
th:if="${posts.hasNext()}"
|
||||
th:href="@{${posts.nextUrl}}"
|
||||
class="wi-category__page-link wi-category__page-link--next"
|
||||
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||
>较旧 →</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<th:block th:replace="~{fragments/post-list}"></th:block>
|
||||
|
||||
<div th:if="${posts.total == 0}" class="feed-empty">暂无文章。</div>
|
||||
|
||||
<nav
|
||||
th:if="${posts.totalPages gt 1}"
|
||||
class="pagination"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<a
|
||||
th:if="${posts.hasPrevious()}"
|
||||
th:href="@{${posts.prevUrl}}"
|
||||
class="pagination__prev">← 较新</a
|
||||
>
|
||||
<span
|
||||
class="pagination__info"
|
||||
th:text="|${posts.page} / ${posts.totalPages}|"></span>
|
||||
<a
|
||||
th:if="${posts.hasNext()}"
|
||||
th:href="@{${posts.nextUrl}}"
|
||||
class="pagination__next">较旧 →</a
|
||||
>
|
||||
</nav>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-category {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-category__header {
|
||||
text-align: center;
|
||||
padding-bottom: var(--space-lg);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-category__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-sm) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-category__subtitle {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wi-category__description {
|
||||
margin: var(--space-md) auto 0;
|
||||
max-width: var(--content-max);
|
||||
font-size: var(--text-md);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
.wi-category__empty {
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-md);
|
||||
padding: var(--space-2xl) 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wi-category__pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-md);
|
||||
padding-top: var(--space-xl);
|
||||
border-top: 1px solid var(--rule);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.wi-category__page-link {
|
||||
color: var(--ink-2);
|
||||
text-decoration: none;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-category__page-link:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-category__page-info {
|
||||
color: var(--ink-3);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout wide>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${theme.config?.equipment?.equipment_page_title ?: '装备'} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<section
|
||||
class="wi-equipment"
|
||||
th:if="${pluginFinder.available('equipment')}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-equipment__header">
|
||||
<h1
|
||||
class="wi-equipment__title"
|
||||
th:text="${theme.config?.equipment?.equipment_page_title ?: '装备'}"
|
||||
>
|
||||
装备
|
||||
</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
</div>
|
||||
|
||||
<th:block th:if="${equipmentFinder != null}">
|
||||
<th:block th:each="group : ${equipmentFinder.listByGroup()}">
|
||||
<h2
|
||||
class="wi-equipment__group"
|
||||
th:if="${group.spec?.displayName != null and !#strings.isEmpty(group.spec?.displayName)}"
|
||||
th:text="${group.spec?.displayName}"
|
||||
></h2>
|
||||
<div class="wi-equipment__grid">
|
||||
<div
|
||||
class="wi-equipment__card"
|
||||
th:each="equip : ${group.equipments}"
|
||||
>
|
||||
<img
|
||||
th:if="${equip.spec?.cover}"
|
||||
th:src="${equip.spec?.cover}"
|
||||
th:alt="${equip.spec?.displayName}"
|
||||
class="wi-equipment__avatar"
|
||||
/>
|
||||
<div
|
||||
th:unless="${equip.spec?.cover}"
|
||||
class="wi-equipment__avatar wi-equipment__avatar--placeholder"
|
||||
>
|
||||
<span
|
||||
th:text="${#strings.substring(equip.spec?.displayName ?: '', 0, 1)}"
|
||||
></span>
|
||||
</div>
|
||||
<div class="wi-equipment__info">
|
||||
<span
|
||||
class="wi-equipment__name"
|
||||
th:text="${equip.spec?.displayName}"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<th:block th:if="${equipmentFinder == null}">
|
||||
<div class="wi-equipment__empty">
|
||||
<p>装备插件加载异常,请检查插件状态</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="wi-equipment"
|
||||
th:unless="${pluginFinder.available('equipment')}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-equipment__header">
|
||||
<h1 class="wi-equipment__title">装备</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
</div>
|
||||
<div class="wi-equipment__empty">
|
||||
<p>装备功能需要安装「装备管理」插件,<a href="https://www.halo.run/store/apps/app-ytygyqml">前往应用市场安装</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-equipment {
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-equipment__header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-3xl);
|
||||
}
|
||||
|
||||
.wi-equipment__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-sm) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-equipment__empty {
|
||||
text-align: center;
|
||||
padding: var(--space-4xl) var(--space-xl);
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
.wi-equipment__empty a {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-equipment__group {
|
||||
font-size: var(--text-xl);
|
||||
color: var(--ink-2);
|
||||
margin-bottom: var(--space-lg);
|
||||
padding-bottom: var(--space-sm);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-equipment__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: var(--space-lg);
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
.wi-equipment__card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-lg);
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 16px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition:
|
||||
transform var(--duration-normal) var(--ease-out-expo),
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
border-color var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-equipment__card:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
border-color: var(--accent);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.wi-equipment__avatar {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 16px;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.wi-equipment__avatar--placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--accent-bg);
|
||||
color: var(--accent);
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 700;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.wi-equipment__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wi-equipment__name {
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.wi-equipment__grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-equipment__card {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,310 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout wide>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${theme.config?.friends?.friends_page_title ?: '朋友圈'} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<section
|
||||
class="wi-friends-page"
|
||||
th:if="${pluginFinder.available('plugin-friends')}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-friends-page__header">
|
||||
<h1
|
||||
class="wi-friends-page__title"
|
||||
th:text="${theme.config?.friends?.friends_page_title ?: '朋友圈'}"
|
||||
>
|
||||
朋友圈
|
||||
</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
</div>
|
||||
|
||||
<div class="wi-friends-page__flow">
|
||||
<div
|
||||
class="wi-friends-page__item"
|
||||
th:each="friend : ${friends.items}"
|
||||
th:with="spec = ${friend.spec}"
|
||||
>
|
||||
<div class="wi-friends-page__card">
|
||||
<div class="wi-friends-page__author">
|
||||
<img
|
||||
th:if="${spec.logo}"
|
||||
th:src="${spec.logo}"
|
||||
th:alt="${spec.author}"
|
||||
class="wi-friends-page__avatar"
|
||||
/>
|
||||
<div
|
||||
th:unless="${spec.logo}"
|
||||
class="wi-friends-page__avatar wi-friends-page__avatar--placeholder"
|
||||
>
|
||||
<span
|
||||
th:text="${#strings.substring(spec.author ?: '', 0, 1)}"
|
||||
></span>
|
||||
</div>
|
||||
<div class="wi-friends-page__author-info">
|
||||
<a
|
||||
th:if="${spec.author}"
|
||||
th:href="${spec.authorUrl}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="wi-friends-page__author-name"
|
||||
th:text="${spec.author}"
|
||||
></a>
|
||||
<span
|
||||
class="wi-friends-page__author-bio"
|
||||
th:if="${spec.pubDate}"
|
||||
th:text="${#temporals.format(spec.pubDate, 'yyyy-MM-dd')}"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wi-friends-page__post">
|
||||
<a
|
||||
th:if="${spec.postLink}"
|
||||
th:href="${spec.postLink}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="wi-friends-page__post-title"
|
||||
th:text="${spec.title}"
|
||||
></a>
|
||||
<p
|
||||
class="wi-friends-page__post-content"
|
||||
th:if="${spec.description}"
|
||||
th:text="${spec.description}"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="wi-friends-page__pagination"
|
||||
th:if="${friends.hasPrevious() || friends.hasNext()}"
|
||||
>
|
||||
<a
|
||||
th:if="${friends.hasPrevious()}"
|
||||
th:href="@{${friends.prevUrl}}"
|
||||
class="wi-friends-page__page-link"
|
||||
>
|
||||
← 上一页
|
||||
</a>
|
||||
<span
|
||||
class="wi-friends-page__page-info"
|
||||
th:text="${friends.page} + ' / ' + ${friends.totalPages}"
|
||||
></span>
|
||||
<a
|
||||
th:if="${friends.hasNext()}"
|
||||
th:href="@{${friends.nextUrl}}"
|
||||
class="wi-friends-page__page-link"
|
||||
>
|
||||
下一页 →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="wi-friends-page"
|
||||
th:unless="${pluginFinder.available('plugin-friends')}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-friends-page__header">
|
||||
<h1 class="wi-friends-page__title">朋友圈</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
</div>
|
||||
<div class="wi-friends-page__empty">
|
||||
<p>朋友圈功能需要安装「朋友圈」插件,<a href="https://www.halo.run/store/apps/app-friends">前往应用市场安装</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-friends-page {
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-friends-page__header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-3xl);
|
||||
}
|
||||
|
||||
.wi-friends-page__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-sm) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-friends-page__empty {
|
||||
text-align: center;
|
||||
padding: var(--space-4xl) var(--space-xl);
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
.wi-friends-page__empty a {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-friends-page__flow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-lg);
|
||||
max-width: 720px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.wi-friends-page__card {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 16px;
|
||||
padding: var(--space-xl);
|
||||
transition:
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
transform var(--duration-normal) var(--ease-out-expo),
|
||||
border-color var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-friends-page__card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-friends-page__author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-friends-page__avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 9999px;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.wi-friends-page__avatar--placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--accent-bg);
|
||||
color: var(--accent);
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-md);
|
||||
font-weight: 700;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.wi-friends-page__author-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wi-friends-page__author-name {
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-friends-page__author-name:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-friends-page__author-bio {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wi-friends-page__post {
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-friends-page__post-title {
|
||||
display: block;
|
||||
font-size: var(--text-base);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
margin-bottom: var(--space-xs);
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-friends-page__post-title:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-friends-page__post-content {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-relaxed);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
.wi-friends-page__pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-lg);
|
||||
margin-top: var(--space-2xl);
|
||||
padding-top: var(--space-xl);
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-friends-page__page-link {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 500;
|
||||
transition: opacity var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-friends-page__page-link:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.wi-friends-page__page-info {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.wi-friends-page__card {
|
||||
padding: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-friends-page__avatar {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+554
-20
@@ -1,31 +1,565 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import HeroSection from "../components/HeroSection.astro";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Layout home>
|
||||
<Fragment slot="head">
|
||||
<title th:text="${site.title}"></title>
|
||||
</Fragment>
|
||||
|
||||
<th:block th:replace="~{fragments/post-list}"></th:block>
|
||||
<HeroSection />
|
||||
|
||||
<nav
|
||||
th:if="${posts.totalPages gt 1}"
|
||||
class="pagination"
|
||||
aria-label="Pagination"
|
||||
<section
|
||||
class="wi-home-pinned"
|
||||
th:if="${posts != null and !#lists.isEmpty(posts.items)}"
|
||||
>
|
||||
<a
|
||||
th:if="${posts.hasPrevious()}"
|
||||
th:href="@{${posts.prevUrl}}"
|
||||
class="pagination__prev">← 较新</a
|
||||
>
|
||||
<span
|
||||
class="pagination__info"
|
||||
th:text="|${posts.page} / ${posts.totalPages}|"></span>
|
||||
<a
|
||||
th:if="${posts.hasNext()}"
|
||||
th:href="@{${posts.nextUrl}}"
|
||||
class="pagination__next">较旧 →</a
|
||||
>
|
||||
</nav>
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_pinned_title ?: '置顶'}">置顶</h2>
|
||||
</div>
|
||||
<div class="wi-pinned">
|
||||
<a
|
||||
th:each="post : ${posts.items}"
|
||||
th:if="${post.spec.pinned}"
|
||||
th:href="@{${post.status.permalink}}"
|
||||
class="wi-pinned__card"
|
||||
>
|
||||
<div
|
||||
class="wi-pinned__cover"
|
||||
th:if="${!#strings.isEmpty(post.spec.cover)}"
|
||||
>
|
||||
<img th:src="${post.spec.cover}" th:alt="${post.spec.title}" class="wi-pinned__image" />
|
||||
</div>
|
||||
<div class="wi-pinned__body">
|
||||
<div class="wi-pinned__meta">
|
||||
<span
|
||||
class="wi-pinned__category"
|
||||
th:if="${!#lists.isEmpty(post.categories)}"
|
||||
th:text="${post.categories[0].spec.displayName}"
|
||||
></span>
|
||||
<time
|
||||
class="wi-pinned__date"
|
||||
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
|
||||
></time>
|
||||
</div>
|
||||
<h3
|
||||
class="wi-pinned__title"
|
||||
th:text="${post.spec.title}"
|
||||
></h3>
|
||||
<p
|
||||
class="wi-pinned__excerpt"
|
||||
th:if="${post.status.excerpt}"
|
||||
th:text="${post.status.excerpt}"
|
||||
></p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="wi-home-posts" id="wi-home-posts">
|
||||
<div class="wi-container">
|
||||
<div class="wi-flow" id="wi-post-grid">
|
||||
<a
|
||||
th:each="post : ${posts.items}"
|
||||
th:if="${!post.spec.pinned}"
|
||||
th:href="@{${post.status.permalink}}"
|
||||
class="wi-flow__card"
|
||||
th:classappend="${!#strings.isEmpty(post.spec.cover)} ? 'wi-flow__card--with-cover' : ''"
|
||||
>
|
||||
<div class="wi-flow__body">
|
||||
<div class="wi-flow__meta">
|
||||
<span
|
||||
class="wi-flow__category"
|
||||
th:if="${!#lists.isEmpty(post.categories)}"
|
||||
th:text="${post.categories[0].spec.displayName}"
|
||||
></span>
|
||||
<time
|
||||
class="wi-flow__date"
|
||||
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
|
||||
></time>
|
||||
</div>
|
||||
<h3
|
||||
class="wi-flow__title"
|
||||
th:text="${post.spec.title}"
|
||||
></h3>
|
||||
<p
|
||||
class="wi-flow__excerpt"
|
||||
th:if="${post.status.excerpt}"
|
||||
th:text="${post.status.excerpt}"
|
||||
th:style="'-webkit-line-clamp:' + (${theme.config?.home?.home_excerpt_lines ?: 3})"
|
||||
></p>
|
||||
</div>
|
||||
<div
|
||||
class="wi-flow__cover"
|
||||
th:if="${!#strings.isEmpty(post.spec.cover)}"
|
||||
>
|
||||
<img th:src="${post.spec.cover}" th:alt="${post.spec.title}" class="wi-flow__image" />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav
|
||||
th:if="${posts.totalPages gt 1 and theme.config?.home?.home_post_loading != 'infinite_scroll'}"
|
||||
class="wi-pagination"
|
||||
id="wi-post-pagination"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<a
|
||||
th:if="${posts.hasPrevious()}"
|
||||
th:href="@{${posts.prevUrl}}"
|
||||
class="wi-pagination__prev"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||
>← 较新</a>
|
||||
<span
|
||||
class="wi-pagination__info"
|
||||
th:text="|${posts.page} / ${posts.totalPages}|"
|
||||
></span>
|
||||
<a
|
||||
th:if="${posts.hasNext()}"
|
||||
th:href="@{${posts.nextUrl}}"
|
||||
class="wi-pagination__next"
|
||||
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||
>较旧 →</a>
|
||||
</nav>
|
||||
|
||||
<div
|
||||
th:if="${posts.hasNext() and theme.config?.home?.home_post_loading == 'infinite_scroll'}"
|
||||
id="wi-infinite-scroll-sentinel"
|
||||
th:attr="data-next-url=${posts.nextUrl}, data-all-loaded-text=${theme.config?.home?.home_label_all_loaded ?: '已加载全部文章'}"
|
||||
>
|
||||
<div class="wi-infinite-scroll__loader">
|
||||
<div class="wi-infinite-scroll__spinner"></div>
|
||||
<span th:text="${theme.config?.home?.home_label_loading ?: '加载中...'}">加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
th:if="${!posts.hasNext() and posts.totalPages gt 1 and theme.config?.home?.home_post_loading == 'infinite_scroll'}"
|
||||
class="wi-infinite-scroll__end"
|
||||
>
|
||||
<span th:text="${theme.config?.home?.home_label_all_loaded ?: '已加载全部文章'}">已加载全部文章</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
var sentinel = document.getElementById("wi-infinite-scroll-sentinel");
|
||||
if (!sentinel) return;
|
||||
|
||||
var grid = document.getElementById("wi-post-grid");
|
||||
var loading = false;
|
||||
var nextUrl = sentinel.getAttribute("data-next-url");
|
||||
|
||||
var observer = new IntersectionObserver(
|
||||
function (entries) {
|
||||
if (entries[0].isIntersecting && !loading && nextUrl) {
|
||||
loading = true;
|
||||
var loader = sentinel.querySelector(".wi-infinite-scroll__loader");
|
||||
if (loader) loader.style.display = "flex";
|
||||
|
||||
fetch(nextUrl)
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error("Fetch failed");
|
||||
return res.text();
|
||||
})
|
||||
.then(function (html) {
|
||||
var parser = new DOMParser();
|
||||
var doc = parser.parseFromString(html, "text/html");
|
||||
|
||||
var newCards = doc.querySelectorAll("#wi-post-grid .wi-flow__card");
|
||||
for (var i = 0; i < newCards.length; i++) {
|
||||
var card = newCards[i];
|
||||
card.style.opacity = "0";
|
||||
card.style.transform = "translateY(20px)";
|
||||
grid.appendChild(card);
|
||||
(function (c) {
|
||||
requestAnimationFrame(function () {
|
||||
c.style.transition = "opacity 0.4s ease, transform 0.4s ease";
|
||||
c.style.opacity = "1";
|
||||
c.style.transform = "translateY(0)";
|
||||
});
|
||||
})(card);
|
||||
}
|
||||
|
||||
var newSentinel = doc.getElementById("wi-infinite-scroll-sentinel");
|
||||
if (newSentinel && newSentinel.getAttribute("data-next-url")) {
|
||||
nextUrl = newSentinel.getAttribute("data-next-url");
|
||||
} else {
|
||||
nextUrl = null;
|
||||
observer.disconnect();
|
||||
sentinel.style.display = "none";
|
||||
var endMsg = document.createElement("div");
|
||||
endMsg.className = "wi-infinite-scroll__end";
|
||||
endMsg.innerHTML = "<span>" + allLoadedText + "</span>";
|
||||
sentinel.parentNode.appendChild(endMsg);
|
||||
}
|
||||
|
||||
loading = false;
|
||||
if (loader) loader.style.display = "none";
|
||||
})
|
||||
.catch(function () {
|
||||
loading = false;
|
||||
if (loader) loader.style.display = "none";
|
||||
});
|
||||
}
|
||||
},
|
||||
{ rootMargin: "300px" }
|
||||
);
|
||||
|
||||
observer.observe(sentinel);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.wi-home-pinned {
|
||||
padding-block: 2rem;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.wi-home-pinned:not(:has(.wi-pinned__card)) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wi-section__header {
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.wi-section__title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.wi-section__title::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wi-pinned {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-pinned__card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background: var(--bg-raised);
|
||||
box-shadow: var(--shadow-sm);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition:
|
||||
transform var(--duration-normal) var(--ease-out-expo),
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-pinned__card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.wi-pinned__card:first-child:nth-last-child(1) {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.wi-pinned__card:first-child:nth-last-child(1) .wi-pinned__cover {
|
||||
aspect-ratio: 21 / 9;
|
||||
}
|
||||
|
||||
.wi-pinned__card:first-child:nth-last-child(1) .wi-pinned__title {
|
||||
font-size: var(--text-2xl);
|
||||
}
|
||||
|
||||
.wi-pinned__cover {
|
||||
aspect-ratio: 16 / 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wi-pinned__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.wi-pinned__card:hover .wi-pinned__image {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.wi-pinned__body {
|
||||
padding: var(--space-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.wi-pinned__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.wi-pinned__category {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--accent);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wi-pinned__category::after {
|
||||
content: '·';
|
||||
margin-left: var(--space-sm);
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.wi-pinned__date {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wi-pinned__title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-xl);
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-pinned__card:hover .wi-pinned__title {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-pinned__excerpt {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-relaxed);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-pinned {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.wi-pinned__card:first-child:nth-last-child(1) .wi-pinned__cover {
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
.wi-pinned__card:first-child:nth-last-child(1) .wi-pinned__title {
|
||||
font-size: var(--text-xl);
|
||||
}
|
||||
}
|
||||
|
||||
.wi-home-posts {
|
||||
padding-block: 2rem;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.wi-flow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-flow__card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background: var(--bg-raised);
|
||||
box-shadow: var(--shadow-sm);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition:
|
||||
transform var(--duration-normal) var(--ease-out-expo),
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-flow__card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.wi-flow__card--with-cover .wi-flow__body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wi-flow__cover {
|
||||
width: 240px;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wi-flow__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.wi-flow__card:hover .wi-flow__image {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.wi-flow__body {
|
||||
padding: var(--space-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wi-flow__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.wi-flow__category {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--accent);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wi-flow__category::after {
|
||||
content: '·';
|
||||
margin-left: var(--space-sm);
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.wi-flow__date {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wi-flow__title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-flow__card:hover .wi-flow__title {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-flow__excerpt {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-relaxed);
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
.wi-pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding-top: var(--space-xl);
|
||||
margin-top: var(--space-xl);
|
||||
border-top: 1px solid var(--rule);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.wi-pagination__prev,
|
||||
.wi-pagination__next {
|
||||
color: var(--ink-2);
|
||||
text-decoration: none;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-pagination__prev:hover,
|
||||
.wi-pagination__next:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-pagination__info {
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.wi-infinite-scroll__loader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-xl) 0;
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.wi-infinite-scroll__spinner {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid var(--rule);
|
||||
border-top-color: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: wi-spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes wi-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.wi-infinite-scroll__end {
|
||||
text-align: center;
|
||||
padding: var(--space-xl) 0;
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.wi-flow__card {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wi-flow__cover {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout wide>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${theme.config?.links?.links_page_title ?: '友情链接'} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<section
|
||||
class="wi-links"
|
||||
th:if="${pluginFinder.available('PluginLinks')}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-links__header">
|
||||
<h1
|
||||
class="wi-links__title"
|
||||
th:text="${theme.config?.links?.links_page_title ?: '友情链接'}"
|
||||
>
|
||||
友情链接
|
||||
</h1>
|
||||
<p class="wi-links__subtitle">山水一程,三生有幸</p>
|
||||
<span class="wi-page-accent"></span>
|
||||
</div>
|
||||
|
||||
<th:block th:each="group : ${groups}">
|
||||
<h2
|
||||
class="wi-links__group"
|
||||
th:if="${group.spec.displayName != null and !#strings.isEmpty(group.spec.displayName)}"
|
||||
th:text="${group.spec.displayName}"
|
||||
></h2>
|
||||
<div class="wi-links__grid">
|
||||
<a
|
||||
th:each="link : ${group.links}"
|
||||
th:href="${link.spec.url}"
|
||||
th:title="${link.spec.displayName}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="wi-links__card"
|
||||
>
|
||||
<div class="wi-links__card-glow"></div>
|
||||
<div class="wi-links__card-inner">
|
||||
<img
|
||||
th:if="${link.spec.logo}"
|
||||
th:src="${link.spec.logo}"
|
||||
th:alt="${link.spec.displayName}"
|
||||
class="wi-links__avatar"
|
||||
/>
|
||||
<div
|
||||
th:unless="${link.spec.logo}"
|
||||
class="wi-links__avatar wi-links__avatar--placeholder"
|
||||
>
|
||||
<span
|
||||
th:text="${#strings.substring(link.spec.displayName, 0, 1)}"
|
||||
></span>
|
||||
</div>
|
||||
<div class="wi-links__info">
|
||||
<span
|
||||
class="wi-links__name"
|
||||
th:text="${link.spec.displayName}"
|
||||
></span>
|
||||
<span
|
||||
class="wi-links__desc"
|
||||
th:if="${link.spec.description}"
|
||||
th:text="${link.spec.description}"
|
||||
></span>
|
||||
</div>
|
||||
<svg class="wi-links__arrow" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17L17 7"/><path d="M7 7h10v10"/></svg>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-links {
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-links__header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-3xl);
|
||||
}
|
||||
|
||||
.wi-links__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.wi-links__subtitle {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-md) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-links__group {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--ink-2);
|
||||
margin-bottom: var(--space-lg);
|
||||
padding-bottom: var(--space-sm);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wi-links__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: var(--space-md);
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
.wi-links__card {
|
||||
position: relative;
|
||||
border-radius: 16px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wi-links__card-glow {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 16px;
|
||||
opacity: 0;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
color-mix(in srgb, var(--accent) 8%, transparent) 0%,
|
||||
transparent 60%
|
||||
);
|
||||
transition: opacity var(--duration-normal) var(--ease-out-expo);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.wi-links__card:hover .wi-links__card-glow {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.wi-links__card-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-lg);
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 16px;
|
||||
transition:
|
||||
transform var(--duration-normal) var(--ease-out-expo),
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
border-color var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-links__card:hover .wi-links__card-inner {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
border-color: color-mix(in srgb, var(--accent) 40%, var(--glass-border));
|
||||
}
|
||||
|
||||
.wi-links__avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-links__card:hover .wi-links__avatar {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.wi-links__avatar--placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--accent-bg);
|
||||
color: var(--accent);
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 700;
|
||||
box-shadow: none;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.wi-links__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.wi-links__name {
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-links__card:hover .wi-links__name {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-links__desc {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
line-height: var(--leading-snug);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wi-links__arrow {
|
||||
flex-shrink: 0;
|
||||
color: var(--ink-3);
|
||||
opacity: 0;
|
||||
transform: translate(-4px, 4px);
|
||||
transition:
|
||||
opacity var(--duration-normal) var(--ease-out-expo),
|
||||
transform var(--duration-normal) var(--ease-out-expo),
|
||||
color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-links__card:hover .wi-links__arrow {
|
||||
opacity: 1;
|
||||
transform: translate(0, 0);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.wi-links__grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-links__card-inner {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-links__avatar {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,735 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import LightGallery from "../components/LightGallery.astro";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${theme.config?.moments?.moments_page_title ?: '瞬间'} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<section class="wi-moments-page">
|
||||
<div class="wi-container">
|
||||
<div class="wi-moments-page__header">
|
||||
<h1 class="wi-moments-page__title" th:text="${theme.config?.moments?.moments_page_title ?: '瞬间'}">瞬间</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
</div>
|
||||
|
||||
<th:block th:if="${pluginFinder.available('PluginMoments')}">
|
||||
<div class="wi-moments-page__content">
|
||||
<th:block th:if="${momentFinder != null}">
|
||||
<th:block th:with="momentsResult = ${momentFinder.list(1, 50)}">
|
||||
<div
|
||||
class="wi-moments-page__timeline"
|
||||
th:if="${theme.config?.moments?.moments_style == 'timeline' or theme.config?.moments?.moments_style == null}"
|
||||
>
|
||||
<th:block th:if="${momentsResult != null and not #lists.isEmpty(momentsResult.items)}">
|
||||
<div class="wi-moments-page__item" th:each="moment : ${momentsResult.items}">
|
||||
<div class="wi-moments-page__dot"></div>
|
||||
<div class="wi-moments-page__line"></div>
|
||||
<div class="wi-moments-page__card">
|
||||
<div class="wi-moments-page__text" th:utext="${moment.spec?.content?.html ?: moment.spec?.content?.raw ?: moment.spec?.content}"></div>
|
||||
<div class="wi-moments-page__media" th:if="${moment.spec.content.medium != null and !#lists.isEmpty(moment.spec.content.medium)}">
|
||||
<th:block th:each="media : ${moment.spec.content.medium}">
|
||||
<img
|
||||
th:if="${media.type.name == 'PHOTO'}"
|
||||
th:src="${media.url}"
|
||||
th:alt="''"
|
||||
class="wi-moments-page__image"
|
||||
loading="lazy"
|
||||
/>
|
||||
</th:block>
|
||||
</div>
|
||||
<div class="wi-moments-page__footer">
|
||||
<time
|
||||
class="wi-moments-page__date"
|
||||
th:text="${#dates.format(moment.spec?.releaseTime ?: moment.metadata?.creationTimestamp, 'yyyy-MM-dd HH:mm')}"
|
||||
></time>
|
||||
<div class="wi-moments-page__actions">
|
||||
<button
|
||||
class="wi-moments-page__like-btn"
|
||||
type="button"
|
||||
th:attr="data-moment-name=${moment.metadata.name}"
|
||||
>
|
||||
<svg class="wi-moments-page__like-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>
|
||||
<span class="wi-moments-page__like-count" th:text="${moment.stats?.upvote ?: 0}">0</span>
|
||||
</button>
|
||||
<button
|
||||
class="wi-moments-page__comment-btn"
|
||||
type="button"
|
||||
th:attr="data-moment-name=${moment.metadata.name}"
|
||||
>
|
||||
<svg class="wi-moments-page__comment-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
<span class="wi-moments-page__comment-count" th:text="${moment.stats?.approvedComment ?: 0}">0</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wi-moments-page__comment-area" th:attr="data-moment-name=${moment.metadata.name}" style="display:none;">
|
||||
<halo:comment
|
||||
group="moment.halo.run"
|
||||
kind="Moment"
|
||||
th:attr="name=${moment.metadata.name}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
|
||||
<div class="wi-moments-page__empty">
|
||||
<p>还没有瞬间,快去记录当下的心情吧 🌙</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
|
||||
<div class="wi-moments-page__cards" th:if="${theme.config?.moments?.moments_style == 'cards'}">
|
||||
<th:block th:if="${momentsResult != null and not #lists.isEmpty(momentsResult.items)}">
|
||||
<div class="wi-moments-page__card-item" th:each="moment : ${momentsResult.items}">
|
||||
<div class="wi-moments-page__card-text" th:utext="${moment.spec?.content?.html ?: moment.spec?.content?.raw ?: moment.spec?.content}"></div>
|
||||
<div class="wi-moments-page__card-media" th:if="${moment.spec.content.medium != null and !#lists.isEmpty(moment.spec.content.medium)}">
|
||||
<th:block th:each="media : ${moment.spec.content.medium}">
|
||||
<img
|
||||
th:if="${media.type.name == 'PHOTO'}"
|
||||
th:src="${media.url}"
|
||||
th:alt="''"
|
||||
class="wi-moments-page__card-image"
|
||||
loading="lazy"
|
||||
/>
|
||||
</th:block>
|
||||
</div>
|
||||
<div class="wi-moments-page__card-footer">
|
||||
<time
|
||||
class="wi-moments-page__card-date"
|
||||
th:text="${#dates.format(moment.spec?.releaseTime ?: moment.metadata?.creationTimestamp, 'yyyy-MM-dd HH:mm')}"
|
||||
></time>
|
||||
<div class="wi-moments-page__actions">
|
||||
<button
|
||||
class="wi-moments-page__like-btn"
|
||||
type="button"
|
||||
th:attr="data-moment-name=${moment.metadata.name}"
|
||||
>
|
||||
<svg class="wi-moments-page__like-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>
|
||||
<span class="wi-moments-page__like-count" th:text="${moment.stats?.upvote ?: 0}">0</span>
|
||||
</button>
|
||||
<button
|
||||
class="wi-moments-page__comment-btn"
|
||||
type="button"
|
||||
th:attr="data-moment-name=${moment.metadata.name}"
|
||||
>
|
||||
<svg class="wi-moments-page__comment-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
<span class="wi-moments-page__comment-count" th:text="${moment.stats?.approvedComment ?: 0}">0</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wi-moments-page__comment-area" th:attr="data-moment-name=${moment.metadata.name}" style="display:none;">
|
||||
<halo:comment
|
||||
group="moment.halo.run"
|
||||
kind="Moment"
|
||||
th:attr="name=${moment.metadata.name}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
|
||||
<div class="wi-moments-page__empty">
|
||||
<p>还没有瞬间,快去记录当下的心情吧 🌙</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
|
||||
<div class="wi-moments-page__masonry" th:if="${theme.config?.moments?.moments_style == 'masonry'}">
|
||||
<th:block th:if="${momentsResult != null and not #lists.isEmpty(momentsResult.items)}">
|
||||
<div class="wi-moments-page__masonry-item" th:each="moment : ${momentsResult.items}">
|
||||
<div class="wi-moments-page__masonry-text" th:utext="${moment.spec?.content?.html ?: moment.spec?.content?.raw ?: moment.spec?.content}"></div>
|
||||
<div class="wi-moments-page__masonry-media" th:if="${moment.spec.content.medium != null and !#lists.isEmpty(moment.spec.content.medium)}">
|
||||
<th:block th:each="media : ${moment.spec.content.medium}">
|
||||
<img
|
||||
th:if="${media.type.name == 'PHOTO'}"
|
||||
th:src="${media.url}"
|
||||
th:alt="''"
|
||||
class="wi-moments-page__masonry-image"
|
||||
loading="lazy"
|
||||
/>
|
||||
</th:block>
|
||||
</div>
|
||||
<div class="wi-moments-page__masonry-footer">
|
||||
<time
|
||||
class="wi-moments-page__masonry-date"
|
||||
th:text="${#dates.format(moment.spec?.releaseTime ?: moment.metadata?.creationTimestamp, 'yyyy-MM-dd HH:mm')}"
|
||||
></time>
|
||||
<div class="wi-moments-page__actions">
|
||||
<button
|
||||
class="wi-moments-page__like-btn"
|
||||
type="button"
|
||||
th:attr="data-moment-name=${moment.metadata.name}"
|
||||
>
|
||||
<svg class="wi-moments-page__like-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>
|
||||
<span class="wi-moments-page__like-count" th:text="${moment.stats?.upvote ?: 0}">0</span>
|
||||
</button>
|
||||
<button
|
||||
class="wi-moments-page__comment-btn"
|
||||
type="button"
|
||||
th:attr="data-moment-name=${moment.metadata.name}"
|
||||
>
|
||||
<svg class="wi-moments-page__comment-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
<span class="wi-moments-page__comment-count" th:text="${moment.stats?.approvedComment ?: 0}">0</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wi-moments-page__comment-area" th:attr="data-moment-name=${moment.metadata.name}" style="display:none;">
|
||||
<halo:comment
|
||||
group="moment.halo.run"
|
||||
kind="Moment"
|
||||
th:attr="name=${moment.metadata.name}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
|
||||
<div class="wi-moments-page__empty">
|
||||
<p>还没有瞬间,快去记录当下的心情吧 🌙</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
</th:block>
|
||||
</th:block>
|
||||
<th:block th:if="${momentFinder == null}">
|
||||
<div class="wi-moments-page__empty">
|
||||
<p>瞬间插件加载异常,请检查插件状态</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block th:unless="${pluginFinder.available('PluginMoments')}">
|
||||
<div class="wi-moments-page__empty">
|
||||
<p>瞬间功能需要安装「瞬间」插件,<a href="https://www.halo.run/store/apps/app-hqbe">前往应用市场安装</a></p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<LightGallery />
|
||||
</Layout>
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
document.addEventListener("click", function (e) {
|
||||
var likeBtn = e.target.closest(".wi-moments-page__like-btn");
|
||||
if (likeBtn) {
|
||||
handleLike(likeBtn);
|
||||
return;
|
||||
}
|
||||
|
||||
var commentBtn = e.target.closest(".wi-moments-page__comment-btn");
|
||||
if (commentBtn) {
|
||||
toggleComment(commentBtn);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
function handleLike(btn) {
|
||||
var momentName = btn.getAttribute("data-moment-name");
|
||||
var likedKey = "wi-moment-liked-" + momentName;
|
||||
var countEl = btn.querySelector(".wi-moments-page__like-count");
|
||||
|
||||
if (localStorage.getItem(likedKey)) return;
|
||||
if (btn.classList.contains("wi-moments-page__like-btn--liked")) return;
|
||||
|
||||
fetch("/apis/api.halo.run/v1alpha1/trackers/upvote", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json;charset=UTF-8" },
|
||||
body: JSON.stringify({
|
||||
group: "moment.halo.run",
|
||||
plural: "moments",
|
||||
name: momentName,
|
||||
}),
|
||||
})
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error("Like failed");
|
||||
btn.classList.add("wi-moments-page__like-btn--liked");
|
||||
if (countEl) {
|
||||
countEl.textContent = parseInt(countEl.textContent || "0") + 1;
|
||||
}
|
||||
localStorage.setItem(likedKey, "1");
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error("Like error:", err);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleComment(btn) {
|
||||
var momentName = btn.getAttribute("data-moment-name");
|
||||
var commentArea = document.querySelector(
|
||||
'.wi-moments-page__comment-area[data-moment-name="' + momentName + '"]'
|
||||
);
|
||||
if (!commentArea) return;
|
||||
|
||||
if (commentArea.style.display === "none") {
|
||||
commentArea.style.display = "block";
|
||||
btn.classList.add("wi-moments-page__comment-btn--active");
|
||||
} else {
|
||||
commentArea.style.display = "none";
|
||||
btn.classList.remove("wi-moments-page__comment-btn--active");
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll(".wi-moments-page__like-btn").forEach(function (btn) {
|
||||
var momentName = btn.getAttribute("data-moment-name");
|
||||
var likedKey = "wi-moment-liked-" + momentName;
|
||||
if (localStorage.getItem(likedKey)) {
|
||||
btn.classList.add("wi-moments-page__like-btn--liked");
|
||||
}
|
||||
});
|
||||
|
||||
function loadMomentStats() {
|
||||
fetch("/apis/api.moment.halo.run/v1alpha1/moments")
|
||||
.then(function (res) {
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
if (!data || !data.items) return;
|
||||
data.items.forEach(function (moment) {
|
||||
var name = moment.metadata.name;
|
||||
var stats = moment.stats || {};
|
||||
|
||||
document.querySelectorAll(".wi-moments-page__like-btn").forEach(function (btn) {
|
||||
if (btn.getAttribute("data-moment-name") === name) {
|
||||
var countEl = btn.querySelector(".wi-moments-page__like-count");
|
||||
if (countEl) countEl.textContent = stats.upvote || 0;
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll(".wi-moments-page__comment-btn").forEach(function (btn) {
|
||||
if (btn.getAttribute("data-moment-name") === name) {
|
||||
var countEl = btn.querySelector(".wi-moments-page__comment-count");
|
||||
if (countEl) countEl.textContent = stats.approvedComment || 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function () {});
|
||||
}
|
||||
|
||||
loadMomentStats();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.wi-moments-page {
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-moments-page__header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-3xl);
|
||||
}
|
||||
|
||||
.wi-moments-page__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-sm) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-moments-page__empty {
|
||||
text-align: center;
|
||||
padding: var(--space-4xl) var(--space-xl);
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
.wi-moments-page__empty a {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-moments-page__timeline {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-xl);
|
||||
padding-left: var(--space-2xl);
|
||||
}
|
||||
|
||||
.wi-moments-page__item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wi-moments-page__dot {
|
||||
position: absolute;
|
||||
left: calc(-1 * var(--space-2xl) - 5px);
|
||||
top: 10px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--bg), 0 0 0 4px var(--accent);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.wi-moments-page__line {
|
||||
position: absolute;
|
||||
left: calc(-1 * var(--space-2xl));
|
||||
top: 20px;
|
||||
bottom: calc(-1 * var(--space-xl));
|
||||
width: 1px;
|
||||
background: var(--rule);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.wi-moments-page__item:last-child .wi-moments-page__line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wi-moments-page__card {
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 16px;
|
||||
padding: var(--space-xl);
|
||||
transition:
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
transform var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-moments-page__card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.wi-moments-page__text {
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-relaxed);
|
||||
color: var(--ink);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-moments-page__media {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 6px;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-moments-page__image {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.wi-moments-page__image:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.wi-moments-page__media:has(.wi-moments-page__image:only-child) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.wi-moments-page__media:has(.wi-moments-page__image:only-child) .wi-moments-page__image {
|
||||
aspect-ratio: auto;
|
||||
max-height: 300px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.wi-moments-page__media:has(.wi-moments-page__image:nth-child(2):last-child) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.wi-moments-page__media:has(.wi-moments-page__image:nth-child(4)) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.wi-moments-page__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-moments-page__date {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
.wi-moments-page__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-moments-page__like-btn,
|
||||
.wi-moments-page__comment-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 9999px;
|
||||
background: transparent;
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-body);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
color 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
background 0.2s ease;
|
||||
}
|
||||
|
||||
.wi-moments-page__like-btn:hover,
|
||||
.wi-moments-page__comment-btn:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-moments-page__like-btn--liked {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 8%, var(--bg));
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.wi-moments-page__like-btn--liked .wi-moments-page__like-icon {
|
||||
fill: var(--accent);
|
||||
}
|
||||
|
||||
.wi-moments-page__comment-btn--active {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 8%, var(--bg));
|
||||
}
|
||||
|
||||
.wi-moments-page__like-icon,
|
||||
.wi-moments-page__comment-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wi-moments-page__like-count,
|
||||
.wi-moments-page__comment-count {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.wi-moments-page__comment-area {
|
||||
margin-top: var(--space-md);
|
||||
padding-top: var(--space-md);
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-moments-page__comment-area halo\:comment {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wi-moments-page__cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-moments-page__card-item {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 16px;
|
||||
padding: var(--space-xl);
|
||||
transition:
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
transform var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-moments-page__card-item:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.wi-moments-page__card-text {
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-relaxed);
|
||||
color: var(--ink);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-moments-page__card-media {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 6px;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-moments-page__card-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.wi-moments-page__card-image:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.wi-moments-page__card-media:has(.wi-moments-page__card-image:only-child) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.wi-moments-page__card-media:has(.wi-moments-page__card-image:only-child) .wi-moments-page__card-image {
|
||||
aspect-ratio: auto;
|
||||
max-height: 300px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.wi-moments-page__card-media:has(.wi-moments-page__card-image:nth-child(2):last-child) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.wi-moments-page__card-media:has(.wi-moments-page__card-image:nth-child(4)) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.wi-moments-page__card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-moments-page__card-date {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry {
|
||||
columns: 2;
|
||||
column-gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-item {
|
||||
break-inside: avoid;
|
||||
margin-bottom: var(--space-lg);
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 16px;
|
||||
padding: var(--space-xl);
|
||||
transition:
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
transform var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-item:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-text {
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-relaxed);
|
||||
color: var(--ink);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-media {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 6px;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-image:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-media:has(.wi-moments-page__masonry-image:only-child) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-media:has(.wi-moments-page__masonry-image:only-child) .wi-moments-page__masonry-image {
|
||||
aspect-ratio: auto;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-media:has(.wi-moments-page__masonry-image:nth-child(2):last-child) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-media:has(.wi-moments-page__masonry-image:nth-child(4)) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry-date {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-moments-page__timeline {
|
||||
padding-left: var(--space-xl);
|
||||
}
|
||||
|
||||
.wi-moments-page__dot {
|
||||
left: calc(-1 * var(--space-xl) - 5px);
|
||||
}
|
||||
|
||||
.wi-moments-page__line {
|
||||
left: calc(-1 * var(--space-xl));
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry {
|
||||
columns: 1;
|
||||
}
|
||||
|
||||
.wi-moments-page__cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.wi-moments-page__timeline {
|
||||
padding-left: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-moments-page__dot {
|
||||
left: calc(-1 * var(--space-lg) - 5px);
|
||||
}
|
||||
|
||||
.wi-moments-page__line {
|
||||
left: calc(-1 * var(--space-lg));
|
||||
}
|
||||
|
||||
.wi-moments-page__media,
|
||||
.wi-moments-page__card-media,
|
||||
.wi-moments-page__masonry-media {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+131
-14
@@ -1,25 +1,142 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import LightGallery from "../components/LightGallery.astro";
|
||||
---
|
||||
|
||||
<Layout contentClass="prose">
|
||||
<Layout>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${singlePage.spec.title} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<div class="page-heading">
|
||||
<h1 th:text="${singlePage.spec.title}"></h1>
|
||||
<p class="page-meta">
|
||||
<span
|
||||
th:text="${#dates.format(singlePage.spec.publishTime, 'yyyy-MM-dd')}"
|
||||
></span>
|
||||
</p>
|
||||
</div>
|
||||
<article class="wi-page">
|
||||
<header class="wi-page__header">
|
||||
<h1
|
||||
class="wi-page__title"
|
||||
th:text="${singlePage.spec.title}"
|
||||
></h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<div class="wi-page__meta">
|
||||
<time
|
||||
th:datetime="${singlePage.spec.publishTime}"
|
||||
th:text="${#dates.format(singlePage.spec.publishTime, 'yyyy-MM-dd')}"
|
||||
></time>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div th:utext="${singlePage.content.content}"></div>
|
||||
<div
|
||||
class="wi-page__body"
|
||||
th:utext="${singlePage.content.content}"
|
||||
></div>
|
||||
|
||||
<halo:comment
|
||||
group="content.halo.run"
|
||||
kind="SinglePage"
|
||||
th:attr="name=${singlePage.metadata.name}"></halo:comment>
|
||||
<div class="wi-page__comment">
|
||||
<halo:comment
|
||||
th:if="${haloCommentEnabled}"
|
||||
group="content.halo.run"
|
||||
kind="SinglePage"
|
||||
th:attr="name=${singlePage.metadata.name}"
|
||||
></halo:comment>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<LightGallery />
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-page {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-page__header {
|
||||
display: grid;
|
||||
gap: var(--space-md);
|
||||
padding-bottom: var(--space-lg);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wi-page__title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: clamp(1.8rem, 4vw, 2.8rem);
|
||||
font-weight: 700;
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--ink);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-sm) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-page__meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.4rem;
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-sm);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
.wi-page__body {
|
||||
max-width: var(--content-max);
|
||||
margin-inline: auto;
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--leading-relaxed);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.wi-page__body :is(h1, h2, h3, h4, h5, h6) {
|
||||
font-family: var(--font-sans);
|
||||
margin-top: 2em;
|
||||
margin-bottom: 0.6em;
|
||||
}
|
||||
|
||||
.wi-page__body p {
|
||||
margin-block-end: 1.2em;
|
||||
}
|
||||
|
||||
.wi-page__body img {
|
||||
border-radius: 12px;
|
||||
margin-block: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-page__body a {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
.wi-page__body ul,
|
||||
.wi-page__body ol {
|
||||
padding-inline-start: 1.5em;
|
||||
margin-block-end: 1.2em;
|
||||
}
|
||||
|
||||
.wi-page__body li {
|
||||
margin-block-end: 0.4em;
|
||||
}
|
||||
|
||||
.wi-page__comment {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
halo\:comment {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-page__header {
|
||||
padding-bottom: var(--space-xl);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Fragment slot="head">
|
||||
<title
|
||||
th:text="|${theme.config?.messageboard?.messageboard_title ?: '留言板'} - ${site.title}|"
|
||||
></title>
|
||||
</Fragment>
|
||||
|
||||
<div class="wi-messageboard">
|
||||
<header class="wi-messageboard__header">
|
||||
<span class="wi-messageboard__deco">"</span>
|
||||
<h1
|
||||
class="wi-messageboard__title"
|
||||
th:text="${theme.config?.messageboard?.messageboard_title ?: '留言板'}"
|
||||
></h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p
|
||||
class="wi-messageboard__subtitle"
|
||||
th:text="${theme.config?.messageboard?.messageboard_subtitle}"
|
||||
></p>
|
||||
</header>
|
||||
|
||||
<div class="wi-messageboard__content">
|
||||
<div
|
||||
class="wi-messageboard__body"
|
||||
th:utext="${singlePage.content.content}"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="wi-messageboard__comment">
|
||||
<halo:comment
|
||||
th:if="${haloCommentEnabled}"
|
||||
group="content.halo.run"
|
||||
kind="SinglePage"
|
||||
th:attr="name=${singlePage.metadata.name}"
|
||||
></halo:comment>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-messageboard {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
margin-top: calc(-1 * 100px - var(--space-lg));
|
||||
padding-top: 100px;
|
||||
padding-block-end: 1rem;
|
||||
}
|
||||
|
||||
.wi-messageboard__header {
|
||||
display: grid;
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-3xl) var(--space-xl);
|
||||
text-align: center;
|
||||
background: var(--bg-raised);
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--rule);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.wi-messageboard__deco {
|
||||
font-family: var(--font-sans);
|
||||
font-size: clamp(4rem, 10vw, 7rem);
|
||||
line-height: 1;
|
||||
color: var(--accent);
|
||||
opacity: 0.18;
|
||||
position: absolute;
|
||||
top: -0.15em;
|
||||
left: 0.15em;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.wi-messageboard__title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: clamp(1.8rem, 4vw, 2.8rem);
|
||||
font-weight: 700;
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--ink);
|
||||
margin: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-sm) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-messageboard__subtitle {
|
||||
font-size: var(--text-md);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-relaxed);
|
||||
margin: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wi-messageboard__content {
|
||||
max-width: var(--content-max);
|
||||
margin-inline: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wi-messageboard__body {
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--leading-relaxed);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.wi-messageboard__body p {
|
||||
margin-block-end: 1.2em;
|
||||
}
|
||||
|
||||
.wi-messageboard__comment {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
halo\:comment {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-messageboard__header {
|
||||
padding: var(--space-2xl) var(--space-lg);
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,643 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import LightGallery from "../components/LightGallery.astro";
|
||||
---
|
||||
|
||||
<Layout wide>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${title ?: (theme.config?.photos?.photos_page_title ?: '图库')} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<section class="wi-photos-page">
|
||||
<div class="wi-container">
|
||||
<div class="wi-photos-page__header">
|
||||
<h1
|
||||
class="wi-photos-page__title"
|
||||
th:text="${theme.config?.photos?.photos_page_title ?: '图库'}"
|
||||
>
|
||||
图库
|
||||
</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
</div>
|
||||
|
||||
<th:block th:if="${pluginFinder.available('PluginPhotos')}">
|
||||
<th:block th:if="${groups != null}" th:with="currentGroup=${'' + param.group}, showGroupTitle=${theme.config?.photos?.photos_show_group_title != false}, showUngrouped=${theme.config?.photos?.photos_show_ungrouped == true}">
|
||||
<nav
|
||||
class="wi-photos-page__groups"
|
||||
th:if="${groups != null and !#lists.isEmpty(groups)}"
|
||||
>
|
||||
<a
|
||||
th:href="@{${photoUrl.list()}}"
|
||||
class="wi-photos-page__group-link"
|
||||
th:classappend="${#strings.isEmpty(currentGroup)} ? 'wi-photos-page__group-link--active' : ''"
|
||||
>
|
||||
全部
|
||||
</a>
|
||||
<a
|
||||
th:each="group : ${groups}"
|
||||
th:href="@{${photoUrl.list(group.metadata.name)}}"
|
||||
th:text="${group.spec.displayName}"
|
||||
class="wi-photos-page__group-link"
|
||||
th:classappend="${#strings.equals(currentGroup, group.metadata.name)} ? 'wi-photos-page__group-link--active' : ''"
|
||||
></a>
|
||||
</nav>
|
||||
|
||||
<th:block th:if="${!#lists.isEmpty(groups) or (showUngrouped and photos != null and !#lists.isEmpty(photos.items))}">
|
||||
<th:block th:if="${#strings.isEmpty(currentGroup)}">
|
||||
<th:block th:if="${showGroupTitle}">
|
||||
<div class="wi-photos-page__all-groups">
|
||||
<th:block th:if="${showUngrouped and photos != null and !#lists.isEmpty(photos.items)}">
|
||||
<div class="wi-photos-page__section">
|
||||
<h2 class="wi-photos-page__section-title">全部</h2>
|
||||
<div
|
||||
class="wi-photos-page__grid"
|
||||
th:attr="data-style=${theme.config?.photos?.photos_style ?: 'masonry'}, data-columns=${theme.config?.photos?.photos_columns ?: 3}"
|
||||
>
|
||||
<th:block th:each="photo : ${photos.items}">
|
||||
<div
|
||||
class="wi-photos-page__item"
|
||||
th:if="${#strings.isEmpty(photo.spec.groupName)}"
|
||||
>
|
||||
<a
|
||||
th:href="${photo.spec.url}"
|
||||
class="wi-photos-page__link"
|
||||
th:attr="data-src=${photo.spec.url}, data-sub-html=${photo.spec.displayName ?: ''}, data-exif=${photo.exif != null ? (photo.exif.make ?: '') + '|' + (photo.exif.model ?: '') + '|' + (photo.exif.lensModel ?: '') + '|' + (photo.exif.fNumber ?: '') + '|' + (photo.exif.exposureTime ?: '') + '|' + (photo.exif.iso ?: '') + '|' + (photo.exif.focalLength ?: '') + '|' + (photo.exif.focalLengthIn35mm ?: '') : ''}"
|
||||
>
|
||||
<img
|
||||
th:src="${photo.spec.cover ?: photo.spec.url}"
|
||||
th:alt="${photo.spec.displayName ?: ''}"
|
||||
class="wi-photos-page__image"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="wi-photos-page__overlay">
|
||||
<span
|
||||
class="wi-photos-page__caption"
|
||||
th:if="${photo.spec.displayName}"
|
||||
th:text="${photo.spec.displayName}"
|
||||
></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block th:each="group : ${groups}">
|
||||
<div
|
||||
class="wi-photos-page__section"
|
||||
th:if="${!#lists.isEmpty(group.photos)}"
|
||||
>
|
||||
<h2
|
||||
class="wi-photos-page__section-title"
|
||||
th:if="${group.spec?.displayName != null}"
|
||||
th:text="${group.spec?.displayName}"
|
||||
></h2>
|
||||
<div
|
||||
class="wi-photos-page__grid"
|
||||
th:attr="data-style=${theme.config?.photos?.photos_style ?: 'masonry'}, data-columns=${theme.config?.photos?.photos_columns ?: 3}"
|
||||
>
|
||||
<div
|
||||
class="wi-photos-page__item"
|
||||
th:each="photo : ${group.photos}"
|
||||
>
|
||||
<a
|
||||
th:href="${photo.spec.url}"
|
||||
class="wi-photos-page__link"
|
||||
th:attr="data-src=${photo.spec.url}, data-sub-html=${photo.spec.displayName ?: ''}, data-exif=${photo.exif != null ? (photo.exif.make ?: '') + '|' + (photo.exif.model ?: '') + '|' + (photo.exif.lensModel ?: '') + '|' + (photo.exif.fNumber ?: '') + '|' + (photo.exif.exposureTime ?: '') + '|' + (photo.exif.iso ?: '') + '|' + (photo.exif.focalLength ?: '') + '|' + (photo.exif.focalLengthIn35mm ?: '') : ''}"
|
||||
>
|
||||
<img
|
||||
th:src="${photo.spec.cover ?: photo.spec.url}"
|
||||
th:alt="${photo.spec.displayName ?: ''}"
|
||||
class="wi-photos-page__image"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="wi-photos-page__overlay">
|
||||
<span
|
||||
class="wi-photos-page__caption"
|
||||
th:if="${photo.spec.displayName}"
|
||||
th:text="${photo.spec.displayName}"
|
||||
></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block th:if="${!showGroupTitle}">
|
||||
<div
|
||||
class="wi-photos-page__grid"
|
||||
th:attr="data-style=${theme.config?.photos?.photos_style ?: 'masonry'}, data-columns=${theme.config?.photos?.photos_columns ?: 3}"
|
||||
>
|
||||
<th:block th:if="${showUngrouped and photos != null and !#lists.isEmpty(photos.items)}">
|
||||
<th:block th:each="photo : ${photos.items}">
|
||||
<div
|
||||
class="wi-photos-page__item"
|
||||
th:if="${#strings.isEmpty(photo.spec.groupName)}"
|
||||
>
|
||||
<a
|
||||
th:href="${photo.spec.url}"
|
||||
class="wi-photos-page__link"
|
||||
th:attr="data-src=${photo.spec.url}, data-sub-html=${photo.spec.displayName ?: ''}, data-exif=${photo.exif != null ? (photo.exif.make ?: '') + '|' + (photo.exif.model ?: '') + '|' + (photo.exif.lensModel ?: '') + '|' + (photo.exif.fNumber ?: '') + '|' + (photo.exif.exposureTime ?: '') + '|' + (photo.exif.iso ?: '') + '|' + (photo.exif.focalLength ?: '') + '|' + (photo.exif.focalLengthIn35mm ?: '') : ''}"
|
||||
>
|
||||
<img
|
||||
th:src="${photo.spec.cover ?: photo.spec.url}"
|
||||
th:alt="${photo.spec.displayName ?: ''}"
|
||||
class="wi-photos-page__image"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="wi-photos-page__overlay">
|
||||
<span
|
||||
class="wi-photos-page__caption"
|
||||
th:if="${photo.spec.displayName}"
|
||||
th:text="${photo.spec.displayName}"
|
||||
></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<th:block th:each="group : ${groups}">
|
||||
<div
|
||||
class="wi-photos-page__item"
|
||||
th:each="photo : ${group.photos}"
|
||||
>
|
||||
<a
|
||||
th:href="${photo.spec.url}"
|
||||
class="wi-photos-page__link"
|
||||
th:attr="data-src=${photo.spec.url}, data-sub-html=${photo.spec.displayName ?: ''}, data-exif=${photo.exif != null ? (photo.exif.make ?: '') + '|' + (photo.exif.model ?: '') + '|' + (photo.exif.lensModel ?: '') + '|' + (photo.exif.fNumber ?: '') + '|' + (photo.exif.exposureTime ?: '') + '|' + (photo.exif.iso ?: '') + '|' + (photo.exif.focalLength ?: '') + '|' + (photo.exif.focalLengthIn35mm ?: '') : ''}"
|
||||
>
|
||||
<img
|
||||
th:src="${photo.spec.cover ?: photo.spec.url}"
|
||||
th:alt="${photo.spec.displayName ?: ''}"
|
||||
class="wi-photos-page__image"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="wi-photos-page__overlay">
|
||||
<span
|
||||
class="wi-photos-page__caption"
|
||||
th:if="${photo.spec.displayName}"
|
||||
th:text="${photo.spec.displayName}"
|
||||
></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<th:block th:if="${!#strings.isEmpty(currentGroup)}">
|
||||
<th:block th:each="group : ${groups}">
|
||||
<th:block th:if="${#strings.equals(currentGroup, group.metadata.name)}">
|
||||
<div
|
||||
class="wi-photos-page__section"
|
||||
th:if="${!#lists.isEmpty(group.photos)}"
|
||||
>
|
||||
<h2
|
||||
class="wi-photos-page__section-title"
|
||||
th:if="${group.spec?.displayName != null}"
|
||||
th:text="${group.spec?.displayName}"
|
||||
></h2>
|
||||
<div
|
||||
class="wi-photos-page__grid"
|
||||
th:attr="data-style=${theme.config?.photos?.photos_style ?: 'masonry'}, data-columns=${theme.config?.photos?.photos_columns ?: 3}"
|
||||
>
|
||||
<div
|
||||
class="wi-photos-page__item"
|
||||
th:each="photo : ${group.photos}"
|
||||
>
|
||||
<a
|
||||
th:href="${photo.spec.url}"
|
||||
class="wi-photos-page__link"
|
||||
th:attr="data-src=${photo.spec.url}, data-sub-html=${photo.spec.displayName ?: ''}, data-exif=${photo.exif != null ? (photo.exif.make ?: '') + '|' + (photo.exif.model ?: '') + '|' + (photo.exif.lensModel ?: '') + '|' + (photo.exif.fNumber ?: '') + '|' + (photo.exif.exposureTime ?: '') + '|' + (photo.exif.iso ?: '') + '|' + (photo.exif.focalLength ?: '') + '|' + (photo.exif.focalLengthIn35mm ?: '') : ''}"
|
||||
>
|
||||
<img
|
||||
th:src="${photo.spec.cover ?: photo.spec.url}"
|
||||
th:alt="${photo.spec.displayName ?: ''}"
|
||||
class="wi-photos-page__image"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="wi-photos-page__overlay">
|
||||
<span
|
||||
class="wi-photos-page__caption"
|
||||
th:if="${photo.spec.displayName}"
|
||||
th:text="${photo.spec.displayName}"
|
||||
></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wi-photos-page__empty" th:if="${#lists.isEmpty(group.photos)}">
|
||||
<p>该分组暂无图片</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<div class="wi-photos-page__empty" th:if="${#lists.isEmpty(groups) and !(showUngrouped and photos != null and !#lists.isEmpty(photos.items))}">
|
||||
<p>暂无图片</p>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block th:if="${groups == null}">
|
||||
<div class="wi-photos-page__empty">
|
||||
<p>图库插件加载异常,请检查插件状态</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<th:block th:unless="${pluginFinder.available('PluginPhotos')}">
|
||||
<div class="wi-photos-page__empty">
|
||||
<p>
|
||||
图库功能需要安装「图库」插件,<a
|
||||
href="https://www.halo.run/store/apps/app-PgHgw"
|
||||
>前往应用市场安装</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<LightGallery />
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-photos-page {
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-photos-page__header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-3xl);
|
||||
}
|
||||
|
||||
.wi-photos-page__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-sm) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-photos-page__groups {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
justify-content: center;
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
.wi-photos-page__group-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 16px;
|
||||
border-radius: 9999px;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 500;
|
||||
color: var(--ink-2);
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--glass-border);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
color var(--duration-fast) var(--ease-out-expo),
|
||||
background var(--duration-fast) var(--ease-out-expo),
|
||||
border-color var(--duration-fast) var(--ease-out-expo),
|
||||
transform var(--duration-fast) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-photos-page__group-link:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.wi-photos-page__group-link--active {
|
||||
color: var(--bg);
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-photos-page__group-link--active:hover {
|
||||
color: var(--bg);
|
||||
background: var(--accent);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.wi-photos-page__section {
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
.wi-photos-page__section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.wi-photos-page__section-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 600;
|
||||
color: var(--ink-2);
|
||||
margin-bottom: var(--space-lg);
|
||||
padding-bottom: var(--space-sm);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-photos-page__empty {
|
||||
text-align: center;
|
||||
padding: var(--space-4xl) var(--space-xl);
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
.wi-photos-page__empty a {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid {
|
||||
display: grid;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="masonry"] {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-auto-rows: 10px;
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="masonry"][data-columns="2"] {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="masonry"][data-columns="4"] {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="grid"] {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="grid"][data-columns="2"] {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="grid"][data-columns="4"] {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="grid"] .wi-photos-page__image {
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="carousel"] {
|
||||
grid-template-columns: unset;
|
||||
display: flex;
|
||||
gap: var(--space-md);
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
scroll-padding: var(--space-md);
|
||||
padding-bottom: var(--space-md);
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--accent) transparent;
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="carousel"]::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="carousel"]::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="carousel"]::-webkit-scrollbar-thumb {
|
||||
background: var(--accent);
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="carousel"] .wi-photos-page__item {
|
||||
flex: 0 0 auto;
|
||||
scroll-snap-align: start;
|
||||
width: calc(100% / 3 - var(--space-md) * 2 / 3);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="carousel"][data-columns="2"] .wi-photos-page__item {
|
||||
width: calc(100% / 2 - var(--space-md) / 2);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="carousel"][data-columns="4"] .wi-photos-page__item {
|
||||
width: calc(100% / 4 - var(--space-md) * 3 / 4);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="carousel"] .wi-photos-page__image {
|
||||
aspect-ratio: 4 / 3;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.wi-photos-page__item {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wi-photos-page__link {
|
||||
display: block;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.wi-photos-page__image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 12px;
|
||||
transition:
|
||||
transform var(--duration-slow) var(--ease-out-expo),
|
||||
filter var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-photos-page__link:hover .wi-photos-page__image {
|
||||
transform: scale(1.05);
|
||||
filter: brightness(0.92);
|
||||
}
|
||||
|
||||
.wi-photos-page__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: var(--space-lg) var(--space-md);
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
rgba(0, 0, 0, 0.45) 0%,
|
||||
transparent 50%
|
||||
);
|
||||
opacity: 0;
|
||||
transition: opacity var(--duration-normal) var(--ease-out-expo);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.wi-photos-page__link:hover .wi-photos-page__overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.wi-photos-page__caption {
|
||||
font-size: var(--text-sm);
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: var(--leading-snug);
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-photos-page__grid[data-style="masonry"] {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="grid"] {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="carousel"] .wi-photos-page__item {
|
||||
width: calc(100% / 2 - var(--space-md) / 2);
|
||||
}
|
||||
|
||||
.wi-photos-page__groups {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.wi-photos-page__group-link {
|
||||
padding: 4px 12px;
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.wi-photos-page__grid[data-style="masonry"] {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="grid"] {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-photos-page__grid[data-style="carousel"] .wi-photos-page__item {
|
||||
width: 75%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
var rafPending = false;
|
||||
|
||||
function layoutMasonry() {
|
||||
if (rafPending) return;
|
||||
rafPending = true;
|
||||
requestAnimationFrame(function () {
|
||||
rafPending = false;
|
||||
var grids = document.querySelectorAll(
|
||||
'.wi-photos-page__grid[data-style="masonry"]'
|
||||
);
|
||||
grids.forEach(function (grid) {
|
||||
var items = grid.querySelectorAll('.wi-photos-page__item');
|
||||
if (items.length === 0) return;
|
||||
|
||||
var rowGap = parseInt(getComputedStyle(grid).rowGap) || 8;
|
||||
var rowHeight = 10;
|
||||
|
||||
items.forEach(function (item) {
|
||||
var img = item.querySelector('.wi-photos-page__image');
|
||||
if (!img) return;
|
||||
|
||||
var ratio = 0;
|
||||
var w = img.getAttribute('width');
|
||||
var h = img.getAttribute('height');
|
||||
if (w && h && parseInt(w) > 0 && parseInt(h) > 0) {
|
||||
ratio = parseInt(h) / parseInt(w);
|
||||
} else if (img.naturalWidth > 0 && img.naturalHeight > 0) {
|
||||
ratio = img.naturalHeight / img.naturalWidth;
|
||||
}
|
||||
if (!ratio || ratio <= 0) return;
|
||||
|
||||
var itemWidth = item.getBoundingClientRect().width;
|
||||
var rowSpan = Math.ceil(
|
||||
(itemWidth * ratio + rowGap) / (rowHeight + rowGap)
|
||||
);
|
||||
|
||||
item.style.gridRowEnd = 'span ' + rowSpan;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function observeImages() {
|
||||
var images = document.querySelectorAll(
|
||||
'.wi-photos-page__grid[data-style="masonry"] .wi-photos-page__image'
|
||||
);
|
||||
if (images.length === 0) return;
|
||||
|
||||
images.forEach(function (img) {
|
||||
if (img.complete && img.naturalHeight > 0) {
|
||||
layoutMasonry();
|
||||
} else {
|
||||
img.addEventListener('load', layoutMasonry);
|
||||
img.addEventListener('error', layoutMasonry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
var ro = new ResizeObserver(function () {
|
||||
layoutMasonry();
|
||||
});
|
||||
var observeGrids = function () {
|
||||
var grids = document.querySelectorAll(
|
||||
'.wi-photos-page__grid[data-style="masonry"]'
|
||||
);
|
||||
grids.forEach(function (grid) {
|
||||
ro.observe(grid);
|
||||
});
|
||||
};
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
observeGrids();
|
||||
observeImages();
|
||||
});
|
||||
} else {
|
||||
observeGrids();
|
||||
observeImages();
|
||||
}
|
||||
} else {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', observeImages);
|
||||
} else {
|
||||
observeImages();
|
||||
}
|
||||
}
|
||||
|
||||
var resizeTimer;
|
||||
window.addEventListener('resize', function () {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(layoutMasonry, 150);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
+1041
-14
File diff suppressed because it is too large
Load Diff
+242
-48
@@ -7,53 +7,247 @@ import Layout from "../layouts/Layout.astro";
|
||||
<title th:text="|${tag.spec.displayName} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<div class="page-heading">
|
||||
<h1 th:text="${tag.spec.displayName}"></h1>
|
||||
<p class="page-meta">
|
||||
<span th:text="|${posts.total} 篇文章|"></span>
|
||||
</p>
|
||||
<div class="wi-tag">
|
||||
<header class="wi-tag__header">
|
||||
<h1 class="wi-tag__title" th:text="${tag.spec.displayName}"></h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p class="wi-tag__subtitle" th:text="${theme.config?.home?.home_label_post_count ?: '共 {total} 篇文章}'.replace('{total}', posts.total)}"></p>
|
||||
<p
|
||||
th:if="${not #strings.isEmpty(tag.spec.description)}"
|
||||
class="wi-tag__description"
|
||||
th:text="${tag.spec.description}"
|
||||
></p>
|
||||
</header>
|
||||
|
||||
<ul class="wi-tag__list" role="list">
|
||||
<li
|
||||
th:each="post : ${posts.items}"
|
||||
class="wi-tag__entry"
|
||||
>
|
||||
<a
|
||||
class="wi-tag__entry-link"
|
||||
th:href="@{${post.status.permalink}}"
|
||||
>
|
||||
<div class="wi-tag__entry-body">
|
||||
<time
|
||||
class="wi-tag__entry-date"
|
||||
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
|
||||
datetime="${post.spec.publishTime}"
|
||||
></time>
|
||||
<h2 class="wi-tag__entry-title" th:text="${post.spec.title}"></h2>
|
||||
<p
|
||||
class="wi-tag__entry-excerpt"
|
||||
th:text="${post.status.excerpt}"
|
||||
></p>
|
||||
</div>
|
||||
<img
|
||||
th:unless="${#strings.isEmpty(post.spec.cover)}"
|
||||
class="wi-tag__entry-image"
|
||||
th:src="${post.spec.cover}"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
th:if="${posts.total == 0}"
|
||||
class="wi-tag__empty"
|
||||
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章。'}"
|
||||
>暂无文章。</div>
|
||||
|
||||
<nav
|
||||
th:if="${posts.totalPages gt 1}"
|
||||
class="wi-tag__pagination"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<a
|
||||
th:if="${posts.hasPrevious()}"
|
||||
th:href="@{${posts.prevUrl}}"
|
||||
class="wi-tag__page-link wi-tag__page-link--prev"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||
>← 较新</a>
|
||||
<span
|
||||
class="wi-tag__page-info"
|
||||
th:text="|${posts.page} / ${posts.totalPages}|"
|
||||
></span>
|
||||
<a
|
||||
th:if="${posts.hasNext()}"
|
||||
th:href="@{${posts.nextUrl}}"
|
||||
class="wi-tag__page-link wi-tag__page-link--next"
|
||||
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||
>较旧 →</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<ul class="post-feed__list" role="list">
|
||||
<li th:each="post : ${posts.items}" class="post-feed__item">
|
||||
<a class="post-feed__link" th:href="@{${post.status.permalink}}">
|
||||
<div class="post-feed__body">
|
||||
<p
|
||||
class="post-feed__date"
|
||||
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
|
||||
>
|
||||
</p>
|
||||
<h2 class="post-feed__title" th:text="${post.spec.title}"></h2>
|
||||
<p class="post-feed__excerpt" th:text="${post.status.excerpt}"></p>
|
||||
</div>
|
||||
<img
|
||||
th:unless="${#strings.isEmpty(post.spec.cover)}"
|
||||
class="post-feed__image"
|
||||
th:src="${post.spec.cover}"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div th:if="${posts.total == 0}" class="feed-empty">暂无文章。</div>
|
||||
|
||||
<nav
|
||||
th:if="${posts.totalPages gt 1}"
|
||||
class="pagination"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<a
|
||||
th:if="${posts.hasPrevious()}"
|
||||
th:href="@{${posts.prevUrl}}"
|
||||
class="pagination__prev">← 较新</a
|
||||
>
|
||||
<span
|
||||
class="pagination__info"
|
||||
th:text="|${posts.page} / ${posts.totalPages}|"></span>
|
||||
<a
|
||||
th:if="${posts.hasNext()}"
|
||||
th:href="@{${posts.nextUrl}}"
|
||||
class="pagination__next">较旧 →</a
|
||||
>
|
||||
</nav>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-tag {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-tag__header {
|
||||
text-align: center;
|
||||
padding-bottom: var(--space-lg);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-tag__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-sm) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-tag__subtitle {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wi-tag__description {
|
||||
margin: var(--space-md) auto 0;
|
||||
max-width: var(--content-max);
|
||||
font-size: var(--text-md);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
.wi-tag__list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-tag__entry {
|
||||
border-bottom: 1px dashed var(--rule);
|
||||
transition: background var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-tag__entry:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.wi-tag__entry:hover {
|
||||
background: var(--bg-raised);
|
||||
margin: 0 calc(-1 * var(--space-md));
|
||||
padding-left: var(--space-md);
|
||||
padding-right: var(--space-md);
|
||||
border-radius: $border-radius-sm;
|
||||
}
|
||||
|
||||
.wi-tag__entry-link {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 200px;
|
||||
gap: var(--space-lg);
|
||||
padding: var(--space-lg) 0;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.wi-tag__entry-body {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.wi-tag__entry-date {
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-sm);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.wi-tag__entry-title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
line-height: var(--leading-snug);
|
||||
margin: 0.25rem 0;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-tag__entry-link:hover .wi-tag__entry-title {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-tag__entry-excerpt {
|
||||
margin: 0;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-normal);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wi-tag__entry-image {
|
||||
width: 200px;
|
||||
height: 140px;
|
||||
object-fit: cover;
|
||||
border-radius: $border-radius-sm;
|
||||
align-self: center;
|
||||
transition: transform var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-tag__entry:hover .wi-tag__entry-image {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.wi-tag__empty {
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-md);
|
||||
padding: var(--space-2xl) 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wi-tag__pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-md);
|
||||
padding-top: var(--space-xl);
|
||||
border-top: 1px solid var(--rule);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.wi-tag__page-link {
|
||||
color: var(--ink-2);
|
||||
text-decoration: none;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-tag__page-link:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-tag__page-info {
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-tag__entry-link {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.wi-tag__entry-image {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
order: -1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+109
-11
@@ -2,20 +2,118 @@
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout pageTitle="标签">
|
||||
<Layout>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|标签 - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<div class="tag-cloud" role="list" aria-label="Tag list">
|
||||
<a
|
||||
th:each="tag : ${tags}"
|
||||
class="tag-pill"
|
||||
role="listitem"
|
||||
th:href="@{${tag.status.permalink}}"
|
||||
>
|
||||
<span th:text="${tag.spec.displayName}"></span>
|
||||
<span class="tag-pill__count" th:text="${tag.postCount}"></span>
|
||||
</a>
|
||||
<div class="wi-tags">
|
||||
<header class="wi-tags__header">
|
||||
<h1 class="wi-tags__title">标签</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p class="wi-tags__subtitle" th:text="|共 ${tags.size} 个标签|"></p>
|
||||
</header>
|
||||
|
||||
<div class="wi-tags__cloud" role="list" aria-label="Tag list">
|
||||
<a
|
||||
th:each="tag : ${tags}"
|
||||
class="wi-tags__pill"
|
||||
role="listitem"
|
||||
th:href="@{${tag.status.permalink}}"
|
||||
>
|
||||
<span class="wi-tags__pill-name" th:text="${tag.spec.displayName}"></span>
|
||||
<span class="wi-tags__pill-count" th:text="${tag.postCount}"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-tags {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-tags__header {
|
||||
text-align: center;
|
||||
padding-bottom: var(--space-lg);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-tags__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.wi-page-accent {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 3px;
|
||||
margin: var(--space-sm) auto 0;
|
||||
border-radius: 9999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-tags__subtitle {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wi-tags__cloud {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-tags__pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 999px;
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
color: var(--ink-2);
|
||||
font-size: var(--text-sm);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background var(--duration-fast) var(--ease-out-quart),
|
||||
color var(--duration-fast) var(--ease-out-quart),
|
||||
border-color var(--duration-fast) var(--ease-out-quart),
|
||||
transform var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-tags__pill:hover {
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
border-color: var(--accent);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.wi-tags__pill-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.wi-tags__pill-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 0 0.35rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.8em;
|
||||
background: var(--bg);
|
||||
color: var(--ink-3);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wi-tags__pill:hover .wi-tags__pill-count {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: var(--bg);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
:root {
|
||||
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
|
||||
--ease-in-out-cubic: cubic-bezier(0.65, 0, 0.35, 1);
|
||||
|
||||
--duration-fast: 150ms;
|
||||
--duration-normal: 300ms;
|
||||
--duration-slow: 500ms;
|
||||
--duration-breath: 3s;
|
||||
}
|
||||
|
||||
@keyframes breathe {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
transform: scale(1.08);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scrollHint {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
:root {
|
||||
--bg: #faf7f2;
|
||||
--bg-raised: #f3ede5;
|
||||
--bg-overlay: rgba(250, 247, 242, 0.85);
|
||||
--ink: #2c2420;
|
||||
--ink-2: #7a6e64;
|
||||
--ink-3: #b5a99e;
|
||||
--rule: #e8e0d6;
|
||||
--accent: #d4764e;
|
||||
--accent-hover: #c4613a;
|
||||
--accent-bg: #fdf0e8;
|
||||
--mist-pink: #f0e4de;
|
||||
--sea-salt: #e8e0d6;
|
||||
--caramel: #8b6f5e;
|
||||
--glass-bg: rgba(250, 247, 242, 0.72);
|
||||
--glass-border: rgba(232, 224, 214, 0.5);
|
||||
--shadow-sm: 0 1px 3px rgba(44, 36, 32, 0.06);
|
||||
--shadow-md: 0 4px 16px rgba(44, 36, 32, 0.08);
|
||||
--shadow-lg: 0 8px 32px rgba(44, 36, 32, 0.12);
|
||||
--shadow-glow: 0 0 40px rgba(212, 118, 78, 0.15);
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--bg: #1a1614;
|
||||
--bg-raised: #242018;
|
||||
--bg-overlay: rgba(26, 22, 20, 0.85);
|
||||
--ink: #ede6de;
|
||||
--ink-2: #9a8e84;
|
||||
--ink-3: #5e544a;
|
||||
--rule: #2e2822;
|
||||
--accent: #e8955f;
|
||||
--accent-hover: #f0a872;
|
||||
--accent-bg: #2a1e16;
|
||||
--mist-pink: #2e2420;
|
||||
--sea-salt: #2e2822;
|
||||
--caramel: #a08878;
|
||||
--glass-bg: rgba(26, 22, 20, 0.72);
|
||||
--glass-border: rgba(46, 40, 34, 0.5);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.25);
|
||||
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.35);
|
||||
--shadow-glow: 0 0 40px rgba(232, 149, 95, 0.12);
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
.halo-comment-widget {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: $border-radius-lg;
|
||||
padding: var(--space-md);
|
||||
margin-top: var(--space-xl);
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
|
||||
.comment-item {
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: $border-radius-md;
|
||||
padding: var(--space-sm);
|
||||
margin-bottom: var(--space-xs);
|
||||
transition:
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
transform var(--duration-normal) var(--ease-out-expo);
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-sm);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.comment-reply-item {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: $border-radius-md;
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
margin-top: var(--space-xs);
|
||||
transition:
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo),
|
||||
transform var(--duration-normal) var(--ease-out-expo);
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-sm);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.comment-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.comment-avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: $border-radius-full;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.comment-author {
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.comment-time {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--leading-relaxed);
|
||||
color: var(--ink);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.comment-form {
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: $border-radius-md;
|
||||
padding: var(--space-sm);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.comment-input,
|
||||
.comment-textarea {
|
||||
width: 100%;
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: $border-radius-sm;
|
||||
color: var(--ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-sm);
|
||||
transition:
|
||||
border-color var(--duration-fast) var(--ease-out-quart),
|
||||
box-shadow var(--duration-fast) var(--ease-out-quart);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(212, 118, 78, 0.15);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--ink-3);
|
||||
}
|
||||
}
|
||||
|
||||
.comment-textarea {
|
||||
min-height: 80px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.comment-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-sm) var(--space-lg);
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
border: none;
|
||||
border-radius: $border-radius-sm;
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background var(--duration-fast) var(--ease-out-quart),
|
||||
transform var(--duration-fast) var(--ease-out-quart),
|
||||
box-shadow var(--duration-fast) var(--ease-out-quart);
|
||||
|
||||
&:hover {
|
||||
background: var(--accent-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.comment-pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
margin-top: var(--space-lg);
|
||||
}
|
||||
|
||||
.comment-pagination-button {
|
||||
padding: var(--space-xs) var(--space-md);
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: $border-radius-sm;
|
||||
color: var(--ink-2);
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color var(--duration-fast) var(--ease-out-quart),
|
||||
color var(--duration-fast) var(--ease-out-quart);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
.comment-reply-button {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
cursor: pointer;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
|
||||
&:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
.comment-smiley-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--text-lg);
|
||||
transition: transform var(--duration-fast) var(--ease-out-quart);
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.dark .halo-comment-widget {
|
||||
.comment-input,
|
||||
.comment-textarea {
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 3px rgba(232, 149, 95, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.comment-button {
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
@use 'variables' as *;
|
||||
@use 'mixins' as *;
|
||||
|
||||
$lg-backdrop-light: rgba(18, 14, 12, 0.96);
|
||||
$lg-backdrop-dark: rgba(10, 8, 6, 0.97);
|
||||
$lg-glass-bg-light: rgba(255, 255, 255, 0.08);
|
||||
$lg-glass-bg-dark: rgba(255, 255, 255, 0.06);
|
||||
$lg-glass-border: rgba(255, 255, 255, 0.06);
|
||||
$lg-icon-color-light: rgba(255, 255, 255, 0.7);
|
||||
$lg-icon-color-dark: rgba(255, 255, 255, 0.6);
|
||||
$lg-icon-hover-bg-light: rgba(255, 255, 255, 0.12);
|
||||
$lg-icon-hover-bg-dark: rgba(255, 255, 255, 0.1);
|
||||
$lg-toolbar-height: 44px;
|
||||
$lg-icon-size: 36px;
|
||||
$lg-nav-size: 48px;
|
||||
$lg-thumb-height: 100px;
|
||||
|
||||
.lg-backdrop {
|
||||
background-color: $lg-backdrop-light;
|
||||
}
|
||||
|
||||
.lg-toolbar {
|
||||
background: $lg-glass-bg-light;
|
||||
backdrop-filter: blur(24px) saturate(1.2);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.2);
|
||||
border-bottom: none;
|
||||
padding: 4px 12px;
|
||||
height: $lg-toolbar-height;
|
||||
}
|
||||
|
||||
.lg-toolbar .lg-icon {
|
||||
color: $lg-icon-color-light;
|
||||
width: $lg-icon-size;
|
||||
height: $lg-icon-size;
|
||||
font-size: 17px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
padding: 0 !important;
|
||||
line-height: $lg-icon-size !important;
|
||||
|
||||
&::before {
|
||||
vertical-align: middle !important;
|
||||
line-height: $lg-icon-size !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: $lg-icon-hover-bg-light;
|
||||
}
|
||||
}
|
||||
|
||||
.lg-inner {
|
||||
top: $lg-toolbar-height !important;
|
||||
}
|
||||
|
||||
.lg-actions .lg-next,
|
||||
.lg-actions .lg-prev {
|
||||
background: $lg-glass-bg-light;
|
||||
backdrop-filter: blur(16px) saturate(1.2);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(1.2);
|
||||
color: $lg-icon-color-light;
|
||||
border-radius: 14px;
|
||||
width: $lg-nav-size;
|
||||
height: $lg-nav-size;
|
||||
font-size: 24px;
|
||||
transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
border: 1px solid $lg-glass-border;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.lg-sub-html {
|
||||
background: linear-gradient(to top, rgba(18, 14, 12, 0.8) 0%, transparent 100%);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-family: var(--font-body);
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.02em;
|
||||
padding: 48px 16px 12px;
|
||||
}
|
||||
|
||||
.wi-lg-caption {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.wi-lg-exif {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px 10px;
|
||||
}
|
||||
|
||||
.wi-lg-exif__camera,
|
||||
.wi-lg-exif__lens {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.wi-lg-exif__params {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.wi-lg-exif__aperture,
|
||||
.wi-lg-exif__shutter,
|
||||
.wi-lg-exif__iso,
|
||||
.wi-lg-exif__focal {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
letter-spacing: 0.04em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.lg-thumb-outer {
|
||||
background: rgba(18, 14, 12, 0.85);
|
||||
backdrop-filter: blur(24px) saturate(1.2);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.2);
|
||||
border-top: 1px solid $lg-glass-border;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.lg-thumb-item {
|
||||
border-color: transparent;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border-width: 2px;
|
||||
transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
border-color: var(--accent) !important;
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(255, 255, 255, 0.3) !important;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.lg-counter {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 12px;
|
||||
font-family: var(--font-body);
|
||||
letter-spacing: 0.08em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.lg-progress-bar {
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.lg-progress-bar .lg-progress {
|
||||
background: linear-gradient(90deg, var(--accent), var(--accent-hover));
|
||||
height: 2px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.lg-outer .lg-thumb-outer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.lg-outer.wi-thumb-shown .lg-thumb-outer {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.lg-outer.wi-thumb-shown .lg-inner {
|
||||
bottom: $lg-thumb-height !important;
|
||||
}
|
||||
|
||||
.lg-outer .lg-item {
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.lg-outer .lg-image {
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.lg-outer .lg-item.lg-complete .lg-image {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.lg-outer .lg-item .lg-image {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
.lg-backdrop {
|
||||
background-color: $lg-backdrop-dark;
|
||||
}
|
||||
|
||||
.lg-toolbar {
|
||||
background: $lg-glass-bg-dark;
|
||||
}
|
||||
|
||||
.lg-toolbar .lg-icon {
|
||||
color: $lg-icon-color-dark;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: $lg-icon-hover-bg-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.lg-actions .lg-next,
|
||||
.lg-actions .lg-prev {
|
||||
background: $lg-glass-bg-dark;
|
||||
color: $lg-icon-color-dark;
|
||||
border-color: rgba(255, 255, 255, 0.04);
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.lg-sub-html {
|
||||
background: linear-gradient(to top, rgba(10, 8, 6, 0.85) 0%, transparent 100%);
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
.lg-thumb-outer {
|
||||
background: rgba(10, 8, 6, 0.88);
|
||||
border-top-color: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.lg-thumb-item {
|
||||
opacity: 0.4;
|
||||
|
||||
&.active {
|
||||
border-color: var(--accent) !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(255, 255, 255, 0.2) !important;
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
.lg-counter {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.wi-lg-caption {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
.wi-lg-exif__camera,
|
||||
.wi-lg-exif__lens {
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
|
||||
.wi-lg-exif__aperture,
|
||||
.wi-lg-exif__shutter,
|
||||
.wi-lg-exif__iso,
|
||||
.wi-lg-exif__focal {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-md) {
|
||||
.lg-toolbar {
|
||||
padding: 2px 8px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.lg-toolbar .lg-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 15px;
|
||||
line-height: 32px !important;
|
||||
|
||||
&::before {
|
||||
line-height: 32px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lg-inner {
|
||||
top: 40px !important;
|
||||
}
|
||||
|
||||
.lg-actions .lg-next,
|
||||
.lg-actions .lg-prev {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 20px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.lg-sub-html {
|
||||
font-size: 12px;
|
||||
padding: 36px 12px 8px;
|
||||
}
|
||||
|
||||
.wi-lg-caption {
|
||||
font-size: 12px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.wi-lg-exif {
|
||||
gap: 4px 8px;
|
||||
}
|
||||
|
||||
.wi-lg-exif__camera,
|
||||
.wi-lg-exif__lens {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.wi-lg-exif__aperture,
|
||||
.wi-lg-exif__shutter,
|
||||
.wi-lg-exif__iso,
|
||||
.wi-lg-exif__focal {
|
||||
font-size: 9px;
|
||||
padding: 1px 5px;
|
||||
}
|
||||
|
||||
.lg-outer.wi-thumb-shown .lg-inner {
|
||||
bottom: 80px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-sm - 1px) {
|
||||
.lg-toolbar .lg-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 14px;
|
||||
line-height: 28px !important;
|
||||
|
||||
&::before {
|
||||
line-height: 28px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lg-actions .lg-next,
|
||||
.lg-actions .lg-prev {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 18px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
@use 'sass:math';
|
||||
@use 'variables' as *;
|
||||
|
||||
@mixin glass-effect {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
@mixin card-hover {
|
||||
transition: transform var(--duration-normal) var(--ease-out-expo),
|
||||
box-shadow var(--duration-normal) var(--ease-out-expo);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin responsive-container {
|
||||
width: 100%;
|
||||
max-width: $container-max;
|
||||
margin-inline: auto;
|
||||
padding-inline: clamp(1rem, 3vw, 2rem);
|
||||
}
|
||||
|
||||
@mixin section-spacing {
|
||||
padding-block: var(--section-spacing);
|
||||
}
|
||||
|
||||
@mixin focus-visible {
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
border-radius: $border-radius-sm;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@mixin hide-scrollbar {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin mobile-only {
|
||||
@media (max-width: #{$breakpoint-sm - 1px}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin tablet-up {
|
||||
@media (min-width: $breakpoint-md) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin desktop-up {
|
||||
@media (min-width: $breakpoint-lg) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
.halo-search-widget {
|
||||
.search-modal {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(24px);
|
||||
-webkit-backdrop-filter: blur(24px);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: $border-radius-xl;
|
||||
box-shadow: var(--shadow-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: var(--space-lg) var(--space-xl);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
color: var(--ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-lg);
|
||||
outline: none;
|
||||
transition: border-color var(--duration-fast) var(--ease-out-quart);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
padding: var(--space-md) var(--space-xl);
|
||||
border-bottom: 1px solid var(--rule);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background var(--duration-fast) var(--ease-out-quart),
|
||||
padding-left var(--duration-fast) var(--ease-out-quart);
|
||||
|
||||
&:hover {
|
||||
background: var(--accent-bg);
|
||||
padding-left: calc(var(--space-xl) + 4px);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search-result-title {
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.search-result-content {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-2);
|
||||
line-height: var(--leading-relaxed);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search-result-label {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
.search-empty {
|
||||
padding: var(--space-3xl) var(--space-xl);
|
||||
text-align: center;
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.search-hint {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-3);
|
||||
padding: var(--space-sm) var(--space-xl);
|
||||
border-top: 1px solid var(--rule);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.search-highlight {
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.search-backdrop {
|
||||
background: rgba(44, 36, 32, 0.4);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
}
|
||||
}
|
||||
|
||||
html.dark .halo-search-widget {
|
||||
.search-backdrop {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
&:hover {
|
||||
background: var(--accent-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
:root {
|
||||
--space-xs: 0.25rem;
|
||||
--space-sm: 0.5rem;
|
||||
--space-md: 1rem;
|
||||
--space-lg: 1.5rem;
|
||||
--space-xl: 2rem;
|
||||
--space-2xl: 3rem;
|
||||
--space-3xl: 4rem;
|
||||
--space-4xl: 6rem;
|
||||
|
||||
--container-sm: 640px;
|
||||
--container-md: 768px;
|
||||
--container-lg: 1024px;
|
||||
--container-xl: 1200px;
|
||||
--content-max: 800px;
|
||||
|
||||
--section-spacing: clamp(3rem, 2rem + 3vw, 6rem);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
:root {
|
||||
--font-sans: 'Noto Serif SC', 'Source Han Serif SC', Georgia, serif;
|
||||
--font-body: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
|
||||
--text-xs: clamp(0.7rem, 0.66rem + 0.2vw, 0.75rem);
|
||||
--text-sm: clamp(0.8rem, 0.76rem + 0.2vw, 0.875rem);
|
||||
--text-base: clamp(0.938rem, 0.89rem + 0.24vw, 1rem);
|
||||
--text-md: clamp(1.05rem, 0.99rem + 0.3vw, 1.125rem);
|
||||
--text-lg: clamp(1.15rem, 1.06rem + 0.45vw, 1.25rem);
|
||||
--text-xl: clamp(1.3rem, 1.16rem + 0.7vw, 1.5rem);
|
||||
--text-2xl: clamp(1.55rem, 1.34rem + 1.05vw, 1.875rem);
|
||||
--text-3xl: clamp(1.85rem, 1.52rem + 1.65vw, 2.25rem);
|
||||
--text-4xl: clamp(2.15rem, 1.7rem + 2.25vw, 3rem);
|
||||
--text-5xl: clamp(2.6rem, 1.9rem + 3.5vw, 3.75rem);
|
||||
|
||||
--leading-none: 1;
|
||||
--leading-tight: 1.2;
|
||||
--leading-snug: 1.35;
|
||||
--leading-normal: 1.6;
|
||||
--leading-relaxed: 1.75;
|
||||
--leading-loose: 2;
|
||||
|
||||
--tracking-tight: -0.02em;
|
||||
--tracking-normal: 0;
|
||||
--tracking-wide: 0.04em;
|
||||
--tracking-wider: 0.08em;
|
||||
--tracking-widest: 0.12em;
|
||||
}
|
||||
|
||||
/* 分级标题 */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 700;
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
color: var(--ink);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--text-3xl);
|
||||
padding-left: var(--space-md);
|
||||
border-left: 3px solid var(--accent);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--text-2xl);
|
||||
position: relative;
|
||||
padding-left: var(--space-lg);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: var(--text-xl);
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: var(--text-base);
|
||||
color: var(--ink-3);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
$breakpoint-sm: 640px;
|
||||
$breakpoint-md: 768px;
|
||||
$breakpoint-lg: 1024px;
|
||||
$breakpoint-xl: 1280px;
|
||||
|
||||
$container-max: 1200px;
|
||||
$content-max: 800px;
|
||||
|
||||
$navbar-height: 64px;
|
||||
|
||||
$border-radius-sm: 8px;
|
||||
$border-radius-md: 12px;
|
||||
$border-radius-lg: 16px;
|
||||
$border-radius-xl: 24px;
|
||||
$border-radius-full: 9999px;
|
||||
@@ -1,465 +0,0 @@
|
||||
:root {
|
||||
--bg: #faf8f5;
|
||||
--bg-raised: #f2ede8;
|
||||
--ink: #1a1410;
|
||||
--ink-2: #7a7068;
|
||||
--ink-3: #b5aca2;
|
||||
--rule: #ddd8d0;
|
||||
--accent: #c2410c;
|
||||
--accent-bg: #fff4ed;
|
||||
--black: 26, 20, 16;
|
||||
--gray: 122, 112, 104;
|
||||
--gray-light: 221, 216, 208;
|
||||
--gray-dark: 44, 36, 30;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--bg: #181613;
|
||||
--bg-raised: #242018;
|
||||
--ink: #ede9e3;
|
||||
--ink-2: #9a9088;
|
||||
--ink-3: #5e5650;
|
||||
--rule: #2e2a24;
|
||||
--accent: #f47040;
|
||||
--accent-bg: #2a1a0c;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: ui-sans-serif, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--bg);
|
||||
color: var(--ink);
|
||||
font-size: 17px;
|
||||
line-height: 1.75;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
main {
|
||||
width: 820px;
|
||||
max-width: calc(100% - 2.5rem);
|
||||
margin: 0 auto;
|
||||
padding: 3rem 0 5rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: ui-sans-serif, sans-serif;
|
||||
margin: 0 0 0.5em;
|
||||
color: var(--ink);
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
strong,
|
||||
b {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
a:hover {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 1em;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
}
|
||||
input {
|
||||
font-size: 16px;
|
||||
}
|
||||
table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
img,
|
||||
video,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.88em;
|
||||
padding: 0.15em 0.4em;
|
||||
background: var(--bg-raised);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--rule);
|
||||
}
|
||||
pre {
|
||||
padding: 1.4em 1.6em;
|
||||
border-radius: 10px;
|
||||
overflow-x: auto;
|
||||
background: var(--ink);
|
||||
color: #f8f8f2;
|
||||
}
|
||||
pre > code {
|
||||
all: unset;
|
||||
font-size: 0.9em;
|
||||
color: inherit;
|
||||
}
|
||||
blockquote {
|
||||
margin: 1.5em 0;
|
||||
padding: 0.75em 0 0.75em 1.5em;
|
||||
border-left: 3px solid var(--accent);
|
||||
color: var(--ink-2);
|
||||
font-size: 1.1em;
|
||||
}
|
||||
hr {
|
||||
margin: 2.5rem 0;
|
||||
border: none;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
/* ─── Page shell ─────────────────────────────────── */
|
||||
|
||||
.page-shell {
|
||||
display: grid;
|
||||
gap: 2.5rem;
|
||||
}
|
||||
|
||||
.page-heading {
|
||||
padding-bottom: 1.5rem;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.page-heading h1 {
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.page-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
margin-bottom: 0.6rem;
|
||||
color: var(--ink-3);
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
/* ─── Post feed ──────────────────────────────────── */
|
||||
|
||||
.post-feed__list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.post-feed__item + .post-feed__item {
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.post-feed__link {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 200px;
|
||||
gap: 2rem;
|
||||
align-items: start;
|
||||
padding: 2rem 0;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post-feed__item:first-child .post-feed__link {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.post-feed__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.post-feed__date {
|
||||
margin: 0;
|
||||
color: var(--ink-3);
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.post-feed__title {
|
||||
margin: 0;
|
||||
font-size: 1.35rem;
|
||||
line-height: 1.3;
|
||||
color: var(--ink);
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.post-feed__excerpt {
|
||||
margin: 0;
|
||||
color: var(--ink-2);
|
||||
font-size: 0.96rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.post-feed__image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 4 / 3;
|
||||
object-fit: cover;
|
||||
border-radius: 10px;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.post-feed__link:hover .post-feed__title {
|
||||
color: var(--accent);
|
||||
}
|
||||
.post-feed__link:hover .post-feed__image {
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
/* ─── Taxonomy list (categories) ─────────────────── */
|
||||
|
||||
.taxonomy-list__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 0.55rem 0;
|
||||
}
|
||||
|
||||
.taxonomy-list__link {
|
||||
color: var(--ink);
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.taxonomy-list__link:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.taxonomy-list__count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.2em 0.7em;
|
||||
border-radius: 999px;
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
color: var(--ink-2);
|
||||
font-size: 0.82rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ─── Tag cloud (badges) ─────────────────────────── */
|
||||
|
||||
.tag-cloud {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.tag-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.35em 0.85em;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
color: var(--ink-2);
|
||||
font-size: 0.88rem;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background 0.15s ease,
|
||||
color 0.15s ease,
|
||||
border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.tag-pill:hover {
|
||||
background: var(--accent-bg);
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.tag-pill__count {
|
||||
font-size: 0.8em;
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
/* ─── Archive ────────────────────────────────────── */
|
||||
|
||||
.archive-list {
|
||||
display: grid;
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.archive-group {
|
||||
display: grid;
|
||||
grid-template-columns: 80px minmax(0, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.archive-group__year {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
color: var(--ink-3);
|
||||
letter-spacing: -0.01em;
|
||||
padding-top: 0.2rem;
|
||||
}
|
||||
|
||||
.archive-group__items {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.archive-entry {
|
||||
display: grid;
|
||||
grid-template-columns: 80px minmax(0, 1fr);
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.archive-entry__date {
|
||||
color: var(--ink-3);
|
||||
font-size: 0.85rem;
|
||||
padding-top: 0.15rem;
|
||||
}
|
||||
|
||||
.archive-entry__title {
|
||||
margin: 0 0 0.2rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.archive-entry__summary {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
/* ─── Responsive ─────────────────────────────────── */
|
||||
|
||||
@media (max-width: 680px) {
|
||||
main {
|
||||
max-width: calc(100% - 2rem);
|
||||
padding: 2rem 0 4rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.9rem;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.post-feed__link {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
.post-feed__image {
|
||||
aspect-ratio: 16 / 9;
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.taxonomy-list__item,
|
||||
.archive-group,
|
||||
.archive-entry {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.archive-group {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.taxonomy-list__item {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Pagination ─────────────────────────────────── */
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid var(--rule);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.pagination__prev,
|
||||
.pagination__next {
|
||||
color: var(--ink-2);
|
||||
text-decoration: none;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.pagination__prev:hover,
|
||||
.pagination__next:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.pagination__info {
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.archive-empty,
|
||||
.feed-empty {
|
||||
color: var(--ink-3);
|
||||
font-size: 0.95rem;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
@use 'variables' as *;
|
||||
@use 'mixins';
|
||||
@use 'colors';
|
||||
@use 'typography';
|
||||
@use 'spacing';
|
||||
@use 'animations';
|
||||
@use 'comment';
|
||||
@use 'search';
|
||||
@use 'lightgallery';
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
hanging-punctuation: first last;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-normal);
|
||||
color: var(--ink);
|
||||
background-color: var(--bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
overflow-x: hidden;
|
||||
tab-size: 4;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: var(--accent);
|
||||
color: var(--bg);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--caramel);
|
||||
border-radius: $border-radius-full;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
||||
|
||||
&:hover {
|
||||
color: var(--accent-hover);
|
||||
}
|
||||
|
||||
@include mixins.focus-visible;
|
||||
}
|
||||
|
||||
img,
|
||||
video,
|
||||
svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: $border-radius-md;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-block-end: var(--space-md);
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-block: var(--space-lg);
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
border-left: 3px solid var(--accent);
|
||||
color: var(--ink-2);
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--leading-relaxed);
|
||||
background: var(--bg-raised);
|
||||
border-radius: 0 $border-radius-sm $border-radius-sm 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: var(--space-lg);
|
||||
border-radius: $border-radius-md;
|
||||
overflow-x: auto;
|
||||
background: var(--ink);
|
||||
color: #f8f8f2;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--leading-relaxed);
|
||||
tab-size: 2;
|
||||
|
||||
@include mixins.hide-scrollbar;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.88em;
|
||||
padding: 0.15em 0.4em;
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: $border-radius-sm;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
all: unset;
|
||||
font-size: 0.9em;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-block: var(--space-2xl);
|
||||
border: none;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--text-sm);
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
border: 1px solid var(--rule);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--bg-raised);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
td {
|
||||
color: var(--ink-2);
|
||||
}
|
||||
}
|
||||
|
||||
.wi-container {
|
||||
@include mixins.responsive-container;
|
||||
}
|
||||
|
||||
.wi-section {
|
||||
@include mixins.section-spacing;
|
||||
}
|
||||
|
||||
.wi-sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.wi-main {
|
||||
padding-top: 100px;
|
||||
padding-bottom: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background-color: var(--bg);
|
||||
}
|
||||
|
||||
.wi-main--wide.wi-main--home {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
padding-inline: 0;
|
||||
background-color: transparent;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.wi-content-wrap {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding-inline: clamp(1rem, 3vw, 2rem);
|
||||
}
|
||||
|
||||
.wi-content-wrap--wide {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.wi-main--wide {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
Reference in New Issue
Block a user