1
0
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:
2025-10-14 11:02:37 +08:00
parent 68f88291d0
commit 020bd84b98
7 changed files with 694 additions and 430 deletions

View File

@@ -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;

View 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>

View File

@@ -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");
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -34,6 +34,7 @@ const filteredPosts = computed(() => {
display: flex;
flex-direction: column;
gap: 16px;
grid-column: 1 / 13;
padding: 24px;