Switch to pnpm, add home page and navigation menu
This commit is contained in:
parent
2ae875cb45
commit
e86f299580
21 changed files with 3209 additions and 2173 deletions
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"root": true,
|
||||
"extends": "awzzm-vue/v3"
|
||||
"extends": "awzzm-vue/v3",
|
||||
"rules": {
|
||||
"vue/no-static-inline-styles": "off"
|
||||
}
|
||||
}
|
||||
|
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
|||
15
|
||||
16
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# moritzruth.de
|
||||
> My personal website
|
||||
|
||||
:fire: [**moritzruth.de**](https://moritzruth.de)
|
||||
🔥 [**moritzruth.de**](https://moritzruth.de)
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Moritz Ruth</title>
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
<meta name="description" content="I do web and print things, sometimes photography.">
|
||||
<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>
|
||||
<body class="bg-[#fefefe]">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
<script
|
||||
|
|
24
package.json
24
package.json
|
@ -2,22 +2,28 @@
|
|||
"name": "moritzruth.de",
|
||||
"author": "Moritz Ruth <dev@moritzruth.de>",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev": "vite --host",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"lint": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@zhuowenli/vue-feather-icons": "^5.0.2",
|
||||
"vue": "^3.0.11"
|
||||
"@fontsource/plus-jakarta-sans": "^4.4.5",
|
||||
"@fontsource/syne": "^4.4.5",
|
||||
"@vueuse/core": "^5.0.3",
|
||||
"blobs": "^2.2.1-beta.1",
|
||||
"kute.js": "^2.1.2",
|
||||
"vue": "^3.1.1",
|
||||
"vue-router": "4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^1.2.2",
|
||||
"@vue/compiler-sfc": "^3.0.11",
|
||||
"eslint": "^7.25.0",
|
||||
"@vitejs/plugin-vue": "^1.2.3",
|
||||
"@vue/compiler-sfc": "^3.1.1",
|
||||
"eslint": "^7.28.0",
|
||||
"eslint-config-awzzm-vue": "^1.6.0",
|
||||
"vite": "^2.2.3",
|
||||
"vite-plugin-windicss": "^0.15.10",
|
||||
"windicss": "^2.5.14"
|
||||
"vite": "^2.3.7",
|
||||
"vite-plugin-pages": "^0.13.1",
|
||||
"vite-plugin-windicss": "^1.0.4",
|
||||
"windicss": "^3.1.3"
|
||||
}
|
||||
}
|
||||
|
|
2705
pnpm-lock.yaml
generated
Normal file
2705
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,2 +1 @@
|
|||
https://moritz-ruth.de/* https://moritzruth.de/:splat 301!
|
||||
/impressum /impressum.txt 200
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
User-agent: *
|
||||
Disallow: /impressum
|
||||
Disallow: /impressum.txt
|
||||
|
|
52
src/App.vue
52
src/App.vue
|
@ -1,59 +1,23 @@
|
|||
<template>
|
||||
<div class="bg-black w-screen h-screen text-white overflow-hidden flex justify-center items-center">
|
||||
<div class="px-8 mx-auto max-w-4xl w-full h-96 font-extrabold relative">
|
||||
<div class="app__circle w-52 h-52 rounded-full bg-gradient-to-bl from-yellow-300 to-yellow-800 mr-15"/>
|
||||
<div class="app__box w-96 h-44 bg-gradient-to-bl from-yellow-300 to-yellow-800 rotate-12 transform"/>
|
||||
<div class="text-6xl sm:text-8xl font-black relative -left-1">
|
||||
Moritz Ruth
|
||||
</div>
|
||||
<div class="flex mt-8 justify-items-stretch">
|
||||
<div class="w-1 bg-gradient-to-b from-yellow-300 to-yellow-500"/>
|
||||
<div class="text-2xl sm:text-4xl ml-5 sm:ml-8 space-y-2 pb-1">
|
||||
<p>I do web and print things.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fixed flex bottom-15 sm:bottom-8 space-x-5">
|
||||
<a class="app__social-link" href="https://twitter.com/moritz_ruth" aria-label="Twitter"><TwitterIcon size="1x"/></a>
|
||||
<a class="app__social-link" href="https://github.com/moritzruth" aria-label="GitHub"><GithubIcon size="1x"/></a>
|
||||
<a class="app__social-link" href="mailto:hey@m0.is" aria-label="Email"><MailIcon size="1x"/></a>
|
||||
</div>
|
||||
<div class="fixed bottom-0 right-0 p-2 sm:p-8 sm:text-gray-400 font-bold">
|
||||
<a href="/impressum" target="_blank">Impressum</a>
|
||||
<NavigationMenu/>
|
||||
<div class="overflow-x-hidden">
|
||||
<div class="bg-white text-black max-w-1200px mx-auto px-6 sm:px-10">
|
||||
<router-view/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style>
|
||||
body {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.app__circle {
|
||||
position: absolute;
|
||||
top: -10em;
|
||||
right: -15vw;
|
||||
}
|
||||
|
||||
.app__box {
|
||||
position: absolute;
|
||||
top: 25em;
|
||||
left: -25vw;
|
||||
}
|
||||
|
||||
.app__social-link {
|
||||
@apply text-black text-3xl p-4 bg-white bg-opacity-50 from-yellow-300 to-yellow-500 -sm:rounded-full flex items-center justify-center
|
||||
transition duration-200 sm:bg-opacity-0 sm:text-white sm:hover:bg-opacity-30;
|
||||
|
||||
backdrop-filter: blur(5px);
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { TwitterIcon, MailIcon, GithubIcon } from "@zhuowenli/vue-feather-icons"
|
||||
import NavigationMenu from "./components/NavigationMenu.vue"
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: { TwitterIcon, GithubIcon, MailIcon }
|
||||
components: { NavigationMenu }
|
||||
}
|
||||
</script>
|
||||
|
|
132
src/components/BlurredBlobCanvas.vue
Normal file
132
src/components/BlurredBlobCanvas.vue
Normal file
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<svg :style="{ width: size, height: size, filter: `blur(${blur}px)` }">
|
||||
<path
|
||||
v-for="(blob, index) in blobs"
|
||||
:key="index"
|
||||
:ref="blob"
|
||||
style="transition: 2000ms; transition-property: opacity, transform; transform-origin: center"
|
||||
d=""
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as blobs2 from "blobs/v2"
|
||||
import KUTE from "kute.js"
|
||||
import { ref } from "vue"
|
||||
import { getListOfIndexes } from "../getListOfIndexes.js"
|
||||
|
||||
const BLOB_OPTIONS = {
|
||||
extraPoints: 6,
|
||||
randomness: 30
|
||||
}
|
||||
|
||||
// let lastMouseEvent = null
|
||||
// let currentMouseEvent = null
|
||||
// let mouseSpeed = 0
|
||||
//
|
||||
// window.addEventListener("mousemove", event => {
|
||||
// currentMouseEvent = event
|
||||
// })
|
||||
//
|
||||
// setInterval(() => {
|
||||
// if (currentMouseEvent !== null && lastMouseEvent !== null && currentMouseEvent !== lastMouseEvent) {
|
||||
// const deltaX = currentMouseEvent.screenX - lastMouseEvent.screenX
|
||||
// const deltaY = currentMouseEvent.screenY - lastMouseEvent.screenY
|
||||
// mouseSpeed = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY))
|
||||
// } else {
|
||||
// mouseSpeed = 0
|
||||
// }
|
||||
//
|
||||
// lastMouseEvent = currentMouseEvent
|
||||
// console.log(mouseSpeed)
|
||||
// }, 100)
|
||||
|
||||
export default {
|
||||
name: "BlurredBlobCanvas",
|
||||
props: {
|
||||
colors: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
blur: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
minimumDuration: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
durationVariation: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
minimumOpacity: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
opacityVariation: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// eslint-disable-next-line unicorn/no-new-array
|
||||
blobs: getListOfIndexes(this.colors.length).map(() => ref(null))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const animate = index => {
|
||||
const blob = this.blobs[index]
|
||||
const duration = (Math.random() * this.durationVariation) + this.minimumDuration
|
||||
|
||||
if (blob.value) {
|
||||
KUTE.to(
|
||||
blob.value,
|
||||
{
|
||||
path: blobs2.svgPath(this.getRandomBlobOptions())
|
||||
},
|
||||
{
|
||||
duration,
|
||||
morphPrecision: 5,
|
||||
easing: "easingSinusoidalInOut",
|
||||
onComplete() {
|
||||
animate(index)
|
||||
}
|
||||
}
|
||||
).start()
|
||||
|
||||
this.setOpacity(blob.value)
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.blobs.forEach((blob, index) => {
|
||||
blob.value.setAttribute("d", blobs2.svgPath(this.getRandomBlobOptions()))
|
||||
|
||||
blob.value.setAttribute("fill", this.colors[index])
|
||||
this.setOpacity(blob.value)
|
||||
|
||||
animate(index)
|
||||
})
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
getRandomBlobOptions() {
|
||||
return {
|
||||
...BLOB_OPTIONS,
|
||||
seed: Math.random(),
|
||||
size: this.size
|
||||
}
|
||||
},
|
||||
setOpacity(element) {
|
||||
element.style.opacity = (Math.random() * this.opacityVariation) + this.minimumOpacity
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
55
src/components/ClickToBounce.vue
Normal file
55
src/components/ClickToBounce.vue
Normal file
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div ref="wrapper" class="select-none">
|
||||
<slot :setTrigger="setTrigger"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, watchEffect } from "vue"
|
||||
import KUTE from "kute.js"
|
||||
|
||||
export default {
|
||||
name: "ClickToBounce",
|
||||
setup() {
|
||||
const trigger = ref(null)
|
||||
const wrapper = ref(null)
|
||||
|
||||
let current = null
|
||||
|
||||
watchEffect(onInvalidate => {
|
||||
if (trigger.value !== null) {
|
||||
const listener = () => {
|
||||
current?.stop()
|
||||
|
||||
current = KUTE.to(wrapper.value, { scale: 0.9 }, {
|
||||
duration: 100,
|
||||
easing: "easingElasticInOut",
|
||||
onComplete() {
|
||||
current = KUTE.to(wrapper.value, { scale: 1 }, {
|
||||
duration: 100,
|
||||
easing: "easingElasticInOut",
|
||||
onComplete() {
|
||||
current = null
|
||||
}
|
||||
}).start()
|
||||
}
|
||||
}).start()
|
||||
}
|
||||
|
||||
trigger.value.addEventListener("click", listener, { passive: true })
|
||||
|
||||
onInvalidate(() => {
|
||||
trigger.value.removeEventListener("click", listener)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
setTrigger(element) {
|
||||
trigger.value = element
|
||||
},
|
||||
wrapper
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
104
src/components/NavigationMenu.vue
Normal file
104
src/components/NavigationMenu.vue
Normal file
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<div class="sm:hidden fixed z-101 bottom-3 right-3 rounded-full backdrop-filter backdrop-blur-20 bg-white bg-opacity-20 w-20 h-20 flex justify-center items-center shadow-lg">
|
||||
<div role="button" class="flex flex-col justify-evenly items-center h-10" @click="active = !active">
|
||||
<div class="w-10 h-2px bg-black transition duration-200 transform" :style="`transform: ${active ? 'translateY(350%)' : ''} rotate(${active ? 45 : 0}deg)`"/>
|
||||
<div class="w-10 h-2px bg-black transition duration-200 transform" :style="`transform: ${active ? 'translateY(-350%)' : ''} rotate(${active ? -45 : 0}deg)`"/>
|
||||
</div>
|
||||
</div>
|
||||
<nav
|
||||
class="fixed sm:sticky top-0 z-100 w-screen h-screen sm:h-20 backdrop-filter backdrop-blur-40 bg-white bg-opacity-40 transition duration-200"
|
||||
: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="fixed transition-all duration-500" :style="{ left: blobState.x + 'px', top: blobState.y + 'px', opacity: blobState.show ? 1 : 0 }">
|
||||
<BlurredBlobCanvas
|
||||
:colors="['#eb34cf', '#79faff']"
|
||||
:opacity-variation="0"
|
||||
:minimum-opacity="0.9"
|
||||
:minimum-duration="1000"
|
||||
:duration-variation="500"
|
||||
:blur="10"
|
||||
:size="100"
|
||||
/>
|
||||
</div>
|
||||
<router-link class="uppercase font-special relative top-1 -sm:mt-20" 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"
|
||||
>
|
||||
{{ item.label }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useWindowScroll, useWindowSize } from "@vueuse/core"
|
||||
import { computed, ref, watch, reactive } from "vue"
|
||||
import { useRoute } from "vue-router"
|
||||
import { useWindowScrollLock } from "../utils/useWindowScrollLock.js"
|
||||
import BlurredBlobCanvas from "./BlurredBlobCanvas.vue"
|
||||
|
||||
const ITEMS = [
|
||||
{
|
||||
label: "Projects",
|
||||
to: "/projects"
|
||||
},
|
||||
{
|
||||
label: "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: 20,
|
||||
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) - 20
|
||||
blobState.y = y - 20
|
||||
blobState.show = true
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
return {
|
||||
scrolled: computed(() => windowScroll.value > 0),
|
||||
blobState,
|
||||
items,
|
||||
active
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
2
src/getListOfIndexes.js
Normal file
2
src/getListOfIndexes.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
// eslint-disable-next-line unicorn/no-new-array
|
||||
export const getListOfIndexes = size => [...new Array(size).keys()]
|
11
src/main.js
11
src/main.js
|
@ -1,7 +1,14 @@
|
|||
import "virtual:windi.css"
|
||||
import routes from "virtual:generated-pages"
|
||||
import { createApp } from "vue"
|
||||
import { createRouter, createWebHistory } from "vue-router"
|
||||
import App from "./App.vue"
|
||||
|
||||
createApp(App).mount("#app")
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
if (window.location.path !== "/") history.replaceState({}, "", "/")
|
||||
createApp(App)
|
||||
.use(router)
|
||||
.mount("#app")
|
||||
|
|
11
src/pages/contact.vue
Normal file
11
src/pages/contact.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<div>
|
||||
contact
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ContactPage"
|
||||
}
|
||||
</script>
|
113
src/pages/index.vue
Normal file
113
src/pages/index.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<main class="flex -md:flex-col justify-between items-start md:items-center min-h-80vh">
|
||||
<div class="font-bold relative -md:pt-20vh -md:pb-20 transform skew-x-7 -skew-y-7 rotate-7">
|
||||
<div class="_fade-2">
|
||||
<div class="_pattern transform rotate-179 absolute w-full h-40 -left-4 md:-left-10 top-20vh -md:-mt-10 md:-top-10 opacity-3 md:opacity-5"/>
|
||||
</div>
|
||||
<div class="_slide">
|
||||
<ClickToBounce v-slot="{ setTrigger }" class="text-6xl md:text-7xl -md:-mt-2 mb-30 font-special" style="--delay: 0">
|
||||
Hey <span :ref="setTrigger" class="_clap">👋</span>
|
||||
</ClickToBounce>
|
||||
</div>
|
||||
<div class="font-special text-3xl sm:text-5xl mb-5 _slide" style="--delay: 200">
|
||||
I’m<br>
|
||||
<span class="text-1.1em text-transparent bg-clip-text bg-gradient-to-b from-blue-500 to-blue-900">Moritz Ruth</span>
|
||||
</div>
|
||||
<div class="text-gray-800 text-2xl sm:text-3xl">
|
||||
<ul class="_list">
|
||||
<li class="_slide" style="--delay: 400">
|
||||
software developer
|
||||
</li>
|
||||
<li class="_slide" style="--delay: 600">
|
||||
graphic designer
|
||||
</li>
|
||||
<li class="_slide" style="--delay: 800">
|
||||
typography enthusiast
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="self-center">
|
||||
<BlurredBlobCanvas
|
||||
class="_fade-1"
|
||||
:colors="['#6577fc', '#eb34cf', '#6577fc', '#eb34cf']"
|
||||
:size="300"
|
||||
:blur="30"
|
||||
:minimum-duration="2000"
|
||||
:duration-variation="500"
|
||||
:minimum-opacity="0.2"
|
||||
:opacity-variation="0.4"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<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");
|
||||
}
|
||||
|
||||
._list > li::before {
|
||||
@apply text-blue-900;
|
||||
content: "*";
|
||||
}
|
||||
|
||||
/* created using emojicursor.app */
|
||||
._clap {
|
||||
cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='50' height='90' viewport='0 0 100 100' style='fill:black;font-size:50px;'><text y='50%'>✋\
|
||||
</text></svg>") 16 0,auto;
|
||||
}
|
||||
|
||||
._fade-1 {
|
||||
animation: fade 2s ease-in both;
|
||||
}
|
||||
|
||||
._fade-2 {
|
||||
animation: fade 2s 2s 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 + 1.5s);
|
||||
}
|
||||
}
|
||||
|
||||
@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 BlurredBlobCanvas from "../components/BlurredBlobCanvas.vue"
|
||||
import ClickToBounce from "../components/ClickToBounce.vue"
|
||||
|
||||
export default {
|
||||
name: "IndexPage",
|
||||
components: { ClickToBounce, BlurredBlobCanvas }
|
||||
}
|
||||
</script>
|
11
src/pages/projects.vue
Normal file
11
src/pages/projects.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<div>
|
||||
projects
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ProjectsPage"
|
||||
}
|
||||
</script>
|
22
src/utils/useWindowScrollLock.js
Normal file
22
src/utils/useWindowScrollLock.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
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()
|
||||
})
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import vuePlugin from "@vitejs/plugin-vue"
|
||||
import windicssPlugin from "vite-plugin-windicss"
|
||||
import pagesPlugin from "vite-plugin-pages"
|
||||
|
||||
/**
|
||||
* https://vitejs.dev/config/
|
||||
|
@ -8,6 +9,7 @@ import windicssPlugin from "vite-plugin-windicss"
|
|||
export default {
|
||||
plugins: [
|
||||
vuePlugin(),
|
||||
pagesPlugin(),
|
||||
windicssPlugin()
|
||||
]
|
||||
}
|
||||
|
|
|
@ -3,7 +3,15 @@ import { defineConfig } from "vite-plugin-windicss"
|
|||
export default defineConfig({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ["Inter var", "sans-serif"]
|
||||
sans: ["Plus Jakarta Sans", "sans-serif"],
|
||||
special: ["Syne", "monospace"]
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
blue: {
|
||||
900: "#0041ff"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue