1
0
Fork 0
This commit is contained in:
Moritz Ruth 2021-12-18 15:44:33 +01:00
parent c44f07a576
commit 8ec13b18f0
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
14 changed files with 288 additions and 34 deletions

View file

@ -6,6 +6,7 @@
<title>Moritz Ruth</title>
<link rel="preload" as="font" href="/node_modules/@fontsource/plus-jakarta-sans/files/plus-jakarta-sans-latin-400-normal.woff2" crossorigin="anonymous">
<link rel="stylesheet" href="/node_modules/@fontsource/plus-jakarta-sans/400.css">
<link rel="stylesheet" href="/node_modules/@fontsource/syne/700.css">
<link rel="stylesheet" href="/node_modules/@fontsource/plus-jakarta-sans/700.css">
<meta name="description" content="freelance software developer, graphic design enthusiast and hobby photographer">
<meta name="keywords" content="web, dev, development, coding, moritz, ruth, development, design, kotlin, android">

View file

@ -19,11 +19,16 @@
"windicss": "^3.2.1"
},
"dependencies": {
"@fontsource/fanwood-text": "^4.5.0",
"@fontsource/league-gothic": "^4.5.0",
"@fontsource/linden-hill": "^4.5.0",
"@fontsource/plus-jakarta-sans": "^4.5.0",
"@fontsource/syne": "^4.5.0",
"@iconify/json": "1.1.444",
"@vueuse/core": "^7.2.2",
"@vueuse/head": "^0.7.4",
"blobs": "^2.2.1-beta.1",
"ohmyfetch": "^0.4.11",
"unplugin-icons": "^0.12.23",
"vue": "^3.2.26",
"vue-router": "^4.0.12"

81
pnpm-lock.yaml generated
View file

@ -1,7 +1,11 @@
lockfileVersion: 5.3
specifiers:
'@fontsource/fanwood-text': ^4.5.0
'@fontsource/league-gothic': ^4.5.0
'@fontsource/linden-hill': ^4.5.0
'@fontsource/plus-jakarta-sans': ^4.5.0
'@fontsource/syne': ^4.5.0
'@iconify/json': 1.1.444
'@vitejs/plugin-vue': ^2.0.1
'@vue/compiler-sfc': ^3.1.1
@ -10,6 +14,7 @@ specifiers:
blobs: ^2.2.1-beta.1
eslint: ^7.32.0
eslint-config-awzzm-vue: ^2.0.1
ohmyfetch: ^0.4.11
typescript: ^4.5.4
unplugin-icons: ^0.12.23
vite: ^2.3.7
@ -20,11 +25,16 @@ specifiers:
windicss: ^3.2.1
dependencies:
'@fontsource/fanwood-text': 4.5.0
'@fontsource/league-gothic': 4.5.0
'@fontsource/linden-hill': 4.5.0
'@fontsource/plus-jakarta-sans': 4.5.0
'@fontsource/syne': 4.5.0
'@iconify/json': 1.1.444
'@vueuse/core': 7.3.0_vue@3.2.26
'@vueuse/head': 0.7.4_vue@3.2.26
blobs: 2.2.1-beta.1
ohmyfetch: 0.4.11
unplugin-icons: 0.12.23_fb6292b47fe9c67e26b404bea724bbe1
vue: 3.2.26
vue-router: 4.0.12_vue@3.2.26
@ -285,10 +295,26 @@ packages:
- supports-color
dev: true
/@fontsource/fanwood-text/4.5.0:
resolution: {integrity: sha512-dY/ELXRZ0rOtGh7LT6ACLoYeFebKmjfhLTVrfVOOGT+qqYUZTsiPE8+SxdKJ8wO2Kr/zBbLYTmowMFjIdhIVnw==}
dev: false
/@fontsource/league-gothic/4.5.0:
resolution: {integrity: sha512-o1svytj1bOHDy0GYBxcHZmQ+CbAJq8OjrxTz30VhEuQuegpKfgH4d/2g3AhQk8aq4bhW5rkIl2sii9SN5xNn1A==}
dev: false
/@fontsource/linden-hill/4.5.0:
resolution: {integrity: sha512-O0tH0sNGivUh98gXi9omUJNFpQQ6x4D8FNFSbTVyWslCo6dXZSAN9+UJL/WQxT1GdRclZQCEYAG76+vcq6VGmA==}
dev: false
/@fontsource/plus-jakarta-sans/4.5.0:
resolution: {integrity: sha512-PUVYjxqIXa8tf7VyQWnRP5FsGTwTlOlmHNrbF7UQ9UOd4c25X4VGkb2ZCZsJaPt6MECXIbJYwl49LLV6Kk6BHg==}
dev: false
/@fontsource/syne/4.5.0:
resolution: {integrity: sha512-jPBpootysCJpJ0X9g8kb7411kwCIHx9HKaI26be86hpUdX1Q0q0uFEZhc8Bnhhub6UinU78wIE1o4aNTraH5iQ==}
dev: false
/@humanwhocodes/config-array/0.5.0:
resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==}
engines: {node: '>=10.10.0'}
@ -857,6 +883,11 @@ packages:
resolution: {integrity: sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ==}
dev: false
/data-uri-to-buffer/4.0.0:
resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==}
engines: {node: '>= 12'}
dev: false
/debug/2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
dependencies:
@ -911,6 +942,10 @@ packages:
object-keys: 1.1.1
dev: true
/destr/1.1.0:
resolution: {integrity: sha512-Ev/sqS5AzzDwlpor/5wFCDu0dYMQu/0x2D6XfAsQ0E7uQmamIgYJ6Dppo2T2EOFVkeVYWjc+PCLKaqZZ57qmLg==}
dev: false
/dir-glob/3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@ -1493,6 +1528,13 @@ packages:
reusify: 1.0.4
dev: true
/fetch-blob/3.1.3:
resolution: {integrity: sha512-ax1Y5I9w+9+JiM+wdHkhBoxew+zG4AJ2SvAD1v1szpddUIiPERVGBxrMcB2ZqW0Y3PP8bOWYv2zqQq1Jp2kqUQ==}
engines: {node: ^12.20 || >= 14.13}
dependencies:
web-streams-polyfill: 3.2.0
dev: false
/file-entry-cache/6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
@ -1546,6 +1588,13 @@ packages:
resolution: {integrity: sha1-C+4AUBiusmDQo6865ljdATbsG5k=}
dev: true
/formdata-polyfill/4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
dependencies:
fetch-blob: 3.1.3
dev: false
/fs.realpath/1.0.0:
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
dev: true
@ -2074,6 +2123,15 @@ packages:
resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=}
dev: true
/node-fetch/3.1.0:
resolution: {integrity: sha512-QU0WbIfMUjd5+MUzQOYhenAazakV7Irh1SGkWCsRzBwvm4fAhzEUaHMJ6QLP7gWT6WO9/oH2zhKMMGMuIrDyKw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
data-uri-to-buffer: 4.0.0
fetch-blob: 3.1.3
formdata-polyfill: 4.0.10
dev: false
/node-releases/2.0.1:
resolution: {integrity: sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==}
dev: true
@ -2130,6 +2188,15 @@ packages:
es-abstract: 1.19.1
dev: true
/ohmyfetch/0.4.11:
resolution: {integrity: sha512-HfD15iWbISD7eCqtsKoOU35b54/fuaNRGUN0Jr1v8tyP7yLrebvqqkISLAOyrQSpUExPOrXNPk0ykfgpqkRdaQ==}
dependencies:
destr: 1.1.0
node-fetch: 3.1.0
ufo: 0.7.9
undici: 4.12.0
dev: false
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
@ -2627,6 +2694,10 @@ packages:
hasBin: true
dev: true
/ufo/0.7.9:
resolution: {integrity: sha512-6t9LrLk3FhqTS+GW3IqlITtfRB5JAVr5MMNjpBECfK827W+Vh5Ilw/LhTcHWrt6b3hkeBvcbjx4Ti7QVFzmcww==}
dev: false
/unbox-primitive/1.0.1:
resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==}
dependencies:
@ -2636,6 +2707,11 @@ packages:
which-boxed-primitive: 1.0.2
dev: true
/undici/4.12.0:
resolution: {integrity: sha512-sJ4CyO3ZPaoxWpLQTJpH/gWD+tCIra2OJ9UPvrX1siyJkgh8NOAybRejJ/g2xHyOdAuoSE0lPRJwRl8AZSXYJQ==}
engines: {node: '>=12.18'}
dev: false
/unplugin-icons/0.12.23_fb6292b47fe9c67e26b404bea724bbe1:
resolution: {integrity: sha512-jhCogt+/3WEdPrfHkUGwiLnNJAOrE469J/Zlsh57KAaeEDxrw+PMqXDXRFA/fZjtal/btGPFcDOeQPPHGW6JHg==}
peerDependencies:
@ -2814,6 +2890,11 @@ packages:
'@vue/shared': 3.2.26
dev: false
/web-streams-polyfill/3.2.0:
resolution: {integrity: sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==}
engines: {node: '>= 8'}
dev: false
/webpack-virtual-modules/0.4.3:
resolution: {integrity: sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==}
dev: false

View file

@ -1,6 +1,10 @@
<template>
<div class="h-100vh w-100vw text-light-100 overflow-x-hidden">
<router-view/>
<router-view v-slot="{ Component }">
<suspense>
<component :is="Component"/>
</suspense>
</router-view>
</div>
</template>
@ -10,6 +14,7 @@
overflow-x: hidden;
width: 100vw;
min-height: 100vh;
font-size: 17px;
}
</style>

View file

@ -3,11 +3,11 @@
<component
:is="link.to ? 'router-link' : 'a'"
v-for="link in links"
:key="link.to"
:key="link.to || link.href"
:to="link.to"
:href="link.href"
class="px-5 sm:px-6 py-4 bg-light-300 bg-opacity-5 rounded-lg backdrop-blur-lg flex cursor-pointer
hover:bg-opacity-10 focus-visible:bg-opacity-10 transform hover:scale-104 transition duration-200 group"
hover:bg-opacity-10 focus-visible:bg-opacity-10 transform hover:scale-104 transition duration-200"
>
<div class="flex items-center justify-center text-xl sm:text-2xl relative pr-4 sm:pr-5" :class="link.iconClasses">
<template v-if="typeof link.icon === 'string'">

View file

@ -6,18 +6,34 @@
<style>
.prose {
@apply text-light-900 text-lg;
@apply text-light-900 text-base sm:text-lg;
address, p, ol, ul {
@apply max-w-180 m-0 p-0;
}
.intro {
@apply text-xl pb-2;
}
h2, h3 {
@apply font-bold text-gray-100 m-0 font-special;
}
h2 {
@apply font-bold text-2xl pt-8 pb-4 text-gray-100;
@apply text-2xl pt-8 pb-4;
}
h3 {
@apply text-xl pt-6 pb-3;
}
h2 + h3 {
@apply pt-0 -mt-2;
}
a {
@apply text-blue-400;
@apply text-blue-400 can-hover:hover:text-pink-400 transition duration-200;
}
ol {
@ -29,18 +45,18 @@
}
ul {
@apply list-disc list-inside space-y-1;
}
@apply list-disc list-inside space-y-1 pl-1;
address, p, ol, ul {
@apply max-w-180;
::marker {
@apply text-gray-400;
}
}
address {
@apply not-italic;
}
:where(address, p, ol, ul) ~ :where(address, p, ol, ul) {
:where(address, p, ol, ul) + :where(address, p, ol, ul) {
@apply pt-4;
}
}

View file

@ -1,22 +1,32 @@
<template>
<div class="w-full max-w-1000px mx-auto">
<div class="bg-background bg-opacity-70 backdrop-filter backdrop-blur-sm backdrop-saturate-200 px-6 py-8 text-light-900 sticky top-0 z-10 flex items-center justify-between">
<div class="w-0">
<router-link :to="backTarget" class="w-8 flex items-center group relative">
<ArrowLeftIcon class="text-2xl"/>
<div class="text-lg absolute left-10 opacity-0 sm:can-hover:group-hover:opacity-100 pointer-events-none transition duration-200">
Back
</div>
</router-link>
<div class="h-100vh flex flex-col justify-between">
<div class="w-full max-w-1000px mx-auto">
<div class="bg-background bg-opacity-70 backdrop-filter backdrop-blur-sm backdrop-saturate-200 px-6 py-4 sm:py-8 text-light-900 sticky top-0 z-10 flex items-center justify-between">
<div class="w-0">
<router-link :to="backTarget" class="w-8 flex items-center group relative">
<ArrowLeftIcon class="text-2xl"/>
<div class="text-lg absolute left-10 opacity-0 sm:can-hover:group-hover:opacity-100 pointer-events-none transition duration-200">
Back
</div>
</router-link>
</div>
<div class="font-bold text-xl sm:text-2xl text-center transform -translate-y-2px">
{{ title }}
</div>
<div/>
</div>
<div class="font-bold text-2xl text-center transform -translate-y-2px">
{{ title }}
<div class="pt-8 pb-20 px-5" v-bind="$attrs">
<slot/>
</div>
<div/>
</div>
<div class="pt-8 pb-10 px-5" v-bind="$attrs">
<slot/>
</div>
<footer class="flex justify-center opacity-30 hover:opacity-60 transition duration-400 space-x-5 pb-6">
<router-link to="/terms">
Terms
</router-link>
<router-link to="/legal-notice">
Legal Notice
</router-link>
</footer>
</div>
</template>

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

@ -0,0 +1,50 @@
<template>
<TopBarLayout title="Blog" back-target="/">
<article>
<h1 class="font-bold text-3xl sm:text-3xl sm:text-center font-special">
{{ post.title }}
</h1>
<XSpacer v="4"/>
<div class="flex justify-center -sm:flex-col -sm:space-y-1 -sm:pt-2 sm:space-x-3 text-sm sm:text-base">
<div>
Published at {{ post.published_at.slice(0, 10) }}
</div>
<div class="-sm:hidden">
|
</div>
<div>
Reading time: {{ post.reading_time_minutes }} minute{{ post.reading_time_minutes === 1 ? "" : "s" }}
</div>
</div>
<XSpacer v="8"/>
<Prose v-html="html"/>
</article>
</TopBarLayout>
</template>
<style module>
</style>
<script lang="ts">
import TopBarLayout from "../../components/TopBarLayout.vue"
import { getPostBySlug } from "../../posts"
import { useRoute } from "vue-router"
import Prose from "../../components/Prose.vue"
import XSpacer from "../../components/XSpacer.vue"
export default {
name: "BlogPostPage",
components: { XSpacer, Prose, TopBarLayout },
async setup() {
const route = useRoute()
const post = await getPostBySlug(route.params.slug as string)
return {
post,
// Yep, that's awful code
html: post.body_html.replaceAll("h2>", "h3>").replaceAll("h1>", "h2>")
}
}
}
</script>

65
src/pages/blog/index.vue Normal file
View file

@ -0,0 +1,65 @@
<template>
<TopBarLayout title="Blog" back-target="/">
<div class="fixed top-0 left-0 bottom-0 right-0 flex justify-center items-center">
<BlurredBlobCanvas
:blur="70"
:size="500"
:randomness="400"
:minimum-duration="3000"
:duration-variation="1000"
:minimum-opacity="0.4"
:opacity-variation="0"
:colors="['#eb34cf', '#6577fc']"
/>
</div>
<div class="flex flex-col space-y-8 max-w-120 mx-auto">
<router-link
v-for="post in posts"
:key="post.id"
:to="`/blog/${post.slug}`"
class="bg-light-300 bg-opacity-5 rounded-lg backdrop-blur-lg cursor-pointer hover:bg-opacity-10 focus-visible:bg-opacity-10 transform hover:scale-104
transition duration-200 p-5 sm:p-7 flex flex-col overflow-hidden"
>
<div class="font-bold text-xl">
{{ post.title }}
</div>
<XSpacer v="2"/>
<div class="text-lg">
{{ post.description }}
</div>
<XSpacer v="3"/>
<div class="font-bold text-sm flex justify-between">
<div>
Published at {{ new Date(post.published_at).toLocaleDateString() }}
</div>
<div>
{{ post.reading_time_minutes }} minute{{ post.reading_time_minutes === 1 ? "" : "s" }}
</div>
</div>
</router-link>
</div>
</TopBarLayout>
</template>
<style module>
</style>
<script lang="ts">
import TopBarLayout from "../../components/TopBarLayout.vue"
import { getPosts } from "../../posts"
import XSpacer from "../../components/XSpacer.vue"
import BlurredBlobCanvas from "../../components/BlurredBlobCanvas.vue"
export default {
name: "BlogIndexPage",
components: { BlurredBlobCanvas, XSpacer, TopBarLayout },
async setup() {
const posts = await getPosts()
return {
posts: [...posts, ...posts, ...posts]
}
}
}
</script>

View file

@ -15,7 +15,7 @@
/>
</div>
<main class="relative max-w-130 p-2 lg:pl-10">
<div class="font-extrabold text-3xl sm:text-4xl">
<div class="font-bold font-special text-3xl sm:text-4xl">
Moritz Ruth
</div>
<div class="text-lg sm:text-xl font-medium leading-8 pt-5">
@ -53,12 +53,12 @@
<LinkCardList :links="navigationLinks"/>
</div>
</div>
<footer class="flex justify-center opacity-30 hover:opacity-60 transition duration-400 space-x-4 pb-6">
<footer class="flex justify-center opacity-30 hover:opacity-60 transition duration-400 space-x-5 pb-6">
<router-link to="/terms">
Terms
</router-link>
<router-link to="/legal-notice">
Legal notice
Legal Notice
</router-link>
</footer>
</div>

View file

@ -12,13 +12,13 @@
class="w-full lg:max-w-150 max-h-80vh block object-contain"
>
<div>
<div class="text-gray-400 text-sm pt-1">
<div class="text-gray-400">
{{ photo.date }}
</div>
<div class="font-bold text-2xl text-light-500">
<div class="font-bold text-2xl text-light-500 font-special">
{{ photo.title }}
</div>
<div class="opacity-90 max-w-100 space-y-1 pt-4 whitespace-pre-line text-light-600">
<div class="opacity-90 max-w-100 space-y-1 pt-3 whitespace-pre-line text-light-600 text-lg">
{{ photo.description }}
</div>
</div>
@ -40,6 +40,7 @@
name: "PhotographyPage",
components: { XSpacer, TopBarLayout },
setup() {
return {
photos
}

View file

@ -8,7 +8,7 @@ export const photos = [
{
"file": "Martyrdom.webp",
"title": "Martyrdom",
"date": "2020-10-19",
"date": "2019",
"description": "The Sankt-Laurentius church right by the Mosel river in Bremm (Germany)."
}
]

19
src/posts.ts Normal file
View file

@ -0,0 +1,19 @@
import { $fetch } from "ohmyfetch"
const USERNAME = "moritzruth"
export interface Post {
id: number
title: string
slug: string
description: string
published_at: string
reading_time_minutes: number
}
export interface FullPost extends Post {
body_html: string
}
export const getPosts = () => $fetch<Post[]>(`https://dev.to/api/articles?username=${USERNAME}&per_page=1000`)
export const getPostBySlug = (slug: string) => $fetch<FullPost>(`https://dev.to/api/articles/${USERNAME}/${slug}`)

View file

@ -18,7 +18,8 @@ export default defineConfig({
background: "#070707"
},
fontFamily: {
sans: ["Plus Jakarta Sans", "sans-serif"]
sans: ["Plus Jakarta Sans", "sans-serif"],
special: ["Syne", "sans-serif"]
}
},
plugins: [