feat: add blog

This commit is contained in:
limes.pink
2025-04-16 13:53:42 +02:00
parent 6119c1a861
commit a0f5fdd205
90 changed files with 643 additions and 32 deletions

View File

@@ -50,7 +50,8 @@ body,
}
code {
background-color: var(--bg-shade-0);
display: inline-block;
background-color: #191919;
padding: 4px;
border-radius: 4px;
font-family: Consolas, Monaco, "Andale Mono", monospace;
@@ -58,6 +59,20 @@ code {
color: var(--text-shade-2);
}
h1.dot:not([data-title-suffix]):after,
h2.dot:not([data-title-suffix]):after {
content: ".";
display: inline-block;
color: var(--accent-shade-1);
}
h1.dot[data-title-suffix]:after,
h2.dot[data-title-suffix]:after {
content: attr(data-title-suffix);
display: inline-block;
color: var(--accent-shade-1);
}
/* BUTTONS */
button,
@@ -186,7 +201,7 @@ input[type="checkbox"] {
cursor: pointer;
}
input[type="checkbox"]:checked {
background: no-repeat center/contain url(../images/check.svg), var(--accent-shade-0);
background: no-repeat center/contain url(/assets/images/check.svg), var(--accent-shade-0);
}
input {

309
src/pages/blog/[slug].vue Normal file
View File

@@ -0,0 +1,309 @@
<script setup>
const slug = useRoute().params.slug;
const { data: post } = await useAsyncData(`blog-${slug}`, () => {
return queryCollection('blog').path(`/blog/${slug}`).first();
});
useHead({
title: `${post.value.title} | Pretendo Network Blog`,
meta: [
{ property: 'description', content: post.value.caption },
{ property: 'og:title', content: `${post.value.title} | Pretendo Network Blog` },
{ property: 'og:description', content: post.value.caption },
{ property: 'og:type', content: 'website' },
{ property: 'og:url', content: 'https://pretendo.network/' },
{ property: 'og:image', content: post.value.cover_image },
{ property: 'og:image:alt', content: 'Pretendo Network' },
{ property: 'og:site_name', content: 'Pretendo Network' },
{ name: 'twitter:url', content: 'https://pretendo.network/' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:site', content: '@PretendoNetwork' },
{ name: 'twitter:title', content: `${post.value.title} | Pretendo Network Blog` },
{ name: 'twitter:description', content: post.value.caption },
{ name: 'twitter:image', content: post.value.cover_image }
]
});
</script>
<template>
<div class="card-wrap">
<div class="blog-card">
<h1 class="title">
{{ post.title }}
</h1>
<div class="pub-info">
<span>{{ $t('blogPage.published') }}</span>
<div class="profile">
<img
class="profile-picture"
:src="post.author_image"
:alt="post.author"
>
<span>{{ post.author }}</span>
</div>
<span>{{ $t('blogPage.publishedOn') }}
<span class="date">{{ post.date }}</span>
</span>
</div>
<ContentRenderer :value="post" />
</div>
</div>
</template>
<style scoped>
.card-wrap {
width: 100%;
display: flex;
flex-flow: column nowrap;
align-items: center;
margin: 8rem 0;
}
.blog-card {
padding: 60px;
border-radius: 10px;
background: var(--bg-shade-0);
max-width: 1100px;
color: var(--text-shade-1);
}
.blog-card :deep(h1),
.blog-card :deep(h2),
.blog-card :deep(h3),
.blog-card :deep(h4),
.blog-card :deep(h5),
.blog-card :deep(h6) {
margin: 40px 0 10px;
color: var(--text-shade-3) !important;
}
.blog-card :deep(strong) {
color: var(--text-shade-3);
}
.blog-card :deep(a), .blog-card :deep(a) * {
color: inherit;
text-decoration: none;
}
.blog-card :deep(a:hover), .blog-card :deep(a:hover) * {
text-decoration: underline;
}
.blog-card :deep(*:not(h1, h2, h3, h4, h5, h6) > a),
.blog-card :deep(*:not(h1, h2, h3, h4, h5, h6) > a) * {
color: var(--accent-shade-1);
text-decoration: none;
font-weight: bold;
}
.blog-card :deep(*:not(h1, h2, h3, h4, h5, h6) > a):hover,
.blog-card :deep(*:not(h1, h2, h3, h4, h5, h6) > a):hover * {
text-decoration: underline;
}
.blog-card .title {
margin: 0;
margin-bottom: 8px;
}
.blog-card .pub-info {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: left;
margin-top: auto;
margin-bottom: 30px;
}
.blog-card .pub-info .date {
font-weight: bold;
color: var(--text-shade-3);
}
.blog-card .pub-info>* {
margin-right: 0.5em;
margin-top: 0.2em;
}
.blog-card .profile {
display: inline-grid;
grid-template-columns: 30px auto;
grid-gap: 10px;
font-weight: bold;
color: var(--text-shade-3);
align-items: center;
height: 32px;
margin-right: 0.3em;
}
.blog-card .profile img {
margin: 0;
border-radius: 4px;
border: 1px solid var(--border);
max-width: 100%;
}
.blog-card p,
.post-info {
color: var(--text-shade-1);
}
.blog-card :deep(img) {
max-width: 100%;
max-height: 800px;
margin: 10px auto;
display: block;
border-radius: 4px;
border: 1px solid var(--border);
}
.blog-card img.emoji {
display: inline;
margin: 0;
border: none;
}
.blog-card :deep(video) {
width: 100%;
border-radius: 4px;
border: 1px solid var(--border);
}
.blog-card :deep(iframe) {
width: 100%;
height: auto;
aspect-ratio: 16/9;
border-radius: 4px;
border: 1px solid var(--border);
}
/* Some twitter iframe specific stuff */
.blog-card .twitter-tweet {
margin: auto;
}
.blog-card .twitter-tweet iframe {
border: none;
/* Fixes the double border */
}
.blog-card :deep(table) {
border-radius: 4px;
border-collapse: collapse;
background: var(--bg-shade-3);
margin-bottom: 30px;
overflow: hidden;
}
.blog-card :deep(table) th {
padding: 8px 12px;
background: var(--bg-shade-4);
color: var(--text-shade-3);
}
.blog-card :deep(table) td {
padding: 8px 12px;
vertical-align: top;
border-radius: inherit;
}
.blog-card :deep(table) tr:nth-child(even) {
background: var(--bg-shade-2);
}
.blog-card :deep(pre code) {
border-radius: 4px;
margin-bottom: 30px;
}
.blog-card :deep(input[type="checkbox"]) {
appearance: none;
-webkit-appearance: none;
display: inline-block;
background: var(--bg-shade-3);
padding: 12px;
margin: 4px;
border-radius: 4px;
vertical-align: -60%;
}
.blog-card :deep(input[type="checkbox"]):checked {
content: "checkboxtest";
background: no-repeat center/contain url(/assets/images/check.svg),
var(--accent-shade-0);
}
.blog-card :deep(hr) {
border: 1px solid var(--text-shade-1);
margin: 30px 0;
}
.blog-card :deep(blockquote) {
border-left: 2px solid var(--text-shade-1);
padding: 8px 24px;
margin: 0;
margin-bottom: 30px;
}
.blog-card :deep(del) {
text-decoration: line-through;
}
.blog-card :deep(a > del) {
text-decoration: line-through;
}
.blog-card :deep(a:hover > del) {
text-decoration: line-through underline;
}
</style>
<style lang="scss">
@media screen and (min-width: 901px) {
#root {
.blog-card :deep(h1),
.blog-card :deep(h2),
.blog-card :deep(h3),
.blog-card :deep(h4),
.blog-card :deep(h5),
.blog-card :deep(h6) {
scroll-margin-top: 110px;
}
}
}
@media screen and (max-width: 800px) {
#root {
.blog-card {
padding: 40px;
}
}
}
@media screen and (max-width: 600px) {
#root {
.wrapper {
width: 100%;
}
header {
width: 90%;
margin: 35px auto;
}
.blog-card {
padding: 40px 5vw;
border-radius: 0;
margin-top: 0;
}
footer {
width: 95%;
margin: auto auto 40px;
}
}
}
</style>

270
src/pages/blog/index.vue Normal file
View File

@@ -0,0 +1,270 @@
<script setup lang="ts">
/* eslint-disable vue/no-v-html -- we might wanna avoid this by rewriting the locales to use variables */
const { data: allPosts } = await useAsyncData('blog', () => queryCollection('blog').all());
const posts = allPosts.value?.filter(p => !p.path.startsWith('/blog/_')).sort((a, b) => {
return new Date(b.date).getTime() - new Date(a.date).getTime();
});
</script>
<template>
<div class="blog">
<div class="progress-hero">
<div class="hero-meta reduced-margin">
<svg
xmlns="http://www.w3.org/2000/svg"
width="48.87"
height="71.093"
viewBox="0 0 48.87 71.093"
>
<g
id="XMLID_6_"
transform="translate(0)"
>
<path
id="XMLID_15_"
d="M69.581,29.593c-2.029,1.068-.249,4.129,1.78,3.061,5.162-2.67,11.463-2.6,16.981-1.1,4.735,1.282,9.5,3.845,12.246,8.045,1.246,1.922,4.307.142,3.061-1.78C96.921,27.386,80.3,24.04,69.581,29.593Z"
transform="translate(-60.112 -20.086)"
fill="#9d6ff3"
/>
<path
id="XMLID_14_"
d="M103.359,21.045c-3.951-6.159-10.751-10-17.657-11.89C77.763,6.948,68.721,7.019,61.281,10.9c-2.029,1.068-.249,4.129,1.78,3.061,6.586-3.453,14.667-3.311,21.644-1.388,5.981,1.638,12.1,4.913,15.521,10.252C101.507,24.783,104.569,23,103.359,21.045Z"
transform="translate(-54.766 -7.693)"
fill="#9d6ff3"
/>
<path
id="XMLID_9_"
d="M65.995,47.8a20.7,20.7,0,0,0-12.958,4.45H47.27a2.579,2.579,0,0,0-2.67,2.456v47.239a2.763,2.763,0,0,0,2.67,2.67h5.838a2.639,2.639,0,0,0,2.528-2.67V87.564A21.228,21.228,0,1,0,65.995,47.8Zm0,33.178a12,12,0,1,1,12-12A12,12,0,0,1,65.995,80.978Z"
transform="translate(-44.6 -33.522)"
fill="#9d6ff3"
/>
</g>
</svg>
<h1 class="title dot">
{{ $t('blogPage.title') }}
</h1>
<p
class="text"
v-html="$t('blogPage.description')"
/>
</div>
</div>
<a
v-for="post in posts"
:key="post.id"
:href="post.path"
class="purple-card blog-card"
>
<div class="post-info">
<h2 class="title">{{ post.title }}</h2>
<p class="caption">{{ post.caption }}</p>
<div class="pub-info">
<span>{{ $t('blogPage.published') }}</span>
<div class="profile">
<img
class="profile-picture"
:src="post.author_image"
:alt="post.author"
>
<span>{{ post.author }}</span>
</div>
<span>{{ $t('blogPage.publishedOn') }}
<span class="date">{{ post.date }}</span>
</span>
</div>
</div>
<div
class="cover"
:style="{ 'background': `no-repeat center/cover url(${post.cover_image})` }"
/>
</a>
<div class="buttons">
<!-- TODO: implement pagination -->
<a
href="/blog/feed.xml"
target="_blank"
title="RSS feed"
>
<button class="button secondary icon-btn">
<Icon
name="ph:rss"
size="36"
/>
</button>
</a>
</div>
</div>
</template>
<style scoped>
.progress-hero {
width: 100%;
display: flex;
justify-items: center;
align-items: center;
text-align: center;
}
.progress-hero svg {
width: 3rem;
height: 3rem;
}
.progress-hero .text {
max-width: 423px;
color: var(--text-shade-1);
line-height: 1.8;
}
.progress-hero .text :deep(a) {
color: var(--accent-shade-1);
text-decoration: none;
font-weight: bold;
}
.progress-hero .text :deep(a):hover {
text-decoration: underline;
}
.hero-meta.reduced-margin {
margin: 10vh 0;
width: 100%;
}
.hero-meta.reduced-margin h1 {
width: 100%;
}
.hero-meta.reduced-margin p {
margin: 0 auto;
}
.blog-card {
display: flex;
flex-flow: row nowrap;
padding: 0;
margin: 0 auto;
max-width: 1000px;
margin-bottom: 30px;
text-decoration: none;
position: relative;
border-radius: 10px;
overflow: hidden;
background: var(--bg-shade-0);
}
.blog-card .post-info {
flex: 50%;
padding: 40px;
display: flex;
flex-flow: column;
color: var(--text-shade-1);
}
.blog-card .post-info .title {
color: var(--text-shade-3);
margin: 0;
}
.blog-card .post-info .caption {
margin: 4px 0 32px 0;
}
.blog-card .pub-info {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: left;
margin-top: auto;
}
.blog-card .pub-info .date {
font-weight: bold;
color: var(--text-shade-3);
}
.blog-card .pub-info>* {
margin-right: 0.5em;
margin-top: 0.2em;
}
.blog-card .profile {
display: inline-grid;
grid-template-columns: 30px auto;
grid-gap: 10px;
font-weight: bold;
color: var(--text-shade-3);
align-items: center;
height: 32px;
margin-right: 0.3em;
}
.blog-card .profile img {
border-radius: 4px;
border: 1px solid var(--border);
max-width: 100%;
}
.blog-card .cover {
flex: 50%;
border: 3px solid var(--bg-shade-0);
border-radius: 0 10px 10px 0;
}
.progress-hero a,
.progress-hero a * {
color: var(--accent-shade-1);
text-decoration: none;
font-weight: bold;
}
.progress-hero a:hover,
.progress-hero a:hover {
text-decoration: underline;
}
.buttons {
margin: 10vh auto;
width: min-content;
}
.buttons button.secondary.icon-btn {
cursor: pointer;
width: 48px;
height: 48px;
padding: 0;
}
footer {
margin-top: 160px;
}
</style>
<style lang="scss">
@media screen and (max-width: 900px) {
#root {
.blog-card {
flex-flow: column;
}
.blog-card .post-info {
padding: 30px;
}
.blog-card .cover {
order: -1;
min-height: 250px;
border-radius: 10px 10px 0 0;
}
footer {
margin-top: 100px;
}
}
}
@media screen and (max-width: 450px) {
#root {
.blog-card .cover {
min-height: 200px;
}
}
}
</style>

View File

@@ -411,20 +411,6 @@ function titleSuffixHandler(path: string) {
margin: 48px auto 0;
}
h1.dot:not([data-title-suffix]):after,
h2.dot:not([data-title-suffix]):after {
content: ".";
display: inline-block;
color: var(--accent-shade-1);
}
h1.dot[data-title-suffix]:after,
h2.dot[data-title-suffix]:after {
content: attr(data-title-suffix);
display: inline-block;
color: var(--accent-shade-1);
}
/* Misc */
.purple-circle {
position: absolute;

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>

After

Width:  |  Height:  |  Size: 255 B