Add /contact and /photography
This commit is contained in:
parent
2863beed61
commit
18d65f5fe4
16 changed files with 771 additions and 418 deletions
|
@ -3,10 +3,8 @@
|
|||
🔥 [**moritzruth.de**](https://moritzruth.de)
|
||||
|
||||
TODO:
|
||||
- Start
|
||||
- Blog
|
||||
- Contact
|
||||
- Photography
|
||||
- Projects
|
||||
- Apps
|
||||
- Libraries
|
||||
- Legal Notice
|
||||
- Terms
|
||||
|
|
4
app.vue
4
app.vue
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<div class="min-h-100vh w-100vw text-light-900 overflow-x-hidden">
|
||||
<div class="h-100vh w-100vw text-light-100 overflow-x-hidden">
|
||||
<NuxtPage/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
html, body {
|
||||
background: #070707;
|
||||
@apply bg-background;
|
||||
overflow-x: hidden;
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
|
|
14
assets/photos.ts
Normal file
14
assets/photos.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
export const photos = [
|
||||
{
|
||||
"file": "Late_afternoon.webp",
|
||||
"title": "Late afternoon",
|
||||
"date": "2019",
|
||||
"description": "Taken in London during a sunny afternoon.\nSeems to be 65 Curzon St."
|
||||
},
|
||||
{
|
||||
"file": "Martyrdom.webp",
|
||||
"title": "Martyrdom",
|
||||
"date": "2020-10-19",
|
||||
"description": "The Sankt-Laurentius church right by the Mosel river in Bremm (Germany)."
|
||||
}
|
||||
]
|
62
components/LinkCardList.vue
Normal file
62
components/LinkCardList.vue
Normal file
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<div class="relative flex flex-col justify-center space-y-4">
|
||||
<component
|
||||
:is="link.to ? 'router-link' : 'a'"
|
||||
v-for="link in links"
|
||||
:key="link.to"
|
||||
: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"
|
||||
>
|
||||
<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'">
|
||||
{{ link.icon }}
|
||||
</template>
|
||||
<component :is="getSafeLinkIconComponent(link.icon)" v-else/>
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
<div class="text-lg font-bold">
|
||||
{{ link.label }}
|
||||
</div>
|
||||
<div class="opacity-60 -sm:text-sm">
|
||||
{{ link.description }}
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, ComponentCustomOptions, PropType } from "vue"
|
||||
|
||||
type Link = {
|
||||
label: string
|
||||
description: string
|
||||
iconClasses?: string
|
||||
icon: Component | string
|
||||
} & ({
|
||||
to: string
|
||||
} | {
|
||||
href: string
|
||||
})
|
||||
|
||||
export default {
|
||||
name: "LinkCardList",
|
||||
props: {
|
||||
links: {
|
||||
type: Array as PropType<Link[]>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getSafeLinkIconComponent(icon: string | Component) {
|
||||
return icon as ComponentCustomOptions
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
44
components/TopBarLayout.vue
Normal file
44
components/TopBarLayout.vue
Normal file
|
@ -0,0 +1,44 @@
|
|||
<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 shadow-2xl 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>
|
||||
<div class="font-bold text-2xl text-center transform -translate-y-2px">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div/>
|
||||
</div>
|
||||
<div class="pt-8 pb-10 px-5">
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import ArrowLeftIcon from "~icons/ph/arrow-left"
|
||||
|
||||
export default {
|
||||
name: "TopBarLayout",
|
||||
components: { ArrowLeftIcon },
|
||||
props: {
|
||||
backTarget: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,24 +1,17 @@
|
|||
import { defineNuxtConfig } from "nuxt3"
|
||||
import Icons from "unplugin-icons/vite"
|
||||
|
||||
// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
|
||||
export default defineNuxtConfig({
|
||||
ssr: true,
|
||||
target: "static",
|
||||
buildModules: [
|
||||
"nuxt-windicss"
|
||||
"nuxt-windicss",
|
||||
"unplugin-icons/nuxt"
|
||||
],
|
||||
css: [
|
||||
"@fontsource/plus-jakarta-sans/400.css",
|
||||
"@fontsource/plus-jakarta-sans/700.css"
|
||||
],
|
||||
vite: {
|
||||
plugins: [
|
||||
Icons({
|
||||
autoInstall: true
|
||||
})
|
||||
]
|
||||
},
|
||||
build: {
|
||||
loaders: {
|
||||
css: {
|
||||
|
|
|
@ -17,9 +17,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@fontsource/plus-jakarta-sans": "^4.5.0",
|
||||
"@fontsource/syne": "^4.5.0",
|
||||
"@iconify/json": "^1.1.441",
|
||||
"@vueuse/core": "^7.2.2",
|
||||
"@windicss/plugin-interaction-variants": "^1.0.0",
|
||||
"blobs": "^2.2.1-beta.1",
|
||||
"unplugin-icons": "^0.12.23",
|
||||
"vue": "^3.2.26"
|
||||
|
|
93
pages/contact.vue
Normal file
93
pages/contact.vue
Normal file
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<TopBarLayout back-target="/" title="Contact">
|
||||
<div class="text-lg p-5 max-w-130 mx-auto">
|
||||
<div class="space-y-4 text-xl">
|
||||
<p>For business inquiries, please use Matrix or Email.</p>
|
||||
<p>Matrix should be used when encryption is desired.</p>
|
||||
</div>
|
||||
<div class="pt-6 relative flex items-center">
|
||||
<div class="absolute w-full">
|
||||
<BlurredBlobCanvas
|
||||
:blur="30"
|
||||
:size="200"
|
||||
:randomness="80"
|
||||
:minimum-duration="600"
|
||||
:duration-variation="400"
|
||||
:minimum-opacity="0.2"
|
||||
:opacity-variation="0.5"
|
||||
:colors="['#eb34cf', '#6577fc']"
|
||||
class="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<LinkCardList :links="links" class="w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
</TopBarLayout>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import TopBarLayout from "../components/TopBarLayout.vue"
|
||||
import LinkCardList from "../components/LinkCardList.vue"
|
||||
import BlurredBlobCanvas from "../components/BlurredBlobCanvas.vue"
|
||||
import MatrixIcon from "~icons/simple-icons/matrix"
|
||||
import TwitterIcon from "~icons/simple-icons/twitter"
|
||||
import EmailIcon from "~icons/carbon/email"
|
||||
import { computed } from "vue"
|
||||
import { useRoute } from "vue-router"
|
||||
|
||||
export default {
|
||||
name: "ContactPage",
|
||||
components: { BlurredBlobCanvas, LinkCardList, TopBarLayout },
|
||||
setup() {
|
||||
const route = useRoute()
|
||||
|
||||
return {
|
||||
links: computed(() => {
|
||||
const l = []
|
||||
|
||||
if (route.query.ref === "jamrss") {
|
||||
l.push({
|
||||
icon: EmailIcon,
|
||||
href: "mailto:apps@moritzruth.de",
|
||||
label: "Email (regarding apps)",
|
||||
description: "apps@moritzruth.de"
|
||||
}, {
|
||||
icon: EmailIcon,
|
||||
href: "mailto:hey@deltaa.xyz",
|
||||
label: "Email (everything else)",
|
||||
description: "hey@deltaa.xyz"
|
||||
})
|
||||
} else {
|
||||
l.push({
|
||||
icon: EmailIcon,
|
||||
href: "mailto:hey@deltaa.xyz",
|
||||
label: "Email",
|
||||
description: "hey@deltaa.xyz"
|
||||
})
|
||||
}
|
||||
|
||||
l.push(
|
||||
{
|
||||
icon: MatrixIcon,
|
||||
href: "https://moritzruth.de/matrix",
|
||||
label: "Matrix",
|
||||
description: "@moritz:moritzruth.de"
|
||||
},
|
||||
{
|
||||
icon: TwitterIcon,
|
||||
href: "https://twitter.com/moritzruth",
|
||||
label: "Twitter",
|
||||
description: "moritzruth"
|
||||
}
|
||||
)
|
||||
|
||||
return l
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="h-100vh w-full max-w-[1200px] mx-auto flex justify-between -lg:flex-col p-5 sm:p-10">
|
||||
<div class="h-100vh w-full max-w-1200px mx-auto flex justify-between -lg:flex-col p-5 sm:p-10">
|
||||
<section class="relative pt-20">
|
||||
<div class="absolute top-60 -left-20 lg:-left-10">
|
||||
<BlurredBlobCanvas
|
||||
|
@ -13,7 +13,7 @@
|
|||
:colors="['#eb34cf', '#6577fc']"
|
||||
/>
|
||||
</div>
|
||||
<div class="relative max-w-130 p-2 lg:pl-10">
|
||||
<main class="relative max-w-130 p-2 lg:pl-10">
|
||||
<div class="font-extrabold text-3xl sm:text-4xl">
|
||||
Moritz Ruth
|
||||
</div>
|
||||
|
@ -34,7 +34,7 @@
|
|||
</div>
|
||||
<XSpacer v="10"/>
|
||||
<router-link to="/contact" :class="$style.reachOut">Reach out</router-link>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
<section class="relative lg:pr-20 pt-20 pb-10 mt-0">
|
||||
<div class="absolute w-full pt-20 flex justify-center">
|
||||
|
@ -49,23 +49,7 @@
|
|||
:colors="['#eb34cf', '#6577fc']"
|
||||
/>
|
||||
</div>
|
||||
<div class="relative flex flex-col justify-center space-y-4">
|
||||
<router-link
|
||||
v-for="link in navigationLinks"
|
||||
:key="link.to"
|
||||
:to="link.to"
|
||||
class="px-5 sm:px-6 py-4 bg-light-300 bg-opacity-5 rounded-lg backdrop-blur-lg flex
|
||||
hover:bg-opacity-10 focus-visible:bg-opacity-10 transform hover:scale-104 transition duration-200 group"
|
||||
>
|
||||
<div class="flex items-center justify-center text-xl sm:text-2xl relative pr-3 sm:pr-5" :class="link.emojiClasses">
|
||||
{{ link.emoji }}
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
<div class="text-lg font-bold">{{ link.label }}</div>
|
||||
<div class="opacity-60 -sm:text-sm">{{ link.text }}</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
<LinkCardList :links="navigationLinks"/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -101,38 +85,40 @@
|
|||
<script>
|
||||
import BlurredBlobCanvas from "../components/BlurredBlobCanvas.vue"
|
||||
import XSpacer from "../components/XSpacer.vue"
|
||||
import LinkCardList from "../components/LinkCardList.vue"
|
||||
|
||||
const NAVIGATION_LINKS = [
|
||||
{
|
||||
emoji: "📝",
|
||||
icon: "📝",
|
||||
to: "/blog",
|
||||
label: "Blog",
|
||||
text: "My thoughts, mostly on dev things"
|
||||
description: "My thoughts, mostly on dev things"
|
||||
},
|
||||
{
|
||||
emoji: "✨",
|
||||
icon: "✨",
|
||||
to: "/projects",
|
||||
label: "Projects",
|
||||
text: "Apps and open-source projects"
|
||||
description: "Apps and open-source projects"
|
||||
},
|
||||
{
|
||||
emojiClasses: "top-[-0.25rem]",
|
||||
emoji: "📷",
|
||||
iconClasses: "top-[-0.25rem]",
|
||||
icon: "📷",
|
||||
to: "/photography",
|
||||
label: "Photography",
|
||||
text: "Some photos I’m proud of"
|
||||
description: "Some photos I’m proud of"
|
||||
},
|
||||
{
|
||||
emoji: "💬",
|
||||
icon: "💬",
|
||||
to: "/contact",
|
||||
label: "Contact me",
|
||||
text: "Email, Matrix, Twitter"
|
||||
description: "Email, Matrix, Twitter"
|
||||
}
|
||||
]
|
||||
|
||||
export default {
|
||||
name: "IndexPage",
|
||||
components: {
|
||||
LinkCardList,
|
||||
XSpacer,
|
||||
BlurredBlobCanvas
|
||||
},
|
||||
|
|
48
pages/photography.vue
Normal file
48
pages/photography.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<TopBarLayout title="Photography" back-target="/">
|
||||
<div class="flex flex-col space-y-30 lg:space-y-20">
|
||||
<div
|
||||
v-for="photo in photos"
|
||||
:key="photo.file"
|
||||
class="flex -lg:space-y-10 lg:space-x-10 -lg:flex-col -lg:items-center"
|
||||
>
|
||||
<img
|
||||
:src="photo.file"
|
||||
:alt="photo.title"
|
||||
class="w-full lg:max-w-150 max-h-80vh block object-contain"
|
||||
>
|
||||
<div>
|
||||
<div class="uppercase opacity-70 text-sm pt-1">
|
||||
{{ photo.date }}
|
||||
</div>
|
||||
<div class="font-bold text-2xl opacity-90">
|
||||
{{ photo.title }}
|
||||
</div>
|
||||
<div class="opacity-90 max-w-100 space-y-1 pt-4 whitespace-pre-line">
|
||||
{{ photo.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TopBarLayout>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import TopBarLayout from "../components/TopBarLayout.vue"
|
||||
import { photos } from "assets/photos"
|
||||
import XSpacer from "~/components/XSpacer.vue"
|
||||
|
||||
export default {
|
||||
name: "PhotographyPage",
|
||||
components: { XSpacer, TopBarLayout },
|
||||
setup() {
|
||||
return {
|
||||
photos
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
834
pnpm-lock.yaml
generated
834
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
BIN
public/photography/Late_afternoon.webp
Normal file
BIN
public/photography/Late_afternoon.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 MiB |
BIN
public/photography/Martyrdom.webp
Normal file
BIN
public/photography/Martyrdom.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 373 KiB |
|
@ -3,7 +3,8 @@
|
|||
"extends": "./.nuxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"nuxt-windicss"
|
||||
"nuxt-windicss",
|
||||
"unplugin-icons/types/vue"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
7
webstorm-shims.d.ts
vendored
Normal file
7
webstorm-shims.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
// Taken from https://github.com/antfu/unplugin-icons/issues/128#issuecomment-992718883
|
||||
|
||||
declare module "~icons/*" {
|
||||
import { FunctionalComponent, SVGAttributes } from "vue"
|
||||
const component: FunctionalComponent<SVGAttributes>
|
||||
export default component
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { defineConfig } from "windicss/helpers"
|
||||
import colors from "windicss/colors"
|
||||
import interactionVariantsPlugin from "@windicss/plugin-interaction-variants"
|
||||
import scrollSnapPlugin from "windicss/plugin/scroll-snap"
|
||||
import plugin from "windicss/plugin"
|
||||
|
||||
export default defineConfig({
|
||||
darkMode: "media",
|
||||
|
@ -12,14 +13,19 @@ export default defineConfig({
|
|||
green: colors.green,
|
||||
blue: colors.blue,
|
||||
dark: colors.dark,
|
||||
light: colors.light
|
||||
light: colors.light,
|
||||
background: "#070707"
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["Plus Jakarta Sans", "sans-serif"],
|
||||
special: ["SyneVariable", "monospace"]
|
||||
sans: ["Plus Jakarta Sans", "sans-serif"]
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
interactionVariantsPlugin
|
||||
scrollSnapPlugin,
|
||||
plugin(({ addVariant }) => {
|
||||
addVariant("can-hover", ({ atRule }) => {
|
||||
return atRule("@media(hover: hover)")
|
||||
})
|
||||
})
|
||||
]
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue