mirror of
https://github.com/sendevia/website.git
synced 2026-03-06 07:40:50 +08:00
feat: restructure header layout and add page indicator component
This commit is contained in:
@@ -13,31 +13,29 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<div class="header">
|
||||
<svg width="0" height="0">
|
||||
<filter id="noise-filter">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="1" numOctaves="5" :seed="seed" result="noise" />
|
||||
<feColorMatrix type="saturate" values="0" result="desaturatedNoise" />
|
||||
<feComponentTransfer>
|
||||
<feFuncR type="discrete" tableValues="0 1" />
|
||||
<feFuncG type="discrete" tableValues="0 1" />
|
||||
<feFuncB type="discrete" tableValues="0 1" />
|
||||
<feFuncA type="discrete" tableValues="1 1" />
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</svg>
|
||||
<div id="header-hero-container">
|
||||
<span id="header-hero-headline">{{ frontmatter.title ? frontmatter.title : page.title }}</span>
|
||||
<span id="header-hero-subtitle">{{ frontmatter.description }}</span>
|
||||
<div id="header-impression">
|
||||
<div id="header-impression-noise"></div>
|
||||
<div
|
||||
id="header-impression-image"
|
||||
:style="{ backgroundImage: frontmatter.impression ? `url('${frontmatter.impression}')` : `url('${defaultImpression}')` }"
|
||||
:impression-color="frontmatter.color"
|
||||
loading="lazy"
|
||||
></div>
|
||||
</div>
|
||||
<div id="header-hero-container">
|
||||
<span id="header-hero-headline">{{ frontmatter.title ? frontmatter.title : page.title }}</span>
|
||||
<span id="header-hero-subtitle">{{ frontmatter.description }}</span>
|
||||
<div id="header-impression">
|
||||
<svg width="0" height="0">
|
||||
<filter id="noise-filter">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="1" numOctaves="5" :seed="seed" result="noise" />
|
||||
<feColorMatrix type="saturate" values="0" result="desaturatedNoise" />
|
||||
<feComponentTransfer>
|
||||
<feFuncR type="discrete" tableValues="0 1" />
|
||||
<feFuncG type="discrete" tableValues="0 1" />
|
||||
<feFuncB type="discrete" tableValues="0 1" />
|
||||
<feFuncA type="discrete" tableValues="1 1" />
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</svg>
|
||||
<div id="header-impression-noise"></div>
|
||||
<div
|
||||
id="header-impression-image"
|
||||
:style="{ backgroundImage: frontmatter.impression ? `url('${frontmatter.impression}')` : `url('${defaultImpression}')` }"
|
||||
:impression-color="frontmatter.color"
|
||||
loading="lazy"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -47,7 +45,9 @@ onMounted(() => {
|
||||
@use "sass:meta";
|
||||
@use "../styles/mixin";
|
||||
|
||||
.header {
|
||||
header {
|
||||
grid-column: 1 / 13;
|
||||
|
||||
position: relative;
|
||||
|
||||
height: 540px;
|
||||
|
||||
203
.vitepress/theme/components/PageIndicator.vue
Normal file
203
.vitepress/theme/components/PageIndicator.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, toRefs, onMounted, onBeforeUnmount, nextTick, watch } from "vue";
|
||||
import { useGlobalData } from "../composables/useGlobalData";
|
||||
|
||||
const { page, frontmatter } = useGlobalData();
|
||||
const props = defineProps<{ headings: Array<{ id: string; text: string; level: number }>; activeId: string }>();
|
||||
const emit = defineEmits<{
|
||||
(e: "navigate", id: string): void;
|
||||
}>();
|
||||
const { headings, activeId } = toRefs(props);
|
||||
const grouped = computed(() => headings.value || []);
|
||||
const navRef = ref<HTMLElement | null>(null);
|
||||
const indicator = ref({ top: "0px", left: "0px", width: "100%", height: "0px", opacity: 0 });
|
||||
|
||||
let ro: ResizeObserver | null = null;
|
||||
let mo: MutationObserver | null = null;
|
||||
|
||||
function scrollToId(id: string) {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
history.replaceState(null, "", `#${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(id: string) {
|
||||
emit("navigate", id);
|
||||
scrollToId(id);
|
||||
}
|
||||
|
||||
function updateIndicator() {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
const nav = navRef.value;
|
||||
if (!nav) return;
|
||||
|
||||
const id = activeId.value as unknown as string;
|
||||
|
||||
if (!id) {
|
||||
indicator.value.opacity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const spanBlock = nav.querySelector(`span[data-id="${CSS.escape(id)}"]`) as HTMLElement | null;
|
||||
|
||||
if (!spanBlock || !spanBlock.offsetParent) {
|
||||
indicator.value.opacity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const conRect = nav.getBoundingClientRect();
|
||||
const spbRect = spanBlock.getBoundingClientRect();
|
||||
|
||||
const left = `${spbRect.left - conRect.left}px`;
|
||||
const top = `${spbRect.top - conRect.top}px`;
|
||||
const width = `${spbRect.width}px`;
|
||||
const height = `${spbRect.height}px`;
|
||||
|
||||
indicator.value = { top, left, width, height, opacity: 0.5 };
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
onMounted(() => {
|
||||
window.addEventListener("resize", updateIndicator, { passive: true });
|
||||
|
||||
nextTick(() => updateIndicator());
|
||||
|
||||
if ((window as any).ResizeObserver) {
|
||||
ro = new ResizeObserver(() => updateIndicator());
|
||||
if (navRef.value) {
|
||||
ro.observe(navRef.value);
|
||||
navRef.value.querySelectorAll("[data-id]").forEach((el) => ro!.observe(el as Element));
|
||||
}
|
||||
}
|
||||
|
||||
if ((window as any).MutationObserver && navRef.value) {
|
||||
mo = new MutationObserver(() => {
|
||||
nextTick(() => {
|
||||
updateIndicator();
|
||||
if (ro && navRef.value) {
|
||||
navRef.value.querySelectorAll("[data-id]").forEach((el) => ro!.observe(el as Element));
|
||||
}
|
||||
});
|
||||
});
|
||||
mo.observe(navRef.value, { childList: true, subtree: true });
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", updateIndicator);
|
||||
|
||||
if (ro) {
|
||||
ro.disconnect();
|
||||
ro = null;
|
||||
}
|
||||
|
||||
if (mo) {
|
||||
mo.disconnect();
|
||||
mo = null;
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => activeId.value,
|
||||
() => {
|
||||
nextTick(() => updateIndicator());
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => grouped.value,
|
||||
() => {
|
||||
nextTick(() => updateIndicator());
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-indicator" aria-label="页面目录" ref="navRef">
|
||||
<p>在此页上</p>
|
||||
<h3>{{ frontmatter.title ? frontmatter.title : page.title }}</h3>
|
||||
|
||||
<div
|
||||
class="indicator"
|
||||
:style="{ top: indicator.top, left: indicator.left, width: indicator.width, height: indicator.height, opacity: indicator.opacity }"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
|
||||
<div class="indicator-container">
|
||||
<span v-for="h in grouped" :key="h.id" :data-id="h.id" :class="[{ active: h.id === activeId }]">
|
||||
<a :href="`#${h.id}`" @click.prevent="navigateTo(h.id)" role="link" :aria-current="h.id === activeId ? 'true' : undefined">{{ h.text }}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "../styles/mixin";
|
||||
|
||||
.page-indicator {
|
||||
.indicator {
|
||||
position: absolute;
|
||||
|
||||
border: 1px solid var(--md-sys-color-primary);
|
||||
border-radius: var(--md-sys-shape-corner-extra-extra-large);
|
||||
|
||||
pointer-events: none;
|
||||
transition: var(--md-sys-motion-spring-fast-spatial-duration) var(--md-sys-motion-spring-fast-spatial);
|
||||
}
|
||||
|
||||
p {
|
||||
@include mixin.typescale-style("label-small");
|
||||
|
||||
margin-inline-start: 18px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-inline-start: 18px;
|
||||
padding-block-end: 18px;
|
||||
|
||||
font-variation-settings: "wght" 600;
|
||||
}
|
||||
|
||||
.indicator-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
span {
|
||||
width: 100%;
|
||||
|
||||
border-radius: var(--md-sys-shape-corner-extra-extra-large);
|
||||
|
||||
a {
|
||||
@include mixin.typescale-style("label-large");
|
||||
|
||||
width: 100%;
|
||||
|
||||
padding-inline: 18px;
|
||||
padding-block: 9px;
|
||||
|
||||
color: var(--md-sys-color-on-surface);
|
||||
font-variation-settings: "wght" 200;
|
||||
text-decoration: none;
|
||||
|
||||
transition: var(--md-sys-motion-spring-fast-effect-duration) var(--md-sys-motion-spring-fast-effect);
|
||||
}
|
||||
|
||||
&.active > a {
|
||||
color: var(--md-sys-color-primary);
|
||||
font-variation-settings: "wght" 700;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--md-sys-color-surface-dim);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -18,6 +18,8 @@ const posts = useAllPosts();
|
||||
@use "../styles/mixin";
|
||||
|
||||
.page-all-posts {
|
||||
grid-column: 1 / 13;
|
||||
|
||||
.posts-card a {
|
||||
@include mixin.typescale-style("headline-small");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import Header from "../components/Header.vue";
|
||||
import { onMounted } from "vue";
|
||||
import PageIndicator from "../components/PageIndicator.vue";
|
||||
import { onMounted, onBeforeUnmount, ref } from "vue";
|
||||
|
||||
function copyAnchorLink(this: HTMLElement) {
|
||||
const anchor = this as HTMLAnchorElement;
|
||||
@@ -18,346 +19,472 @@ function copyAnchorLink(this: HTMLElement) {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const anchors = document.querySelectorAll<HTMLAnchorElement>("a.title-anchor");
|
||||
anchors.forEach((anchor) => {
|
||||
anchor.addEventListener("click", copyAnchorLink);
|
||||
const headings = ref<Array<{ id: string; text: string; level: number }>>([]);
|
||||
const activeId = ref<string>("");
|
||||
|
||||
let observer: IntersectionObserver | null = null;
|
||||
let lockedId: string | null = null;
|
||||
let unlockTimer: number | null = null;
|
||||
|
||||
function onNavigate(id: string) {
|
||||
lockedId = id;
|
||||
activeId.value = id;
|
||||
if (unlockTimer) {
|
||||
window.clearTimeout(unlockTimer);
|
||||
}
|
||||
unlockTimer = window.setTimeout(() => {
|
||||
lockedId = null;
|
||||
unlockTimer = null;
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
function collectHeadings() {
|
||||
if (typeof window === "undefined") return;
|
||||
const nodes = Array.from(document.querySelectorAll("h1[id], h2[id]")) as HTMLElement[];
|
||||
headings.value = nodes.map((n) => ({ id: n.id, text: n.textContent?.trim() || n.id, level: +n.tagName.replace("H", "") }));
|
||||
}
|
||||
|
||||
function createObserver() {
|
||||
if (observer) observer.disconnect();
|
||||
|
||||
const visible = new Map<string, IntersectionObserverEntry>();
|
||||
|
||||
observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
const id = (entry.target as HTMLElement).id;
|
||||
if (entry.isIntersecting) {
|
||||
visible.set(id, entry);
|
||||
} else {
|
||||
visible.delete(id);
|
||||
}
|
||||
});
|
||||
|
||||
if (visible.size === 0) return;
|
||||
|
||||
if (lockedId) {
|
||||
activeId.value = lockedId;
|
||||
return;
|
||||
}
|
||||
|
||||
let bestId: string | null = null;
|
||||
let bestScore = -Infinity;
|
||||
visible.forEach((entry, id) => {
|
||||
const ratio = entry.intersectionRatio || 0;
|
||||
const top = entry.boundingClientRect.top;
|
||||
const score = ratio * 10000 - top;
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestId = id;
|
||||
}
|
||||
});
|
||||
|
||||
if (bestId) activeId.value = bestId;
|
||||
},
|
||||
{ root: null, rootMargin: "-20% 0px -60% 0px", threshold: [0, 0.1, 0.5, 1] }
|
||||
);
|
||||
|
||||
headings.value.forEach((h) => {
|
||||
const el = document.getElementById(h.id);
|
||||
if (el) observer?.observe(el);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const resizeHandler = () => {
|
||||
collectHeadings();
|
||||
createObserver();
|
||||
};
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
onMounted(() => {
|
||||
collectHeadings();
|
||||
createObserver();
|
||||
|
||||
const anchors = document.querySelectorAll<HTMLAnchorElement>("a.title-anchor");
|
||||
anchors.forEach((anchor) => {
|
||||
anchor.addEventListener("click", copyAnchorLink);
|
||||
});
|
||||
|
||||
window.addEventListener("resize", resizeHandler);
|
||||
|
||||
window.addEventListener("hashchange", () => {
|
||||
collectHeadings();
|
||||
createObserver();
|
||||
});
|
||||
window.addEventListener("popstate", () => {
|
||||
collectHeadings();
|
||||
createObserver();
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
observer?.disconnect();
|
||||
observer = null;
|
||||
|
||||
window.removeEventListener("resize", resizeHandler);
|
||||
window.removeEventListener("hashchange", () => {
|
||||
collectHeadings();
|
||||
createObserver();
|
||||
});
|
||||
|
||||
window.removeEventListener("popstate", () => {
|
||||
collectHeadings();
|
||||
createObserver();
|
||||
});
|
||||
|
||||
if (unlockTimer) {
|
||||
window.clearTimeout(unlockTimer);
|
||||
unlockTimer = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article>
|
||||
<Header />
|
||||
<section>
|
||||
<Content />
|
||||
</section>
|
||||
</article>
|
||||
<Header />
|
||||
<section>
|
||||
<Content />
|
||||
</section>
|
||||
<section>
|
||||
<PageIndicator :headings="headings" :activeId="activeId" @navigate="onNavigate" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "../styles/mixin";
|
||||
|
||||
article {
|
||||
width: 100%;
|
||||
section {
|
||||
&:nth-of-type(1) {
|
||||
display: flex;
|
||||
grid-column: 1 / 10;
|
||||
|
||||
section {
|
||||
&:nth-of-type(1) {
|
||||
.title-with-achor {
|
||||
position: relative;
|
||||
margin-inline-start: 24px;
|
||||
|
||||
a.title-anchor {
|
||||
@include mixin.typescale-style("body-large");
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
.title-with-achor {
|
||||
position: relative;
|
||||
|
||||
position: absolute;
|
||||
left: -55px;
|
||||
top: 0px;
|
||||
a.title-anchor {
|
||||
@include mixin.typescale-style("body-large");
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
|
||||
position: absolute;
|
||||
left: -55px;
|
||||
top: 0px;
|
||||
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
|
||||
color: var(--md-sys-color-on-surface);
|
||||
|
||||
opacity: 0;
|
||||
transition: var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized);
|
||||
|
||||
span:nth-of-type(1) {
|
||||
@include mixin.material-symbols($size: 24);
|
||||
|
||||
display: block;
|
||||
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
|
||||
color: var(--md-sys-color-on-surface);
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
opacity: 0;
|
||||
transition: var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized);
|
||||
}
|
||||
|
||||
span.visually-hidden {
|
||||
@include mixin.typescale-style("label-medium");
|
||||
|
||||
padding-block: 3px;
|
||||
padding-inline: 6px;
|
||||
|
||||
word-break: keep-all;
|
||||
|
||||
border-radius: var(--md-sys-shape-corner-small);
|
||||
|
||||
background-color: var(--md-sys-color-surface-container-low);
|
||||
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-block: 24px 12px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
a.title-anchor {
|
||||
opacity: 1;
|
||||
|
||||
span:nth-of-type(1) {
|
||||
@include mixin.material-symbols($size: 24);
|
||||
|
||||
display: block;
|
||||
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
|
||||
border-radius: var(--md-sys-shape-corner-full);
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
span.visually-hidden {
|
||||
@include mixin.typescale-style("label-medium");
|
||||
|
||||
padding-block: 3px;
|
||||
padding-inline: 6px;
|
||||
|
||||
word-break: keep-all;
|
||||
|
||||
border-radius: var(--md-sys-shape-corner-small);
|
||||
|
||||
background-color: var(--md-sys-color-surface-container-low);
|
||||
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-block: 24px 12px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
a.title-anchor {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
span:nth-of-type(1) {
|
||||
opacity: 1;
|
||||
}
|
||||
&:hover {
|
||||
span:nth-of-type(1):hover {
|
||||
background-color: var(--md-sys-color-surface-container-low);
|
||||
|
||||
&:hover {
|
||||
span:nth-of-type(1):hover {
|
||||
background-color: var(--md-sys-color-surface-container-low);
|
||||
|
||||
+ span.visually-hidden {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
+ span.visually-hidden {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-block: 12px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-block: 12px;
|
||||
|
||||
em {
|
||||
display: inline-block;
|
||||
|
||||
font-style: normal;
|
||||
|
||||
transform: skewX(-10deg);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-block: 12px;
|
||||
s {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-variation-settings: "wght" 700;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
|
||||
border-radius: var(--md-sys-shape-corner-small);
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
@include mixin.typescale-style("body-large");
|
||||
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-inline-start: 20px;
|
||||
|
||||
li {
|
||||
@include mixin.typescale-style("body-large");
|
||||
|
||||
position: relative;
|
||||
|
||||
margin-block-end: 5px;
|
||||
|
||||
vertical-align: middle;
|
||||
|
||||
p a:has(img) {
|
||||
max-width: 100%;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-inline-start: 70ch;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
|
||||
li::before {
|
||||
content: "";
|
||||
|
||||
display: inline-block;
|
||||
|
||||
position: absolute;
|
||||
left: -21px;
|
||||
top: 4.5px;
|
||||
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
|
||||
background-color: var(--md-sys-color-on-surface);
|
||||
|
||||
-webkit-mask: var(--via-svg-list-bullet) 0 0/100% no-repeat;
|
||||
mask: var(--via-svg-list-bullet) 0 0/100% no-repeat;
|
||||
}
|
||||
|
||||
li > p::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
p a,
|
||||
li a {
|
||||
display: inline-flex;
|
||||
|
||||
position: relative;
|
||||
|
||||
text-indent: initial;
|
||||
text-decoration: underline dashed;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
|
||||
display: block;
|
||||
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
|
||||
background-color: var(--md-sys-color-primary);
|
||||
|
||||
opacity: 0.2;
|
||||
transition: var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline solid;
|
||||
|
||||
&::before {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-block-end: 12px;
|
||||
margin-inline: 0px;
|
||||
padding-block-start: 12px;
|
||||
|
||||
color: var(--md-sys-color-on-tertiary-container);
|
||||
|
||||
border-inline-start: 6px solid var(--md-sys-color-inverse-on-surface);
|
||||
|
||||
blockquote {
|
||||
margin-inline: 24px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-block: 12px;
|
||||
|
||||
em {
|
||||
display: inline-block;
|
||||
|
||||
font-style: normal;
|
||||
|
||||
transform: skewX(-10deg);
|
||||
}
|
||||
|
||||
s {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-variation-settings: "wght" 700;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
|
||||
border-radius: var(--md-sys-shape-corner-small);
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
@include mixin.typescale-style("body-large");
|
||||
|
||||
overflow: auto;
|
||||
margin-inline: 24px;
|
||||
margin-block: 0px;
|
||||
padding-block-end: 12px;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-inline-start: 20px;
|
||||
|
||||
li {
|
||||
@include mixin.typescale-style("body-large");
|
||||
|
||||
position: relative;
|
||||
|
||||
margin-block-end: 5px;
|
||||
|
||||
vertical-align: middle;
|
||||
|
||||
p a:has(img) {
|
||||
max-width: 100%;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
margin-inline: 24px;
|
||||
padding-block-end: 7px;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-inline-start: 70ch;
|
||||
}
|
||||
.task-list-item {
|
||||
margin: 0px;
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
|
||||
li::before {
|
||||
content: "";
|
||||
|
||||
display: inline-block;
|
||||
|
||||
position: absolute;
|
||||
left: -21px;
|
||||
top: 4.5px;
|
||||
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
|
||||
background-color: var(--md-sys-color-on-surface);
|
||||
|
||||
-webkit-mask: var(--via-svg-list-bullet) 0 0/100% no-repeat;
|
||||
mask: var(--via-svg-list-bullet) 0 0/100% no-repeat;
|
||||
}
|
||||
|
||||
li > p::before {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p a,
|
||||
li a {
|
||||
display: inline-flex;
|
||||
details {
|
||||
margin: 0.5vh 0px;
|
||||
|
||||
position: relative;
|
||||
border-radius: var(--md-sys-shape-corner-small);
|
||||
|
||||
text-indent: initial;
|
||||
text-decoration: underline dashed;
|
||||
overflow: hidden;
|
||||
transition: var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
|
||||
|
||||
overflow: hidden;
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
summary {
|
||||
@include mixin.typescale-style("body-large");
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
gap: 12px;
|
||||
|
||||
padding: 12px;
|
||||
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
transition: var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
@include mixin.material-symbols("expand_more");
|
||||
|
||||
display: block;
|
||||
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
|
||||
background-color: var(--md-sys-color-primary);
|
||||
|
||||
opacity: 0.2;
|
||||
transform: rotateZ(-90deg);
|
||||
transition: var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline solid;
|
||||
|
||||
&::before {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-block-end: 12px;
|
||||
margin-inline: 0px;
|
||||
padding-block-start: 12px;
|
||||
|
||||
color: var(--md-sys-color-on-tertiary-container);
|
||||
|
||||
border-inline-start: 6px solid var(--md-sys-color-inverse-on-surface);
|
||||
|
||||
blockquote {
|
||||
margin-inline: 24px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-inline: 24px;
|
||||
margin-block: 0px;
|
||||
padding-block-end: 12px;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
margin-inline: 24px;
|
||||
padding-block-end: 7px;
|
||||
}
|
||||
|
||||
.task-list-item {
|
||||
margin: 0px;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--md-sys-color-inverse-on-surface);
|
||||
}
|
||||
|
||||
details {
|
||||
margin: 0.5vh 0px;
|
||||
|
||||
border-radius: var(--md-sys-shape-corner-small);
|
||||
|
||||
overflow: hidden;
|
||||
transition: var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-standard);
|
||||
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
&[open] {
|
||||
border-radius: var(--md-sys-shape-corner-extra-large);
|
||||
|
||||
summary {
|
||||
@include mixin.typescale-style("body-large");
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
gap: 12px;
|
||||
|
||||
padding: 12px;
|
||||
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
transition: var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized);
|
||||
&:focus-visible {
|
||||
border-radius: var(--md-sys-shape-corner-extra-large-top);
|
||||
}
|
||||
|
||||
&::before {
|
||||
@include mixin.material-symbols("expand_more");
|
||||
|
||||
transform: rotateZ(-90deg);
|
||||
transition: var(--md-sys-motion-duration-short4) var(--md-sys-motion-easing-emphasized);
|
||||
transform: rotateZ(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--md-sys-color-inverse-on-surface);
|
||||
}
|
||||
|
||||
&[open] {
|
||||
border-radius: var(--md-sys-shape-corner-extra-large);
|
||||
|
||||
summary {
|
||||
&:focus-visible {
|
||||
border-radius: var(--md-sys-shape-corner-extra-large-top);
|
||||
}
|
||||
|
||||
&::before {
|
||||
transform: rotateZ(0deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
margin-block-end: 12px;
|
||||
margin-inline: 24px;
|
||||
padding: 0px;
|
||||
|
||||
width: calc(100% - 48px);
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
margin-block-end: 12px;
|
||||
margin-inline: 24px;
|
||||
padding: 0px;
|
||||
|
||||
width: calc(100% - 48px);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-of-type(2) {
|
||||
grid-column: 11 / 13;
|
||||
|
||||
position: sticky;
|
||||
top: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,21 +97,19 @@ function onAfterEnter() {
|
||||
<template>
|
||||
<div id="layout">
|
||||
<Sidebar />
|
||||
<div id="layout-content-flow">
|
||||
<Transition name="layout-content" mode="out-in" @after-enter="onAfterEnter">
|
||||
<div id="layout-content-filler" :key="route.path">
|
||||
<div id="layout-home-title" v-if="frontmatter.home">
|
||||
<img src="/assets/images/avatar.webp" alt="avatar" />
|
||||
<div>
|
||||
<h1>{{ site.title }}</h1>
|
||||
<p>{{ site.description }}</p>
|
||||
</div>
|
||||
<Transition name="layout-content" mode="out-in" @after-enter="onAfterEnter">
|
||||
<div id="layout-content-flow" :key="route.path">
|
||||
<div id="layout-home-title" v-if="frontmatter.home">
|
||||
<img src="/assets/images/avatar.webp" alt="avatar" />
|
||||
<div>
|
||||
<h1>{{ site.title }}</h1>
|
||||
<p>{{ site.description }}</p>
|
||||
</div>
|
||||
<component v-else :is="currentLayout" />
|
||||
</div>
|
||||
</Transition>
|
||||
<Footer />
|
||||
</div>
|
||||
<component v-else :is="currentLayout" />
|
||||
<Footer />
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -143,7 +141,7 @@ function onAfterEnter() {
|
||||
position: relative;
|
||||
|
||||
padding-block-end: 68px;
|
||||
padding-inline: 4vw;
|
||||
padding-inline: 20vw;
|
||||
|
||||
width: 100%;
|
||||
|
||||
@@ -161,72 +159,38 @@ function onAfterEnter() {
|
||||
height: 100%;
|
||||
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
flex-basis: 100%;
|
||||
width: 0;
|
||||
order: 2;
|
||||
#layout-home-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 42px;
|
||||
grid-column: 1 / 13;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
|
||||
h1 {
|
||||
@include mixin.typescale-style("display-large");
|
||||
|
||||
grid-column: span 9;
|
||||
}
|
||||
|
||||
#layout-home-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 42px;
|
||||
justify-content: center;
|
||||
h6 {
|
||||
grid-column: span 9;
|
||||
|
||||
width: 100%;
|
||||
|
||||
h1 {
|
||||
@include mixin.typescale-style("display-large");
|
||||
|
||||
grid-column: span 9;
|
||||
}
|
||||
|
||||
h6 {
|
||||
grid-column: span 9;
|
||||
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
img {
|
||||
grid-column: 11 / span 2;
|
||||
grid-row: 2 / span 2;
|
||||
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
|
||||
-webkit-mask: var(--via-svg-mask) no-repeat 0 / 100%;
|
||||
mask: var(--via-svg-mask) no-repeat 0 / 100%;
|
||||
}
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
div.card[spec="feed"] {
|
||||
width: calc(50% - 12px);
|
||||
img {
|
||||
grid-column: 11 / span 2;
|
||||
grid-row: 2 / span 2;
|
||||
|
||||
border-radius: var(--md-sys-shape-corner-extra-large-increased);
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
|
||||
&:nth-child(2n + 4),
|
||||
&[size="large"] {
|
||||
margin-inline-end: 12px;
|
||||
|
||||
order: 1;
|
||||
}
|
||||
|
||||
&:nth-child(2n + 3),
|
||||
&[size="small"] {
|
||||
margin-inline-start: 12px;
|
||||
|
||||
order: 2;
|
||||
}
|
||||
|
||||
& > a {
|
||||
width: 100%;
|
||||
|
||||
color: var(--md-sys-color-on-surface);
|
||||
text-decoration: none;
|
||||
}
|
||||
-webkit-mask: var(--via-svg-mask) no-repeat 0 / 100%;
|
||||
mask: var(--via-svg-mask) no-repeat 0 / 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,10 +263,6 @@ function onAfterEnter() {
|
||||
grid-column: calc(($columns + 1) / 2) string.unquote("/") $columns;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-footer {
|
||||
grid-column: span $columns;
|
||||
}
|
||||
}
|
||||
|
||||
&[spec="feed"] {
|
||||
@@ -317,10 +277,6 @@ function onAfterEnter() {
|
||||
grid-column: calc($columns - 1) / calc($columns + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#layout-search {
|
||||
grid-column: span $columns;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,25 +308,6 @@ function onAfterEnter() {
|
||||
grid-column: span calc($columns / 2);
|
||||
}
|
||||
}
|
||||
|
||||
.layout-footer {
|
||||
grid-column: span $columns;
|
||||
}
|
||||
}
|
||||
|
||||
&[spec="feed"] {
|
||||
#layout-content-flow {
|
||||
& > {
|
||||
h1,
|
||||
h6 {
|
||||
grid-column: span calc($columns - 1);
|
||||
}
|
||||
|
||||
img {
|
||||
grid-column: $columns;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,10 +343,6 @@ function onAfterEnter() {
|
||||
grid-column: span calc($columns / 2);
|
||||
}
|
||||
}
|
||||
|
||||
.layout-footer {
|
||||
grid-column: span $columns;
|
||||
}
|
||||
}
|
||||
|
||||
&[spec="feed"] {
|
||||
@@ -434,31 +367,6 @@ function onAfterEnter() {
|
||||
grid-row: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#layout-content-filler {
|
||||
&::before,
|
||||
&::after {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
div.card[spec="feed"] {
|
||||
width: 100%;
|
||||
|
||||
&:nth-child(2n + 4),
|
||||
&[size="large"] {
|
||||
margin-inline-end: 0px;
|
||||
|
||||
order: 1;
|
||||
}
|
||||
|
||||
&:nth-child(2n + 3),
|
||||
&[size="small"] {
|
||||
margin-inline-start: 0px;
|
||||
|
||||
order: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,4 +390,16 @@ function onAfterEnter() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1600px) {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 840px) {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -76,8 +76,16 @@
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 840px) {
|
||||
@media screen and (max-width: 1600px) {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 840px) {
|
||||
.notfound {
|
||||
#notfound-information {
|
||||
h1 {
|
||||
font-size: 75rem;
|
||||
@@ -88,4 +96,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -34,6 +34,7 @@ const filteredPosts = computed(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
grid-column: 1 / 13;
|
||||
|
||||
padding: 24px;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user