This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<head>
|
<head>
|
||||||
<title>Aran Central</title>
|
<title>Aran Central</title>
|
||||||
@@ -11,17 +13,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue';
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
setupTheme()
|
||||||
|
|
||||||
if (isDarkMode) {
|
|
||||||
document.documentElement.setAttribute("data-theme", "dark");
|
|
||||||
} else {
|
|
||||||
document.documentElement.setAttribute("data-theme", "light");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ $themes: (
|
|||||||
background-line: #202324,
|
background-line: #202324,
|
||||||
background-fore: #10141f,
|
background-fore: #10141f,
|
||||||
border-color: #819796,
|
border-color: #819796,
|
||||||
|
border: #202324,
|
||||||
text: #ebede9,
|
text: #ebede9,
|
||||||
link: #a4dddb,
|
|
||||||
container-shadow: #151d28
|
container-shadow: #151d28
|
||||||
),
|
),
|
||||||
light: (
|
light: (
|
||||||
@@ -15,22 +15,46 @@ $themes: (
|
|||||||
background-line: #f0f0f0,
|
background-line: #f0f0f0,
|
||||||
background-fore: #ffffff,
|
background-fore: #ffffff,
|
||||||
border-color: #e0e0e0,
|
border-color: #e0e0e0,
|
||||||
|
border: #f0f0f0,
|
||||||
text: #1e1e1e,
|
text: #1e1e1e,
|
||||||
link: #6590b3,
|
|
||||||
container-shadow: #5f6774
|
container-shadow: #5f6774
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$accents: (
|
||||||
|
alfdir: (
|
||||||
|
link: #a4dddb,
|
||||||
|
),
|
||||||
|
solus: (
|
||||||
|
link: #e6a556,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
@mixin theme-vars($theme-map) {
|
@mixin theme-vars($theme-map) {
|
||||||
@each $name, $value in $theme-map {
|
@each $name, $value in $theme-map {
|
||||||
--color-#{$name}: #{$value};
|
--color-#{$name}: #{$value};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin accent-vars($accent-map) {
|
||||||
|
@each $name, $value in $accent-map {
|
||||||
|
--color-#{$name}: #{$value};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@include theme-vars(map.get($themes, dark));
|
@include theme-vars(map.get($themes, dark));
|
||||||
|
@include accent-vars(map.get($accents, alfdir));
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="light"] {
|
[data-theme="light"] {
|
||||||
@include theme-vars(map.get($themes, light));
|
@include theme-vars(map.get($themes, light));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-accent="alfdir"] {
|
||||||
|
@include accent-vars(map.get($accents, alfdir));
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-accent="solus"] {
|
||||||
|
@include accent-vars(map.get($accents, solus));
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="menus">
|
<div class="menus">
|
||||||
<NuxtLink href="/">About</NuxtLink>
|
<NuxtLink href="/">About</NuxtLink>
|
||||||
<NuxtLink href="/blog">Blogs</NuxtLink>
|
<NuxtLink href="/blog">Blog</NuxtLink>
|
||||||
<NuxtLink class="disabled">Drawings</NuxtLink>
|
<NuxtLink class="disabled">Drawings</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.menus {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menus > a {
|
.menus > a {
|
||||||
margin-right: 20px;
|
margin-right: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: 0 0 1px var(--color-link);
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabled {
|
.disabled {
|
||||||
|
|||||||
@@ -1,21 +1,40 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import HeaderLinks from './HeaderLinks.vue';
|
import HeaderLinks from './HeaderLinks.vue';
|
||||||
|
import SiteOptions from './site_options/SiteOptions.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<Container>
|
<div class="container">
|
||||||
<h1>ARANROIG.COM</h1>
|
</div>
|
||||||
|
<div class="header-container">
|
||||||
<HeaderLinks></HeaderLinks>
|
<HeaderLinks></HeaderLinks>
|
||||||
</Container>
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<SiteOptions></SiteOptions>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.header {
|
.header {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 50px;
|
margin-top: 30px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 15px;
|
||||||
|
margin-right: 15px;
|
||||||
|
background: var(--color-background-fore);
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 1em;
|
||||||
|
color: white;
|
||||||
|
border: 1px solid var(--color-border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 8px -8px 0px 0px var(--color-container-shadow);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,21 +1,41 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import HeaderLinks from './HeaderLinks.vue';
|
import HeaderLinks from './HeaderLinks.vue';
|
||||||
|
import SiteOptions from './site_options/SiteOptions.vue';
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="undertable">
|
<div class="header">
|
||||||
<h1>ARANROIG.COM</h1>
|
<div class="header-container">
|
||||||
<HeaderLinks></HeaderLinks>
|
</div>
|
||||||
<Sprite path="/sprites/alfadir/" frames="13" fps="6" top="-258px" left="-65px" width="1300"></Sprite>
|
<div class="undertable">
|
||||||
|
<h1>ARANROIG.COM</h1>
|
||||||
|
<HeaderLinks></HeaderLinks>
|
||||||
|
<Sprite path="/sprites/alfadir/" frames="13" fps="6" top="-267px" left="-80px" width="1300"></Sprite>
|
||||||
|
</div>
|
||||||
|
<div class="header-container">
|
||||||
|
<SiteOptions></SiteOptions>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.header-conainer {
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 30px;
|
||||||
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
.undertable {
|
.undertable {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 350px;
|
left: 45px;
|
||||||
margin-left: 80px;
|
margin-top: 300px;
|
||||||
|
margin-left: -500px;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const dropdownVisible = ref(false);
|
||||||
|
|
||||||
|
const toggleDropdown = () => {
|
||||||
|
dropdownVisible.value = !dropdownVisible.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuContainer = ref(null);
|
||||||
|
|
||||||
|
const handleClickOutside = (e) => {
|
||||||
|
if (menuContainer.value && !menuContainer.value.contains(e.target)) {
|
||||||
|
dropdownVisible.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('click', handleClickOutside)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('click', handleClickOutside)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// i18n
|
||||||
|
const { locale } = useI18n()
|
||||||
|
|
||||||
|
const setLocale = (lang) => {
|
||||||
|
locale.value = lang
|
||||||
|
dropdownVisible.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="menu-container" ref="menuContainer">
|
||||||
|
<button class="menu-button" @click="toggleDropdown">Language </button>
|
||||||
|
|
||||||
|
<div class="dropdown" v-show="dropdownVisible">
|
||||||
|
<div class="section">
|
||||||
|
<div class="menu-item" @click="setLocale('en')">
|
||||||
|
🇬🇧 English
|
||||||
|
</div>
|
||||||
|
<div class="menu-item" @click="setLocale('es')">
|
||||||
|
🇪🇸 Spanish
|
||||||
|
</div>
|
||||||
|
<div class="menu-item" @click="setLocale('ca')">
|
||||||
|
🇦🇩 Catalan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.menu-button {
|
||||||
|
background: #161b22;
|
||||||
|
color: white;
|
||||||
|
font-size: 1em;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.dropdown {
|
||||||
|
z-index: 10;
|
||||||
|
position: absolute;
|
||||||
|
top: 50px;
|
||||||
|
right: 0;
|
||||||
|
margin-right: 10px;
|
||||||
|
background: #161b22;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 0;
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Items */
|
||||||
|
.menu-item {
|
||||||
|
padding: 8px 16px;
|
||||||
|
color: #c9d1d9;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #30363d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
17
frontend/app/components/parts/site_options/SiteOptions.vue
Normal file
17
frontend/app/components/parts/site_options/SiteOptions.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import LanguageSelector from './LanguageSelector.vue';
|
||||||
|
import ThemeSelector from './ThemeSelector.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="site-options">
|
||||||
|
<ThemeSelector></ThemeSelector>
|
||||||
|
<LanguageSelector></LanguageSelector>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.site-options {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
182
frontend/app/components/parts/site_options/ThemeSelector.vue
Normal file
182
frontend/app/components/parts/site_options/ThemeSelector.vue
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { theme, accent, setTheme, setAccent } from '~/composables/theme'
|
||||||
|
|
||||||
|
// ref for dropdown visibility
|
||||||
|
const dropdownVisible = ref(false)
|
||||||
|
|
||||||
|
// toggle function
|
||||||
|
const toggleDropdown = () => {
|
||||||
|
dropdownVisible.value = !dropdownVisible.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// close dropdown if click outside
|
||||||
|
const menuContainer = ref(null)
|
||||||
|
|
||||||
|
const handleClickOutside = (e) => {
|
||||||
|
if (menuContainer.value && !menuContainer.value.contains(e.target)) {
|
||||||
|
dropdownVisible.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('click', handleClickOutside)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('click', handleClickOutside)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="menu-container" ref="menuContainer">
|
||||||
|
<button class="menu-button" @click="toggleDropdown">Theme </button>
|
||||||
|
|
||||||
|
<div class="dropdown" v-show="dropdownVisible">
|
||||||
|
<div class="two-columns">
|
||||||
|
<div class="section">
|
||||||
|
<div class="menu-section">
|
||||||
|
<div class="menu-header">
|
||||||
|
Theme
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="menu-section">
|
||||||
|
<div :class="{'menu-item': true, 'active': theme === 'dark' }" @click="setTheme('dark')">
|
||||||
|
<div class="circle dark"></div>Dark
|
||||||
|
</div>
|
||||||
|
<div :class="{'menu-item': true, 'active': theme === 'light' }" @click="setTheme('light')">
|
||||||
|
<div class="circle light"></div>Light
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<div class="menu-section">
|
||||||
|
<div class="menu-header">
|
||||||
|
Accent
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="menu-section">
|
||||||
|
<div :class="{'menu-item': true, 'active': accent === 'alfdir' }" @click="setAccent('alfdir')">
|
||||||
|
<div class="circle alfdir"></div>Alfdir
|
||||||
|
</div>
|
||||||
|
<div :class="{'menu-item': true, 'active': accent === 'solus' }" @click="setAccent('solus')">
|
||||||
|
<div class="circle solus"></div>Solus
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.circle {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin-top: -3px;
|
||||||
|
border-radius: 50%; /* makes it a circle */
|
||||||
|
z-index: 200;
|
||||||
|
justify-content: center;
|
||||||
|
border: 2px var(--color-border) solid;
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
background-color: rgb(28, 28, 28);
|
||||||
|
}
|
||||||
|
&.light {
|
||||||
|
background-color: rgb(232, 232, 232);
|
||||||
|
}
|
||||||
|
&.alfdir {
|
||||||
|
background-color: #4f8fba;
|
||||||
|
}
|
||||||
|
&.solus {
|
||||||
|
background-color: #e9a02b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.menu-container {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.two-columns {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button */
|
||||||
|
.menu-button {
|
||||||
|
background: #161b22;
|
||||||
|
color: white;
|
||||||
|
font-size: 1em;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown */
|
||||||
|
.dropdown {
|
||||||
|
z-index: 10;
|
||||||
|
position: absolute;
|
||||||
|
top: 50px;
|
||||||
|
right: 0;
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 320px;
|
||||||
|
background: #161b22;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 0;
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sections */
|
||||||
|
.menu-section {
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Items */
|
||||||
|
.menu-item {
|
||||||
|
padding: 8px 16px;
|
||||||
|
color: #c9d1d9;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
border-radius: 5px;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #30363d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-header {
|
||||||
|
padding: 8px 16px;
|
||||||
|
color: #c9d1d9;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: bold;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover {
|
||||||
|
background: #21262d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Divider */
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: #30363d;
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
47
frontend/app/composables/theme.ts
Normal file
47
frontend/app/composables/theme.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { ref, onMounted, watch } from 'vue'
|
||||||
|
|
||||||
|
type Theme = 'light' | 'dark'
|
||||||
|
type Accent = 'alfdir'
|
||||||
|
|
||||||
|
const theme = ref<Theme>('light')
|
||||||
|
const accent = ref<Accent>('alfdir')
|
||||||
|
|
||||||
|
const applyTheme = () => {
|
||||||
|
document.documentElement.setAttribute('data-theme', theme.value)
|
||||||
|
document.documentElement.setAttribute('data-accent', accent.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTheme = (value: Theme) => {
|
||||||
|
theme.value = value
|
||||||
|
localStorage.setItem('theme', value)
|
||||||
|
applyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
const setAccent = (value: Accent) => {
|
||||||
|
accent.value = value
|
||||||
|
localStorage.setItem('accent', value)
|
||||||
|
applyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
const setupTheme = () => {
|
||||||
|
const savedTheme = localStorage.getItem('theme') as Theme | null
|
||||||
|
const savedAccent = localStorage.getItem('accent') as Accent | null
|
||||||
|
|
||||||
|
const media = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
|
||||||
|
theme.value = savedTheme || (media.matches ? 'dark' : 'light')
|
||||||
|
accent.value = savedAccent || 'alfdir'
|
||||||
|
|
||||||
|
applyTheme()
|
||||||
|
|
||||||
|
media.addEventListener('change', (e) => {
|
||||||
|
if (!localStorage.getItem('theme')) {
|
||||||
|
theme.value = e.matches ? 'dark' : 'light'
|
||||||
|
applyTheme()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
watch([theme, accent], applyTheme)
|
||||||
|
|
||||||
|
export { theme, accent, setTheme, setAccent, setupTheme}
|
||||||
Reference in New Issue
Block a user