1
0
Fork 0
This commit is contained in:
Moritz Ruth 2020-04-07 15:52:52 +02:00
parent 82447b6a92
commit e55d5feced
No known key found for this signature in database
GPG key ID: FE38A0B03AA331BA
28 changed files with 2006 additions and 1543 deletions

View file

@ -1,15 +0,0 @@
body {
font-family: "Alata", sans-serif;
}
.page-enter-active, .page-leave-active {
transition: 500ms ease opacity;
}
.page-enter, .page-leave-to {
opacity: 0;
}
.page-enter-to, .page-leave {
opacity: 1;
}

10
assets/js/footerItems.js Normal file
View file

@ -0,0 +1,10 @@
export const footerItems = [
{
label: "Legal Notice",
to: "/legal-notice"
},
{
label: "Privacy Policy",
to: "/privacy-policy"
}
];

View file

@ -0,0 +1,7 @@
import _kebabCase from "lodash.kebabcase";
export function toModifierClasses(base, classes) {
return Object.entries(classes)
.filter(([, value]) => Boolean(value))
.map(([key]) => `${base}--${_kebabCase(key)}`);
}

View file

@ -0,0 +1,6 @@
$background: black;
$background-c: white;
$pink: #BB2081;
$pink-c: white;
$blue: #14AAD8;
$blue-c: white;

View file

@ -0,0 +1,8 @@
@mixin content($padding: 20px) {
$width: 800px;
max-width: calc(100vw - #{$padding} * 2);
width: $width;
padding: 0 $padding;
margin: 0 auto;
}

View file

@ -0,0 +1,11 @@
@mixin mobile() {
@media (max-width: 800px) {
@content;
}
}
@mixin notMobile() {
@media (min-width: 801px) {
@content;
}
}

View file

@ -0,0 +1 @@
$navigation-bar: 5;

View file

@ -0,0 +1,48 @@
@use "../screenSize";
%heading--1, %heading--2, %heading--3, %heading--4, %heading--5, %heading--6 {
margin-top: 1rem;
margin-bottom: 1rem;
font-weight: bold;
display: block;
}
%heading--1 {
font-size: 3.8rem;
@include screenSize.mobile {
font-size: 2.7rem;
}
}
%heading--2 {
font-size: 3rem;
@include screenSize.mobile {
font-size: 2.5rem;
}
}
%heading--3 {
font-size: 2.6rem;
@include screenSize.mobile {
font-size: 2.2rem;
}
}
%heading--4 {
font-size: 2.2rem;
@include screenSize.mobile {
font-size: 2rem;
}
}
%heading--5 {
font-size: 1.8rem;
}
%heading--6 {
font-size: 1.4rem;
}

View file

@ -0,0 +1,11 @@
%link {
color: var(--colors-link);
text-decoration: none;
transition: 100ms linear opacity;
opacity: 1;
&:hover {
opacity: 0.8;
}
}

View file

@ -0,0 +1,7 @@
%paragraph {
margin: 20px 0;
li {
margin-left: 20px;
}
}

View file

@ -0,0 +1,22 @@
@use "link";
@use "heading";
@use "paragraph";
$_elements: (
"paragraph": ("p", "ul"),
"link": "a",
"heading--1": "h1",
"heading--2": "h2",
"heading--3": "h3",
"heading--4": "h4",
"heading--5": "h5",
"heading--6": "h6",
);
@each $class, $tags in $_elements {
@each $tag in $tags {
#{$tag}.formatted, .formatted #{$tag}, .#{$class} {
@extend %#{$class};
}
}
}

67
assets/styles/global.scss Normal file
View file

@ -0,0 +1,67 @@
@use "./colors";
@use "./content";
@use "./formatted";
// General
html, body {
font-family: "Alata", sans-serif;
font-size: 16px;
padding: 0;
margin: 0;
width: 100vw;
overflow-x: hidden;
min-height: 100vh;
background: colors.$background;
color: colors.$background-c;
}
// Transitions
.page-enter-active, .page-leave-active {
transition: 500ms ease opacity;
}
.page-enter, .page-leave-to {
opacity: 0;
}
.page-enter-to, .page-leave {
opacity: 1;
}
// Other
.content {
@include content.content();
}
.link {
position: relative;
text-decoration: none;
color: colors.$background-c;
&:hover, &:focus {
outline: none;
&::after {
background-position: 0 0;
}
}
&::after {
content: "";
position: absolute;
height: 2px;
top: 102%;
left: 0;
right: 0;
background: linear-gradient(90deg, colors.$pink 0%, colors.$blue 100%) 100% 0;
background-size: 260% 100%;
transition: 500ms ease background-position;
}
}

View file

@ -1,10 +1,30 @@
<template>
<div class="animated-logo">
<svg class="animated-logo__svg" xmlns="http://www.w3.org/2000/svg" viewBox="76.75 182.661 358.5 146.679">
<path class="animated-logo__m1" d=" M 121.75 182.749 L 166.75 329.339 L 76.75 329.339 L 121.75 182.749 Z " fill-rule="evenodd" fill="currentColor"/>
<path class="animated-logo__m2" d=" M 206.75 182.749 L 251.75 329.339 L 161.75 329.339 L 206.75 182.749 Z " fill-rule="evenodd" fill="currentColor"/>
<path class="animated-logo__r1" d=" M 327.25 182.705 L 372.25 329.295 L 282.25 329.295 L 327.25 182.705 Z " fill-rule="evenodd" fill="currentColor"/>
<path class="animated-logo__r2" d=" M 390.537 329.339 L 345.25 182.837 L 435.25 182.661 L 390.537 329.339 Z " fill-rule="evenodd" fill="currentColor"/>
<path
class="animated-logo__m1"
d=" M 121.75 182.749 L 166.75 329.339 L 76.75 329.339 L 121.75 182.749 Z "
fill-rule="evenodd"
fill="currentColor"
/>
<path
class="animated-logo__m2"
d=" M 206.75 182.749 L 251.75 329.339 L 161.75 329.339 L 206.75 182.749 Z "
fill-rule="evenodd"
fill="currentColor"
/>
<path
class="animated-logo__r1"
d=" M 327.25 182.705 L 372.25 329.295 L 282.25 329.295 L 327.25 182.705 Z "
fill-rule="evenodd"
fill="currentColor"
/>
<path
class="animated-logo__r2"
d=" M 390.537 329.339 L 345.25 182.837 L 435.25 182.661 L 390.537 329.339 Z "
fill-rule="evenodd"
fill="currentColor"
/>
</svg>
<div class="animated-logo__name">
Moritz Ruth
@ -21,7 +41,7 @@
$slide-length: 37.5%;
.animated-logo {
width: 250px;
width: 300px;
max-width: 60vw;
animation: scaleUp $scale-up-duration ease-out both;
@ -34,7 +54,7 @@
}
.animated-logo__name {
font-size: 2rem;
font-size: 2.3rem;
animation: fadeIn $slide-duration $scale-up-duration ease both;
text-align: center;
}

View file

@ -0,0 +1,74 @@
<template>
<a
class="external-link link"
rel="noopener"
target="_blank"
:href="href"
>
<template v-if="this.$slots.default && this.$slots.default[0] && this.$slots.default[0].text">
<slot/>
</template>
<template v-else>
{{ label }}
</template>
<ExternalIcon class="external-link__icon"/>
</a>
</template>
<style scoped lang="scss">
.external-link {
padding-right: 2px;
overflow-wrap: break-word;
}
.external-link__icon {
margin-left: 3px;
width: 15px;
position: relative;
top: 2px;
}
</style>
<script>
import ExternalIcon from "@/assets/icons/external.svg";
export default {
name: "ExternalLink",
components: { ExternalIcon },
props: {
href: {
type: String,
required: true
},
showProtocol: {
type: Boolean,
default: false
},
showQuery: {
type: Boolean,
default: false
}
},
computed: {
label() {
// eslint-disable-next-line import/no-extraneous-dependencies
const url = new (process.server ? require("url").URL : window.URL)(this.href);
let label = "";
if (this.showProtocol) {
label += url.protocol;
label += "//";
}
label += url.host + url.pathname;
if (this.showQuery) {
label += url.search;
}
return label;
}
}
};
</script>

47
components/MyFooter.vue Normal file
View file

@ -0,0 +1,47 @@
<template>
<footer class="my-footer">
<nuxt-link
v-for="item in $options.items"
class="my-footer__link link"
:key="item.label"
:to="item.to"
@click.native.passive="open = false"
>
{{ item.label }}
</nuxt-link>
</footer>
</template>
<style lang="scss">
@use "~@/assets/styles/screenSize";
@use "~@/assets/styles/colors";
.my-footer {
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
height: 100px;
}
.my-footer__link:not(:last-child) {
margin-right: 20px;
}
@include screenSize.mobile {
.my-footer__link:not(:last-child) {
margin-right: 0;
margin-bottom: 20px;
}
}
</style>
<script>
import { footerItems } from "@/assets/js/footerItems";
export default {
name: "MyFooter",
items: footerItems
};
</script>

View file

@ -0,0 +1,323 @@
<template>
<div class="navigation-bar" :class="classes">
<div class="navigation-bar__placeholder"></div>
<nav class="navigation-bar__content">
<div class="navigation-bar__title-container">
<span
class="navigation-bar__title"
:class="{ 'navigation-bar__title--show': showTitle }"
>
{{ title }}
</span>
</div>
<div class="navigation-bar__toggle" @click="open = !open">
<span></span>
<span></span>
<span></span>
</div>
<div class="navigation-bar__links">
<div
v-for="item in $options.items"
class="navigation-bar__link-container"
:key="item.label"
>
<nuxt-link
v-if="item.to"
class="navigation-bar__link"
:to="item.to"
@click.native.passive="open = false"
>
{{ item.label }}
</nuxt-link>
<a
v-else
class="navigation-bar__link"
:key="item.label"
rel="noopener"
:href="item.href"
@click.passive="open = false"
>
{{ item.label }}
</a>
</div>
</div>
</nav>
</div>
</template>
<style lang="scss">
@use "~@/assets/styles/screenSize";
@use "~@/assets/styles/colors";
@use "~@/assets/styles/content";
@use "~@/assets/styles/z-indexes";
body {
--navigation-bar-height: 80px;
}
.navigation-bar__placeholder {
height: var(--navigation-bar-height);
}
.navigation-bar__content {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: z-indexes.$navigation-bar;
height: var(--navigation-bar-height);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30px;
.navigation-bar--show-background & {
background: colors.$background;
}
}
.navigation-bar__toggle {
position: relative;
z-index: 1;
& > span {
display: block;
background-color: colors.$background-c;
width: 30px;
height: 2px;
transition: 200ms linear;
transition-property: opacity, transform;
&:nth-child(2) {
margin-top: 8px;
}
&:nth-child(3) {
margin-top: 8px;
}
}
}
.navigation-bar__title-container {
height: 100%;
overflow: hidden;
display: flex;
align-items: center;
}
.navigation-bar__title {
font-size: 1.4rem;
display: block;
opacity: 0;
transform: translateY(10px);
transition: 200ms ease;
transition-property: opacity, transform;
&.navigation-bar__title--show {
opacity: 1;
transform: translateY(0);
}
}
.navigation-bar__links {
position: fixed;
top: 0;
left: 0;
z-index: 0;
width: 100vw;
height: 100vh;
background: colors.$background;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
opacity: 0;
pointer-events: none;
transition: 200ms ease opacity;
border: none;
}
.navigation-bar__link-container {
position: relative;
background: white;
&:not(:last-child) {
margin-bottom: 10px;
}
&::before {
content: "";
display: block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: linear-gradient(135deg, colors.$blue 25%, colors.$pink, colors.$blue 75%);
background-size: 400% 400%;
animation: 1s infinite linear reverse navigation-bar__gradient;
pointer-events: none;
mix-blend-mode: multiply;
opacity: 0;
transition: 200ms opacity ease;
}
&:hover, &:focus-within {
&::before {
opacity: 1;
}
.navigation-bar__link {
outline: none;
transform: translate(-5px, -5px);
}
}
}
.navigation-bar__link {
display: block;
color: colors.$background-c;
text-decoration: none;
font-size: 1.4rem;
background: black;
padding: 10px;
transition: 200ms transform ease;
}
@keyframes navigation-bar__gradient {
from {
background-position: 0 0;
}
to {
background-position: 100% 100%;
}
}
.navigation-bar--open {
.navigation-bar__toggle > span {
&:nth-child(1) {
transform: translateY(10px) rotate(45deg);
}
&:nth-child(2) {
opacity: 0;
}
&:nth-child(3) {
transform: translateY(-10px) rotate(-45deg);
}
}
.navigation-bar__links {
opacity: 1;
pointer-events: auto;
}
}
@include screenSize.notMobile {
body {
--navigation-bar-height: 100px;
}
.navigation-bar__content {
@include content.content(40px);
}
.navigation-bar__title {
font-size: 2.5rem;
}
.navigation-bar__toggle {
display: none;
}
.navigation-bar__links {
position: static;
width: auto;
height: auto;
opacity: 1;
pointer-events: auto;
flex-direction: row;
}
.navigation-bar__link-container:not(:last-child) {
margin-bottom: 0;
margin-right: 40px;
}
}
</style>
<script>
import { toModifierClasses } from "@/assets/js/toModifierClasses";
const ITEMS = [
{
label: "Start",
to: "/"
},
{
label: "Projects",
to: "/projects"
}
];
export default {
name: "NavigationBar",
props: {
backgroundAfterScroll: {
type: Boolean,
default: false
},
title: {
type: String,
default: ""
}
},
data: () => ({
open: false,
scrollPosition: 0
}),
computed: {
showTitle: vm => vm.scrollPosition > 60,
showBackground: vm => vm.backgroundAfterScroll ? vm.scrollPosition > 0 : true,
classes() {
const { open, showBackground } = this;
return toModifierClasses("navigation-bar", {
open,
showBackground
});
}
},
mounted() {
const scrollListener = () => {
this.scrollPosition = window.scrollY;
};
window.addEventListener("scroll", scrollListener, { passive: true });
this.$on("hook:beforeDestroy", () => {
window.removeEventListener("scroll", scrollListener);
});
scrollListener();
},
items: ITEMS
};
</script>

View file

@ -1,96 +0,0 @@
<template>
<div class="project">
<h2 class="heading--3">
{{ title }}
</h2>
<span class="project__type">{{ type }}</span>
<div class="project__description">
<slot/>
</div>
<hr class="project__divider"/>
<div class="project__buttons">
<KButton v-if="link" :href="link">
Open
<template v-slot:suffix>
<ArrowRightIcon/>
</template>
</KButton>
<KButton v-if="github" :href="`https://github.com/${github}`">
GitHub
<template v-slot:prefix>
<GitHubIcon/>
</template>
</KButton>
<KButton v-if="npm" :href="`https://www.npmjs.com/package/${npm}`">
NPM
<template v-slot:prefix>
<NPMIcon style="width: 30px; top: 4px"/>
</template>
</KButton>
</div>
</div>
</template>
<style scoped lang="scss">
.project__type {
margin-top: -1rem;
margin-bottom: 1rem;
display: block;
}
.project__description {
font-size: 1.2rem;
}
.project__divider {
margin-top: 20px;
margin-bottom: 10px;
width: 175px;
height: 1px;
background-color: black;
border: none;
}
.project__buttons {
display: flex;
& > *:not(:last-child) {
margin-right: 5px;
}
}
</style>
<script>
import KButton from "kiste/components/KButton.vue";
import ArrowRightIcon from "@/assets/icons/arrow_right.svg";
import GitHubIcon from "@/assets/icons/github.svg";
import NPMIcon from "@/assets/icons/npm.svg";
export default {
name: "GProject",
components: { KButton, ArrowRightIcon, GitHubIcon, NPMIcon },
props: {
title: {
type: String,
required: true
},
type: {
type: String,
required: true
},
link: {
type: String,
default: null
},
github: {
type: String,
default: null
},
npm: {
type: String,
default: null
}
}
};
</script>

View file

@ -1,22 +1,23 @@
<template>
<div class="default-layout fill-screen">
<KApp>
<div class="default-layout">
<nuxt/>
<KFooter/>
</KApp>
<MyFooter/>
</div>
</template>
<style scoped lang="scss">
.default-layout {
min-height: 100vh;
width: 100vw;
overflow-x: hidden;
}
</style>
<script>
import KApp from "kiste/components/KApp.vue";
import KFooter from "kiste/components/KFooter.vue";
import MyFooter from "@/components/MyFooter";
export default {
name: "DefaultLayout",
components: { KApp, KFooter }
components: { MyFooter }
};
</script>

13
layouts/empty.vue Normal file
View file

@ -0,0 +1,13 @@
<template>
<nuxt/>
</template>
<style>
</style>
<script>
export default {
name: "EmptyLayout"
};
</script>

View file

@ -1,18 +0,0 @@
<template>
<KApp>
<nuxt/>
</KApp>
</template>
<style>
</style>
<script>
import KApp from "kiste/components/KApp.vue";
export default {
name: "WithoutFooterLayout",
components: { KApp }
};
</script>

View file

@ -18,7 +18,7 @@ export default {
/*
** Global CSS
*/
css: ["@/assets/global.scss"],
css: ["@/assets/styles/global.scss"],
layoutTransition: "page",
/*
@ -30,8 +30,7 @@ export default {
*/
buildModules: [
// Doc: https://github.com/nuxt-community/eslint-module
"@nuxtjs/eslint-module",
"kiste/nuxt"
"@nuxtjs/eslint-module"
],
/*
** Nuxt.js modules
@ -41,32 +40,6 @@ export default {
"@nuxtjs/pwa"
],
kiste: {
theme: {
contentPadding: "10px"
},
navigationItems: [
{
label: "Home",
to: "/"
},
{
label: "Projects",
to: "/projects"
}
],
footerItems: [
{
label: "Legal Notice",
to: "/legal-notice"
},
{
label: "Privacy Policy",
to: "/privacy-policy"
}
]
},
// https://pwa.nuxtjs.org/modules/workbox.html
workbox: { workboxExtensions: ["@/assets/js/font-sw.js"] },
@ -77,17 +50,17 @@ export default {
mobileApp: false,
name: "Moritz Ruth",
author: "Moritz Ruth",
description: "The official website of Moritz Ruth",
description: "Moritz Ruth is a web developer, photograph and digital creator.",
lang: "en",
themeColor: "#ffffff"
themeColor: "#000000"
},
// https://developer.mozilla.org/en-US/docs/Web/Manifest
manifest: {
name: "Moritz Ruth",
short_name: "Moritz Ruth",
background_color: "#ffffff",
background_color: "#000000",
display: "browser",
description: "The official website of Moritz Ruth"
description: "Moritz Ruth is a web developer, photograph and digital creator."
}
},

View file

@ -5,30 +5,30 @@
"author": "Moritz Ruth",
"private": true,
"scripts": {
"dev": "nuxt",
"dev": "HOST=0.0.0.0 nuxt",
"generate": "nuxt generate",
"start": "serve dist",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
},
"dependencies": {
"@nuxtjs/pwa": "^3.0.0-beta.19",
"kiste": "^1.2.7",
"nuxt": "^2.11.0",
"@nuxtjs/pwa": "^3.0.0-beta.20",
"lodash.kebabcase": "^4.1.1",
"nuxt": "^2.12.2",
"shaped.js": "^1.0.3",
"vue": "^2.6.11",
"vue-ripple-directive": "^2.0.1"
},
"devDependencies": {
"@moritzruth/eslint-config": "^1.0.6",
"@moritzruth/eslint-config": "^1.0.9",
"@nuxtjs/eslint-module": "^1.1.0",
"babel-eslint": "^10.0.3",
"babel-eslint": "^10.1.0",
"eslint": "^6.8.0",
"eslint-import-resolver-webpack": "^0.12.1",
"eslint-plugin-nuxt": "^0.5.0",
"eslint-plugin-nuxt": "^0.5.2",
"fibers": "^4.0.2",
"sass": "^1.24.4",
"sass-loader": "^8.0.1",
"sass": "^1.26.3",
"sass-loader": "^8.0.2",
"serve": "^11.3.0",
"shaped.js": "^1.0.2",
"svg-to-vue-component": "^0.3.8"
}
}

View file

@ -1,173 +1,109 @@
<template>
<div class="index-page">
<KNavigationBar background-after-scroll/>
<AnimatedLogo/>
<NavigationBar background-after-scroll title="Start"/>
<canvas class="index-page__background" ref="canvas"/>
<main class="index-page__content">
<div class="index-page__socials">
<a class="index-page__social-link" href="https://github.com/moritzruth" title="GitHub">
<GitHubIcon class="index-page__social-icon"/>
</a>
<a class="index-page__social-link" href="https://twitter.com/moritzruth_dev" title="Twitter">
<TwitterIcon class="index-page__social-icon"/>
</a>
<a class="index-page__social-link" href="https://instagram.com/moritzruth_dev" title="Instagram">
<InstagramIcon class="index-page__social-icon"/>
</a>
<a class="index-page__social-link" href="mailto:dev@moritz-ruth.de" title="Email">
<EmailIcon class="index-page__social-icon"/>
</a>
<div class="index-page__content">
<AnimatedLogo/>
</div>
</main>
<KFooter class="index-page__footer"/>
<footer class="index-page__footer">
<nuxt-link
v-for="item in $options.footerItems"
class="index-page__footer-link"
:key="item.label"
:to="item.to"
@click.native.passive="open = false"
>
{{ item.label }}
</nuxt-link>
</footer>
</div>
</template>
<style scoped lang="scss">
@use "~kiste/css/mixins/screenSize";
<style lang="scss">
@use "~@/assets/styles/screenSize";
@use "~@/assets/styles/colors";
.index-page {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: calc(100vh - var(--x-navbar-height));
background: black;
width: 100vw;
}
.index-page__background {
position: fixed;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
z-index: -1;
animation: appear 2s 1.2s linear both;
}
@keyframes appear {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.index-page__socials {
position: absolute;
bottom: 20px;
left: 0;
right: 0;
.index-page__content {
display: flex;
justify-content: center;
align-items: center;
}
.index-page__social-link {
color: black;
&:not(:last-child) {
margin-right: 15px;
}
}
.index-page__social-icon {
width: 40px;
transition: 200ms linear opacity;
&:hover {
opacity: 0.8;
}
height: calc(100vh - var(--navigation-bar-height));
}
.index-page__footer {
position: absolute;
padding: 20px 0;
margin-bottom: 10px;
@include screenSize.notMobile {
top: unset;
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: fit-content;
padding: 20px;
margin: 0;
flex-direction: row;
opacity: 0.5;
transition: 200ms linear opacity;
&:hover {
opacity: 1;
}
.index-page__footer-link {
text-decoration: none;
color: colors.$background-c;
&:not(:last-child) {
margin-right: 20px;
}
}
}
@include screenSize.mobile {
}
</style>
<script>
import KNavigationBar from "kiste/components/KNavigationBar.vue";
import KFooter from "kiste/components/KFooter.vue";
import { Canvas } from "shaped.js";
import AnimatedLogo from "@/components/AnimatedLogo.vue";
import GitHubIcon from "@/assets/icons/github.svg";
import TwitterIcon from "@/assets/icons/twitter.svg";
import InstagramIcon from "@/assets/icons/instagram.svg";
import EmailIcon from "@/assets/icons/email.svg";
import AnimatedLogo from "@/components/AnimatedLogo";
import NavigationBar from "@/components/NavigationBar";
import { footerItems } from "@/assets/js/footerItems";
const COLORS = [
"rgba(0, 0, 0, 0.8)",
"rgba(0, 255, 150, 0.8)",
"rgba(0, 255, 150, 0.2)",
"rgba(0, 150, 255, 0.8)",
"rgba(0, 150, 255, 0.2)"
"#BB2081",
"#14AAD8",
"#ffffff"
];
const LINES = [
{
minCount: 8,
probability: 1 / 10000,
height: 2,
probability: 1 / 20000,
height: 1,
length: 100,
speed: [-0.2, 0.2],
speed: [-0.1, 0.4],
colors: COLORS
},
{
minCount: 8,
probability: 1 / 50000,
probability: 1 / 10000,
height: 5,
length: [20, 200],
speed: [0.2, 0.3],
colors: COLORS,
randomizeYAfterLeave: true
},
{
probability: 1 / 50000,
height: 50,
length: 50,
speed: [0.2, 0.5],
length: 5,
speed: [-0.4, 0.4],
colors: COLORS
},
{
probability: 1 / 5000,
height: 3,
length: 3,
speed: [-1, 1],
colors: COLORS
},
{
minCount: 8,
probability: 1 / 50000,
height: [20, 200],
length: [20, 200],
speed: [0.2, 0.3],
colors: COLORS,
randomizeYAfterLeave: true
},
{
probability: 1 / 5000,
probability: 1 / 10000,
height: [20, 200],
length: 2,
speed: [-0.2, 0.2],
@ -177,31 +113,32 @@
export default {
name: "IndexPage",
layout: "none",
components: { AnimatedLogo, GitHubIcon, TwitterIcon, InstagramIcon, EmailIcon, KNavigationBar, KFooter },
layout: "empty",
components: { NavigationBar, AnimatedLogo },
mounted() {
let nextConfig = 0;
let configIndex = 0;
if (localStorage !== undefined) {
const rawValue = localStorage.getItem("nextBackground");
if (rawValue) {
try {
nextConfig = JSON.parse(rawValue);
// eslint-disable-next-line no-empty
} catch {}
configIndex = JSON.parse(rawValue);
} catch {
// ignored
}
}
}
if (nextConfig > LINES.length - 1) {
nextConfig = 0;
if (configIndex > LINES.length - 1) {
configIndex = 0;
}
if (localStorage !== undefined) {
localStorage.setItem("nextBackground", nextConfig + 1);
localStorage.setItem("nextBackground", JSON.stringify(configIndex + 1));
}
const config = LINES[nextConfig];
const config = LINES[configIndex];
const backgroundCanvas = new Canvas(this.$refs.canvas, {
lines: config,
fillWindowSize: true
@ -210,6 +147,7 @@
this.$once("hook:beforeDestroy", () => {
backgroundCanvas.destroy();
});
}
},
footerItems
};
</script>

View file

@ -1,6 +1,5 @@
<template>
<div class="legal-notice-page">
<KNavigationBar title="Legal Notice"/>
<div class="content">
<h1 class="heading--1">
Legal Notice
@ -59,7 +58,6 @@
class="link"
href="https://datenschutz-generator.de/?l=de"
rel="noopener"
title="Rechtstext von Dr. Schwenke - für weitere Informationen bitte anklicken."
target="_blank"
>Erstellt mit kostenlosem Datenschutz-Generator.de von Dr. Thomas Schwenke</a>
</div>
@ -67,11 +65,8 @@
</template>
<script>
import KNavigationBar from "kiste/components/KNavigationBar.vue";
export default {
name: "LegalNoticePage",
components: { KNavigationBar },
head: { htmlAttrs: { lang: "de" } }
head: () => ({ htmlAttrs: { lang: "de" } })
};
</script>

View file

@ -1,26 +1,26 @@
<template>
<div class="privacy-policy-page">
<KNavigationBar title="Datenschutzerklärung"/>
<div class="content">
<h1 class="heading--1">
<NavigationBar title="Datenschutz"/>
<div class="content formatted">
<h1>
Datenschutzerklärung
</h1>
<p class="paragraph">
<p>
Verantwortlicher im Sinne der Datenschutzgesetze, insbesondere der EU-Datenschutzgrundverordnung (DSGVO), ist:
</p>
<p class="paragraph">
<p>
Moritz Ruth<br>
Zum Galgenberg 19<br>
66539 Neunkirchen<br>
Deutschland
</p>
<h2 class="heading--2">
<h2>
Ihre Betroffenenrechte
</h2>
<p class="paragraph">
<p>
Unter den angegebenen Kontaktdaten unseres Datenschutzbeauftragten können Sie jederzeit folgende Rechte ausüben:
</p>
<ul class="paragraph">
<ul>
<li>Auskunft über Ihre bei uns gespeicherten Daten und deren Verarbeitung (Art. 15 DSGVO),</li>
<li>Berichtigung unrichtiger personenbezogener Daten (Art. 16 DSGVO),</li>
<li>Löschung Ihrer bei uns gespeicherten Daten (Art. 17 DSGVO),</li>
@ -34,260 +34,262 @@
abgeschlossen haben (Art. 20 DSGVO).
</li>
</ul>
<p class="paragraph">
<p>
Sofern Sie uns eine Einwilligung erteilt haben, können Sie diese jederzeit mit Wirkung für die Zukunft
widerrufen.
</p>
<p class="paragraph">
<p>
Sie können sich jederzeit mit einer Beschwerde an eine Aufsichtsbehörde wenden, z. B. an die zuständige
Aufsichtsbehörde des Bundeslands Ihres Wohnsitzes oder an die für uns als verantwortliche Stelle zuständige
Behörde.
</p>
<p class="paragraph">
<p>
Eine Liste der Aufsichtsbehörden (für den nichtöffentlichen Bereich) mit Anschrift finden Sie unter:
<KExternalLink href="https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html"/>.
<ExternalLink href="https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html"/>.
</p>
<h2 class="heading--2">
<h2>
Erfassung allgemeiner Informationen beim Besuch unserer Website
</h2>
<h3 class="heading--3">
<h3>
Art und Zweck der Verarbeitung
</h3>
<p class="paragraph">
<p>
Wenn Sie auf unsere Website zugreifen, d.h., wenn Sie sich nicht registrieren oder anderweitig Informationen
übermitteln, werden automatisch Informationen allgemeiner Natur erfasst. Diese Informationen (Server-Logfiles)
beinhalten etwa die Art des Webbrowsers, das verwendete Betriebssystem, den Domainnamen Ihres
Internet-Service-Providers, Ihre IP-Adresse und ähnliches.
</p>
<p class="paragraph">
<p>
Sie werden insbesondere zu folgenden Zwecken verarbeitet
</p>
<ul class="paragraph">
<ul>
<li>Sicherstellung eines problemlosen Verbindungsaufbaus der Website,</li>
<li>Sicherstellung einer reibungslosen Nutzung unserer Website,</li>
<li>Auswertung der Systemsicherheit und -stabilität sowie</li>
<li>zu weiteren administrativen Zwecken.</li>
</ul>
<p class="paragraph">
<p>
Wir verwenden Ihre Daten nicht, um Rückschlüsse auf Ihre Person zu ziehen. Informationen dieser Art werden von
uns ggfs. statistisch ausgewertet, um unseren Internetauftritt und die dahinterstehende Technik zu
optimieren.
</p>
<h3 class="heading--3">
<h3>
Rechtsgrundlage
</h3>
<p class="paragraph">
<p>
Die Verarbeitung erfolgt gemäß Art. 6 Abs. 1 lit. f DSGVO auf Basis unseres berechtigten Interesses an der
Verbesserung der Stabilität und Funktionalität unserer Website.
</p>
<h3 class="heading--3">
<h3>
Empfänger
</h3>
<p class="paragraph">
<p>
Empfänger der Daten sind ggf. technische Dienstleister, die für den Betrieb und die Wartung unserer Webseite als
Auftragsverarbeiter tätig werden.
</p>
<h3 class="heading--3">
<h3>
Speicherdauer
</h3>
<p class="paragraph">
<p>
Die Daten werden gelöscht, sobald diese für den Zweck der Erhebung nicht mehr erforderlich sind. Dies ist für
die Daten, die der Bereitstellung der Webseite dienen, grundsätzlich der Fall, wenn die jeweilige Sitzung
beendet ist.
</p>
<h3 class="heading--3">
<h3>
Bereitstellung vorgeschrieben oder erforderlich
</h3>
<p class="paragraph">
<p>
Die Bereitstellung der vorgenannten personenbezogenen Daten ist weder gesetzlich noch vertraglich
vorgeschrieben. Ohne die IP-Adresse ist jedoch der Dienst und die Funktionsfähigkeit unserer Website nicht
gewährleistet. Zudem können einzelne Dienste und Services nicht verfügbar oder eingeschränkt sein. Aus diesem
Grund ist ein Widerspruch ausgeschlossen.
</p>
<h2 class="heading--2">
<h2>
Verwendung von Scriptbibliotheken (Google Webfonts)
</h2>
<h3 class="heading--3">
<h3>
Art und Zweck der Verarbeitung
</h3>
<p class="paragraph">
<p>
Um unsere Inhalte browserübergreifend korrekt und grafisch ansprechend darzustellen, verwenden wir auf dieser
Website Google Web Fonts der Google LLC (1600 Amphitheatre Parkway, Mountain View, CA 94043, USA; nachfolgend
Google) zur Darstellung von Schriften.
</p>
<p class="paragraph">
<p>
Die Datenschutzrichtlinie des Bibliothekbetreibers Google finden Sie hier:
<KExternalLink href="https://www.google.com/policies/privacy/"/>
<ExternalLink href="https://www.google.com/policies/privacy/"/>
</p>
<h3 class="heading--3">
<h3>
Rechtsgrundlage
</h3>
<p class="paragraph">
<p>
Rechtsgrundlage für die Einbindung von Google Webfonts und dem damit verbundenen Datentransfer zu Google ist
Ihre Einwilligung (Art. 6 Abs. 1 lit. a DSGVO).
</p>
<h3 class="heading--3">
<h3>
Empfänger
</h3>
<p class="paragraph">
<p>
Der Aufruf von Scriptbibliotheken oder Schriftbibliotheken löst automatisch eine Verbindung zum Betreiber der
Bibliothek aus. Dabei ist es theoretisch möglich aktuell allerdings auch unklar ob und ggf. zu welchen Zwecken
dass der Betreiber in diesem Fall Google Daten erhebt.
</p>
<h3 class="heading--3">
<h3>
Speicherdauer
</h3>
<p class="paragraph">
<p>
Wir erheben keine personenbezogenen Daten, durch die Einbindung von Google Webfonts.
</p>
<p class="paragraph">
<p>
Weitere Informationen zu Google Web Fonts finden Sie unter
<KExternalLink href="https://developers.google.com/fonts/faq"/> und in der Datenschutzerklärung von Google:
<KExternalLink href="https://www.google.com/policies/privacy/"/>.
<ExternalLink href="https://developers.google.com/fonts/faq"/> und in der Datenschutzerklärung von Google:
<ExternalLink href="https://www.google.com/policies/privacy/"/>.
</p>
<h3 class="heading--3">
<h3>
Drittlandtransfer
</h3>
<p class="paragraph">
<p>
Google verarbeitet Ihre Daten in den USA und hat sich dem EU_US Privacy Shield unterworfen
<KExternalLink href="https://www.privacyshield.gov/EU-US-Framework"/>.
<ExternalLink href="https://www.privacyshield.gov/EU-US-Framework"/>.
</p>
<h3 class="heading--3">
<h3>
Bereitstellung vorgeschrieben oder erforderlich
</h3>
<p class="paragraph">
<p>
Die Bereitstellung der personenbezogenen Daten ist weder gesetzlich, noch vertraglich vorgeschrieben. Allerdings
kann ggfs. die korrekte Darstellung der Inhalte durch Standardschriften nicht möglich sein.
</p>
<h3 class="heading--3">
<h3>
Widerruf der Einwilligung
</h3>
<p class="paragraph">
<p>
Zur Darstellung der Inhalte wird regelmäßig die Programmiersprache JavaScript verwendet. Sie können der
Datenverarbeitung daher widersprechen, indem Sie die Ausführung von JavaScript in Ihrem Browser deaktivieren
oder einen JavaScript-Blocker installieren. Bitte beachten Sie, dass es hierdurch zu Funktionseinschränkungen
auf der Website kommen kann.
</p>
<h2 class="heading--2">
<h2>
Eingebettete YouTube-Videos
</h2>
<h3 class="heading--3">
<h3>
Art und Zweck der Verarbeitung
</h3>
<p class="paragraph">
<p>
Auf einigen unserer Webseiten betten wir YouTube-Videos ein. Betreiber der entsprechenden Plugins ist die
YouTube, LLC, 901 Cherry Ave., San Bruno, CA 94066, USA (nachfolgend YouTube). Wenn Sie eine Seite mit dem
YouTube-Plugin besuchen, wird eine Verbindung zu Servern von YouTube hergestellt. Dabei wird YouTube mitgeteilt,
welche Seiten Sie besuchen. Wenn Sie in Ihrem YouTube-Account eingeloggt sind, kann YouTube Ihr Surfverhalten
Ihnen persönlich zuzuordnen. Dies verhindern Sie, indem Sie sich vorher aus Ihrem YouTube-Account ausloggen.
</p>
<p class="paragraph">
<p>
Wird ein YouTube-Video gestartet, setzt der Anbieter Cookies ein, die Hinweise über das Nutzerverhalten sammeln.
</p>
<p class="paragraph">
<p>
Weitere Informationen zu Zweck und Umfang der Datenerhebung und ihrer Verarbeitung durch YouTube erhalten Sie in
den Datenschutzerklärungen des Anbieters, Dort erhalten Sie auch weitere Informationen zu Ihren diesbezüglichen
Rechten und Einstellungsmöglichkeiten zum Schutze Ihrer Privatsphäre
(<KExternalLink href="https://policies.google.com/privacy"/>). Google verarbeitet Ihre Daten in den USA und hat
sich dem EU-US Privacy Shield unterworfen <KExternalLink href="https://www.privacyshield.gov/EU-US-Framework"/>.
(<ExternalLink href="https://policies.google.com/privacy"/>). Google verarbeitet Ihre Daten in den USA und hat
sich dem EU-US Privacy Shield unterworfen <ExternalLink href="https://www.privacyshield.gov/EU-US-Framework"/>.
</p>
<h3 class="heading--3">
<h3>
Rechtsgrundlage
</h3>
<p class="paragraph">
<p>
Rechtsgrundlage für die Einbindung von YouTube und dem damit verbundenen Datentransfer zu Google ist Ihre
Einwilligung (Art. 6 Abs. 1 lit. a DSGVO).
</p>
<h3 class="heading--3">
<h3>
Empfänger
</h3>
<p class="paragraph">
<p>
Der Aufruf von YouTube löst automatisch eine Verbindung zu Google aus.
</p>
<h3 class="heading--3">
<h3>
Speicherdauer und Widerruf der Einwilligung:
</h3>
<p class="paragraph">
<p>
Wer das Speichern von Cookies für das Google-Ad-Programm deaktiviert hat, wird auch beim Anschauen von
YouTube-Videos mit keinen solchen Cookies rechnen müssen. YouTube legt aber auch in anderen Cookies
nicht-personenbezogene Nutzungsinformationen ab. Möchten Sie dies verhindern, so müssen Sie das Speichern von
Cookies im Browser blockieren.
</p>
<p class="paragraph">
<p>
Weitere Informationen zum Datenschutz bei YouTube finden Sie in der Datenschutzerklärung des Anbieters unter:
<KExternalLink href="https://www.google.de/intl/de/policies/privacy/"/>
<ExternalLink href="https://www.google.de/intl/de/policies/privacy/"/>
</p>
<h3 class="heading--3">
<h3>
Drittlandtransfer
</h3>
<p class="paragraph">
<p>
Google verarbeitet Ihre Daten in den USA und hat sich dem EU_US Privacy Shield unterworfen
<KExternalLink href="https://www.privacyshield.gov/EU-US-Framework"/>.
<ExternalLink href="https://www.privacyshield.gov/EU-US-Framework"/>.
</p>
<h3 class="heading--3">
<h3>
Bereitstellung vorgeschrieben oder erforderlich
</h3>
<p class="paragraph">
<p>
Die Bereitstellung Ihrer personenbezogenen Daten erfolgt freiwillig, allein auf Basis Ihrer Einwilligung. Sofern
Sie den Zugriff unterbinden, kann es hierdurch zu Funktionseinschränkungen auf der Website kommen.
</p>
<h2 class="heading--2">
<h2>
SSL-Verschlüsselung
</h2>
<p class="paragraph">
<p>
Um die Sicherheit Ihrer Daten bei der Übertragung zu schützen, verwenden wir dem aktuellen Stand der Technik
entsprechende Verschlüsselungsverfahren (z. B. SSL) über HTTPS.
</p>
<h2 class="heading--2">
<h2>
Änderung unserer Datenschutzbestimmungen
</h2>
<p class="paragraph">
<p>
Wir behalten uns vor, diese Datenschutzerklärung anzupassen, damit sie stets den aktuellen rechtlichen
Anforderungen entspricht oder um Änderungen unserer Leistungen in der Datenschutzerklärung umzusetzen, z.B. bei
der Einführung neuer Services. Für Ihren erneuten Besuch gilt dann die neue Datenschutzerklärung.
</p>
<h2 class="heading--2">
<h2>
Fragen an den Datenschutzbeauftragten
</h2>
<p class="paragraph">
<p>
Wenn Sie Fragen zum Datenschutz haben, schreiben Sie uns bitte eine E-Mail oder wenden Sie sich direkt an die
für den Datenschutz verantwortliche Person:
</p>
<p class="paragraph">
<p>
Moritz Ruth<br>
Zum Galgenberg 19<br>
66539 Neunkirchen<br>
Deutschland
</p>
<p class="paragraph">
<p>
<b>Telefon</b>: +49 176 46146329
</p>
<p class="paragraph">
<p>
<b>E-Mail</b>: <a class="link" href="mailto:legal@moritz-ruth.de">legal@moritz-ruth.de</a>
</p>
<p class="paragraph">
<p>
Die Datenschutzerklärung wurde mithilfe der activeMind AG erstellt, den Experten für
<KExternalLink
<ExternalLink
href="https://www.activemind.de/datenschutz/datenschutzhinweis-generator/"
>
externe Datenschutzbeauftragte
</KExternalLink> (Version #2019-04-10).
</ExternalLink>.
</p>
</div>
</div>
</template>
<style scoped lang="scss">
.privacy-policy-page {
overflow-y: auto;
}
</style>
<script>
import KNavigationBar from "kiste/components/KNavigationBar.vue";
import KExternalLink from "kiste/components/KExternalLink.vue";
import ExternalLink from "@/components/ExternalLink";
import NavigationBar from "@/components/NavigationBar";
export default {
name: "PrivacyPolicyPage",
components: { KNavigationBar, KExternalLink },
head: { htmlAttrs: { lang: "de" } }
components: { NavigationBar, ExternalLink },
head: () => ({ htmlAttrs: { lang: "de" } })
};
</script>

View file

@ -1,100 +0,0 @@
<template>
<div class="projects-page">
<KNavigationBar title="Projects" background-after-scroll/>
<div class="content">
<h1 class="heading--1">
Projects
</h1>
<GProject
type="JavaScript library"
title="shaped.js"
github="moritzruth/shaped.js"
npm="shaped.js"
>
Generate beautiful moving shapes using a canvas element which can for example be used for backgrounds.
See it in action <nuxt-link class="link" to="/">on the home page</nuxt-link>.
Every time you reload the page, it shows another variation.
</GProject>
<GProject
type="App (english)"
title="RelaxYourEyes"
link="https://relaxyoureyes.moritz-ruth.de"
github="moritzruth/relaxyoureyes"
>
A timer which reminds you to relax your eyes every 20 minutes.
</GProject>
<GProject
type="App (german)"
title="Schweredruck-Simulation"
link="https://app.moritz-ruth.de/schweredruck-simulation"
github="moritzruth/schweredruck-simulation"
>
Simulate and calculate the hydrostatic pressure in different liquids.
</GProject>
<GProject
type="Website"
title="cryptic-game.net"
link="https://test.cryptic-game.net"
github="cryptic-game/website/tree/vueWebsite"
>
I am responsible for the new (not yet released) website of the
<a class="link" href="https://github.com/cryptic-game">Cryptic game</a>.
(Just the website, not the game itself.)
</GProject>
<GProject
type="Vue.js/Nuxt.js plugin"
title="Kiste"
github="moritzruth/kiste"
npm="kiste"
>
A collection of UI components and styles used for example by this website.
</GProject>
<GProject
type="Node.js Library"
title="log-groups"
github="moritzruth/log-groups"
npm="log-groups"
>
A pretty console logging library for printing grouped messages with Node.js.
</GProject>
<GProject
type="Node.js script"
title="Zimmerlampe"
github="moritzruth/zimmerlampe"
>
A simple script I use for controlling my Stairville CLB4 as room lamp.
</GProject>
<GProject
type="Node.js library"
title="node-enttec-open-dmx-usb"
github="moritzruth/node-enttec-open-dmx-usb"
>
A Node.js library for sending DMX data through the
<a class="link" href="https://www.enttec.co.uk/en/product/controls/dmx-usb-interfaces/open-dmx-usb/">
Enttec Open DMX USB Interface</a>.
</GProject>
<GProject
type="Configuration"
title="eslint-config"
github="moritzruth/eslint-config"
npm="@moritzruth/eslint-config"
>
My personal ESLint configuration I use in all of my projects.
</GProject>
</div>
</div>
</template>
<style scoped lang="scss">
</style>
<script>
import KNavigationBar from "kiste/components/KNavigationBar.vue";
import GProject from "@/components/pages/projects/GProject.vue";
export default {
name: "ProjectsPage",
components: { GProject, KNavigationBar }
};
</script>

View file

@ -8,6 +8,7 @@ module.exports = {
alias: {
"@": path.resolve(__dirname),
"~": path.resolve(__dirname)
}
},
extensions: [".vue", ".js"]
}
};

2169
yarn.lock

File diff suppressed because it is too large Load diff