Use NuxtJS v3 and add start page
This commit is contained in:
parent
4d5791fc2e
commit
2863beed61
28 changed files with 5679 additions and 1798 deletions
|
@ -1,13 +1,4 @@
|
||||||
{
|
{
|
||||||
"root": true,
|
"root": true,
|
||||||
"extends": "awzzm-vue/v3",
|
"extends": "awzzm-vue"
|
||||||
"rules": {
|
|
||||||
"vue/no-static-inline-styles": "off",
|
|
||||||
"unicorn/prevent-abbreviations": ["warn", {
|
|
||||||
"replacements": {
|
|
||||||
"i": false,
|
|
||||||
"props": false
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
93
.gitignore
vendored
93
.gitignore
vendored
|
@ -1,90 +1,7 @@
|
||||||
# Created by .ignore support plugin (hsz.mobi)
|
.idea/
|
||||||
### Node template
|
/node_modules/
|
||||||
# Logs
|
|
||||||
/logs
|
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# TypeScript v1 declaration files
|
|
||||||
typings/
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variables file
|
|
||||||
.env
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# next.js build output
|
|
||||||
.next
|
|
||||||
|
|
||||||
# nuxt.js build output
|
|
||||||
.nuxt
|
.nuxt
|
||||||
|
nuxt.d.ts
|
||||||
# Nuxt generate
|
.output
|
||||||
dist
|
.env
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless
|
|
||||||
|
|
||||||
# IDE / Editor
|
|
||||||
.idea
|
|
||||||
|
|
||||||
# Service worker
|
|
||||||
sw.*
|
|
||||||
|
|
||||||
# Mac OSX
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# Vim swap files
|
|
||||||
*.swp
|
|
||||||
|
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
||||||
16
|
16.11.0
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
# moritzruth.de
|
# moritzruth.de
|
||||||
|
|
||||||
🔥 [**moritzruth.de**](https://moritzruth.de)
|
🔥 [**moritzruth.de**](https://moritzruth.de)
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- Start
|
||||||
|
- Blog
|
||||||
|
- Contact
|
||||||
|
- Photography
|
||||||
|
- Projects
|
||||||
|
- Apps
|
||||||
|
- Libraries
|
||||||
|
|
20
app.vue
Normal file
20
app.vue
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<div class="min-h-100vh w-100vw text-light-900 overflow-x-hidden">
|
||||||
|
<NuxtPage/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
html, body {
|
||||||
|
background: #070707;
|
||||||
|
overflow-x: hidden;
|
||||||
|
width: 100vw;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "App"
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,12 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<canvas ref="canvasElement" :style="{ width: size + 'px', height: size + 'px', filter: `blur(${blur}px)` }"/>
|
<canvas ref="canvasElement" :style="{ filter: `blur(${blur}px)`, height: size + 'px', width: size + 'px' }"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { canvasPath as createBlobAnimation } from "blobs/v2/animate/index.module.js"
|
import { canvasPath as createBlobAnimation } from "blobs/v2/animate/index.module.js"
|
||||||
import { ref, watchEffect } from "vue"
|
import { ref, watchEffect } from "vue"
|
||||||
import { useRafFn } from "@vueuse/core"
|
import { useRafFn } from "@vueuse/core"
|
||||||
import { getComponentsOfHexColor } from "../utils/getComponentsOfHexColor.js"
|
|
||||||
|
function getComponentsOfHexColor(hexColorString) {
|
||||||
|
return [
|
||||||
|
Number.parseInt(hexColorString.slice(1, 3), 16),
|
||||||
|
Number.parseInt(hexColorString.slice(3, 5), 16),
|
||||||
|
Number.parseInt(hexColorString.slice(5, 7), 16)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "BlurredBlobCanvas",
|
name: "BlurredBlobCanvas",
|
||||||
|
@ -101,6 +108,8 @@
|
||||||
useRafFn(() => {
|
useRafFn(() => {
|
||||||
const canvas = canvasElement.value
|
const canvas = canvasElement.value
|
||||||
if (canvas === null) return
|
if (canvas === null) return
|
||||||
|
canvas.width = props.size
|
||||||
|
canvas.height = props.size
|
||||||
|
|
||||||
const context = canvas.getContext("2d")
|
const context = canvas.getContext("2d")
|
||||||
|
|
31
components/XSpacer.vue
Normal file
31
components/XSpacer.vue
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<template>
|
||||||
|
<div :style="{ height, width }"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "XSpacer",
|
||||||
|
props: {
|
||||||
|
v: {
|
||||||
|
default: 0,
|
||||||
|
type: null,
|
||||||
|
validate: value => ["number", "string"].includes(typeof value)
|
||||||
|
},
|
||||||
|
h: {
|
||||||
|
default: 0,
|
||||||
|
type: null,
|
||||||
|
validate: value => ["number", "string"].includes(typeof value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return {
|
||||||
|
height: (props.v * 0.25) + "rem",
|
||||||
|
width: (props.h * 0.25) + "rem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
27
index.html
27
index.html
|
@ -1,27 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
||||||
<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="preload" as="font" href="/node_modules/@fontsource/syne/files/syne-latin-800-normal.woff2" crossorigin="anonymous">
|
|
||||||
<link rel="stylesheet" href="/node_modules/@fontsource/plus-jakarta-sans/400.css">
|
|
||||||
<link rel="stylesheet" href="/node_modules/@fontsource/plus-jakarta-sans/800.css">
|
|
||||||
<link rel="stylesheet" href="/node_modules/@fontsource/syne/800.css">
|
|
||||||
<meta name="description" content="web development and graphic design.">
|
|
||||||
<meta name="keywords" content="web, dev, development, coding, moritz, ruth, development, design">
|
|
||||||
<link rel="shortcut icon" type="image/png" href="/icon.png">
|
|
||||||
</head>
|
|
||||||
<body class="bg-[#fefefe] overflow-x-hidden">
|
|
||||||
<div id="app"></div>
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
|
||||||
<script
|
|
||||||
async
|
|
||||||
defer
|
|
||||||
src="https://stats.moritzruth.de/umami.js"
|
|
||||||
data-website-id="e68ac4a6-c999-4f8e-be75-5a6252a55f17"
|
|
||||||
data-domains="moritzruth.de"
|
|
||||||
></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
30
nuxt.config.ts
Normal file
30
nuxt.config.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
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"
|
||||||
|
],
|
||||||
|
css: [
|
||||||
|
"@fontsource/plus-jakarta-sans/400.css",
|
||||||
|
"@fontsource/plus-jakarta-sans/700.css"
|
||||||
|
],
|
||||||
|
vite: {
|
||||||
|
plugins: [
|
||||||
|
Icons({
|
||||||
|
autoInstall: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
loaders: {
|
||||||
|
css: {
|
||||||
|
// @ts-expect-error
|
||||||
|
module: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
43
package.json
43
package.json
|
@ -1,30 +1,27 @@
|
||||||
{
|
{
|
||||||
"name": "moritzruth.de",
|
"name": "moritzruth.de",
|
||||||
"author": "Moritz Ruth <dev@moritzruth.de>",
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "nuxi dev",
|
||||||
"build": "vite build",
|
"build": "nuxi build",
|
||||||
"start": "vite preview",
|
"start": "node .output/server/index.mjs",
|
||||||
"lint": "eslint . --fix"
|
"fi": "pnpm i --shamefully-hoist"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fontsource/plus-jakarta-sans": "^4.4.5",
|
|
||||||
"@fontsource/syne": "^4.4.5",
|
|
||||||
"@vueuse/core": "^5.0.3",
|
|
||||||
"blobs": "^2.2.1-beta.1",
|
|
||||||
"vue": "^3.1.1",
|
|
||||||
"vue-i18n": "^9.1.6",
|
|
||||||
"vue-router": "4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/vite-plugin-vue-i18n": "^2.2.1",
|
"eslint": "^7.32.0",
|
||||||
"@vitejs/plugin-vue": "^1.2.3",
|
"eslint-config-awzzm-vue": "^2.0.1",
|
||||||
"@vue/compiler-sfc": "^3.1.1",
|
"nuxt-windicss": "^2.1.1",
|
||||||
"eslint": "^7.28.0",
|
"nuxt3": "latest",
|
||||||
"eslint-config-awzzm-vue": "^1.6.0",
|
"typescript": "^4.5.3",
|
||||||
"vite": "^2.3.7",
|
"windicss": "^3.2.1"
|
||||||
"vite-plugin-pages": "^0.13.1",
|
},
|
||||||
"vite-plugin-windicss": "^1.0.4",
|
"dependencies": {
|
||||||
"windicss": "^3.1.3"
|
"@fontsource/plus-jakarta-sans": "^4.5.0",
|
||||||
|
"@fontsource/syne": "^4.5.0",
|
||||||
|
"@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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
145
pages/index.vue
Normal file
145
pages/index.vue
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
<template>
|
||||||
|
<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
|
||||||
|
:blur="30"
|
||||||
|
:size="300"
|
||||||
|
:randomness="80"
|
||||||
|
:minimum-duration="600"
|
||||||
|
:duration-variation="400"
|
||||||
|
:minimum-opacity="0.2"
|
||||||
|
:opacity-variation="0.5"
|
||||||
|
:colors="['#eb34cf', '#6577fc']"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="relative max-w-130 p-2 lg:pl-10">
|
||||||
|
<div class="font-extrabold text-3xl sm:text-4xl">
|
||||||
|
Moritz Ruth
|
||||||
|
</div>
|
||||||
|
<div class="text-lg sm:text-xl font-medium leading-8 pt-5">
|
||||||
|
<p>
|
||||||
|
I’m a freelance
|
||||||
|
<router-link to="/projects" :class="$style.link">software developer</router-link>,
|
||||||
|
graphic design enthusiast and
|
||||||
|
<router-link to="/photography" :class="$style.link">hobby photographer</router-link>
|
||||||
|
from Europe.
|
||||||
|
</p>
|
||||||
|
<XSpacer v="5"/>
|
||||||
|
<p>
|
||||||
|
I primarily focus on
|
||||||
|
Web and Android development,
|
||||||
|
but I also do Backend sometimes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<XSpacer v="10"/>
|
||||||
|
<router-link to="/contact" :class="$style.reachOut">Reach out</router-link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="relative lg:pr-20 pt-20 pb-10 mt-0">
|
||||||
|
<div class="absolute w-full pt-20 flex justify-center">
|
||||||
|
<BlurredBlobCanvas
|
||||||
|
:blur="30"
|
||||||
|
:size="300"
|
||||||
|
:randomness="100"
|
||||||
|
:minimum-duration="3000"
|
||||||
|
:duration-variation="1000"
|
||||||
|
:minimum-opacity="0.4"
|
||||||
|
:opacity-variation="0"
|
||||||
|
: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>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
.link {
|
||||||
|
background: linear-gradient(to bottom right, rgba(235, 52, 207, 0.5), rgba(101, 119, 252, 0.5));
|
||||||
|
background-size: 200% 200%;
|
||||||
|
animation: gradient 3s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradient {
|
||||||
|
0% {
|
||||||
|
background-position: 0 51%
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 0 51%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reachOut {
|
||||||
|
@apply text-xl font-bold rounded-md bg-pink-900 px-5 py-2;
|
||||||
|
mix-blend-mode: color-dodge;
|
||||||
|
box-shadow: 0 2px 10px 0 rgba(112, 26, 117, 0.8);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BlurredBlobCanvas from "../components/BlurredBlobCanvas.vue"
|
||||||
|
import XSpacer from "../components/XSpacer.vue"
|
||||||
|
|
||||||
|
const NAVIGATION_LINKS = [
|
||||||
|
{
|
||||||
|
emoji: "📝",
|
||||||
|
to: "/blog",
|
||||||
|
label: "Blog",
|
||||||
|
text: "My thoughts, mostly on dev things"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: "✨",
|
||||||
|
to: "/projects",
|
||||||
|
label: "Projects",
|
||||||
|
text: "Apps and open-source projects"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emojiClasses: "top-[-0.25rem]",
|
||||||
|
emoji: "📷",
|
||||||
|
to: "/photography",
|
||||||
|
label: "Photography",
|
||||||
|
text: "Some photos I’m proud of"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: "💬",
|
||||||
|
to: "/contact",
|
||||||
|
label: "Contact me",
|
||||||
|
text: "Email, Matrix, Twitter"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "IndexPage",
|
||||||
|
components: {
|
||||||
|
XSpacer,
|
||||||
|
BlurredBlobCanvas
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
navigationLinks: NAVIGATION_LINKS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
6252
pnpm-lock.yaml
generated
6252
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
68
src/App.vue
68
src/App.vue
|
@ -1,68 +0,0 @@
|
||||||
<template>
|
|
||||||
<NavigationMenu/>
|
|
||||||
<div class="bg-white text-black max-w-1200px w-full mx-auto px-6 sm:px-10 _content">
|
|
||||||
<router-view/>
|
|
||||||
</div>
|
|
||||||
<div class="w-full h-20 space-x-10 flex items-center justify-center relative z-2">
|
|
||||||
<router-link
|
|
||||||
v-for="item in items"
|
|
||||||
:key="item.to"
|
|
||||||
class="uppercase tracking-wide text-sm"
|
|
||||||
:to="item.to"
|
|
||||||
>
|
|
||||||
{{ t(item.labelKey) }}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
._content {
|
|
||||||
min-height: calc(100vh - 80px);
|
|
||||||
}
|
|
||||||
|
|
||||||
@screen sm {
|
|
||||||
._content {
|
|
||||||
min-height: calc(100vh - 160px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<i18n lang="yaml">
|
|
||||||
en:
|
|
||||||
footer:
|
|
||||||
tac: Terms
|
|
||||||
legal: Legal Notice
|
|
||||||
|
|
||||||
de:
|
|
||||||
footer:
|
|
||||||
tac: AGB
|
|
||||||
legal: Impressum
|
|
||||||
</i18n>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { useI18n } from "vue-i18n"
|
|
||||||
import NavigationMenu from "./components/NavigationMenu.vue"
|
|
||||||
|
|
||||||
const FOOTER_LINKS = [
|
|
||||||
{
|
|
||||||
labelKey: "footer.tac",
|
|
||||||
to: "/terms"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
labelKey: "footer.legal",
|
|
||||||
to: "/impressum"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "App",
|
|
||||||
components: { NavigationMenu },
|
|
||||||
setup() {
|
|
||||||
const { t } = useI18n()
|
|
||||||
return {
|
|
||||||
t,
|
|
||||||
items: FOOTER_LINKS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,24 +0,0 @@
|
||||||
<template>
|
|
||||||
<a
|
|
||||||
class="text-blue-700"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
:href="href"
|
|
||||||
><slot/></a>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style module>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "ExternalLink",
|
|
||||||
props: {
|
|
||||||
href: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,149 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="sm:hidden fixed z-101 bottom-5 right-5 rounded-full backdrop-filter bg-white w-18 h-18 flex justify-center items-center shadow-lg _blur-backdrop-or-hide"
|
|
||||||
role="button"
|
|
||||||
aria-label="Toggle navigation menu"
|
|
||||||
@click="active = !active"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col justify-evenly items-center h-10">
|
|
||||||
<div class="w-10 h-2px bg-black transition duration-300 transform _easing" :style="`transform: ${active ? 'translateY(350%)' : ''} rotate(${active ? 45 : 0}deg)`"/>
|
|
||||||
<div class="w-10 h-2px bg-black transition duration-300 transform _easing" :style="`transform: ${active ? 'translateY(-350%)' : ''} rotate(${active ? -45 : 0}deg)`"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<nav
|
|
||||||
class="fixed sm:sticky top-0 z-100 w-full h-screen sm:h-20 backdrop-filter bg-white transition duration-400 _blur-backdrop-or-hide"
|
|
||||||
:class="[scrolled && 'shadow-lg', active ? 'opacity-100' : '-sm:opacity-0 -sm:pointer-events-none']"
|
|
||||||
>
|
|
||||||
<div class="flex items-center justify-between h-full max-w-1200px mx-auto flex-grow -sm:flex-col px-6 sm:px-10">
|
|
||||||
<div class="pointer-events-none fixed transition-all duration-500" :style="{ left: blobState.x + 'px', top: blobState.y + 'px', opacity: blobState.show ? 1 : 0 }">
|
|
||||||
<BlurredBlobCanvas
|
|
||||||
:colors="['#eb34cf', '#818cff']"
|
|
||||||
:opacity-variation="0"
|
|
||||||
:minimum-opacity="0.9"
|
|
||||||
:minimum-duration="1000"
|
|
||||||
:duration-variation="500"
|
|
||||||
:blur="10"
|
|
||||||
:size="130"
|
|
||||||
:randomness="20"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<router-link class="uppercase font-special relative top-1 -sm:mt-20 transition duration-600 _home-link" to="/" @click="active = false">
|
|
||||||
Moritz Ruth
|
|
||||||
</router-link>
|
|
||||||
<div class="flex -sm:flex-col -sm:mb-30vh justify-end items-center sm:space-x-20 -sm:space-y-10 relative">
|
|
||||||
<router-link
|
|
||||||
v-for="item in items"
|
|
||||||
:key="item.to"
|
|
||||||
:ref="item.element"
|
|
||||||
class="lowercase text-2xl -sm:text-4xl"
|
|
||||||
:to="item.to"
|
|
||||||
@click="active = false"
|
|
||||||
>
|
|
||||||
{{ t(item.labelKey) }}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<i18n lang="yaml">
|
|
||||||
en:
|
|
||||||
projects: Projects
|
|
||||||
contact: Contact
|
|
||||||
|
|
||||||
de:
|
|
||||||
projects: Projekte
|
|
||||||
contact: Kontakt
|
|
||||||
</i18n>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
._blur-backdrop-or-hide {
|
|
||||||
@apply bg-opacity-90;
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
@supports (backdrop-filter: blur(5px)) {
|
|
||||||
._blur-backdrop-or-hide {
|
|
||||||
@apply bg-opacity-70;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
._easing {
|
|
||||||
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*noinspection CssUnusedSymbol*/
|
|
||||||
@screen sm {
|
|
||||||
._home-link.router-link-active {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { useWindowScroll, useWindowSize } from "@vueuse/core"
|
|
||||||
import { computed, ref, watch, reactive } from "vue"
|
|
||||||
import { useRoute } from "vue-router"
|
|
||||||
import { useI18n } from "vue-i18n"
|
|
||||||
import { useWindowScrollLock } from "../utils/useWindowScrollLock.js"
|
|
||||||
import BlurredBlobCanvas from "./BlurredBlobCanvas.vue"
|
|
||||||
|
|
||||||
const ITEMS = [
|
|
||||||
// {
|
|
||||||
// labelKey: "projects",
|
|
||||||
// to: "/projects"
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
labelKey: "contact",
|
|
||||||
to: "/contact"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "NavigationMenu",
|
|
||||||
components: { BlurredBlobCanvas },
|
|
||||||
setup() {
|
|
||||||
const { y: windowScroll } = useWindowScroll()
|
|
||||||
const { width: windowWidth } = useWindowSize()
|
|
||||||
const route = useRoute()
|
|
||||||
const active = ref(false)
|
|
||||||
|
|
||||||
useWindowScrollLock(active)
|
|
||||||
|
|
||||||
const items = ITEMS.map(item => ({
|
|
||||||
...item,
|
|
||||||
element: ref(null)
|
|
||||||
}))
|
|
||||||
|
|
||||||
const activeItem = computed(() => items.find(item => item.to === route.path) ?? null)
|
|
||||||
const blobState = reactive({
|
|
||||||
x: windowWidth / 2,
|
|
||||||
y: 10,
|
|
||||||
show: false
|
|
||||||
})
|
|
||||||
|
|
||||||
watch([windowWidth, activeItem], () => {
|
|
||||||
if (activeItem.value === null) {
|
|
||||||
blobState.show = false
|
|
||||||
} else {
|
|
||||||
const { x, width, y } = activeItem.value.element.value.$el.getBoundingClientRect()
|
|
||||||
|
|
||||||
blobState.x = x + (width / 2) + 10
|
|
||||||
blobState.y = y - 30
|
|
||||||
blobState.show = true
|
|
||||||
}
|
|
||||||
}, { immediate: true })
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
return {
|
|
||||||
scrolled: computed(() => windowScroll.value > 0),
|
|
||||||
blobState,
|
|
||||||
items,
|
|
||||||
active,
|
|
||||||
t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
47
src/main.css
47
src/main.css
|
@ -1,47 +0,0 @@
|
||||||
@layer components {
|
|
||||||
body {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
@apply bg-blue-900 bg-opacity-80 text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose {
|
|
||||||
h1 {
|
|
||||||
@apply font-bold text-3xl sm:text-5xl text-gray-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
@apply font-bold text-2xl sm:text-4xl text-gray-600 mt-8;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol {
|
|
||||||
@apply list-decimal list-inside space-y-2;
|
|
||||||
|
|
||||||
::marker {
|
|
||||||
@apply text-gray-600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
@apply list-disc list-inside space-y-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
address, p, ol, ul {
|
|
||||||
@apply not-italic text-lg max-w-240 mt-5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.asterisk-list > li {
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
@apply text-blue-900;
|
|
||||||
content: "*";
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
30
src/main.js
30
src/main.js
|
@ -1,30 +0,0 @@
|
||||||
import "virtual:windi.css"
|
|
||||||
import "./main.css"
|
|
||||||
import routes from "virtual:generated-pages"
|
|
||||||
import { createApp } from "vue"
|
|
||||||
import { createRouter, createWebHistory } from "vue-router"
|
|
||||||
import { createI18n } from "vue-i18n"
|
|
||||||
import App from "./App.vue"
|
|
||||||
|
|
||||||
const i18n = createI18n({
|
|
||||||
legacy: false,
|
|
||||||
fallbackLocale: "en",
|
|
||||||
locale: navigator.language
|
|
||||||
})
|
|
||||||
|
|
||||||
document.documentElement.lang = navigator.language.startsWith("de") ? "de" : "en"
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(),
|
|
||||||
routes,
|
|
||||||
scrollBehavior(to, from, savedPosition) {
|
|
||||||
if (savedPosition) return savedPosition
|
|
||||||
if (to.hash) return { el: to.hash }
|
|
||||||
return { top: 0 }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createApp(App)
|
|
||||||
.use(router)
|
|
||||||
.use(i18n)
|
|
||||||
.mount("#app")
|
|
|
@ -1,41 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="prose pt-20 flex items-center justify-center flex-col h-50vh">
|
|
||||||
<h1 class="text-center">
|
|
||||||
{{ t("heading") }}
|
|
||||||
</h1>
|
|
||||||
<p class="mt-10">
|
|
||||||
<router-link to="/" class="text-blue-700">
|
|
||||||
{{ t("back") }}
|
|
||||||
</router-link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<i18n lang="yaml">
|
|
||||||
en:
|
|
||||||
heading: This page does not exist.
|
|
||||||
back: Back to home
|
|
||||||
cat: Show me a picture of a cat
|
|
||||||
|
|
||||||
de:
|
|
||||||
heading: Diese Seite existiert nicht.
|
|
||||||
cat: Zeig mir ein Bild einer Katze
|
|
||||||
back: Zurück zur Startseite
|
|
||||||
</i18n>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { useI18n } from "vue-i18n"
|
|
||||||
import ExternalLink from "../components/ExternalLink.vue"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "NotFoundPage",
|
|
||||||
components: { ExternalLink },
|
|
||||||
setup() {
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
return {
|
|
||||||
t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,51 +0,0 @@
|
||||||
<template>
|
|
||||||
<main class="text-2xl sm:text-3xl pt-26">
|
|
||||||
<h1 class="font-bold text-3xl sm:text-5xl text-gray-900 mb-10">{{ t("heading") }}</h1>
|
|
||||||
<p class="mt-10 mb-10">
|
|
||||||
{{ t("twitter") }}
|
|
||||||
<a
|
|
||||||
class="text-blue-900"
|
|
||||||
href="https://twitter.com/moritz_ruth"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
@moritz_ruth
|
|
||||||
</a>
|
|
||||||
{{ t("or_email") }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a class="text-blue-900 text-4xl" href="mailto:hey@deltaa.xyz">
|
|
||||||
<span class="mr-2 relative bottom-2px">→</span> hey@deltaa.xyz
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p class="mt-10 mb-10">
|
|
||||||
{{ t("happy_to_hear") }}
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<i18n lang="yaml">
|
|
||||||
en:
|
|
||||||
heading: Contact
|
|
||||||
twitter: Hit me up on Twitter
|
|
||||||
or_email: "or send me an email (en/de):"
|
|
||||||
happy_to_hear: I'm looking forward to your message.
|
|
||||||
|
|
||||||
de:
|
|
||||||
heading: Kontakt
|
|
||||||
twitter: Schreib mir auf Twitter
|
|
||||||
or_email: "oder schicke mir eine Email:"
|
|
||||||
happy_to_hear: Ich freue mich auf deine Nachricht.
|
|
||||||
</i18n>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { useI18n } from "vue-i18n"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "ContactPage",
|
|
||||||
setup() {
|
|
||||||
const { t } = useI18n()
|
|
||||||
return { t }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,79 +0,0 @@
|
||||||
<template>
|
|
||||||
<main class="prose pt-20">
|
|
||||||
<h1>{{ t("heading") }}</h1>
|
|
||||||
<p v-if="!isGerman">
|
|
||||||
<i>As required by German law.</i>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ t("scope") }}
|
|
||||||
</p>
|
|
||||||
<h2>{{ t("according") }}</h2>
|
|
||||||
<address>
|
|
||||||
Moritz Ruth<br>
|
|
||||||
Zum Galgenberg 19<br>
|
|
||||||
66539 Neunkirchen
|
|
||||||
</address>
|
|
||||||
<p>
|
|
||||||
{{ t("email") }}:
|
|
||||||
<a class="text-blue-900" href="mailto:hey@deltaa.xyz">
|
|
||||||
hey@deltaa.xyz
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ t("matrix") }}:
|
|
||||||
<ExternalLink href="https://moritzruth.de/matrix">
|
|
||||||
@moritz:moritzruth.de
|
|
||||||
</ExternalLink>
|
|
||||||
</p>
|
|
||||||
<h2 class="text-2xl text-blue-500 mt-20">{{ t("typefaces-used") }}</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<ExternalLink href="https://gitlab.com/bonjour-monde/fonderie/syne-typeface">
|
|
||||||
Syne
|
|
||||||
</ExternalLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<ExternalLink href="https://tokotype.github.io/plusjakarta-sans/">
|
|
||||||
+Jakarta Sans
|
|
||||||
</ExternalLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<i18n lang="yaml">
|
|
||||||
en:
|
|
||||||
heading: Legal Notice
|
|
||||||
scope: The declarations on this page apply to this website (moritzruth.de) and all websites which reside under subdomains of moritzruth.de.
|
|
||||||
according: Information pursuant to § 5 TMG
|
|
||||||
email: Email address
|
|
||||||
typefaces-used: Typefaces used on this site
|
|
||||||
matrix: Matrix
|
|
||||||
|
|
||||||
de:
|
|
||||||
heading: Impressum
|
|
||||||
scope: Dieses Impressum gilt für diese Website (moritzruth.de) und alle Websites, welche unter Subdomains von moritzruth.de erreichbar sind.
|
|
||||||
according: Angaben gemäß § 5 TMG
|
|
||||||
email: Email-Adresse
|
|
||||||
typefaces-used: Typefaces auf dieser Seite
|
|
||||||
matrix: Matrix
|
|
||||||
</i18n>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { useI18n } from "vue-i18n"
|
|
||||||
import { computed } from "vue"
|
|
||||||
import ExternalLink from "../components/ExternalLink.vue"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "LegalNoticePage",
|
|
||||||
components: { ExternalLink },
|
|
||||||
setup() {
|
|
||||||
const { locale, t } = useI18n()
|
|
||||||
|
|
||||||
return {
|
|
||||||
isGerman: computed(() => locale.value.startsWith("de")),
|
|
||||||
t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,123 +0,0 @@
|
||||||
<template>
|
|
||||||
<main class="flex -md:flex-col justify-between items-start md:items-center min-h-80vh">
|
|
||||||
<div class="relative -md:pt-20vh">
|
|
||||||
<div class="_fade-2">
|
|
||||||
<div class="_pattern transform rotate-179.5 absolute w-full h-35 -left-4 md:-left-10 top-20vh -md:-mt-10 md:-top-15 opacity-3 md:opacity-5"/>
|
|
||||||
</div>
|
|
||||||
<div class="_slide">
|
|
||||||
<div class="text-4xl sm:text-5xl -md:-mt-2 mb-10 md:mb-20 font-special" style="--delay: 0">
|
|
||||||
Moritz Ruth
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-gray-800 text-2xl sm:text-3xl">
|
|
||||||
<ul class="asterisk-list">
|
|
||||||
<li class="_slide" style="--delay: 100">
|
|
||||||
{{ t("software_developer") }}
|
|
||||||
</li>
|
|
||||||
<li class="_slide" style="--delay: 200">
|
|
||||||
{{ t("designer") }}
|
|
||||||
</li>
|
|
||||||
<li class="_slide" style="--delay: 300">
|
|
||||||
{{ t("typography_enthusiast") }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<router-link class="mt-10 text-2xl sm:text-3xl block text-blue-900 _slide" style="--delay: 500" to="/contact">
|
|
||||||
<span class="mr-2">→</span> {{ t("contact_me") }}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
<div class="self-center -md:fixed -bottom-30">
|
|
||||||
<BlurredBlobCanvas
|
|
||||||
class="_fade-1"
|
|
||||||
:colors="['#6577fc', '#eb34cf', '#6577fc', '#eb34cf']"
|
|
||||||
:size="350"
|
|
||||||
:blur="30"
|
|
||||||
:minimum-duration="1000"
|
|
||||||
:duration-variation="1500"
|
|
||||||
:minimum-opacity="0.2"
|
|
||||||
:opacity-variation="0.4"
|
|
||||||
:points="10"
|
|
||||||
:randomness="80"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<i18n lang="yaml">
|
|
||||||
en:
|
|
||||||
i_am: I’m
|
|
||||||
software_developer: software developer
|
|
||||||
designer: web and print designer
|
|
||||||
typography_enthusiast: typography enthusiast
|
|
||||||
contact_me: Drop me a message!
|
|
||||||
|
|
||||||
de:
|
|
||||||
i_am: ""
|
|
||||||
software_developer: Software-Entwickler
|
|
||||||
designer: Web– und Print-Designer
|
|
||||||
typography_enthusiast: Typographie-Enthusiast
|
|
||||||
contact_me: Schreib mir!
|
|
||||||
</i18n>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
._pattern {
|
|
||||||
background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='72' viewBox='0 0 36 72'%3E%3Cg fill-rule='evenodd'%3E%3Cg \
|
|
||||||
fill='%23000000' fill-opacity='1'%3E%3Cpath d='M2 6h12L8 18 2 6zm18 36h12l-6 12-6-12z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
._fade-1 {
|
|
||||||
animation: fade 2s ease-in both;
|
|
||||||
}
|
|
||||||
|
|
||||||
._fade-2 {
|
|
||||||
animation: fade 2s 1s ease-out both;
|
|
||||||
}
|
|
||||||
|
|
||||||
._slide {
|
|
||||||
--delay: 0;
|
|
||||||
animation: slide-down 1s ease both;
|
|
||||||
animation-delay: calc(var(--delay) * 1ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
@screen md {
|
|
||||||
._slide {
|
|
||||||
animation-delay: calc(var(--delay) * 1.4ms + 0.4s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slide-down {
|
|
||||||
from {
|
|
||||||
transform: translateY(-50%);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { useI18n } from "vue-i18n"
|
|
||||||
import BlurredBlobCanvas from "../components/BlurredBlobCanvas.vue"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "IndexPage",
|
|
||||||
components: { BlurredBlobCanvas },
|
|
||||||
setup() {
|
|
||||||
const { t } = useI18n()
|
|
||||||
return { t }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,92 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="prose pt-20">
|
|
||||||
<h1>{{ t("heading") }}</h1>
|
|
||||||
<p>{{ t("message") }}</p>
|
|
||||||
<p>Stand: August 2021</p>
|
|
||||||
<section>
|
|
||||||
<h2>1. Generelles</h2>
|
|
||||||
<ol>
|
|
||||||
<li>Die nachfolgenden Bestimmungen gelten für alle zwischen Moritz Ruth und dem Auftraggeber geschlossenen Verträge.</li>
|
|
||||||
<li>Abweichende, widersprüchliche oder ergänzende Allgemeine Geschäftsbedingungen des Auftraggebers gelten nur nach ausdrücklicher, schriftlicher Zustimmung von Moritz Ruth.</li>
|
|
||||||
<li>Änderungen und Ergänzungen des Vertrags bedürfen der Schriftform, um wirksam zu sein.</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>2. Auftragsdurchführung</h2>
|
|
||||||
<ol>
|
|
||||||
<li>Moritz Ruth führt den Auftrag in eigener Verantwortung durch und hat Gestaltungsfreiheit im Rahmen dessen.</li>
|
|
||||||
<li>Handelt es sich um einen Werkvertrag, sind in der Konzeptions- und Entwurfsphase zwei Korrekturschleifen zulässig.</li>
|
|
||||||
<li>Die Mehrkosten durch eventuelle, von der Leistungsbeschreibung im Angebot abweichende Änderungswünsche hat der Auftraggeber zu tragen.</li>
|
|
||||||
<li>Wurde dem Auftraggeber die Fertigstellung einer Werkleistung in Textform mitgeteilt, hat er 14 Tage Zeit, die Abnahme zu verweigern. Tut er dies nicht, gilt die Leistung als abgenommen.</li>
|
|
||||||
<li>Moritz Ruth ist nicht verpflichtet, Quellcode und offene Dateien herauszugeben. Ist dies dennoch gewünscht, so muss es schriftlich vereinbart und gesondert vergütet werden.</li>
|
|
||||||
<li>Moritz Ruth bestimmt den Tätigkeitsort nach freiem Ermessen.</li>
|
|
||||||
<li>Moritz Ruth steht es frei, auch für andere Auftraggeber tätig zu werden.</li>
|
|
||||||
<li>Moritz Ruth unterliegt keinem Weisungs- oder Direktionsrecht des Auftraggebers.</li>
|
|
||||||
<li>Ein sozialversicherungspflichtiges Arbeitsverhältnis wird durch den Vertrag nicht begründet.</li>
|
|
||||||
<li>Moritz Ruth ist es gestattet, den Vertrag auch unter Zuhilfenahme von Erfüllungsgehilfen zu erfüllen.</li>
|
|
||||||
<li>Das Recht zum Rücktritt und Schadensersatz anstelle der ganzen Leistung besteht nur bei erheblichen Mängeln.</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>3. Pflichten des Auftraggebers</h2>
|
|
||||||
<ol>
|
|
||||||
<li>Der Auftraggeber ist verpflichtet, Moritz Ruth sämtliche zur Erfüllung des Vertrags erforderlichen Daten und Dateien zur Verfügung zu stellen.</li>
|
|
||||||
<li>Der Auftraggeber ist zum Ersatz von Schäden, die Moritz Ruth durch Schadprogramme in den vom Auftraggeber bereitgestellten Dateien und Daten entstehen, verpflichtet.</li>
|
|
||||||
<li>Das Anfertigen von Sicherungskopien liegt allein in der Verantwortung des Auftraggebers.</li>
|
|
||||||
<li>Ist zur Erfüllung des Auftrags die Mitwirkung des Auftraggebers erforderlich, muss dieser seiner Mitwirkungspflicht innerhalb von 14 Tagen nach Aufforderung durch Moritz Ruth nachkommen. Andernfalls kann Moritz Ruth den Vertrag kündigen. Sein Entschädigungsanspruch nach § 642 BGB bleibt hiervon unberührt.</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>4. Vergütung</h2>
|
|
||||||
<ol>
|
|
||||||
<li>Die Vergütung ist sofort zum Zeitpunkt der ordnungsgemäßen Rechnungsstellung fällig.</li>
|
|
||||||
<li>Grundsätzlich gilt eine Zahlungsfrist von 14 Tagen.</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Nutzungsrechte</h2>
|
|
||||||
<ol>
|
|
||||||
<li>Moritz Ruth ist es erlaubt, die im Rahmen des Auftrags entstandenen Ergebnisse zur Selbstdarstellung zu verwenden (z. B. auf einer Website). Zudem darf er den Auftraggeber als Referenz nennen und in diesem Zuge auch dessen Logo verwenden.</li>
|
|
||||||
<li>Moritz Ruth überträgt nur die Nutzungsrechte, die zur Erfüllung des Vertrages erforderlich sind.</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Haftung</h2>
|
|
||||||
<ol>
|
|
||||||
<li>Moritz Ruth haftet dem Auftraggeber gegenüber für die von ihm vorsätzlich oder grob fahrlässig verursachten Schäden sowie bei der Verletzung von Leben, Körper oder Gesundheit.</li>
|
|
||||||
<li>Moritz Ruth haftet nicht bei leichter Fahrlässigkeit, außer es handelt sich um die Verletzung wesentlicher Vertragspflichten nach den gesetzlichen Bestimmungen, wobei wesentliche Vertragspflichten solche sind, deren Erfüllung die ordnungsgemäße Durchführung des Vertrages überhaupt erst ermöglichen und auf deren Einhaltung der Auftraggeber regelmäßig vertrauen darf.</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Schlussbestimmungen</h2>
|
|
||||||
<ol>
|
|
||||||
<li>Erfüllungsort und Gerichtsstand für alle Streitigkeiten aus und im Zusammenhang mit dieser Vereinbarung ist der Sitz von Moritz Ruth, sofern der Auftraggeber Kaufmann, juristische Person des öffentlichen Rechts oder öffentlich-rechtliches Sondervermögen ist.</li>
|
|
||||||
<li>Sollte eine Bestimmung dieser Vereinbarung ganz oder teilweise unwirksam sein oder werden oder sollte die Vereinbarung unvollständig sein, so wird die Vereinbarung im Übrigen Inhalt nicht berührt. Die Vertragspartner verpflichten sich, die unwirksame Bestimmung durch eine solche Bestimmung zu ersetzen, welche dem Sinn und Zweck der unwirksamen Bestimmung in rechtswirksamer Weise wirtschaftlich am nächsten kommt.</li>
|
|
||||||
<li>Es gilt deutsches Recht unter Ausschluss des UN-Kaufrechts.</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<i18n lang="yaml">
|
|
||||||
en:
|
|
||||||
heading: Terms and Conditions
|
|
||||||
message: Only available in German at the moment.
|
|
||||||
|
|
||||||
de:
|
|
||||||
heading: Allgemeine Geschäftsbedingungen
|
|
||||||
message: ""
|
|
||||||
</i18n>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { useI18n } from "vue-i18n"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "TermsPage",
|
|
||||||
setup() {
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
return { t }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,7 +0,0 @@
|
||||||
export function getComponentsOfHexColor(hexColorString) {
|
|
||||||
return [
|
|
||||||
Number.parseInt(hexColorString.slice(1, 3), 16),
|
|
||||||
Number.parseInt(hexColorString.slice(3, 5), 16),
|
|
||||||
Number.parseInt(hexColorString.slice(5, 7), 16)
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { watchEffect, onUnmounted, getCurrentInstance } from "vue"
|
|
||||||
|
|
||||||
const lockingInstances = new Set()
|
|
||||||
|
|
||||||
const update = () => {
|
|
||||||
document.body.style.overflowY = lockingInstances.size === 0 ? null : "hidden"
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWindowScrollLock(locked) {
|
|
||||||
const instance = getCurrentInstance()
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (locked.value) lockingInstances.add(instance)
|
|
||||||
else lockingInstances.delete(instance)
|
|
||||||
update()
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
lockingInstances.delete(instance)
|
|
||||||
update()
|
|
||||||
})
|
|
||||||
}
|
|
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
// https://v3.nuxtjs.org/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": [
|
||||||
|
"nuxt-windicss"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
import vuePlugin from "@vitejs/plugin-vue"
|
|
||||||
import windicssPlugin from "vite-plugin-windicss"
|
|
||||||
import pagesPlugin from "vite-plugin-pages"
|
|
||||||
import vueI18nPlugin from "@intlify/vite-plugin-vue-i18n"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://vitejs.dev/config/
|
|
||||||
* @type {import('vite').UserConfig}
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
plugins: [
|
|
||||||
vuePlugin(),
|
|
||||||
vueI18nPlugin(),
|
|
||||||
pagesPlugin(),
|
|
||||||
windicssPlugin()
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { defineConfig } from "vite-plugin-windicss"
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
theme: {
|
|
||||||
fontFamily: {
|
|
||||||
sans: ["Plus Jakarta Sans", "sans-serif"],
|
|
||||||
special: ["Syne", "monospace"]
|
|
||||||
},
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
blue: {
|
|
||||||
900: "#0041ff"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
25
windi.config.ts
Normal file
25
windi.config.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { defineConfig } from "windicss/helpers"
|
||||||
|
import colors from "windicss/colors"
|
||||||
|
import interactionVariantsPlugin from "@windicss/plugin-interaction-variants"
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
darkMode: "media",
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
pink: colors.fuchsia,
|
||||||
|
red: colors.rose,
|
||||||
|
yellow: colors.amber,
|
||||||
|
green: colors.green,
|
||||||
|
blue: colors.blue,
|
||||||
|
dark: colors.dark,
|
||||||
|
light: colors.light
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["Plus Jakarta Sans", "sans-serif"],
|
||||||
|
special: ["SyneVariable", "monospace"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
interactionVariantsPlugin
|
||||||
|
]
|
||||||
|
})
|
Loading…
Add table
Reference in a new issue