initial-commit
This commit is contained in:
commit
c679e0b967
42 changed files with 6275 additions and 0 deletions
0
.env
Normal file
0
.env
Normal file
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Cypress
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Vitest
|
||||||
|
__screenshots__/
|
||||||
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
24
|
||||||
38
README.md
Normal file
38
README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# nicwands.com
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Recommended Browser Setup
|
||||||
|
|
||||||
|
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||||
|
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||||
|
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||||
|
- Firefox:
|
||||||
|
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||||
|
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
13
index.html
Normal file
13
index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
jsconfig.json
Normal file
8
jsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
41
package copy.json
Normal file
41
package copy.json
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"name": "nicwands.com",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite-ssg build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"format": "prettier --write src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fuzzco/font-loader": "^1.0.2",
|
||||||
|
"@unhead/vue": "^2.0.19",
|
||||||
|
"@vueuse/core": "^14.1.0",
|
||||||
|
"gsap": "^3.13.0",
|
||||||
|
"lenis": "^1.3.15",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"sass": "^1.94.2",
|
||||||
|
"sass-embedded": "^1.93.3",
|
||||||
|
"tempus": "^1.0.0-dev.17",
|
||||||
|
"unplugin-vue-components": "^30.0.0",
|
||||||
|
"vite-ssg": "^28.2.2",
|
||||||
|
"vue": "^3.5.22",
|
||||||
|
"vue-router": "^4.6.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"prettier": "3.6.2",
|
||||||
|
"vite": "^7.1.11",
|
||||||
|
"vite-plugin-vue-devtools": "^8.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
4482
package-lock.json
generated
Normal file
4482
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
41
package.json
Normal file
41
package.json
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"name": "nicwands.com",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite-ssg build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"format": "prettier --write src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fuzzco/font-loader": "^1.0.2",
|
||||||
|
"@unhead/vue": "^2.0.19",
|
||||||
|
"@vueuse/core": "^14.1.0",
|
||||||
|
"gsap": "^3.13.0",
|
||||||
|
"lenis": "^1.3.15",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"sass": "^1.94.2",
|
||||||
|
"sass-embedded": "^1.93.3",
|
||||||
|
"tempus": "^1.0.0-dev.17",
|
||||||
|
"unplugin-vue-components": "^30.0.0",
|
||||||
|
"vite-ssg": "^28.2.2",
|
||||||
|
"vue": "^3.5.22",
|
||||||
|
"vue-router": "^4.6.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"prettier": "3.6.2",
|
||||||
|
"vite": "^7.1.11",
|
||||||
|
"vite-plugin-vue-devtools": "^8.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.png
Normal file
BIN
public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
public/fonts/OfficeTimesRound-Regular.woff
Executable file
BIN
public/fonts/OfficeTimesRound-Regular.woff
Executable file
Binary file not shown.
BIN
public/fonts/OfficeTimesRound-Regular.woff2
Executable file
BIN
public/fonts/OfficeTimesRound-Regular.woff2
Executable file
Binary file not shown.
59
src/App.vue
Normal file
59
src/App.vue
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<lenis root :options="{ duration: 1 }">
|
||||||
|
<div :class="classes" :style="styles">
|
||||||
|
<suspense>
|
||||||
|
<layout />
|
||||||
|
</suspense>
|
||||||
|
</div>
|
||||||
|
</lenis>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import loadFonts from '@fuzzco/font-loader'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { height } = useWindowSize()
|
||||||
|
|
||||||
|
const classes = computed(() => [
|
||||||
|
'container',
|
||||||
|
{ 'fonts-ready': !fontsLoading.value },
|
||||||
|
'theme-light',
|
||||||
|
])
|
||||||
|
|
||||||
|
const fontsLoading = ref(true)
|
||||||
|
onMounted(async () => {
|
||||||
|
// Load fonts
|
||||||
|
loadFonts([
|
||||||
|
{
|
||||||
|
name: 'Office Times',
|
||||||
|
weights: [400],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.then(() => {
|
||||||
|
fontsLoading.value = false
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
fontsLoading.value = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const styles = computed(() => ({
|
||||||
|
'--vh': height.value ? height.value / 100 + 'px' : '100vh',
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.container {
|
||||||
|
min-height: calc(100 * var(--vh));
|
||||||
|
max-width: 100vw;
|
||||||
|
overflow-x: clip;
|
||||||
|
background: var(--theme-bg);
|
||||||
|
color: var(--theme-fg);
|
||||||
|
transition: opacity 1000ms;
|
||||||
|
|
||||||
|
&:not(.fonts-ready) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
20
src/components.d.ts
vendored
Normal file
20
src/components.d.ts
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
// oxlint-disable
|
||||||
|
// ------
|
||||||
|
// Generated by unplugin-vue-components
|
||||||
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
|
||||||
|
export {}
|
||||||
|
|
||||||
|
/* prettier-ignore */
|
||||||
|
declare module 'vue' {
|
||||||
|
export interface GlobalComponents {
|
||||||
|
Layout: typeof import('./components/Layout.vue')['default']
|
||||||
|
Lenis: typeof import('./components/Lenis.vue')['default']
|
||||||
|
LoadingSpinner: typeof import('./components/LoadingSpinner.vue')['default']
|
||||||
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/components/Layout.vue
Normal file
20
src/components/Layout.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<div class="layout">
|
||||||
|
<h1>nicwands.com</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useSeoMeta } from '@unhead/vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
// SEO
|
||||||
|
// const seo = computed(() => content.value?.seo?.[0])
|
||||||
|
// useSeoMeta({
|
||||||
|
// title: seo.value?.title || '',
|
||||||
|
// description: seo.value.description || '',
|
||||||
|
// ogImage: {
|
||||||
|
// url: seo.value.og_image?.filename || '',
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
</script>
|
||||||
69
src/components/Lenis.vue
Normal file
69
src/components/Lenis.vue
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="!root" class="lenis" ref="wrapper">
|
||||||
|
<div ref="content">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<slot v-else />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Lenis from 'lenis'
|
||||||
|
import Tempus from 'tempus'
|
||||||
|
import { onBeforeUnmount, onMounted, provide, ref, shallowRef } from 'vue'
|
||||||
|
|
||||||
|
const { root, instance, options } = defineProps({
|
||||||
|
root: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
instance: { type: String },
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
duration: 1.2,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const lenis = shallowRef()
|
||||||
|
const wrapper = ref()
|
||||||
|
const content = ref()
|
||||||
|
const removeRaf = ref()
|
||||||
|
|
||||||
|
// Provide instance for useLenis composable
|
||||||
|
const instanceKey = `lenis${instance ? `-${instance}` : ''}`
|
||||||
|
provide(instanceKey, lenis)
|
||||||
|
|
||||||
|
// Initialize with Tempus
|
||||||
|
const initLenis = () => {
|
||||||
|
if (lenis.value) return
|
||||||
|
|
||||||
|
lenis.value = new Lenis({
|
||||||
|
...options,
|
||||||
|
...(!root
|
||||||
|
? {
|
||||||
|
wrapper: wrapper.value,
|
||||||
|
content: content.value,
|
||||||
|
eventsTarget: wrapper.value,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
})
|
||||||
|
|
||||||
|
removeRaf.value = Tempus.add((time) => {
|
||||||
|
lenis.value.raf(time)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!lenis.value) {
|
||||||
|
initLenis()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Kill lenis before unmount
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
lenis.value?.destroy()
|
||||||
|
removeRaf.value?.()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
32
src/components/LoadingSpinner.vue
Normal file
32
src/components/LoadingSpinner.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
class="loading-spinner"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
style="margin: auto; display: block"
|
||||||
|
width="18px"
|
||||||
|
height="18px"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
preserveAspectRatio="xMidYMid"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
cx="50"
|
||||||
|
cy="50"
|
||||||
|
r="40"
|
||||||
|
stroke-width="4"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-dasharray="62 62"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
>
|
||||||
|
<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
type="rotate"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
dur="1s"
|
||||||
|
keyTimes="0;1"
|
||||||
|
values="0 50 50;360 50 50"
|
||||||
|
></animateTransform>
|
||||||
|
</circle>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
14
src/composables/useLenis.js
Normal file
14
src/composables/useLenis.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { inject, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
|
export default (callback = () => {}, instanceId) => {
|
||||||
|
const instanceKey = `lenis${instanceId ? `-${instanceId}` : ''}`
|
||||||
|
const lenis = inject(instanceKey)
|
||||||
|
|
||||||
|
if (lenis.value) {
|
||||||
|
lenis.value.on('scroll', callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => lenis.value?.off('scroll', callback))
|
||||||
|
|
||||||
|
return lenis
|
||||||
|
}
|
||||||
29
src/composables/useRelativeSize.js
Normal file
29
src/composables/useRelativeSize.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
import { viewports } from '@/libs/theme'
|
||||||
|
|
||||||
|
const { width: wWidth, height: wHeight } = useWindowSize()
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
// Desktop
|
||||||
|
const dvw = (pixels) => {
|
||||||
|
return (pixels / viewports.desktop.width) * wWidth.value
|
||||||
|
}
|
||||||
|
const dvh = (pixels) => {
|
||||||
|
return (pixels / viewports.desktop.height) * wHeight.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile
|
||||||
|
const mvw = (pixels) => {
|
||||||
|
return (pixels / viewports.mobile.width) * wWidth.value
|
||||||
|
}
|
||||||
|
const mvh = (pixels) => {
|
||||||
|
return (pixels / viewports.mobile.height) * wHeight.value
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dvw,
|
||||||
|
dvh,
|
||||||
|
mvw,
|
||||||
|
mvh,
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/composables/useScrollProgress.js
Normal file
46
src/composables/useScrollProgress.js
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import {
|
||||||
|
useElementBounding,
|
||||||
|
useIntersectionObserver,
|
||||||
|
useWindowSize,
|
||||||
|
} from '@vueuse/core'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { mapRange, clamp } from '@/libs/math'
|
||||||
|
import useLenis from '@/composables/useLenis'
|
||||||
|
|
||||||
|
const { height: wHeight } = useWindowSize()
|
||||||
|
|
||||||
|
export const useScrollProgress = (el, callback, entry = 0.5, exit = 0.5) => {
|
||||||
|
const isActive = ref(true)
|
||||||
|
const smoothProgress = ref(0)
|
||||||
|
|
||||||
|
const { height, top } = useElementBounding(el)
|
||||||
|
|
||||||
|
const isIntersected = ref(false)
|
||||||
|
const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {
|
||||||
|
isIntersected.value = isIntersecting
|
||||||
|
})
|
||||||
|
|
||||||
|
useLenis(({ scroll }) => {
|
||||||
|
if (!isActive.value) return
|
||||||
|
if (!height.value || !wHeight.value) return
|
||||||
|
if (!isIntersected.value) return
|
||||||
|
|
||||||
|
const pageTop = scroll + top.value
|
||||||
|
|
||||||
|
const start = pageTop - wHeight.value * entry
|
||||||
|
const end = pageTop + height.value - wHeight.value * exit
|
||||||
|
|
||||||
|
let rawProgress = mapRange(start, end, scroll, 0, 1)
|
||||||
|
rawProgress = clamp(0, rawProgress, 1)
|
||||||
|
|
||||||
|
smoothProgress.value += (rawProgress - smoothProgress.value) * 0.1
|
||||||
|
callback?.(smoothProgress.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const destroy = () => {
|
||||||
|
isActive.value = false
|
||||||
|
stop?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
return { destroy }
|
||||||
|
}
|
||||||
89
src/composables/useSmoothMouse.js
Normal file
89
src/composables/useSmoothMouse.js
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
import gsap from 'gsap'
|
||||||
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
import { useWindowSize, useEventListener } from '@vueuse/core'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared global raw mouse state (only set up once)
|
||||||
|
*/
|
||||||
|
const rawMouse = {
|
||||||
|
x: ref(0),
|
||||||
|
y: ref(0),
|
||||||
|
initialized: false,
|
||||||
|
cleanup: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const useGlobalMouseListener = () => {
|
||||||
|
if (!rawMouse.initialized && !import.meta.env.SSR) {
|
||||||
|
rawMouse.initialized = true
|
||||||
|
rawMouse.cleanup = useEventListener(window, 'mousemove', (e) => {
|
||||||
|
rawMouse.x.value = e.clientX
|
||||||
|
rawMouse.y.value = e.clientY
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable for smoothed mouse position with customizable smoothing and normalization.
|
||||||
|
*/
|
||||||
|
export default (options) => {
|
||||||
|
const smoothFactor = options?.smoothFactor ?? 0.1
|
||||||
|
const normalize = options?.normalize ?? false
|
||||||
|
const callback = options?.onUpdate
|
||||||
|
|
||||||
|
const { width: wWidth, height: wHeight } = useWindowSize()
|
||||||
|
|
||||||
|
const sx = ref(0)
|
||||||
|
const sy = ref(0)
|
||||||
|
|
||||||
|
useGlobalMouseListener()
|
||||||
|
|
||||||
|
const getTargetX = () =>
|
||||||
|
normalize ? rawMouse.x.value / wWidth.value : rawMouse.x.value
|
||||||
|
const getTargetY = () =>
|
||||||
|
normalize ? rawMouse.y.value / wHeight.value : rawMouse.y.value
|
||||||
|
|
||||||
|
let tween
|
||||||
|
onMounted(() => {
|
||||||
|
if (tween) tween.kill
|
||||||
|
|
||||||
|
// Start smoothing tween
|
||||||
|
tween = gsap.to(
|
||||||
|
{ x: sx.value, y: sy.value },
|
||||||
|
{
|
||||||
|
duration: 1,
|
||||||
|
ease: 'linear',
|
||||||
|
repeat: -1,
|
||||||
|
onUpdate: () => {
|
||||||
|
const newX = gsap.utils.interpolate(
|
||||||
|
sx.value,
|
||||||
|
getTargetX(),
|
||||||
|
smoothFactor,
|
||||||
|
)
|
||||||
|
const newY = gsap.utils.interpolate(
|
||||||
|
sy.value,
|
||||||
|
getTargetY(),
|
||||||
|
smoothFactor,
|
||||||
|
)
|
||||||
|
|
||||||
|
sx.value = newX
|
||||||
|
sy.value = newY
|
||||||
|
|
||||||
|
callback?.({ sx: sx.value, sy: sy.value })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
tween?.kill()
|
||||||
|
rawMouse.cleanup()
|
||||||
|
rawMouse.initialized = false
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
sx,
|
||||||
|
sy,
|
||||||
|
rawX: rawMouse.x,
|
||||||
|
rawY: rawMouse.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/composables/useSubmitForm.js
Normal file
34
src/composables/useSubmitForm.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export default (url) => {
|
||||||
|
const loading = ref(false)
|
||||||
|
const success = ref(false)
|
||||||
|
const error = ref('')
|
||||||
|
|
||||||
|
const submit = async (headers = {}, body = {}) => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: new URLSearchParams(body),
|
||||||
|
})
|
||||||
|
|
||||||
|
success.value = true
|
||||||
|
error.value = ''
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
error.value = err.toString().split('Error: ')[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
success,
|
||||||
|
error,
|
||||||
|
submit,
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/composables/useTimelineTransition.js
Normal file
37
src/composables/useTimelineTransition.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import gsap from 'gsap'
|
||||||
|
|
||||||
|
export default (timelineSetup = () => {}) => {
|
||||||
|
const timelines = new WeakMap()
|
||||||
|
const getTimeline = (el) => {
|
||||||
|
if (!timelines.has(el)) {
|
||||||
|
const tl = gsap.timeline({ paused: true })
|
||||||
|
|
||||||
|
timelines.set(el, tl)
|
||||||
|
}
|
||||||
|
return timelines.get(el)
|
||||||
|
}
|
||||||
|
const onEnter = (el, done) => {
|
||||||
|
const tl = getTimeline(el)
|
||||||
|
|
||||||
|
tl.clear()
|
||||||
|
timelineSetup?.(tl, el)
|
||||||
|
|
||||||
|
tl.eventCallback('onReverseComplete', null)
|
||||||
|
tl.eventCallback('onComplete', done)
|
||||||
|
tl.play()
|
||||||
|
}
|
||||||
|
const onLeave = (el, done) => {
|
||||||
|
el.style.pointerEvents = 'none'
|
||||||
|
|
||||||
|
const tl = getTimeline(el)
|
||||||
|
|
||||||
|
tl.eventCallback('onComplete', null)
|
||||||
|
tl.eventCallback('onReverseComplete', done)
|
||||||
|
tl.reverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
onEnter,
|
||||||
|
onLeave,
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/libs/math.js
Normal file
22
src/libs/math.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
export function clamp(min, input, max) {
|
||||||
|
return Math.max(min, Math.min(input, max))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapRange(in_min, in_max, input, out_min, out_max) {
|
||||||
|
return (
|
||||||
|
((input - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lerp(start, end, amt) {
|
||||||
|
return (1 - amt) * start + amt * end
|
||||||
|
}
|
||||||
|
|
||||||
|
export function truncate(value, decimals) {
|
||||||
|
return parseFloat(value.toFixed(decimals))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wrapValue(value, lowBound, highBound) {
|
||||||
|
const range = highBound - lowBound
|
||||||
|
return ((((value - lowBound) % range) + range) % range) + lowBound
|
||||||
|
}
|
||||||
220
src/libs/sass-utils/index.js
Normal file
220
src/libs/sass-utils/index.js
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
// https://github.com/dawaltconley/sass-cast/blob/main/index.js
|
||||||
|
|
||||||
|
import * as sass from 'sass-embedded'
|
||||||
|
import { isQuoted, unquoteString, parseString, getAttr } from './utils'
|
||||||
|
import { List, OrderedMap } from 'immutable'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts any Javascript object to an equivalent Sass value.
|
||||||
|
*
|
||||||
|
* This method is recursive and will convert the values of any array or object,
|
||||||
|
* as well as the array or object itself.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { toSass } = require('sass-cast');
|
||||||
|
*
|
||||||
|
* const string = toSass('a simple string');
|
||||||
|
* // quoted SassString => '"a simple string"'
|
||||||
|
*
|
||||||
|
* const map = toSass({
|
||||||
|
* key: 'value',
|
||||||
|
* nested: {
|
||||||
|
* 'complex//:key': [ null, 4 ],
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* // SassMap => '("key": "value", "nested": ("complex//:key": (null, 4)))'
|
||||||
|
*
|
||||||
|
* @param {*} value - the value to be converted
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {boolean} [options.parseUnquotedStrings=false] - whether to parse unquoted strings for colors or numbers with units
|
||||||
|
* @param {boolean|*[]} [options.resolveFunctions=false] - if true, resolve functions and attempt to cast their return values. if an array, pass as arguments when resolving
|
||||||
|
* @param {boolean} [options.quotes=true] - controls whether returned SassStrings are quoted. input strings that contain quotes will always return a quoted SassString even if this flag is false.
|
||||||
|
* @return {Value} - a {@link https://sass-lang.com/documentation/js-api/classes/Value Sass value}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const toSass = (value, options = {}) => {
|
||||||
|
let {
|
||||||
|
parseUnquotedStrings = false,
|
||||||
|
resolveFunctions = false,
|
||||||
|
quotes = true,
|
||||||
|
} = options
|
||||||
|
if (value instanceof sass.Value) {
|
||||||
|
return value
|
||||||
|
} else if (value === null || value === undefined) {
|
||||||
|
return sass.sassNull
|
||||||
|
} else if (typeof value === 'boolean') {
|
||||||
|
return value ? sass.sassTrue : sass.sassFalse
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
return new sass.SassNumber(value)
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
const valueIsQuoted = isQuoted(value)
|
||||||
|
if (parseUnquotedStrings && !valueIsQuoted) {
|
||||||
|
let parsed = parseString(value)
|
||||||
|
if (
|
||||||
|
parsed instanceof sass.SassColor ||
|
||||||
|
parsed instanceof sass.SassNumber
|
||||||
|
)
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
return new sass.SassString(value, {
|
||||||
|
quotes: valueIsQuoted || quotes,
|
||||||
|
})
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
let sassList = value.map((value) => toSass(value, options))
|
||||||
|
return new sass.SassList(sassList)
|
||||||
|
} else {
|
||||||
|
let sassMap = OrderedMap(value).mapEntries(([key, value]) => [
|
||||||
|
new sass.SassString(key, { quotes: true }),
|
||||||
|
toSass(value, options),
|
||||||
|
])
|
||||||
|
return new sass.SassMap(sassMap)
|
||||||
|
}
|
||||||
|
} else if (resolveFunctions && typeof value === 'function') {
|
||||||
|
const args = Array.isArray(resolveFunctions) ? resolveFunctions : []
|
||||||
|
return toSass(value(...args), options)
|
||||||
|
}
|
||||||
|
return sass.sassNull
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorProperties = [
|
||||||
|
'red',
|
||||||
|
'green',
|
||||||
|
'blue',
|
||||||
|
'hue',
|
||||||
|
'lightness',
|
||||||
|
'saturation',
|
||||||
|
'whiteness',
|
||||||
|
'blackness',
|
||||||
|
'alpha',
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts Sass values to their Javascript equivalents.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { fromSass, toSass } = require('sass-cast');
|
||||||
|
*
|
||||||
|
* const sassString = toSass('a sass string object');
|
||||||
|
* const string = fromSass(sassString);
|
||||||
|
* // 'a sass string object'
|
||||||
|
*
|
||||||
|
* @param {Value} object - a {@link https://sass-lang.com/documentation/js-api/classes/Value Sass value}
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {boolean} [options.preserveUnits=false] - By default, only the values of numbers are returned, not their units. If true, `fromSass` will return numbers as a two-item Array, i.e. [ value, unit ]
|
||||||
|
* @param {boolean} [options.rgbColors=false] - By default, colors are returned as strings. If true, `fromSass` will return colors as an object with `r`, `g`, `b`, and `a`, properties.
|
||||||
|
* @param {boolean} [options.preserveQuotes=false] - By default, quoted Sass strings return their inner text as a string. If true, `fromSass` will preserve the quotes in the returned string value.
|
||||||
|
* @return {*} - a Javascript value corresponding to the Sass input
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const fromSass = (object, options = {}) => {
|
||||||
|
let {
|
||||||
|
preserveUnits = false,
|
||||||
|
rgbColors = false,
|
||||||
|
preserveQuotes = false,
|
||||||
|
} = options
|
||||||
|
if (object instanceof sass.SassBoolean) {
|
||||||
|
return object.value
|
||||||
|
} else if (object instanceof sass.SassNumber) {
|
||||||
|
if (preserveUnits) {
|
||||||
|
return [
|
||||||
|
object.value,
|
||||||
|
object.numeratorUnits.toArray(),
|
||||||
|
object.denominatorUnits.toArray(),
|
||||||
|
]
|
||||||
|
} else if (object.numeratorUnits.size || object.denominatorUnits.size) {
|
||||||
|
return object.toString()
|
||||||
|
}
|
||||||
|
return object.value
|
||||||
|
} else if (object instanceof sass.SassColor) {
|
||||||
|
if (rgbColors) {
|
||||||
|
return colorProperties.reduce((colorObj, p) => {
|
||||||
|
colorObj[p] = object[p]
|
||||||
|
return colorObj
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
return object.toString()
|
||||||
|
} else if (object instanceof sass.SassString) {
|
||||||
|
return preserveQuotes ? object.text : unquoteString(object.text)
|
||||||
|
} else if (object instanceof sass.SassList || List.isList(object)) {
|
||||||
|
let list = []
|
||||||
|
for (
|
||||||
|
let i = 0, value = object.get(i);
|
||||||
|
value !== undefined;
|
||||||
|
i++, value = object.get(i)
|
||||||
|
) {
|
||||||
|
list.push(fromSass(value, options))
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
} else if (object instanceof sass.SassMap) {
|
||||||
|
return object.contents
|
||||||
|
.mapEntries(([k, v]) => [k.text, fromSass(v, options)])
|
||||||
|
.toObject()
|
||||||
|
} else {
|
||||||
|
return object.realNull
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object defining Sass utility functions.
|
||||||
|
*
|
||||||
|
* @example <caption>Pass to sass using the JS API</caption>
|
||||||
|
* const { sassFunctions } = require('sass-cast');
|
||||||
|
* const sass = require('sass');
|
||||||
|
*
|
||||||
|
* sass.compile('main.scss', { functions: sassFunctions });
|
||||||
|
*/
|
||||||
|
export const sassFunctions = {
|
||||||
|
/**
|
||||||
|
* Sass function for importing data from Javascript or JSON files.
|
||||||
|
* Calls the CommonJS `require` function under the hood.
|
||||||
|
*
|
||||||
|
* #### Examples
|
||||||
|
*
|
||||||
|
* ```scss
|
||||||
|
* // import config info from tailwindcss
|
||||||
|
* $tw: require('./tailwind.config.js', $parseUnquotedStrings: true);
|
||||||
|
* $tw-colors: map.get($tw, theme, extend, colors);
|
||||||
|
* ```
|
||||||
|
* @name require
|
||||||
|
* @memberof sassFunctions
|
||||||
|
* @param {SassString} $module - Path to the file or module. Relative paths are relative to the Node process running Sass compilation.
|
||||||
|
* @param {SassList} [$properties=()] - List of properties, if you only want to parse part of the module data.
|
||||||
|
* @param {SassBoolean} [$parseUnquotedStrings=false] - Passed as an option to {@link #tosass toSass}.
|
||||||
|
* @param {SassBoolean} [$resolveFunctions=false] - Passed as an option to {@link #tosass toSass}.
|
||||||
|
* @param {SassBoolean} [$quotes=true] - Passed as an option to {@link #tosass toSass}.
|
||||||
|
* @return {Value} - a {@link https://sass-lang.com/documentation/js-api/classes/Value Sass value}
|
||||||
|
*/
|
||||||
|
'require($module, $properties: (), $parseUnquotedStrings: false, $resolveFunctions: false, $quotes: true)':
|
||||||
|
(args) => {
|
||||||
|
const moduleName = args[0].assertString('module').text
|
||||||
|
const properties = args[1].realNull && fromSass(args[1].asList)
|
||||||
|
const parseUnquotedStrings = args[2].isTruthy
|
||||||
|
const resolveFunctions = args[3].isTruthy
|
||||||
|
const quotes = args[4].isTruthy
|
||||||
|
const options = {
|
||||||
|
parseUnquotedStrings,
|
||||||
|
resolveFunctions,
|
||||||
|
quotes,
|
||||||
|
}
|
||||||
|
const convert = (data) =>
|
||||||
|
toSass(properties ? getAttr(data, properties) : data, options)
|
||||||
|
|
||||||
|
let mod,
|
||||||
|
paths = [moduleName, `${process.cwd()}/${moduleName}`]
|
||||||
|
for (let path of paths) {
|
||||||
|
try {
|
||||||
|
mod = require(path)
|
||||||
|
break
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== 'MODULE_NOT_FOUND') throw e
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mod) throw new Error(`Couldn't find module: ${moduleName}`)
|
||||||
|
|
||||||
|
if (resolveFunctions && typeof mod === 'function') mod = mod()
|
||||||
|
if (mod instanceof Promise) return mod.then(convert)
|
||||||
|
return convert(mod)
|
||||||
|
},
|
||||||
|
}
|
||||||
108
src/libs/sass-utils/utils.js
Normal file
108
src/libs/sass-utils/utils.js
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
// https://github.com/dawaltconley/sass-cast/blob/main/utils.js
|
||||||
|
|
||||||
|
import * as sass from 'sass-embedded'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if string is quoted
|
||||||
|
* @private
|
||||||
|
* @param {string} str
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export const isQuoted = (str) => /^['"].*['"]$/.test(str)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Surrounds a string with quotes
|
||||||
|
* @private
|
||||||
|
* @param {string} str
|
||||||
|
* @param {string} q - quotes, double or single
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export const quoteString = (str, q) => {
|
||||||
|
if (!q) return str
|
||||||
|
if (isQuoted(str)) {
|
||||||
|
q = str[0]
|
||||||
|
str = str.slice(1, -1)
|
||||||
|
}
|
||||||
|
let r = new RegExp(q, 'g')
|
||||||
|
return q + str.replace(r, '\\' + q) + q
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unquotes a string
|
||||||
|
* @private
|
||||||
|
* @param {string} str
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export const unquoteString = (str) => (isQuoted(str) ? str.slice(1, -1) : str)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a string as a Sass object
|
||||||
|
* cribbed from davidkpiano/sassport
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} str
|
||||||
|
* @return {Value}
|
||||||
|
*/
|
||||||
|
export const parseString = (str) => {
|
||||||
|
let result
|
||||||
|
|
||||||
|
try {
|
||||||
|
sass.compileString(`$_: ___(${str});`, {
|
||||||
|
functions: {
|
||||||
|
'___($value)': (args) => {
|
||||||
|
result = args[0]
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a string as a legacy Sass object
|
||||||
|
* cribbed from davidkpiano/sassport
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} str
|
||||||
|
* @return {LegacyObject}
|
||||||
|
*/
|
||||||
|
export const parseStringLegacy = (str) => {
|
||||||
|
let result
|
||||||
|
|
||||||
|
try {
|
||||||
|
sass.renderSync({
|
||||||
|
data: `$_: ___((${str}));`,
|
||||||
|
functions: {
|
||||||
|
'___($value)': (value) => {
|
||||||
|
result = value
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to handle 'toString()' methods with legacy API.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {LegacyObject} obj
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export const legacyToString = (obj) => (obj.dartValue || obj).toString()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a value from an object and a list of keys.
|
||||||
|
* @private
|
||||||
|
* @param {Object|Array} obj
|
||||||
|
* @param {*[]} attrs
|
||||||
|
*/
|
||||||
|
export const getAttr = (obj, attrs) => attrs.reduce((o, attr) => o[attr], obj)
|
||||||
43
src/libs/theme.js
Normal file
43
src/libs/theme.js
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
const colors = {
|
||||||
|
black: '#000000',
|
||||||
|
white: '#d9d9d9',
|
||||||
|
pink: '#DAC3C3',
|
||||||
|
green: '#D8E3D3',
|
||||||
|
cream: '#F2F1F1',
|
||||||
|
grey: '#2B2B2B',
|
||||||
|
}
|
||||||
|
|
||||||
|
const themes = {
|
||||||
|
light: {
|
||||||
|
bg: colors.white,
|
||||||
|
fg: colors.black,
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
bg: colors.black,
|
||||||
|
fg: colors.white,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const breakpoints = {
|
||||||
|
mobile: 800,
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewports = {
|
||||||
|
mobile: {
|
||||||
|
width: 440,
|
||||||
|
height: 956,
|
||||||
|
},
|
||||||
|
desktop: {
|
||||||
|
width: 1728,
|
||||||
|
height: 1117,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export { colors, themes, breakpoints, viewports }
|
||||||
|
|
||||||
|
export default {
|
||||||
|
colors,
|
||||||
|
themes,
|
||||||
|
breakpoints,
|
||||||
|
viewports,
|
||||||
|
}
|
||||||
7
src/main.js
Normal file
7
src/main.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import './styles/main.scss'
|
||||||
|
import App from './App.vue'
|
||||||
|
import { ViteSSG } from 'vite-ssg/single-page'
|
||||||
|
|
||||||
|
export const createApp = ViteSSG(App, ({ app }) => {
|
||||||
|
// Plugins
|
||||||
|
})
|
||||||
7
src/styles/_colors.scss
Normal file
7
src/styles/_colors.scss
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
@use 'sass:color';
|
||||||
|
|
||||||
|
:root {
|
||||||
|
@each $name, $color in getColors() {
|
||||||
|
--#{$name}: #{$color};
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/styles/_easings.scss
Normal file
22
src/styles/_easings.scss
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
:root {
|
||||||
|
--ease-in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
|
||||||
|
--ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
||||||
|
--ease-in-quart: cubic-bezier(0.895, 0.03, 0.685, 0.22);
|
||||||
|
--ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
||||||
|
--ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035);
|
||||||
|
--ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.335);
|
||||||
|
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||||
|
--ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||||
|
--ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
|
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
|
--ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||||
|
--ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
|
||||||
|
--ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||||
|
--ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
|
||||||
|
--ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
|
||||||
|
--ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
|
||||||
|
--ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
|
||||||
|
|
||||||
|
--ease-custom: cubic-bezier(0.315, 0.365, 0.23, 0.985);
|
||||||
|
}
|
||||||
31
src/styles/_font-style.scss
Normal file
31
src/styles/_font-style.scss
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
@use 'functions' as *;
|
||||||
|
|
||||||
|
@mixin size-font($ds, $ms) {
|
||||||
|
font-size: mobile-vw($ms);
|
||||||
|
|
||||||
|
&.vh {
|
||||||
|
font-size: mobile-vh($ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
font-size: desktop-vw($ds);
|
||||||
|
|
||||||
|
&.vh {
|
||||||
|
font-size: desktop-vh($ds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin h1 {
|
||||||
|
font-family: var(--font-times);
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
@include size-font(42px, 27px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin p {
|
||||||
|
font-family: var(--font-times);
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
@include size-font(25px, 18px);
|
||||||
|
}
|
||||||
26
src/styles/_fonts.scss
Normal file
26
src/styles/_fonts.scss
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
Font Weights:
|
||||||
|
100 - Thin
|
||||||
|
200 - Extra Light (Ultra Light)
|
||||||
|
300 - Light
|
||||||
|
400 - Normal
|
||||||
|
500 - Medium
|
||||||
|
600 - Semi Bold (Demi Bold)
|
||||||
|
700 - Bold
|
||||||
|
800 - Extra Bold (Ultra Bold)
|
||||||
|
900 - Black (Heavy)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* OFFICE TIMES */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Office Times';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url('/fonts/OfficeTimesRound-Regular.woff2') format('woff2'),
|
||||||
|
url('/fonts/OfficeTimesRound-Regular.woff') format('woff');
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-times: 'Office Times', monospace;
|
||||||
|
}
|
||||||
187
src/styles/_functions.scss
Normal file
187
src/styles/_functions.scss
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
@use 'sass:math';
|
||||||
|
|
||||||
|
/* Breakpoints */
|
||||||
|
$mobile-breakpoint: get('breakpoints.mobile');
|
||||||
|
|
||||||
|
// Viewport Sizes
|
||||||
|
$desktop-width: get('viewports.desktop.width');
|
||||||
|
$desktop-height: get('viewports.desktop.height');
|
||||||
|
|
||||||
|
$mobile-width: get('viewports.mobile.width');
|
||||||
|
$mobile-height: get('viewports.mobile.height');
|
||||||
|
|
||||||
|
// Breakpoint
|
||||||
|
@mixin mobile {
|
||||||
|
@media (max-width: #{$mobile-breakpoint * 1px - 1px}) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin desktop {
|
||||||
|
@media (min-width: #{$mobile-breakpoint * 1px}) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@function mobile-vw($pixels, $base-vw: $mobile-width) {
|
||||||
|
$px: math.div($pixels, $base-vw);
|
||||||
|
$perc: math.div($px, 1px);
|
||||||
|
@return calc($perc * 100vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@function mobile-vh($pixels, $base-vh: $mobile-height) {
|
||||||
|
$px: math.div($pixels, $base-vh);
|
||||||
|
$perc: math.div($px, 1px);
|
||||||
|
@return calc($perc * 100vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
@function desktop-vw($pixels, $base-vw: $desktop-width) {
|
||||||
|
$px: math.div($pixels, $base-vw);
|
||||||
|
$perc: math.div($px, 1px);
|
||||||
|
@return calc($perc * 100vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@function desktop-vh($pixels, $base-vh: $desktop-height) {
|
||||||
|
$px: math.div($pixels, $base-vh);
|
||||||
|
$perc: math.div($px, 1px);
|
||||||
|
@return calc($perc * 100vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
@function columns($columns) {
|
||||||
|
@return calc(
|
||||||
|
(#{$columns} * var(--layout-column-width)) +
|
||||||
|
((#{$columns} - 1) * var(--layout-column-gap))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin child-grid($columns) {
|
||||||
|
grid-template-columns: repeat($columns, minmax(0, 1fr));
|
||||||
|
column-gap: var(--layout-columns-gap);
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin reduced-motion {
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin fill($position: absolute) {
|
||||||
|
position: #{$position};
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin fade-on-ready($class: 'ready', $duration: 400ms) {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity $duration ease;
|
||||||
|
|
||||||
|
&.#{$class} {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp text block to number of lines
|
||||||
|
@mixin line-clamp($lines: 3, $mobile-lines: $lines) {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: $lines;
|
||||||
|
|
||||||
|
@include mobile {
|
||||||
|
-webkit-line-clamp: $mobile-lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flip animations
|
||||||
|
@keyframes flip-r {
|
||||||
|
50% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
51% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes flip-l {
|
||||||
|
50% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
51% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes flip-d {
|
||||||
|
50% {
|
||||||
|
transform: translateY(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
51% {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes flip-u {
|
||||||
|
50% {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
51% {
|
||||||
|
transform: translateY(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin flip-animation(
|
||||||
|
$direction: 'r',
|
||||||
|
$duration: 600ms,
|
||||||
|
$easing: var(--ease-out-expo),
|
||||||
|
$iteration-count: 1
|
||||||
|
) {
|
||||||
|
overflow: hidden;
|
||||||
|
animation: flip-#{$direction} $duration $easing $iteration-count forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin link-hover {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--theme-fg);
|
||||||
|
transform-origin: left;
|
||||||
|
transform: scaleX(0);
|
||||||
|
transition: transform 300ms var(--ease-out-quad);
|
||||||
|
}
|
||||||
|
@include desktop {
|
||||||
|
&:hover::before {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin stagger-animate($stagger: 100, $num-children: 10, $base-delay: 0) {
|
||||||
|
@for $i from 0 through $num-children {
|
||||||
|
&:nth-child(#{$i + 1}) {
|
||||||
|
transition-delay: #{$stagger * $i + $base-delay}ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin hover {
|
||||||
|
@include desktop {
|
||||||
|
&:hover {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/styles/_layers.scss
Normal file
7
src/styles/_layers.scss
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// z-index
|
||||||
|
.lily-cursor {
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
.site-header {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
91
src/styles/_layout.scss
Normal file
91
src/styles/_layout.scss
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
@use 'sass:list';
|
||||||
|
@use 'functions' as *;
|
||||||
|
|
||||||
|
// css variables exposed globally:
|
||||||
|
// --layout-column-count: columns count in the layout
|
||||||
|
// --layout-column-gap: gap size between columns
|
||||||
|
// --layout-margin: layout margin size (left or right)
|
||||||
|
// --layout-width: 100vw minus 2 * --layout-margin
|
||||||
|
// --layout-column-width: size of a single column
|
||||||
|
|
||||||
|
// css classes exposed globally:
|
||||||
|
// .layout-block: element takes the whole layout width
|
||||||
|
// .layout-block-inner: same as .layout-block but using padding instead of margin
|
||||||
|
// .layout-grid: extends .layout-block with grid behaviour using layout settings
|
||||||
|
// .layout-grid-inner: same as .layout-grid but using padding instead of margin
|
||||||
|
|
||||||
|
@use 'sass:map';
|
||||||
|
|
||||||
|
// config to fill
|
||||||
|
// 'variable': (mobile, desktop)
|
||||||
|
$layout: (
|
||||||
|
'columns-count': (
|
||||||
|
5,
|
||||||
|
18,
|
||||||
|
),
|
||||||
|
'columns-gap': (
|
||||||
|
20px,
|
||||||
|
20px,
|
||||||
|
),
|
||||||
|
'margin': (
|
||||||
|
30px,
|
||||||
|
60px,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
//internal process, do not touch
|
||||||
|
:root {
|
||||||
|
--layout-column-count: #{list.nth(map.get($layout, 'columns-count'), 1)};
|
||||||
|
--layout-column-gap: #{mobile-vw(
|
||||||
|
list.nth(map.get($layout, 'columns-gap'), 1)
|
||||||
|
)};
|
||||||
|
--layout-margin: #{mobile-vw(list.nth(map.get($layout, 'margin'), 1))};
|
||||||
|
--layout-width: calc(100vw - (2 * var(--layout-margin)));
|
||||||
|
--layout-column-width: calc(
|
||||||
|
(
|
||||||
|
var(--layout-width) -
|
||||||
|
(
|
||||||
|
(var(--layout-column-count) - 1) *
|
||||||
|
var(--layout-column-gap)
|
||||||
|
)
|
||||||
|
) /
|
||||||
|
var(--layout-column-count)
|
||||||
|
);
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
--layout-column-count: #{list.nth(map.get($layout, 'columns-count'), 2)};
|
||||||
|
--layout-column-gap: #{desktop-vw(
|
||||||
|
list.nth(map.get($layout, 'columns-gap'), 2)
|
||||||
|
)};
|
||||||
|
--layout-margin: #{desktop-vw(list.nth(map.get($layout, 'margin'), 2))};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-block {
|
||||||
|
max-width: var(--layout-width);
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-block-inner {
|
||||||
|
padding-left: var(--layout-margin);
|
||||||
|
padding-right: var(--layout-margin);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-grid {
|
||||||
|
@extend .layout-block;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(var(--layout-column-count), minmax(0, 1fr));
|
||||||
|
grid-gap: var(--layout-column-gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-grid-inner {
|
||||||
|
@extend .layout-block-inner;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(var(--layout-column-count), minmax(0, 1fr));
|
||||||
|
grid-gap: var(--layout-column-gap);
|
||||||
|
}
|
||||||
99
src/styles/_reset.scss
Normal file
99
src/styles/_reset.scss
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
/***
|
||||||
|
The new CSS reset - version 1.9 (last updated 19.6.2023)
|
||||||
|
GitHub page: https://github.com/elad2412/the-new-css-reset
|
||||||
|
***/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property
|
||||||
|
- The "symbol *" part is to solve Firefox SVG sprite bug
|
||||||
|
- The "html" element is excluded, otherwise a bug in Chrome breaks the CSS hyphens property (https://github.com/elad2412/the-new-css-reset/issues/36)
|
||||||
|
*/
|
||||||
|
*:where(
|
||||||
|
:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)
|
||||||
|
) {
|
||||||
|
all: unset;
|
||||||
|
display: revert;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Preferred box-sizing value */
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reapply the pointer cursor for anchor tags */
|
||||||
|
a,
|
||||||
|
button {
|
||||||
|
cursor: revert;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For images to not be able to exceed their container */
|
||||||
|
img {
|
||||||
|
max-inline-size: 100%;
|
||||||
|
max-block-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* removes spacing between cells in tables */
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safari - solving issue when using user-select:none on the <body> text input doesn't working */
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
-webkit-user-select: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* revert the 'white-space' property for textarea elements on Safari */
|
||||||
|
textarea {
|
||||||
|
white-space: revert;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* minimum style to allow to style meter element */
|
||||||
|
meter {
|
||||||
|
-webkit-appearance: revert;
|
||||||
|
appearance: revert;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* preformatted text - use only for this feature */
|
||||||
|
:where(pre) {
|
||||||
|
all: revert;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reset default text opacity of input placeholder */
|
||||||
|
::placeholder {
|
||||||
|
color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove default dot (•) sign */
|
||||||
|
::marker {
|
||||||
|
content: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fix the feature of 'hidden' attribute.
|
||||||
|
display:revert; revert to element instead of attribute */
|
||||||
|
:where([hidden]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* revert for bug in Chromium browsers
|
||||||
|
- fix for the content editable attribute will work properly.
|
||||||
|
- webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element*/
|
||||||
|
:where([contenteditable]:not([contenteditable='false'])) {
|
||||||
|
-moz-user-modify: read-write;
|
||||||
|
-webkit-user-modify: read-write;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
-webkit-line-break: after-white-space;
|
||||||
|
-webkit-user-select: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* apply back the draggable feature - exist only in Chromium and Safari */
|
||||||
|
:where([draggable='true']) {
|
||||||
|
-webkit-user-drag: element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Revert Modal native behavior */
|
||||||
|
:where(dialog:modal) {
|
||||||
|
all: revert;
|
||||||
|
}
|
||||||
34
src/styles/_scroll.scss
Normal file
34
src/styles/_scroll.scss
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
html {
|
||||||
|
&:not(.dev) {
|
||||||
|
&,
|
||||||
|
* {
|
||||||
|
scrollbar-width: none !important;
|
||||||
|
-ms-overflow-style: none !important;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html.lenis {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lenis.lenis-smooth {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lenis.lenis-smooth [data-lenis-prevent] {
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lenis.lenis-stopped {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lenis.lenis-scrolling iframe {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
11
src/styles/_themes.scss
Normal file
11
src/styles/_themes.scss
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
@use 'sass:color';
|
||||||
|
|
||||||
|
:root {
|
||||||
|
@each $name, $theme in getThemes() {
|
||||||
|
.theme-#{$name} {
|
||||||
|
@each $name, $color in $theme {
|
||||||
|
--theme-#{$name}: #{$color};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/styles/_transitions.scss
Normal file
43
src/styles/_transitions.scss
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Fades
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 400ms;
|
||||||
|
}
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.quick-fade-enter-active,
|
||||||
|
.quick-fade-leave-active {
|
||||||
|
transition: opacity 100ms;
|
||||||
|
}
|
||||||
|
.quick-fade-enter-from,
|
||||||
|
.quick-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.slow-fade-enter-active,
|
||||||
|
.slow-fade-leave-active {
|
||||||
|
transition: opacity 600ms;
|
||||||
|
}
|
||||||
|
.slow-fade-enter-from,
|
||||||
|
.slow-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slides
|
||||||
|
.slide-up-enter-active,
|
||||||
|
.slide-up-leave-active {
|
||||||
|
transition: transform 400ms var(--ease-out-quad);
|
||||||
|
}
|
||||||
|
.slide-up-enter-from,
|
||||||
|
.slide-up-leave-to {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
.slide-left-enter-active,
|
||||||
|
.slide-left-leave-active {
|
||||||
|
transition: transform 400ms var(--ease-out-quad);
|
||||||
|
}
|
||||||
|
.slide-left-enter-from,
|
||||||
|
.slide-left-leave-to {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
42
src/styles/_utils.scss
Normal file
42
src/styles/_utils.scss
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
@use 'functions' as *;
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
width: 100vw;
|
||||||
|
position: relative;
|
||||||
|
left: 50%;
|
||||||
|
right: 50%;
|
||||||
|
margin-left: -50vw;
|
||||||
|
margin-right: -50vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow-hidden {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-only {
|
||||||
|
@include desktop {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-only {
|
||||||
|
@include mobile {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html:not(.has-scroll-smooth) {
|
||||||
|
.hide-on-native-scroll {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html.has-scroll-smooth {
|
||||||
|
.hide-on-smooth-scroll {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
111
src/styles/main.scss
Normal file
111
src/styles/main.scss
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
@use 'colors' as *;
|
||||||
|
@use 'themes' as *;
|
||||||
|
@use 'easings' as *;
|
||||||
|
@use 'reset' as *;
|
||||||
|
@use 'layers' as *;
|
||||||
|
@use 'functions' as *;
|
||||||
|
@use 'utils' as *;
|
||||||
|
@use 'fonts' as *;
|
||||||
|
@use 'font-style' as *;
|
||||||
|
@use 'layout' as *;
|
||||||
|
@use 'scroll' as *;
|
||||||
|
@use 'transitions' as *;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
|
||||||
|
background: var(--white);
|
||||||
|
color: var(--black);
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type
|
||||||
|
.h1,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
@include h1;
|
||||||
|
}
|
||||||
|
.p,
|
||||||
|
p,
|
||||||
|
a,
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
pre {
|
||||||
|
@include p;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
min-height: 1px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
& > * {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
& > *:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
& > *:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
li {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
width: desktop-vw(577px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style: disc;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
5% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
95% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bonk {
|
||||||
|
0% {
|
||||||
|
transform: rotate(calc(var(--bonk-angle) * -1));
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: rotate(var(--bonk-angle));
|
||||||
|
}
|
||||||
|
}
|
||||||
55
vite.config.js
Normal file
55
vite.config.js
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import { toSass } from './src/libs/sass-utils'
|
||||||
|
import theme from './src/libs/theme'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
Components({
|
||||||
|
dirs: ['src/components'],
|
||||||
|
extensions: ['vue'],
|
||||||
|
deep: true,
|
||||||
|
dts: 'src/components.d.ts',
|
||||||
|
directoryAsNamespace: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
api: 'modern-compiler',
|
||||||
|
additionalData:
|
||||||
|
'@use "/src/styles/_functions.scss" as *; @use "/src/styles/_font-style.scss" as *;',
|
||||||
|
functions: {
|
||||||
|
'get($keys)': function (keys) {
|
||||||
|
keys = keys.toString().replace(/['"]+/g, '').split('.')
|
||||||
|
|
||||||
|
let result = theme
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
result = result[keys[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
return toSass(result)
|
||||||
|
},
|
||||||
|
'getColors()': function () {
|
||||||
|
return toSass(theme.colors)
|
||||||
|
},
|
||||||
|
'getThemes()': function () {
|
||||||
|
return toSass(theme.themes)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue