Rewrite to TailwindCSS, enhance the DOM, uninstall Vuetify, improve styles
This commit is contained in:
parent
9339416c1b
commit
e72a61b317
|
@ -1,5 +0,0 @@
|
|||
> 1%
|
||||
last 5 major versions
|
||||
not dead
|
||||
not ie <= 11
|
||||
not op_mini all
|
|
@ -1,33 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'@nuxtjs/eslint-config-typescript',
|
||||
'plugin:nuxt/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
plugins: [],
|
||||
rules: {
|
||||
lintOnStart: 0,
|
||||
indent: ['error', 2, { SwitchCase: 1 }],
|
||||
quotes: [2, 'single', { avoidEscape: true }],
|
||||
'no-console': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'vue/no-multiple-template-root': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
},
|
||||
settings: {
|
||||
'import/ignore': ['vue-fontawesome'],
|
||||
},
|
||||
}
|
|
@ -9,6 +9,9 @@ dist
|
|||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Cache
|
||||
.stylelintcache
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
###
|
||||
# Place your Prettier ignore content here
|
||||
|
||||
###
|
||||
# .gitignore content is duplicated here due to https://github.com/prettier/prettier/issues/8506
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
/logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# Nuxt generate
|
||||
dist
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# Service worker
|
||||
sw.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Vim swap files
|
||||
*.swp
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all"
|
||||
}
|
|
@ -1 +1 @@
|
|||
[{"D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\transitions.scss":"1","D:\\Software\\Development\\Websites\\enderman.ch\\index\\app.vue":"2","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\EMail.vue":"3","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\about.vue":"4","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\index.vue":"5","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\projects.vue":"6","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\social.vue":"7","D:\\Software\\Development\\Websites\\enderman.ch\\index\\layouts\\Card.vue":"8","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Options.vue":"9","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Flooter.vue":"10","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\SwipeControls.vue":"11","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Logo.vue":"12","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Route.vue":"13","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\animations\\Portal.vue":"14","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\ui\\Icon.vue":"15","D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\vuetify.scss":"16","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\[...slug].vue":"17","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\index.vue":"18","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\[slug].vue":"19","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\content\\Card.vue":"20","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\content\\OgImage.vue":"21","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\animations\\NotFound.vue":"22"},{"size":165,"mtime":1700688268778,"hashOfConfig":"23"},{"size":1550,"mtime":1700850776284,"hashOfConfig":"23"},{"size":804,"mtime":1700688952880,"hashOfConfig":"23"},{"size":2135,"mtime":1700689265787,"hashOfConfig":"23"},{"size":3117,"mtime":1700689271383,"hashOfConfig":"23"},{"size":2304,"mtime":1700689265769,"hashOfConfig":"23"},{"size":3232,"mtime":1700689265781,"hashOfConfig":"23"},{"size":1623,"mtime":1700748530633,"hashOfConfig":"23"},{"size":3855,"mtime":1708455917401,"hashOfConfig":"24"},{"size":1132,"mtime":1708254584095,"hashOfConfig":"24"},{"size":1269,"mtime":1708255068679,"hashOfConfig":"24"},{"size":1005,"mtime":1718446378238,"hashOfConfig":"24"},{"size":961,"mtime":1718445804903,"hashOfConfig":"24"},{"size":17150,"mtime":1708455647910,"hashOfConfig":"24"},{"size":935,"mtime":1706445231843,"hashOfConfig":"24"},{"size":120,"mtime":1706789613657,"hashOfConfig":"25"},{"size":2084,"mtime":1708424343371,"hashOfConfig":"26"},{"size":2560,"mtime":1708202402381,"hashOfConfig":"26"},{"size":983,"mtime":1708206997656,"hashOfConfig":"26"},{"size":1895,"mtime":1718444102273,"hashOfConfig":"24"},{"size":976,"mtime":1708431988520,"hashOfConfig":"26"},{"size":1673,"mtime":1718386852848,"hashOfConfig":"24"},"5tgxr3","lsooul","1lk8nat","1nljphs"]
|
||||
[{"D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\animations\\NotFound.vue":"1","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Route.vue":"2","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Logo.vue":"3","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Settings.vue":"4","D:\\Software\\Development\\Websites\\enderman.ch\\index\\app.vue":"5","D:\\Software\\Development\\Websites\\enderman.ch\\index\\layouts\\Card.vue":"6"},{"size":1679,"mtime":1718453058852,"hashOfConfig":"7"},{"size":1065,"mtime":1718555359979,"hashOfConfig":"7"},{"size":985,"mtime":1718555386128,"hashOfConfig":"7"},{"size":3590,"mtime":1718563207348,"hashOfConfig":"7"},{"size":1874,"mtime":1718548398710,"hashOfConfig":"8"},{"size":1807,"mtime":1718548507733,"hashOfConfig":"8"},"2j789g","1lkkhgi"]
|
|
@ -1,2 +0,0 @@
|
|||
node_modules/
|
||||
assets/styles/**/*.css
|
|
@ -1,11 +0,0 @@
|
|||
module.exports = {
|
||||
defaultSeverity: 'warning',
|
||||
extends: [
|
||||
'stylelint-config-standard-scss',
|
||||
'stylelint-config-recommended-vue',
|
||||
],
|
||||
plugins: [],
|
||||
rules: {
|
||||
'declaration-empty-line-before': null,
|
||||
},
|
||||
}
|
|
@ -2,13 +2,4 @@ import config from './config'
|
|||
|
||||
export default defineAppConfig({
|
||||
...config,
|
||||
nuxtIcon: {
|
||||
size: '1em',
|
||||
class: 'icon',
|
||||
aliases: {},
|
||||
iconifyApiOptions: {
|
||||
url: 'https://api.iconify.design',
|
||||
publicApiFallback: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
63
app.vue
63
app.vue
|
@ -1,17 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import 'iconify-icon'
|
||||
|
||||
const config = useAppConfig()
|
||||
const theme = useVThemeSSR()
|
||||
const vdisp = ref(useVDisplay())
|
||||
const { reader } = storeToRefs(usePageStore())
|
||||
const route = useRoute()
|
||||
|
||||
const { reader, animate } = storeToRefs(usePageStore())
|
||||
|
||||
useHead({
|
||||
titleTemplate: (chunk?) => {
|
||||
if (route.fullPath === '/') return config.title.full
|
||||
if (route.fullPath.split('/').length === 2)
|
||||
switch (route.fullPath.split('/')[1]) {
|
||||
case 'blog':
|
||||
return 'The Enderchest'
|
||||
return 'The Ender Chest'
|
||||
default:
|
||||
return `${chunk} – ${config.title.short}`
|
||||
}
|
||||
|
@ -22,47 +23,39 @@ useHead({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<VThemeProvider
|
||||
:theme="theme.dark.value ? config.theme.dark : config.theme.light"
|
||||
>
|
||||
<div
|
||||
id="ender-layout"
|
||||
class="p-animated flex flex-col items-center h-full"
|
||||
:class="{ 'sm:pt-8': !reader }"
|
||||
class="flex flex-col lm:justify-center lt:justify-center items-center h-full"
|
||||
>
|
||||
<AClientOnly appear mode="out-in">
|
||||
<SwipeControls v-if="vdisp.xs" />
|
||||
</AClientOnly>
|
||||
<ClientOnly>
|
||||
<LazySwipeControls class="block sm:hidden" />
|
||||
</ClientOnly>
|
||||
|
||||
<NuxtLayout
|
||||
name="card"
|
||||
class="animate__animated-sm animate__delay-1-5s animate__fadeInDown"
|
||||
>
|
||||
<NuxtLayout name="card">
|
||||
<template #footer>
|
||||
<AClientOnly
|
||||
appear
|
||||
mode="in-out"
|
||||
enter="animate__animated animate__delay-1s animate__fadeIn"
|
||||
leave="animate__animated animate__fadeOut"
|
||||
<footer
|
||||
v-if="!reader"
|
||||
class="lm:static sm:fixed sm:bottom-0 sm:left-0 sm:right-0 mx-auto w-auto lm:mb-0 sm:mb-2 pass-through text-center"
|
||||
>
|
||||
<Flooter v-if="vdisp.xs" opaque />
|
||||
</AClientOnly>
|
||||
<small
|
||||
class="lm:text-inherit sm:accent-text sm:drop-shadow-lg sm:transition-all lm:opacity-100 sm:opacity-25 sm:hover:opacity-100"
|
||||
>
|
||||
© 2018-{{ new Date().getFullYear() }},
|
||||
<a class="sm:link-dark" :href="config.url">Enderman</a>. All rights
|
||||
reserved.
|
||||
<wbr />
|
||||
<sub class="whitespace-nowrap">
|
||||
β{{ config.build.version ? config.build.version : '?.?.?' }} ({{
|
||||
config.build.date ? config.build.date : '1970-01-01'
|
||||
}})
|
||||
</sub>
|
||||
</small>
|
||||
</footer>
|
||||
</template>
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
|
||||
<AClientOnly
|
||||
appear
|
||||
mode="out-in"
|
||||
enter="animate__animated animate__delay-1s animate__fadeInUpBig animate__slow"
|
||||
leave="animate__animated animate__fadeOutDown"
|
||||
>
|
||||
<Flooter v-if="vdisp.smAndUp && !reader" class="floaty mb-4 px-2" />
|
||||
</AClientOnly>
|
||||
|
||||
<ClientOnly>
|
||||
<template #fallback> </template>
|
||||
<LazyPortal layout="#ender-layout" animate randomize fade />
|
||||
<LazyPortal v-model="animate" layout="#ender-layout" randomize fade />
|
||||
</ClientOnly>
|
||||
</VThemeProvider>
|
||||
</template>
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 536 KiB |
|
@ -1 +0,0 @@
|
|||
@layer base, vuetify, overrides
|
|
@ -31,5 +31,9 @@
|
|||
|
||||
&-faq {
|
||||
list-style-type: faq;
|
||||
|
||||
> li:nth-child(2n):not(:last-child) {
|
||||
@apply mb-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
// Modules.
|
||||
@use 'transitions';
|
||||
@use 'lists';
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: Enchant;
|
||||
src:
|
||||
url("~/assets/fonts/enchant/enchant.woff") format('woff'),
|
||||
url("~/assets/fonts/enchant/enchant.woff2") format('woff2')
|
||||
}
|
||||
|
||||
// CSS Variables.
|
||||
:root {
|
||||
|
@ -11,10 +19,47 @@
|
|||
--animate-repeat: 1;
|
||||
}
|
||||
|
||||
@-moz-document url-prefix() {
|
||||
html {
|
||||
min-height: 128px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgb(255 255 255 / 15%) rgb(0 0 0 / 20%);
|
||||
|
||||
&.light {
|
||||
scrollbar-color: rgb(0 0 0 / 15%) rgb(255 255 255 / 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The HTML page.
|
||||
html {
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
border-radius: 8px;
|
||||
background-color: rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 8px;
|
||||
background-color: rgb(255 255 255 / 15%);
|
||||
}
|
||||
|
||||
&.light {
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: rgb(255 255 255 / 15%);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 8px;
|
||||
background-color: rgb(0 0 0 / 30%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The body element.
|
||||
|
@ -69,8 +114,12 @@ h6 {
|
|||
font-size: 1rem;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-inherit no-underline transition-all;
|
||||
@apply no-underline transition-all;
|
||||
|
||||
color: cornflowerblue;
|
||||
|
||||
|
@ -79,34 +128,41 @@ a {
|
|||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
// Entry point (Vue mount).
|
||||
#ender-app {
|
||||
flex: 1 1 0;
|
||||
image-rendering: auto;
|
||||
}
|
||||
|
||||
// Helper classes that don't exist in Bootstrap.
|
||||
.inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.center {
|
||||
center {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.nobr {
|
||||
white-space: nowrap;
|
||||
:not(nav) > ul {
|
||||
list-style-type: disc;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
:is(section, article, aside).page {
|
||||
:is(div, p):not(:last-child) {
|
||||
@apply mb-4;
|
||||
}
|
||||
|
||||
:is(h1, h2, h3, h4, h5, h6):not(:last-child) {
|
||||
@apply mb-2;
|
||||
}
|
||||
|
||||
:is(ul, ol):not(:last-child) {
|
||||
@apply mb-4;
|
||||
}
|
||||
}
|
||||
|
||||
iconify-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// Entry point (Vue mount).
|
||||
#ender-app {
|
||||
flex: 1 0 auto;
|
||||
image-rendering: auto;
|
||||
}
|
||||
|
||||
// Helper classes that don't exist in Bootstrap.
|
||||
.overlay {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
|
@ -119,59 +175,61 @@ ul {
|
|||
}
|
||||
}
|
||||
|
||||
.no-decoration {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
ruby {
|
||||
ruby-position: under;
|
||||
|
||||
ruby {
|
||||
ruby-position: over;
|
||||
}
|
||||
}
|
||||
|
||||
.text-align-center {
|
||||
text-align: center;
|
||||
rt {
|
||||
ruby-align: space-around;
|
||||
}
|
||||
|
||||
|
||||
.font-small {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.grid-cols-3 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.display-sm {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alex {
|
||||
font-family: Alexandria, sans-serif;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.pre-wrap {
|
||||
white-space: pre-wrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.h-animated {
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.p-animated {
|
||||
transition: padding 0.3s ease;
|
||||
.transition-ease {
|
||||
@extend %transition;
|
||||
}
|
||||
|
||||
// Query-overridable classes.
|
||||
.background {
|
||||
background-color: rgb(0 0 0 / 50%);
|
||||
}
|
||||
|
||||
.dimensions {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.accent {
|
||||
.accent-background {
|
||||
background-color: rgb(0 0 0 / 50%);
|
||||
}
|
||||
|
||||
.accent-overlay-background {
|
||||
background-color: rgb(0 0 0 / 90%);
|
||||
}
|
||||
|
||||
@responsive {
|
||||
.accent-text {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.link-dark {
|
||||
@apply text-inherit no-underline transition-all;
|
||||
|
||||
color: cornflowerblue;
|
||||
|
||||
&:hover {
|
||||
color: royalblue;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-mask {
|
||||
mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0%), rgb(0 0 0 / 100%) 5%, rgb(0 0 0 / 100%) 95%, rgb(0 0 0 / 0%));
|
||||
}
|
||||
}
|
||||
|
||||
.dialog {
|
||||
display: grid;
|
||||
|
||||
|
@ -212,16 +270,8 @@ ul {
|
|||
transition: 0.3s ease;
|
||||
}
|
||||
|
||||
.link {
|
||||
&-hi-force-dark {
|
||||
@apply text-inherit no-underline transition-all;
|
||||
|
||||
color: cornflowerblue;
|
||||
|
||||
&:hover {
|
||||
color: royalblue;
|
||||
}
|
||||
}
|
||||
.parallax {
|
||||
transition: all 0.6942s ease-in;
|
||||
}
|
||||
|
||||
.accent-gradient {
|
||||
|
@ -233,53 +283,26 @@ ul {
|
|||
);
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
min-height: 128px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgb(255 255 255 / 15%) rgb(0 0 0 / 20%);
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
.accent-text-shadow {
|
||||
text-shadow: black 2px 3px 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
border-radius: 8px;
|
||||
background-color: rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 8px;
|
||||
background-color: rgb(255 255 255 / 15%);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-button {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:where(html.light) {
|
||||
body {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
scrollbar-color: rgb(0 0 0 / 20%) rgb(255 255 255 / 20%);
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: rgb(255 255 255 / 20%);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 8px;
|
||||
background-color: rgb(0 0 0 / 20%);
|
||||
}
|
||||
}
|
||||
|
||||
.background {
|
||||
.accent-background {
|
||||
background-color: rgb(255 255 255 / 80%);
|
||||
}
|
||||
|
||||
.accent-overlay-background {
|
||||
background-color: rgb(255 255 255 / 90%);
|
||||
}
|
||||
|
||||
a {
|
||||
@apply no-underline transition-all;
|
||||
|
||||
color: royalblue;
|
||||
|
||||
&:hover {
|
||||
|
@ -287,7 +310,7 @@ ul {
|
|||
}
|
||||
}
|
||||
|
||||
.accent {
|
||||
.accent-text {
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
@ -299,20 +322,14 @@ ul {
|
|||
rgb(0 0 0 / 0%)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.floaty {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
margin: auto;
|
||||
.accent-text-shadow {
|
||||
text-shadow: white 1px 2px 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.post {
|
||||
scroll-snap-type: both mandatory;
|
||||
scroll-snap-type: y mandatory;
|
||||
scroll-snap-stop: normal;
|
||||
|
||||
&::after {
|
||||
|
@ -337,19 +354,10 @@ ul {
|
|||
}
|
||||
|
||||
&-pocket {
|
||||
position: absolute;
|
||||
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
text-transform: uppercase;
|
||||
background-color: rgb(153 153 255 / 10%);
|
||||
|
||||
border-left: 1px solid rgb(153 153 255 / 60%);
|
||||
border-top: 1px solid rgb(153 153 255 / 60%);
|
||||
|
||||
border-top-left-radius: var(--bs-border-radius-xl);
|
||||
border-bottom-right-radius: var(--bs-border-radius-xl);
|
||||
}
|
||||
|
||||
&-thumb {
|
||||
|
@ -377,45 +385,16 @@ ul {
|
|||
}
|
||||
|
||||
&-preamble {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
|
||||
border-radius: 1em;
|
||||
|
||||
scroll-snap-align: end;
|
||||
|
||||
text-shadow: black 1px 1px 7px;
|
||||
|
||||
iconify-icon {
|
||||
filter: drop-shadow(1px 2px 3px rgb(0 0 0 / 100%));
|
||||
}
|
||||
|
||||
&-control {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&-share {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
|
@ -550,6 +529,13 @@ ul {
|
|||
}
|
||||
|
||||
// Dynamic classes.
|
||||
@screen lm {
|
||||
.dimensions {
|
||||
width: 100% !important;
|
||||
min-height: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@screen sm {
|
||||
html {
|
||||
font-size: 16px;
|
||||
|
@ -557,9 +543,7 @@ ul {
|
|||
|
||||
#ender-app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
|
@ -569,18 +553,6 @@ ul {
|
|||
max-height: 90%;
|
||||
}
|
||||
|
||||
.fade-mask-sm {
|
||||
mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0%), rgb(0 0 0 / 100%) 5%, rgb(0 0 0 / 100%) 95%, rgb(0 0 0 / 0%));
|
||||
}
|
||||
|
||||
.rounded-1-sm {
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
.display-sm {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.animate__animated-sm {
|
||||
animation-duration: var(--animate-duration);
|
||||
animation-fill-mode: both;
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
@forward 'vuetify/settings' with (
|
||||
$utilities: false,
|
||||
$color-pack: false,
|
||||
$reset: false,
|
||||
$layers: true,
|
||||
);
|
|
@ -47,9 +47,10 @@ onMounted(() => setTimeout(type, 1500))
|
|||
|
||||
<template>
|
||||
<section>
|
||||
<h3 class="alex">Page not found!</h3>
|
||||
<pre
|
||||
class="whitespace-pre-wrap break-keep">{{ buffer }}<span class="blinker">▊</span></pre>
|
||||
<h3>Page not found!</h3>
|
||||
<pre class="whitespace-pre-wrap break-keep font-small">{{
|
||||
buffer
|
||||
}}<span class="blinker">▊</span></pre>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
import sky from '~/assets/images/textures/sky.png'
|
||||
import particles from '~/assets/images/textures/particles.png'
|
||||
|
||||
const { $local } = useNuxtApp()
|
||||
const config = useAppConfig()
|
||||
const fqdn = config.url.split('//')[1]
|
||||
const fqdn = config.url.split('//').at(1)
|
||||
|
||||
const resources: string[] = [sky, particles]
|
||||
|
||||
const pages = storeToRefs(usePageStore())
|
||||
const animated = defineModel<boolean>({
|
||||
required: true,
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
layout: {
|
||||
|
@ -138,7 +140,7 @@ class Portal {
|
|||
constructor(
|
||||
directory: string = '/',
|
||||
create: boolean = true,
|
||||
animate: boolean = true,
|
||||
animate: boolean = false,
|
||||
randomize: boolean = false,
|
||||
fade: boolean = false,
|
||||
speed: number = 1,
|
||||
|
@ -563,9 +565,6 @@ class Portal {
|
|||
// Run the scene.
|
||||
this.scene()
|
||||
|
||||
// TODO: Make it into a composable? The toggle will not work. Hardcoded.
|
||||
if (!pages.animate.value) this.pause()
|
||||
|
||||
// Request the next animation frame if animation is enabled.
|
||||
if (this.animate) requestAnimationFrame(this.render.bind(this))
|
||||
}
|
||||
|
@ -573,10 +572,6 @@ class Portal {
|
|||
resize() {
|
||||
if (!this.canvas.full()) this.canvas.fill()
|
||||
|
||||
// Pause the animation if the viewport becomes too small.
|
||||
if (window.innerWidth <= 600) this.pause()
|
||||
else this.continue()
|
||||
|
||||
// If we have animation disabled, we still have to re-render the scene once on resize.
|
||||
if (!this.animate) requestAnimationFrame(this.render.bind(this))
|
||||
}
|
||||
|
@ -585,8 +580,8 @@ class Portal {
|
|||
this.clickTime = Date.now()
|
||||
}
|
||||
|
||||
continue() {
|
||||
if (this.pauseTime === 0) return
|
||||
play() {
|
||||
if (this.pauseTime === 0) this.pauseTime = this.currentTime
|
||||
|
||||
// Add the time that has passed since the pause to the current time.
|
||||
this.currentTime += Date.now() - this.pauseTime
|
||||
|
@ -619,47 +614,50 @@ class Portal {
|
|||
}
|
||||
|
||||
onMounted(() => {
|
||||
// If the localStorage value is null, trigger the setup.
|
||||
// If the viewport is too small, disable by default.
|
||||
const ecmaportal: string | null | undefined = $local.getItem('ecmaportal')
|
||||
|
||||
if (ecmaportal === null) {
|
||||
const notMobile = window.innerWidth > 600
|
||||
|
||||
$local.setItem('ecmaportal', notMobile)
|
||||
animated.value = notMobile
|
||||
} else animated.value = ecmaportal === 'true'
|
||||
|
||||
const layout = document.querySelector(props.layout)
|
||||
const portal = new Portal(
|
||||
props.directory,
|
||||
props.create,
|
||||
props.animate && window.innerWidth > 600,
|
||||
animated.value,
|
||||
props.randomize,
|
||||
props.fade,
|
||||
props.speed,
|
||||
)
|
||||
|
||||
layout!.addEventListener('mousedown', ((e: UIEvent) => {
|
||||
if (e.target === e.currentTarget && e.detail >= 2) {
|
||||
const click: EventListener = (e: Event) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
e.preventDefault()
|
||||
portal.click()
|
||||
}
|
||||
}) as EventListener)
|
||||
}
|
||||
|
||||
for (const event of ['dblclick', 'touchstart'])
|
||||
layout!.addEventListener(event, click)
|
||||
|
||||
// Save the new value to local storage.
|
||||
watch(animated, (newAnimate) => {
|
||||
$local.setItem('ecmaportal', newAnimate)
|
||||
newAnimate ? portal.play() : portal.pause()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas id="ecmaportal" class="parallax">
|
||||
<canvas id="ecmaportal" class="fixed top-0 left-0 opacity-0 parallax -z-10">
|
||||
<span>
|
||||
Your browser does not support the <canvas /> element, which is
|
||||
required for parallax animation.
|
||||
</span>
|
||||
</canvas>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.parallax {
|
||||
position: fixed;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
opacity: 0;
|
||||
transition: all 0.6942s ease-in;
|
||||
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<hr class="accent-text accent-gradient border-0 h-px my-4" />
|
||||
<p>
|
||||
<strong>🚧 This page is currently under construction.</strong> Expect a
|
||||
lot more content to be added as time passes.
|
||||
<em>Please report all bugs you encounter via the link in the footer</em>,
|
||||
I will make sure to sand them down.
|
||||
</p>
|
||||
<hr class="accent-text accent-gradient border-0 h-px my-4" />
|
||||
</div>
|
||||
</template>
|
|
@ -1,59 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
opaque: Boolean,
|
||||
})
|
||||
|
||||
const config = useAppConfig()
|
||||
const currentYear = new Date().getFullYear()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer
|
||||
class="user-select-none pass-through text-align-center line-height-1-5"
|
||||
>
|
||||
<span
|
||||
class="font-small"
|
||||
:class="{
|
||||
'footer-transparent': !props.opaque,
|
||||
'text-shadow': !props.opaque,
|
||||
}"
|
||||
>
|
||||
© 2018-{{ currentYear }},
|
||||
<a
|
||||
:class="{
|
||||
'link-hi-force-dark': !props.opaque,
|
||||
'link-hi': props.opaque,
|
||||
}"
|
||||
:href="config.url"
|
||||
>Enderman</a
|
||||
>. All rights reserved.
|
||||
<wbr />
|
||||
<sub class="nobr">
|
||||
β{{ config.build.version ? config.build.version : '?.?.?' }} ({{
|
||||
config.build.date ? config.build.date : '1970-01-01'
|
||||
}})
|
||||
</sub>
|
||||
</span>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.footer-transparent {
|
||||
color: white;
|
||||
|
||||
opacity: 0.25;
|
||||
transition: ease 0.3s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.line-height-1-5 {
|
||||
line-height: 14px * 1.5;
|
||||
}
|
||||
|
||||
.text-shadow {
|
||||
text-shadow: black 0 2px 5px;
|
||||
}
|
||||
</style>
|
|
@ -29,10 +29,11 @@ const props = defineProps({
|
|||
|
||||
<template>
|
||||
<NuxtLink
|
||||
class="flex flex-row items-center text-inherit hover:text-inherit select-none sm:m-auto lg:m-0"
|
||||
class="flex flex-row items-center text-inherit hover:text-inherit select-none sm:m-auto lm:m-0 lt:m-0 lg:m-0"
|
||||
to="/"
|
||||
>
|
||||
<img
|
||||
class="transition-all"
|
||||
draggable="false"
|
||||
:width="props.width"
|
||||
:height="props.height"
|
||||
|
@ -41,22 +42,14 @@ const props = defineProps({
|
|||
/>
|
||||
<div>
|
||||
<h2>{{ props.title }}</h2>
|
||||
<hr class="accent accent-gradient border-0 h-px" />
|
||||
<hr class="accent-text accent-gradient border-0 h-px" />
|
||||
<p>{{ props.description }}</p>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
a {
|
||||
> img {
|
||||
transition: ease 0.3s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> img {
|
||||
a:hover > img {
|
||||
transform: scale(105%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -12,7 +12,7 @@ const links = toRaw(pages.value).filter((page) => page.path !== '/')
|
|||
|
||||
<template>
|
||||
<nav
|
||||
class="flex flex-row flex-wrap sm:flex-col lg:flex-row justify-around sm:justify-start lg:justify-between gap-2 lg:gap-3"
|
||||
class="flex flex-row flex-wrap lm:flex-row lt:flex-row sm:flex-col lg:flex-row justify-around sm:justify-start lm:justify-between lt:justify-between lg:justify-between gap-2 lg:gap-4"
|
||||
>
|
||||
<Logo
|
||||
:src="logo"
|
||||
|
@ -21,7 +21,7 @@ const links = toRaw(pages.value).filter((page) => page.path !== '/')
|
|||
description="official website"
|
||||
/>
|
||||
<ul
|
||||
class="flex flex-row flex-wrap items-center sm:items-start lg:items-center justify-center gap-3 m-2 sm:m-0 lg:mx-2 sm:my-2 lg:my-0"
|
||||
class="flex flex-row flex-wrap items-center sm:items-start lm:items-center lt:items-center lg:items-center justify-center gap-4 m-2 sm:m-0 lm:mx-2 lt:mx-2 lg:mx-2 sm:my-2 lg:my-0"
|
||||
>
|
||||
<li v-for="(page, index) in links" :key="index" class="nav-item">
|
||||
<Route
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import cogIcon from '~/assets/images/icons/cog.png'
|
||||
import pearlIcon from '~/assets/images/icons/pearl.gif'
|
||||
|
||||
const theme = useVThemeSSR()
|
||||
const config = useAppConfig()
|
||||
const fqdn = config.url.split('//')[1]
|
||||
|
||||
const mailTemplate = `I've just found a bug on ${config.url} and would like to report it.%0D%0A%0D%0AWebsite version: ${
|
||||
config.build.version
|
||||
}%0D%0ABuild date: ${
|
||||
config.build.date
|
||||
}%0D%0ATimestamp: ${new Date().toISOString()}%0D%0A%0D%0A%0D%0ASteps to reproduce:%0D%0A{ Explain in detail what happened here, or attach a video/screenshot }%0D%0A%0D%0AAdditional information:%0D%0A{ Helpful information, such as developer console output / Leave empty if none }%0D%0A%0D%0A%0D%0A// Keep in mind that it's just a template, you can change it as you wish! :)`
|
||||
|
||||
const pages = storeToRefs(usePageStore())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog width="auto">
|
||||
<template #activator="{ props }">
|
||||
<div v-bind="props" class="options clickable">
|
||||
<img
|
||||
draggable="false"
|
||||
:src="cogIcon"
|
||||
alt="Options"
|
||||
class="logo user-select-none"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ isActive }">
|
||||
<VCard>
|
||||
<img
|
||||
draggable="false"
|
||||
:src="pearlIcon"
|
||||
alt="Pearl"
|
||||
class="icon-badge user-select-none"
|
||||
/>
|
||||
|
||||
<template #title>
|
||||
<h3 class="alex text-align-center">Site options</h3>
|
||||
</template>
|
||||
|
||||
<div class="overlay background"></div>
|
||||
<template #text>
|
||||
<p class="center mb-3">
|
||||
This tab is experimental and isn't ready for production.
|
||||
<em>I was too lazy to exclude it from the build.</em><br />
|
||||
<strong>Features provided here may or may not work.</strong>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<VRow>
|
||||
<VCol>
|
||||
<strong>Theme</strong>
|
||||
<p>Available in all flavors, vanilla and chocolate.</p>
|
||||
<VBtn variant="flat" color="secondary" @click="theme.toggle()">
|
||||
Toggle theme
|
||||
</VBtn>
|
||||
</VCol>
|
||||
<VCol>
|
||||
<strong>Animation</strong>
|
||||
<p>
|
||||
Some computers may have issues rendering that gorgeous
|
||||
background animation. Disabling it substantially decreases
|
||||
power consumption, but also makes the website less cool.
|
||||
</p>
|
||||
<VBtn
|
||||
variant="flat"
|
||||
color="primary"
|
||||
@click="pages.animate.value = !pages.animate.value"
|
||||
>
|
||||
Stop animation
|
||||
</VBtn>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
<span class="mt-3">
|
||||
Since you're so curious and thus here, why not report a bug from the
|
||||
production environment behind this tab?<br />
|
||||
It will greatly help me improve the website!
|
||||
<EMail
|
||||
class="link-hi"
|
||||
:address="`contact@${fqdn}`"
|
||||
:cc="`admin@${fqdn}`"
|
||||
:subject="`Bug report: ${fqdn}`"
|
||||
:body="mailTemplate"
|
||||
>
|
||||
<strong>Report a bug</strong>
|
||||
</EMail>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="outlined"
|
||||
prepend-icon="close"
|
||||
@click="isActive.value = false"
|
||||
>
|
||||
Close
|
||||
</VBtn>
|
||||
</template>
|
||||
</VCard>
|
||||
</template>
|
||||
</VDialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.options {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.icon-badge {
|
||||
position: fixed;
|
||||
|
||||
top: -48px;
|
||||
left: -32px;
|
||||
|
||||
transform: rotate(-15deg);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
</style>
|
|
@ -17,23 +17,32 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: 'Link',
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 32,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 32,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink
|
||||
class="flex flex-row items-center gap-2 select-none no-decoration"
|
||||
active-class="icon-active sm:px-3"
|
||||
class="flex flex-row items-center gap-2 select-none text-inherit no-underline"
|
||||
active-class="active"
|
||||
:to="!props.external ? props.path : undefined"
|
||||
:href="props.external ? props.path : undefined"
|
||||
>
|
||||
<img
|
||||
class="icon-image"
|
||||
draggable="false"
|
||||
:src="props.icon"
|
||||
:alt="props.alt"
|
||||
:width="props.width"
|
||||
:height="props.height"
|
||||
/>
|
||||
<span class="display-sm">
|
||||
<span class="hidden lm:hidden lt:hidden sm:block">
|
||||
<strong>
|
||||
{{ props.name }}
|
||||
</strong>
|
||||
|
@ -42,14 +51,8 @@ const props = defineProps({
|
|||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.icon {
|
||||
&-active {
|
||||
.active {
|
||||
@apply px-4;
|
||||
transform: translate(2px, 2px) rotate3d(1, 1, 1, 5deg) scale(1.25);
|
||||
}
|
||||
|
||||
&-image {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
<script setup lang="ts">
|
||||
import cogIcon from '~/assets/images/icons/cog.png'
|
||||
import pearlIcon from '~/assets/images/icons/pearl.gif'
|
||||
|
||||
const config = useAppConfig()
|
||||
const { animate } = storeToRefs(usePageStore())
|
||||
|
||||
const fqdn = config.url.split('//').at(1)
|
||||
const mailTemplate = `I've just found a bug on ${config.url} and would like to report it.%0D%0A%0D%0AWebsite version: ${
|
||||
config.build.version
|
||||
}%0D%0ABuild date: ${
|
||||
config.build.date
|
||||
}%0D%0ATimestamp: ${new Date().toISOString()}%0D%0A%0D%0A%0D%0ASteps to reproduce:%0D%0A{ Explain in detail what happened here, or attach a video/screenshot }%0D%0A%0D%0AAdditional information:%0D%0A{ Helpful information, such as developer console output / Leave empty if none }%0D%0A%0D%0A%0D%0A// Keep in mind that it's just a template, you can change it as you wish! :)`
|
||||
|
||||
const active = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="absolute top-0 right-0 flex flex-col cursor-pointer select-none p-2"
|
||||
@click="active = true"
|
||||
>
|
||||
<img
|
||||
draggable="false"
|
||||
:src="cogIcon"
|
||||
alt="Options"
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
</div>
|
||||
<Transition
|
||||
enter-active-class="animate__animated animate__fadeIn"
|
||||
leave-active-class="animate__animated animate__fadeOut"
|
||||
>
|
||||
<div
|
||||
v-if="active"
|
||||
class="fixed top-0 left-0 flex flex-row justify-center items-center w-full h-full z-20"
|
||||
>
|
||||
<div
|
||||
class="relative flex flex-col gap-4 accent-overlay-background rounded-xl max-w-[800px] mx-4 p-4"
|
||||
>
|
||||
<img
|
||||
draggable="false"
|
||||
:src="pearlIcon"
|
||||
alt="Pearl"
|
||||
class="absolute -top-8 -left-6 badge select-none w-16 lg:w-auto"
|
||||
/>
|
||||
|
||||
<h3 class="text-center">Site settings</h3>
|
||||
|
||||
<div class="overlay accent-overlay-background" />
|
||||
<div class="overlay">
|
||||
<p class="mb-3">
|
||||
The site settings are experimental. Suggest what you want to see
|
||||
here next!
|
||||
<strong>Please report any bugs that may occur.</strong>
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<div class="md:basis-0 flex-grow">
|
||||
<strong>Theme</strong>
|
||||
<p>Available in vanilla and chocolate flavors.</p>
|
||||
|
||||
<select
|
||||
v-model="$colorMode.preference"
|
||||
class="accent accent-overlay-background"
|
||||
>
|
||||
<option value="system">System</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="sepia">Sepia</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="md:basis-0 flex-grow">
|
||||
<strong>Animation</strong>
|
||||
<p>
|
||||
Some computers may have issues rendering that gorgeous
|
||||
background animation. Disabling it substantially decreases power
|
||||
consumption, but also makes the website less cool.
|
||||
</p>
|
||||
<input v-model="animate" type="checkbox" color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
<span class="mt-3">
|
||||
<EMail
|
||||
:address="`contact@${fqdn}`"
|
||||
:cc="`admin@${fqdn}`"
|
||||
:subject="`Bug report: ${fqdn}`"
|
||||
:body="mailTemplate"
|
||||
>
|
||||
<strong>Report a bug</strong>
|
||||
</EMail>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button @click="active = false">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.badge {
|
||||
transform: rotate(-15deg);
|
||||
}
|
||||
</style>
|
|
@ -1,9 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { usePageStore } from '~/stores/pages'
|
||||
|
||||
const pageStore = usePageStore()
|
||||
const { pages } = storeToRefs(pageStore)
|
||||
const { pages } = storeToRefs(usePageStore())
|
||||
|
||||
const route = useRoute()
|
||||
const state = computed(() => {
|
||||
|
@ -24,39 +20,18 @@ const state = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="control-layout pass-through">
|
||||
<NuxtLink class="control-icon no-decoration" :to="state.prev">
|
||||
<Icon name="iconoir:nav-arrow-left" />
|
||||
<div
|
||||
class="flex fixed flex-row justify-between items-center w-full h-full pass-through"
|
||||
>
|
||||
<NuxtLink
|
||||
class="opacity-25 hover:opacity-100 text-inherit no-underline transition-all"
|
||||
:to="state.prev"
|
||||
>
|
||||
<iconify-icon icon="iconoir:nav-arrow-left" inline />
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink class="control-icon no-decoration" :to="state.next">
|
||||
<Icon name="iconoir:nav-arrow-right" />
|
||||
<NuxtLink class="control-icon text-inherit no-underline" :to="state.next">
|
||||
<iconify-icon icon="iconoir:nav-arrow-right" inline />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.control {
|
||||
&-layout {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
position: fixed;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
opacity: 0.25;
|
||||
transition: 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -68,7 +68,7 @@ switch (props.icon) {
|
|||
:alt="`Download`"
|
||||
class="icon-image"
|
||||
/>
|
||||
<h3 class="mb-0 nobr">{{ props.title }}</h3>
|
||||
<h3 class="whitespace-nowrap mb-0">{{ props.title }}</h3>
|
||||
</div>
|
||||
<div class="box p-4 rounded-xl mb-4">
|
||||
<slot />
|
||||
|
|
|
@ -30,7 +30,7 @@ const props = defineProps({
|
|||
})
|
||||
|
||||
const sentence = computed(() => {
|
||||
return props.description.split('.')[0] + '...'
|
||||
return props.description.split('.').at(0) + '...'
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -50,9 +50,9 @@ const sentence = computed(() => {
|
|||
|
||||
<div class="mb-5 px-5" style="text-shadow: black 1px 1px 10px">
|
||||
<h1 class="m-0 text-[60px]">{{ title }}</h1>
|
||||
<strong class="min-h-[90px] m-0 text-[24px]" style="font-family: Lato">{{
|
||||
sentence
|
||||
}}</strong>
|
||||
<strong class="min-h-[90px] m-0 text-[24px]" style="font-family: Lato">
|
||||
{{ sentence }}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import { useDark, useToggle } from '@vueuse/core'
|
||||
|
||||
export default () => {
|
||||
const { $vuetify } = useNuxtApp()
|
||||
const config = useAppConfig()
|
||||
|
||||
const dark = useDark({
|
||||
valueLight: config.theme.light,
|
||||
valueDark: config.theme.dark,
|
||||
onChanged: (dark: boolean) => {
|
||||
$vuetify.theme.global.name.value = dark
|
||||
? config.theme.dark
|
||||
: config.theme.light
|
||||
},
|
||||
})
|
||||
|
||||
const toggle = useToggle(dark)
|
||||
|
||||
return { dark, toggle }
|
||||
}
|
50
config.ts
50
config.ts
|
@ -1,4 +1,3 @@
|
|||
import type { ThemeDefinition } from 'vuetify'
|
||||
import packageJSON from './package.json'
|
||||
|
||||
interface TitleConfig {
|
||||
|
@ -11,15 +10,6 @@ interface BuildConfig {
|
|||
version: string
|
||||
}
|
||||
|
||||
interface ThemeConfig {
|
||||
file: string
|
||||
cookie: string
|
||||
default: string
|
||||
light: string
|
||||
dark: string
|
||||
themes: Record<string, ThemeDefinition>
|
||||
}
|
||||
|
||||
type config = {
|
||||
url: string
|
||||
shortener: string
|
||||
|
@ -28,7 +18,6 @@ type config = {
|
|||
description: string
|
||||
locale: string
|
||||
build: BuildConfig
|
||||
theme: ThemeConfig
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -46,43 +35,4 @@ export default {
|
|||
date: new Date().toISOString().split('T')[0],
|
||||
version: packageJSON.version || '0.0.0',
|
||||
},
|
||||
theme: {
|
||||
file: './assets/styles/vuetify.scss',
|
||||
cookie: 'color-scheme',
|
||||
default: 'chocolate',
|
||||
light: 'vanilla',
|
||||
dark: 'chocolate',
|
||||
themes: {
|
||||
vanilla: {
|
||||
dark: false,
|
||||
colors: {
|
||||
background: '#FFFFFF',
|
||||
surface: '#FFFFFF',
|
||||
primary: '#6200EE',
|
||||
'primary-darken-1': '#3700B3',
|
||||
secondary: '#03DAC6',
|
||||
'secondary-darken-1': '#018786',
|
||||
error: '#B00020',
|
||||
info: '#2196F3',
|
||||
success: '#4CAF50',
|
||||
warning: '#FB8C00',
|
||||
},
|
||||
},
|
||||
chocolate: {
|
||||
dark: true,
|
||||
colors: {
|
||||
background: '#000',
|
||||
surface: '#000',
|
||||
primary: '#795548',
|
||||
'primary-darken-1': '#5D4037',
|
||||
secondary: '#FF9800',
|
||||
'secondary-darken-1': '#F57C00',
|
||||
error: '#B00020',
|
||||
info: '#2196F3',
|
||||
success: '#4CAF50',
|
||||
warning: '#FB8C00',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies config as config
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// @ts-check
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
|
||||
export default withNuxt({
|
||||
rules: {
|
||||
'no-unused-vars': 'warn',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/max-attributes-per-line': [
|
||||
'warn',
|
||||
{
|
||||
singleline: 3,
|
||||
multiline: 1,
|
||||
},
|
||||
],
|
||||
'space-in-parens': 'off',
|
||||
'computed-property-spacing': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
})
|
|
@ -1,65 +0,0 @@
|
|||
import 'iconify-icon'
|
||||
import type { IconAliases, IconSet } from 'vuetify'
|
||||
|
||||
const aliases = <IconAliases>{
|
||||
/* custom ones */
|
||||
email: 'mdi:email',
|
||||
/* vuetify aliases */
|
||||
collapse: 'mdi:chevron-up',
|
||||
complete: 'mdi:check',
|
||||
cancel: 'mdi:close-circle',
|
||||
close: 'mdi:close',
|
||||
delete: 'mdi:close-circle',
|
||||
// delete (e.g. v-chip close)
|
||||
clear: 'mdi:close-circle',
|
||||
success: 'mdi:check-circle',
|
||||
info: 'mdi:information',
|
||||
warning: 'mdi:alert-circle',
|
||||
error: 'mdi:close-circle',
|
||||
prev: 'mdi:chevron-left',
|
||||
next: 'mdi:chevron-right',
|
||||
checkboxOn: 'mdi:checkbox-marked',
|
||||
checkboxOff: 'mdi:checkbox-blank-outline',
|
||||
checkboxIndeterminate: 'mdi:minus-box',
|
||||
delimiter: 'mdi:circle',
|
||||
// for carousel
|
||||
sortAsc: 'mdi:arrow-up',
|
||||
sortDesc: 'mdi:arrow-down',
|
||||
expand: 'mdi:chevron-down',
|
||||
menu: 'mdi:menu',
|
||||
subgroup: 'mdi:menu-down',
|
||||
dropdown: 'mdi:menu-down',
|
||||
radioOn: 'mdi:radiobox-marked',
|
||||
radioOff: 'mdi:radiobox-blank',
|
||||
edit: 'mdi:pencil',
|
||||
ratingEmpty: 'mdi:star-outline',
|
||||
ratingFull: 'mdi:star',
|
||||
ratingHalf: 'mdi:star-half-full',
|
||||
loading: 'mdi:cached',
|
||||
first: 'mdi:page-first',
|
||||
last: 'mdi:page-last',
|
||||
unfold: 'mdi:unfold-more-horizontal',
|
||||
file: 'mdi:paperclip',
|
||||
plus: 'mdi:plus',
|
||||
minus: 'mdi:minus',
|
||||
calendar: 'mdi:calendar',
|
||||
$checkboxOn: 'mdi:checkbox-marked',
|
||||
$checkboxOff: 'mdi:checkbox-blank-outline',
|
||||
}
|
||||
|
||||
const iconify = <IconSet>{
|
||||
component: (props: any) => {
|
||||
const { icon, tag, ...rest } = props
|
||||
const strIcon = icon as string
|
||||
|
||||
return h(tag, rest, [
|
||||
h('iconify-icon', {
|
||||
key: strIcon,
|
||||
icon: aliases[strIcon] ?? icon,
|
||||
...rest,
|
||||
}),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
export { aliases, iconify }
|
|
@ -1,8 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { UseSwipeDirection } from '@vueuse/core'
|
||||
import { useSwipe } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { usePageStore } from '~/stores/pages'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
@ -36,23 +34,34 @@ const swipe = useSwipe(card, {
|
|||
}
|
||||
},
|
||||
})
|
||||
|
||||
const animationComplete = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
if (card.value)
|
||||
card.value.addEventListener('animationend', () => {
|
||||
animationComplete.value = true
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
ref="card"
|
||||
class="dimensions background h-animated overflow-auto flex flex-col gap-3 sm:gap-2 px-4 pt-4 pb-3"
|
||||
class="dimensions accent-background transition-ease overflow-auto flex flex-col gap-4 lm:gap-0 sm:gap-2 py-2 px-6 sm:p-4"
|
||||
:class="{
|
||||
'sm:rounded-xl': !reader,
|
||||
'animate__animated-sm animate__delay-1-5s animate__fadeInDown':
|
||||
!animationComplete,
|
||||
'lm:rounded-none sm:rounded-xl sm:mt-8 lm:mt-0 lt:mt-0': !reader,
|
||||
'!max-h-full h-full': reader,
|
||||
}"
|
||||
>
|
||||
<Options />
|
||||
<Settings v-if="animationComplete" />
|
||||
<Navigation />
|
||||
<slot name="header" />
|
||||
<NuxtPage
|
||||
class="scrollbar fade-mask-sm flex-grow overflow-x-hidden overflow-y-auto min-h-full sm:py-4 sm:pe-3"
|
||||
class="sm:fade-mask flex-grow overflow-y-auto h-full sm:py-4 sm:pe-4"
|
||||
/>
|
||||
<slot name="footer" />
|
||||
<slot v-if="animationComplete" name="footer" />
|
||||
</main>
|
||||
</template>
|
||||
|
|
176
nuxt.config.ts
176
nuxt.config.ts
|
@ -1,35 +1,35 @@
|
|||
import config from './config'
|
||||
import config from "./config";
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
app: {
|
||||
head: {
|
||||
htmlAttrs: {
|
||||
lang: config.locale || 'en',
|
||||
lang: config.locale || "en",
|
||||
},
|
||||
link: [
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/x-icon',
|
||||
href: '/favicon.ico',
|
||||
rel: "icon",
|
||||
type: "image/x-icon",
|
||||
href: "/favicon.ico",
|
||||
},
|
||||
],
|
||||
meta: [
|
||||
{
|
||||
charset: 'utf-8',
|
||||
charset: "utf-8",
|
||||
},
|
||||
{
|
||||
name: 'viewport',
|
||||
content: 'width=device-width, initial-scale=1',
|
||||
name: "viewport",
|
||||
content: "width=device-width, initial-scale=1",
|
||||
},
|
||||
{
|
||||
'http-equiv': 'X-UA-Compatible',
|
||||
content: 'ie=edge',
|
||||
"http-equiv": "X-UA-Compatible",
|
||||
content: "ie=edge",
|
||||
},
|
||||
],
|
||||
},
|
||||
pageTransition: { name: 'page', mode: 'out-in' },
|
||||
rootId: 'ender-app',
|
||||
pageTransition: { name: "page", mode: "out-in" },
|
||||
rootId: "ender-app",
|
||||
},
|
||||
devtools: {
|
||||
enabled: true,
|
||||
|
@ -43,56 +43,64 @@ export default defineNuxtConfig({
|
|||
components: {
|
||||
dirs: [
|
||||
{
|
||||
path: '~/components',
|
||||
path: "~/components",
|
||||
pathPrefix: false,
|
||||
extensions: ['.vue'],
|
||||
extensions: [".vue"],
|
||||
},
|
||||
],
|
||||
},
|
||||
css: ['~/assets/styles/main.scss'],
|
||||
css: ["~/assets/styles/main.scss"],
|
||||
plugins: [],
|
||||
modules: [
|
||||
['@nuxtjs/eslint-module', { failOnError: false, lintOnStart: false }],
|
||||
['@nuxtjs/stylelint-module', { failOnError: true, lintOnStart: false }],
|
||||
'@pinia/nuxt',
|
||||
'@nuxt/content',
|
||||
'vuetify-nuxt-module',
|
||||
'@nuxtjs/seo',
|
||||
'@nuxtjs/google-fonts',
|
||||
'@nuxtjs/tailwindcss',
|
||||
"@pinia/nuxt",
|
||||
"@nuxt/content",
|
||||
"@nuxtjs/seo",
|
||||
"@nuxtjs/google-fonts",
|
||||
"@nuxtjs/tailwindcss",
|
||||
"@nuxtjs/color-mode",
|
||||
"@nuxt/eslint",
|
||||
["@nuxtjs/stylelint-module", { failOnError: true, lintOnStart: false }],
|
||||
],
|
||||
colorMode: {
|
||||
preference: "system",
|
||||
fallback: "dark",
|
||||
classPrefix: "",
|
||||
classSuffix: "",
|
||||
componentName: "NuxtTheme",
|
||||
storageKey: "ecmatheme",
|
||||
},
|
||||
content: {
|
||||
markdown: {
|
||||
remarkPlugins: ['remark-reading-time'],
|
||||
remarkPlugins: ["remark-reading-time"],
|
||||
},
|
||||
highlight: {
|
||||
theme: 'github-dark',
|
||||
theme: "github-dark",
|
||||
langs: [
|
||||
'shell',
|
||||
'batch',
|
||||
'vb',
|
||||
'ini',
|
||||
'asm',
|
||||
'c',
|
||||
'cpp',
|
||||
'java',
|
||||
'python',
|
||||
'csv',
|
||||
'xml',
|
||||
'json',
|
||||
'yaml',
|
||||
'html',
|
||||
'css',
|
||||
'sass',
|
||||
'php',
|
||||
'js',
|
||||
'ts',
|
||||
'vue',
|
||||
'md',
|
||||
'mdc',
|
||||
'pascal',
|
||||
'lisp',
|
||||
'sql',
|
||||
"shell",
|
||||
"batch",
|
||||
"vb",
|
||||
"ini",
|
||||
"asm",
|
||||
"c",
|
||||
"cpp",
|
||||
"java",
|
||||
"python",
|
||||
"csv",
|
||||
"xml",
|
||||
"json",
|
||||
"yaml",
|
||||
"html",
|
||||
"css",
|
||||
"sass",
|
||||
"php",
|
||||
"js",
|
||||
"ts",
|
||||
"vue",
|
||||
"md",
|
||||
"mdc",
|
||||
"pascal",
|
||||
"lisp",
|
||||
"sql",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -101,53 +109,7 @@ export default defineNuxtConfig({
|
|||
crawlLinks: true,
|
||||
autoSubfolderIndex: true,
|
||||
failOnError: true,
|
||||
routes: ['/robots.txt', '/sitemap.xml'],
|
||||
},
|
||||
},
|
||||
vuetify: {
|
||||
moduleOptions: {
|
||||
importComposables: true,
|
||||
prefixComposables: true,
|
||||
styles: {
|
||||
configFile: config.theme.file,
|
||||
},
|
||||
includeTransformAssetsUrls: true,
|
||||
ssrClientHints: {
|
||||
reloadOnFirstRequest: false,
|
||||
viewportSize: true,
|
||||
prefersColorScheme: true,
|
||||
prefersColorSchemeOptions: {
|
||||
cookieName: config.theme.cookie,
|
||||
lightThemeName: config.theme.light,
|
||||
darkThemeName: config.theme.dark,
|
||||
useBrowserThemeOnly: true,
|
||||
},
|
||||
prefersReducedMotion: false,
|
||||
},
|
||||
},
|
||||
vuetifyOptions: {
|
||||
ssr: {
|
||||
clientWidth: 0,
|
||||
clientHeight: 0,
|
||||
},
|
||||
components: false,
|
||||
labComponents: true,
|
||||
directives: false,
|
||||
date: {
|
||||
adapter: 'vuetify',
|
||||
},
|
||||
theme: {
|
||||
defaultTheme: config.theme.default,
|
||||
themes: config.theme.themes,
|
||||
},
|
||||
icons: {
|
||||
defaultSet: 'custom',
|
||||
},
|
||||
defaults: {
|
||||
VBtn: {
|
||||
style: 'text-transform: none; letter-spacing: normal;',
|
||||
},
|
||||
},
|
||||
routes: ["/robots.txt", "/sitemap.xml"],
|
||||
},
|
||||
},
|
||||
googleFonts: {
|
||||
|
@ -198,22 +160,22 @@ export default defineNuxtConfig({
|
|||
// },
|
||||
// },
|
||||
sitemap: {
|
||||
sources: ['/api/__sitemap__/content'],
|
||||
sources: ["/api/__sitemap__/content"],
|
||||
cacheMaxAgeSeconds: 360,
|
||||
exclude: [],
|
||||
credits: false,
|
||||
xslColumns: [
|
||||
{ label: 'URL', width: '50%' },
|
||||
{ label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' },
|
||||
{ label: 'Priority', select: 'sitemap:priority', width: '12.5%' },
|
||||
{ label: "URL", width: "50%" },
|
||||
{ label: "Last Modified", select: "sitemap:lastmod", width: "25%" },
|
||||
{ label: "Priority", select: "sitemap:priority", width: "12.5%" },
|
||||
{
|
||||
label: 'Change Frequency',
|
||||
select: 'sitemap:changefreq',
|
||||
width: '12.5%',
|
||||
label: "Change Frequency",
|
||||
select: "sitemap:changefreq",
|
||||
width: "12.5%",
|
||||
},
|
||||
],
|
||||
defaults: {
|
||||
changefreq: 'yearly',
|
||||
changefreq: "yearly",
|
||||
priority: 0.7,
|
||||
lastmod: config.build.date,
|
||||
},
|
||||
|
@ -232,7 +194,7 @@ export default defineNuxtConfig({
|
|||
},
|
||||
vue: {
|
||||
compilerOptions: {
|
||||
isCustomElement: (tag) => tag === 'iconify-icon',
|
||||
isCustomElement: (tag) => tag === "iconify-icon",
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "enderapp",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
@ -17,41 +17,43 @@
|
|||
"devDependencies": {
|
||||
"@nuxt/content": "^2.12.1",
|
||||
"@nuxt/devtools": "^1.3.3",
|
||||
"@nuxt/eslint": "^0.3.13",
|
||||
"@nuxt/types": "^2.17.3",
|
||||
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
||||
"@nuxtjs/eslint-module": "^4.1.0",
|
||||
"@nuxtjs/color-mode": "^3.4.1",
|
||||
"@nuxtjs/google-fonts": "^3.2.0",
|
||||
"@nuxtjs/seo": "^2.0.0-rc.10",
|
||||
"@nuxtjs/stylelint-module": "^5.2.0",
|
||||
"@nuxtjs/tailwindcss": "^6.12.0",
|
||||
"@pinia/nuxt": "^0.5.1",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@typescript-eslint/eslint-plugin": "^7.13.0",
|
||||
"@typescript-eslint/parser": "^7.13.0",
|
||||
"animate.css": "latest",
|
||||
"caniuse-lite": "^1.0.30001634",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-nuxt": "^4.0.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"iconify-icon": "^1.0.8",
|
||||
"nuxt": "^3.12.1",
|
||||
"pinia": "^2.1.7",
|
||||
"prettier": "^3.3.2",
|
||||
"sass": "^1.77.5",
|
||||
"stylelint": "^16.6.1",
|
||||
"stylelint-config-recommended-scss": "^14.0.0",
|
||||
"stylelint-config-recommended-vue": "^1.5.0",
|
||||
"stylelint-config-standard-scss": "^13.1.0",
|
||||
"stylelint-config-tailwindcss": "^0.0.7",
|
||||
"stylelint-scss": "^6.3.1",
|
||||
"typescript": "^5.4.5",
|
||||
"vue": "^3.4.28",
|
||||
"vue-router": "^4.3.3",
|
||||
"vue-tsc": "^1.8.22",
|
||||
"vuetify": "^3.6.9",
|
||||
"vuetify-nuxt-module": "^0.14.1"
|
||||
"vue-tsc": "^1.8.22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@date-io/date-fns": "^3.0.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"iconify-icon": "^2.1.0",
|
||||
"remark-reading-time": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
">0.3%",
|
||||
"not dead",
|
||||
"defaults",
|
||||
"fully supports es6-module",
|
||||
"maintained node versions"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -41,27 +41,21 @@ useHead({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<h4>About me</h4>
|
||||
<hr class="accent accent-gradient border-0 h-px" />
|
||||
<p>
|
||||
<strong>🚧 This page is currently under construction.</strong> Expect a
|
||||
lot more content to be added as time passes.
|
||||
<em>Please report all bugs you encounter via the link in the footer</em>,
|
||||
I will make sure to sand them down.
|
||||
</p>
|
||||
<hr class="accent accent-gradient border-0 h-px" />
|
||||
<section class="page">
|
||||
<h3>About me</h3>
|
||||
<Construction />
|
||||
<p>
|
||||
Nice to meet you! I'm Andrew, a {{ age }}-year-old guy from Kaluga,
|
||||
Russia. I have been developing software since I was 10, and I have always
|
||||
been interested in technology. I'm a middle-senior C/++ developer and a
|
||||
junior full-stack engineer.
|
||||
<sup>1</sup>
|
||||
</p>
|
||||
<p class="font-small">
|
||||
<br />
|
||||
<small>
|
||||
<sup>1</sup>
|
||||
All titles are based on knowledge and confidence, not on official work
|
||||
experience.
|
||||
</small>
|
||||
</p>
|
||||
<p><strong>Here's a little more about myself:</strong></p>
|
||||
<ul>
|
||||
|
@ -70,11 +64,11 @@ useHead({
|
|||
<li>My favorite field in mathematics is Algebraic Geometry.</li>
|
||||
</ul>
|
||||
<p>
|
||||
I work on all sorts of projects, most of the times simultaneously, which
|
||||
I work on all sorts of projects, most of the time simultaneously, which
|
||||
doesn't help with adhering to deadlines at all. However, you can always
|
||||
check them out!
|
||||
</p>
|
||||
<h5 class="alex">FAQ</h5>
|
||||
<h5>FAQ</h5>
|
||||
<ul class="list-style-type-faq">
|
||||
<li><strong>Why are you called Endermanch?</strong></li>
|
||||
<li>
|
||||
|
@ -84,10 +78,9 @@ useHead({
|
|||
end sounded pretty good. I was 14 at the time of making that decision.
|
||||
</li>
|
||||
<li>
|
||||
<strong
|
||||
>When will you make a new video? What happened to your
|
||||
schedule?</strong
|
||||
>
|
||||
<strong>
|
||||
When will you make a new video? What happened to your schedule?
|
||||
</strong>
|
||||
</li>
|
||||
<li>
|
||||
One day. Haven't been particularly motivated lately, but life's busy. 🙁
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import { formatDate } from 'date-fns'
|
||||
import { useAsyncData } from '#app'
|
||||
import chestAnimation from '~/assets/images/chest.webp'
|
||||
|
||||
const config = useAppConfig()
|
||||
const route = useRoute()
|
||||
|
||||
let thumbnail: string | null = null
|
||||
let slug = useRoute().params.slug
|
||||
let slug = route.params.slug
|
||||
|
||||
if (Array.isArray(slug)) slug = slug.join('/')
|
||||
|
||||
const { data } = await useAsyncData('home', () =>
|
||||
const { data, status } = await useAsyncData('home', () =>
|
||||
queryContent(`/blog/${slug}`).findOne(),
|
||||
)
|
||||
|
||||
|
@ -58,55 +59,55 @@ if (data.value) {
|
|||
)
|
||||
|
||||
// Hydrate the rendered items.
|
||||
onMounted(() => {
|
||||
document.querySelectorAll('pre').forEach((pre) => {
|
||||
const icon = document.createElement('iconify-icon')
|
||||
|
||||
icon.setAttribute('icon', 'mdi:content-copy')
|
||||
icon.setAttribute('inline', 'true')
|
||||
|
||||
icon.classList.add('button')
|
||||
|
||||
pre.appendChild(icon)
|
||||
})
|
||||
|
||||
document
|
||||
.querySelectorAll('code:not(pre *), pre > iconify-icon.button')
|
||||
.forEach((code) => {
|
||||
if (code instanceof HTMLElement) {
|
||||
code.onclick = () => {
|
||||
const area = document.createElement('textarea')
|
||||
|
||||
area.textContent =
|
||||
code.nodeName && code.nodeName.toLowerCase() === 'code'
|
||||
? code.textContent
|
||||
: code.parentElement!.textContent
|
||||
|
||||
// It's necessary to create the textarea element every time you copy to get access to the select() method.
|
||||
area.setSelectionRange(0, 99999) // An iOS gotcha.
|
||||
area.select()
|
||||
|
||||
// Copy the text inside the textarea.
|
||||
navigator.clipboard.writeText(area.value)
|
||||
|
||||
// TODO: Alert the user text has been successfully copied.
|
||||
|
||||
// Remove the textarea element.
|
||||
area.remove()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.querySelectorAll('code').forEach((code) => {
|
||||
code.onclick = null
|
||||
})
|
||||
|
||||
document.querySelectorAll('pre').forEach((pre) => {
|
||||
pre.querySelector('.clipboard')?.remove()
|
||||
})
|
||||
})
|
||||
// onMounted(() => {
|
||||
// document.querySelectorAll('pre').forEach((pre) => {
|
||||
// const icon = document.createElement('iconify-icon')
|
||||
//
|
||||
// icon.setAttribute('icon', 'mdi:content-copy')
|
||||
// icon.setAttribute('inline', 'true')
|
||||
//
|
||||
// icon.classList.add('button')
|
||||
//
|
||||
// pre.appendChild(icon)
|
||||
// })
|
||||
//
|
||||
// document
|
||||
// .querySelectorAll('code:not(pre *), pre > iconify-icon.button')
|
||||
// .forEach((code) => {
|
||||
// if (code instanceof HTMLElement) {
|
||||
// code.onclick = () => {
|
||||
// const area = document.createElement('textarea')
|
||||
//
|
||||
// area.textContent =
|
||||
// code.nodeName && code.nodeName.toLowerCase() === 'code'
|
||||
// ? code.textContent
|
||||
// : code.parentElement!.textContent
|
||||
//
|
||||
// // It's necessary to create the textarea element every time you copy to get access to the select() method.
|
||||
// area.setSelectionRange(0, 99999) // An iOS gotcha.
|
||||
// area.select()
|
||||
//
|
||||
// // Copy the text inside the textarea.
|
||||
// navigator.clipboard.writeText(area.value)
|
||||
//
|
||||
// // TODO: Alert the user text has been successfully copied.
|
||||
//
|
||||
// // Remove the textarea element.
|
||||
// area.remove()
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// onUnmounted(() => {
|
||||
// document.querySelectorAll('code').forEach((code) => {
|
||||
// code.onclick = null
|
||||
// })
|
||||
//
|
||||
// document.querySelectorAll('pre').forEach((pre) => {
|
||||
// pre.querySelector('.clipboard')?.remove()
|
||||
// })
|
||||
// })
|
||||
}
|
||||
|
||||
useHead({
|
||||
|
@ -125,44 +126,68 @@ useHead({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="status === 'pending'"
|
||||
class="flex flex-col items-center justify-center gap-4 w-full select-none text-center"
|
||||
>
|
||||
<img draggable="false" :src="chestAnimation" alt="The Ender Chest" />
|
||||
<div>
|
||||
<h1 class="font-enchant">Loading document...</h1>
|
||||
<span class="opacity-0 hover:opacity-100 transition-ease">
|
||||
<em>Loading document...</em>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<article
|
||||
v-if="data"
|
||||
class="post scrollbar fade-mask-sm flex flex-col gap-3 flex-grow-1 overflow-x-hidden overflow-y-auto sm:py-4 sm:pe-3"
|
||||
v-else-if="status === 'success' && data"
|
||||
class="flex-grow post fade-mask-sm flex flex-col gap-4 overflow-x-hidden overflow-y-auto sm:py-4 sm:pe-4"
|
||||
>
|
||||
<div
|
||||
class="post-preamble"
|
||||
class="relative flex flex-col justify-end items-start w-full min-h-[400px] rounded-xl accent-text-shadow post-preamble"
|
||||
:style="{ backgroundImage: 'url(' + thumbnail + ')' }"
|
||||
>
|
||||
<div class="p-2">
|
||||
<h3 class="alex">{{ data.title }}</h3>
|
||||
<div class="flex flex-row gap-x-0 gap-y-2 flex-wrap">
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<h3>{{ data.title }}</h3>
|
||||
<div class="flex flex-row flex-wrap gap-x-2 gap-y-0">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<iconify-icon icon="mdi:calendar" />
|
||||
<strong class="nobr font-small">
|
||||
<small class="whitespace-nowrap">
|
||||
<strong>
|
||||
{{ formatDate(data.created, 'LLLL do, y – HH:mm') }}
|
||||
</strong>
|
||||
</small>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<iconify-icon icon="mdi:clock-outline" />
|
||||
<strong class="nobr font-small">
|
||||
<small class="whitespace-nowrap">
|
||||
<strong>
|
||||
{{ data!.readingTime.text.split(' ')[0] + ' minutes to read' }}
|
||||
</strong>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NuxtLink
|
||||
to="/blog"
|
||||
class="absolute top-0 left-0 p-2 text-inherit no-underline"
|
||||
>
|
||||
<iconify-icon
|
||||
icon="icon-park-solid:back"
|
||||
width="2em"
|
||||
height="2em"
|
||||
style="color: lavender"
|
||||
/>
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
:href="`https://twitter.com/share?url=${config.url}/blog/${slug}&text=${data.title}&hashtags=${data.tags.slice(0, 3).join(',').replace(/ /g, '')}`"
|
||||
target="_blank"
|
||||
class="link post-preamble-share px-2 py-1"
|
||||
class="absolute top-0 right-0 p-2"
|
||||
>
|
||||
<iconify-icon icon="mdi:twitter"></iconify-icon>
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/blog" class="link post-preamble-control px-2 py-1">
|
||||
<iconify-icon icon="icon-park-solid:back"></iconify-icon>
|
||||
<iconify-icon icon="logos:twitter" width="2em" height="2em" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="post-content">
|
||||
<hr class="accent accent-gradient border-0 h-px" />
|
||||
<hr class="accent-text accent-gradient border-0 h-px" />
|
||||
<ContentRenderer :value="data" />
|
||||
</div>
|
||||
</article>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { formatDate } from 'date-fns'
|
|||
|
||||
const config = useAppConfig()
|
||||
const meta = {
|
||||
title: 'The Enderchest',
|
||||
title: 'The Ender Chest',
|
||||
description:
|
||||
'A blog about software development, scientific research and Windows quirks.',
|
||||
image: `${config.url}/images/chest.png`,
|
||||
|
@ -25,7 +25,7 @@ useSeoMeta({
|
|||
})
|
||||
|
||||
useHead({
|
||||
title: 'The Enderchest',
|
||||
title: 'The Ender Chest',
|
||||
htmlAttrs: {
|
||||
lang: config.locale || 'en',
|
||||
},
|
||||
|
@ -41,12 +41,12 @@ useHead({
|
|||
|
||||
<template>
|
||||
<section>
|
||||
<h3 class="alex">The Enderchest</h3>
|
||||
<p>
|
||||
You're browsing the enderchest — a blog about software development,
|
||||
<h3 class="mb-2">The Ender Chest</h3>
|
||||
<p class="mb-4">
|
||||
You're browsing the Ender Chest — a blog about software development,
|
||||
scientific research and Windows quirks.
|
||||
</p>
|
||||
<h4 class="alex">Recent posts</h4>
|
||||
<h4 class="mb-2">Recent posts</h4>
|
||||
<ContentList
|
||||
:query="{
|
||||
path: '/blog',
|
||||
|
@ -56,15 +56,15 @@ useHead({
|
|||
}"
|
||||
>
|
||||
<template #default="{ list }">
|
||||
<div class="grid gap-3">
|
||||
<div class="grid gap-4">
|
||||
<NuxtLink
|
||||
v-for="post in list"
|
||||
:key="post._path"
|
||||
:to="post._path"
|
||||
class="no-decoration"
|
||||
class="text-inherit hover:text-inherit no-underline"
|
||||
>
|
||||
<article
|
||||
class="post-box flex flex-col md:flex-row gap-3 rounded-xl h-full"
|
||||
class="post-box flex flex-col md:flex-row gap-4 rounded-xl h-full"
|
||||
>
|
||||
<div class="post-thumb">
|
||||
<img
|
||||
|
@ -78,46 +78,48 @@ useHead({
|
|||
/>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<h3 class="post-title alex">{{ post.title }}</h3>
|
||||
<h3 class="post-title">{{ post.title }}</h3>
|
||||
<p class="post-description mb-0">{{ post.description }}</p>
|
||||
|
||||
<div class="post-tags flex flex-row flex-wrap gap-2 py-2">
|
||||
<span
|
||||
v-for="(tag, index) in post.tags.slice(0, 3)"
|
||||
:key="index"
|
||||
class="nobr"
|
||||
class="whitespace-nowrap"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
<span v-if="post.tags.length > 3" class="nobr">
|
||||
<span v-if="post.tags.length > 3" class="whitespace-nowrap">
|
||||
{{ post.tags.length - 3 }} more
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<hr class="accent accent-gradient border-0 h-px" />
|
||||
<hr class="accent-text accent-gradient border-0 h-px" />
|
||||
|
||||
<div class="post-details py-2">
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<iconify-icon icon="mdi:calendar" />
|
||||
<small class="nobr">
|
||||
<small class="whitespace-nowrap">
|
||||
{{ formatDate(post.created, 'LLLL do, y – HH:mm') }}
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
v-if="post.updated"
|
||||
class="flex flex-row items-center gap-1"
|
||||
class="flex flex-row items-center gap-2"
|
||||
>
|
||||
<iconify-icon icon="mdi:pencil" />
|
||||
<small class="nobr">
|
||||
<small class="whitespace-nowrap">
|
||||
{{ formatDate(post.updated, 'LLLL do, y – HH:mm') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post-pocket flex flex-row items-center gap-1 p-2">
|
||||
<div
|
||||
class="post-pocket absolute bottom-0 right-0 uppercase rounded-tl-xl flex flex-row items-center gap-2 p-2"
|
||||
>
|
||||
<iconify-icon icon="mdi:clock-outline" />
|
||||
<small class="nobr font-monospace font-small">
|
||||
<small class="whitespace-nowrap font-mono font-small">
|
||||
{{ post.readingTime.text }}
|
||||
</small>
|
||||
</div>
|
||||
|
@ -127,13 +129,11 @@ useHead({
|
|||
</template>
|
||||
|
||||
<template #not-found>
|
||||
<div class="flex flex-col justify-center items-center gap-3">
|
||||
<div class="flex flex-col justify-center items-center gap-4">
|
||||
<span>No posts found 🙁</span>
|
||||
<NuxtLink to="/" class="link-hi">Back to index</NuxtLink>
|
||||
<NuxtLink to="/">Back to index</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
</ContentList>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
|
@ -46,18 +46,17 @@ defineRouteRules({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<section class="page">
|
||||
<h3>Welcome 👋</h3>
|
||||
<p class="mb-4">
|
||||
<p>
|
||||
I'm <strong>Enderman</strong> – a software engineer, a malware
|
||||
enthusiast and most importantly, a weird tall creature. I have over 300K
|
||||
subscribers on <a :href="`${config.shortener}/youtube`">YouTube</a> and
|
||||
over 25K followers on
|
||||
<a class="text-inherit no-underline" :href="`${config.shortener}/twitter`"
|
||||
>Twitter</a
|
||||
>. Sometimes I wish there were 48 hours in a day.
|
||||
<a :href="`${config.shortener}/twitter`">Twitter</a>. Sometimes I wish
|
||||
there were 48 hours in a day.
|
||||
</p>
|
||||
<div class="flex flex-col md:flex-row gap-3 mb-4">
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<div class="md:basis-0 md:flex-grow-[2]">
|
||||
<h6>What I do</h6>
|
||||
<ul class="list-style-type-do">
|
||||
|
@ -66,7 +65,7 @@ defineRouteRules({
|
|||
<strong>C/C++</strong> for desktop applications and any common
|
||||
backend stack with framework-loaded TypeScript for web. You can take
|
||||
a look at my code on
|
||||
<a class="link-hi" :href="`${config.shortener}/github`">GitHub</a>.
|
||||
<a :href="`${config.shortener}/github`">GitHub</a>.
|
||||
</li>
|
||||
<li>
|
||||
I have the most unnecessary, yet fascinating knowledge about
|
||||
|
@ -79,8 +78,7 @@ defineRouteRules({
|
|||
<li>
|
||||
I research and analyze modern malware, educate computer users about
|
||||
it and preserve history. The repository can be found
|
||||
<a class="link-hi" :href="`${config.shortener}/repository`">here</a
|
||||
>.
|
||||
<a :href="`${config.shortener}/repository`">here</a>.
|
||||
</li>
|
||||
<li>I make videos for you to enjoy!</li>
|
||||
</ul>
|
||||
|
@ -95,7 +93,7 @@ defineRouteRules({
|
|||
<li>Philosophy</li>
|
||||
<li>Geopolitics</li>
|
||||
<li>
|
||||
<a class="link-hi" :href="`${config.shortener}/chess`">Chess</a>
|
||||
<a :href="`${config.shortener}/chess`">Chess</a>
|
||||
</li>
|
||||
<li>Solitude</li>
|
||||
</ul>
|
||||
|
|
|
@ -67,20 +67,13 @@ useHead({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<h3 class="alex">Projects</h3>
|
||||
<hr class="accent accent-gradient border-0 h-px" />
|
||||
<p>
|
||||
<strong>🚧 This page is currently under construction.</strong> Expect a
|
||||
lot more content to be added as time passes.
|
||||
<em>Please report all bugs you encounter via the link in the footer</em>,
|
||||
I will make sure to sand them down.
|
||||
</p>
|
||||
<hr class="accent accent-gradient border-0 h-px" />
|
||||
<section class="page">
|
||||
<h3>Projects</h3>
|
||||
<Construction />
|
||||
<p><strong>My current projects are:</strong></p>
|
||||
<ul>
|
||||
<li v-for="(item, index) in projects" :key="index">
|
||||
<a class="link-hi" :href="item.url">
|
||||
<a :href="item.url">
|
||||
{{ item.name }}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -115,39 +115,34 @@ useHead({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<h3 class="alex">Online presence</h3>
|
||||
<hr class="accent accent-gradient border-0 h-px" />
|
||||
<p>
|
||||
<strong>🚧 This page is currently under construction.</strong> Expect a
|
||||
lot more content to be added as time passes.
|
||||
<em>Please report all bugs you encounter via the link in the footer</em>,
|
||||
I will make sure to sand them down.
|
||||
</p>
|
||||
<hr class="accent accent-gradient border-0 h-px" />
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-3">
|
||||
<h5 class="alex">Social media</h5>
|
||||
<section class="page">
|
||||
<h3>Online presence</h3>
|
||||
<Construction />
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<div class="md:basis-0 md:flex-grow-[1]">
|
||||
<h5>Social media</h5>
|
||||
<ul class="list-style-type-none p-0">
|
||||
<li v-for="(page, index) in socials" :key="index">
|
||||
<a class="link-hi" target="_blank" rel="noopener" :href="page.url">
|
||||
<Icon :name="page.icon.name" :color="page.icon.color" inline />
|
||||
<NuxtLink target="_blank" rel="noopener" :href="page.url">
|
||||
<iconify-icon
|
||||
:icon="page.icon.name"
|
||||
:style="{ color: page.icon.color }"
|
||||
width="1em"
|
||||
height="1em"
|
||||
inline
|
||||
/>
|
||||
<span class="mx-2">{{ page.name }}</span>
|
||||
</a>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-12 col-md-9">
|
||||
<h5 class="alex">Contact me</h5>
|
||||
<div class="md:basis-0 md:flex-grow-[3]">
|
||||
<h5>Contact me</h5>
|
||||
<p>
|
||||
Personal:
|
||||
<EMail
|
||||
class="link-hi"
|
||||
:address="`contact@${fqdn}`"
|
||||
subject="Hey Enderman!"
|
||||
/><br />
|
||||
Manager: <EMail class="link-hi" :address="`manager@${fqdn}`" /><br />
|
||||
Abuse: <EMail class="link-hi" :address="`abuse@${fqdn}`" />
|
||||
<EMail :address="`contact@${fqdn}`" subject="Hey Enderman!" /><br />
|
||||
Manager: <EMail :address="`manager@${fqdn}`" /><br />
|
||||
Abuse: <EMail :address="`abuse@${fqdn}`" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
export default defineNuxtPlugin(() => {
|
||||
return {
|
||||
provide: {
|
||||
local: {
|
||||
getItem(item: string) {
|
||||
if (import.meta.client) {
|
||||
return localStorage.getItem(item)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
},
|
||||
|
||||
setItem(item: string, value: any) {
|
||||
if (import.meta.client) {
|
||||
return localStorage.setItem(item, value)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
|
@ -1,11 +0,0 @@
|
|||
import { aliases, iconify } from '~/iconify'
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.hook('vuetify:before-create', ({ vuetifyOptions }) => {
|
||||
vuetifyOptions.icons = {
|
||||
defaultSet: 'iconify',
|
||||
aliases,
|
||||
sets: { iconify },
|
||||
}
|
||||
})
|
||||
})
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* @see https://prettier.io/docs/en/configuration.html
|
||||
* @type {import("prettier").Config}
|
||||
*/
|
||||
export default {
|
||||
semi: false,
|
||||
quoteProps: 'as-needed',
|
||||
singleQuote: true,
|
||||
useTabs: false,
|
||||
tabWidth: 2,
|
||||
trailingComma: 'all',
|
||||
bracketSpacing: true,
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
import aboutIcon from '~/assets/images/icons/accent/info.png'
|
||||
import projectIcon from '~/assets/images/icons/defrag.png'
|
||||
import socialIcon from '~/assets/images/icons/user.png'
|
||||
|
@ -50,8 +48,9 @@ export const usePageStore = defineStore('page', () => {
|
|||
},
|
||||
])
|
||||
|
||||
const reader = ref(false)
|
||||
const animate = ref(true)
|
||||
const reader = ref<boolean>(false)
|
||||
const animate = ref<boolean>(false)
|
||||
const dark = ref<boolean>(false)
|
||||
|
||||
function _autoFetchPages() {
|
||||
while (pages.value.length) pages.value.pop()
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/** @type {import("stylelint").Config} */
|
||||
export default {
|
||||
defaultSeverity: 'warning',
|
||||
formatter: 'compact',
|
||||
cache: false,
|
||||
fix: true,
|
||||
extends: [
|
||||
'stylelint-config-standard-scss',
|
||||
'stylelint-config-recommended-scss',
|
||||
'stylelint-config-tailwindcss/scss',
|
||||
'stylelint-config-recommended-vue/scss',
|
||||
],
|
||||
plugins: ['stylelint-scss'],
|
||||
rules: {
|
||||
'at-rule-no-unknown': null,
|
||||
'scss/at-rule-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreAtRules: ['tailwind', 'responsive', 'screen'],
|
||||
},
|
||||
],
|
||||
'declaration-empty-line-before': null,
|
||||
},
|
||||
}
|
|
@ -11,6 +11,14 @@ export default <Partial<Config>>{
|
|||
'2xl': '2560px',
|
||||
|
||||
desktop: '960px',
|
||||
// Landscape Mobile (LM)
|
||||
lm: {
|
||||
raw: '(max-height: 600px)',
|
||||
},
|
||||
// Landscape Tablet (LT)
|
||||
lt: {
|
||||
raw: '(max-height: 996px) and (min-width: 601px) and (max-width: 1280px)',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
|
@ -38,6 +46,7 @@ export default <Partial<Config>>{
|
|||
'monospace',
|
||||
],
|
||||
alex: ['Alexandria', 'serif'],
|
||||
enchant: ['Enchant', 'serif'],
|
||||
lato: ['Lato', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue