Enhance backend configuration and error handling: Update CORS settings to allow dynamic origins, improve RabbitMQ connection handling in chat services, and adjust API server host configuration. Refactor environment variables for better flexibility and add fallback mechanisms for WebSocket and chat services. Update frontend environment files for consistent API and WebSocket URLs.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
VITE_API_BASE_URL=http://localhost:3001
|
||||
VITE_API_BASE_URL=http://127.0.0.1:2020
|
||||
VITE_PUBLIC_BASE_URL=http://localhost:5173
|
||||
VITE_TINYMCE_API_KEY=xjqnfymt2wd5q95onkkwgblzexams6l6naqjs01x72ftzryg
|
||||
VITE_DAEMON_SOCKET=ws://localhost:4551
|
||||
VITE_DAEMON_SOCKET=ws://127.0.0.1:4551
|
||||
VITE_CHAT_WS_URL=ws://127.0.0.1:1235
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
VITE_API_BASE_URL=https://www.your-part.de
|
||||
VITE_PUBLIC_BASE_URL=https://www.your-part.de
|
||||
VITE_TINYMCE_API_KEY=xjqnfymt2wd5q95onkkwgblzexams6l6naqjs01x72ftzryg
|
||||
VITE_DAEMON_SOCKET=wss://www.your-part.de:4551
|
||||
VITE_CHAT_WS_URL=wss://www.your-part.de:1235
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
VITE_API_BASE_URL=https://www.your-part.de
|
||||
VITE_PUBLIC_BASE_URL=https://www.your-part.de
|
||||
VITE_TINYMCE_API_KEY=xjqnfymt2wd5q95onkkwgblzexams6l6naqjs01x72ftzryg
|
||||
VITE_DAEMON_SOCKET=wss://www.your-part.de:4551
|
||||
VITE_CHAT_WS_URL=wss://www.your-part.de:1235
|
||||
VITE_SOCKET_IO_URL=https://www.your-part.de:4443
|
||||
|
||||
|
||||
@@ -8,25 +8,25 @@
|
||||
<meta name="description" content="YourPart vereint Community, Chat, Forum, soziales Netzwerk mit Bildergalerie, Vokabeltrainer, das Aufbauspiel Falukant sowie Minispiele wie Match3 und Taxi. Die Plattform befindet sich in der Beta‑Phase und wird laufend erweitert." />
|
||||
<meta name="keywords" content="YourPart, Community, Chat, Forum, soziales Netzwerk, Vokabeltrainer, Sprachen lernen, Falukant, Aufbauspiel, Minispiele, Match3, Taxi, Bildergalerie, Tagebuch, Freundschaften" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<link rel="canonical" href="https://www.your-part.de/" />
|
||||
<link rel="canonical" href="%VITE_PUBLIC_BASE_URL%/" />
|
||||
<meta name="author" content="YourPart" />
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="YourPart" />
|
||||
<meta property="og:title" content="YourPart – Community, Chat, Forum, Vokabeltrainer, Falukant & Minispiele" />
|
||||
<meta property="og:description" content="Community, Chat, Forum, soziales Netzwerk, Vokabeltrainer zum Sprachen lernen, das Aufbauspiel Falukant sowie Minispiele – jetzt in der Beta testen." />
|
||||
<meta property="og:url" content="https://www.your-part.de/" />
|
||||
<meta property="og:url" content="%VITE_PUBLIC_BASE_URL%/" />
|
||||
<meta property="og:locale" content="de_DE" />
|
||||
<meta property="og:image" content="https://www.your-part.de/images/logos/logo.png" />
|
||||
<meta property="og:image" content="%VITE_PUBLIC_BASE_URL%/images/logos/logo.png" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="YourPart – Community, Chat, Forum, Vokabeltrainer, Falukant & Minispiele" />
|
||||
<meta name="twitter:description" content="Community, Chat, Forum, soziales Netzwerk, Vokabeltrainer zum Sprachen lernen, das Aufbauspiel Falukant sowie Minispiele – jetzt in der Beta testen." />
|
||||
<meta name="twitter:image" content="https://www.your-part.de/images/logos/logo.png" />
|
||||
<meta name="twitter:image" content="%VITE_PUBLIC_BASE_URL%/images/logos/logo.png" />
|
||||
|
||||
<meta name="theme-color" content="#FF8C5A" />
|
||||
<link rel="alternate" hreflang="de" href="https://www.your-part.de/" />
|
||||
<link rel="alternate" hreflang="x-default" href="https://www.your-part.de/" />
|
||||
<link rel="alternate" hreflang="de" href="%VITE_PUBLIC_BASE_URL%/" />
|
||||
<link rel="alternate" hreflang="x-default" href="%VITE_PUBLIC_BASE_URL%/" />
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<AppHeader />
|
||||
<AppNavigation v-if="isLoggedIn && user.active" />
|
||||
<AppContent />
|
||||
<div id="app" class="app-shell">
|
||||
<AppHeader class="app-shell__header" />
|
||||
<AppNavigation v-if="isLoggedIn && user.active" class="app-shell__nav" />
|
||||
<AppContent class="app-shell__content" />
|
||||
<AppFooter />
|
||||
<AnswerContact ref="answerContactDialog" />
|
||||
<RandomChatDialog ref="randomChatDialog" />
|
||||
@@ -71,10 +71,10 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
.app-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,50 +1,282 @@
|
||||
:root {
|
||||
/* Moderne Farbpalette für bessere Lesbarkeit */
|
||||
--color-primary-orange: #FFB84D; /* Gelbliches, sanftes Orange */
|
||||
--color-primary-orange-hover: #FFC966; /* Noch helleres gelbliches Orange für Hover */
|
||||
--color-primary-orange-light: #FFF8E1; /* Sehr helles gelbliches Orange für Hover-Hintergründe */
|
||||
--color-primary-green: #4ADE80; /* Helles, freundliches Grün - passt zum Orange */
|
||||
--color-primary-green-hover: #6EE7B7; /* Noch helleres Grün für Hover */
|
||||
--color-text-primary: #1F2937; /* Dunkles Grau für Haupttext (bessere Lesbarkeit) */
|
||||
--color-text-secondary: #5D4037; /* Dunkles Braun für sekundären Text/Hover */
|
||||
--color-text-on-orange: #000000; /* Schwarz auf Orange */
|
||||
--color-text-on-green: #000000; /* Schwarz auf Grün */
|
||||
color-scheme: light;
|
||||
|
||||
--font-display: "Trebuchet MS", "Segoe UI", sans-serif;
|
||||
--font-body: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||
|
||||
--color-bg: #f4f1ea;
|
||||
--color-bg-elevated: #faf7f1;
|
||||
--color-bg-muted: #f5eee2;
|
||||
--color-surface: rgba(255, 251, 246, 0.94);
|
||||
--color-surface-strong: #fffdfa;
|
||||
--color-surface-accent: #fff4e5;
|
||||
--color-border: rgba(93, 64, 55, 0.12);
|
||||
--color-border-strong: rgba(93, 64, 55, 0.24);
|
||||
|
||||
--color-text-primary: #211910;
|
||||
--color-text-secondary: #5f4b39;
|
||||
--color-text-muted: #7a6857;
|
||||
--color-text-on-accent: #fffaf4;
|
||||
|
||||
--color-primary: #f8a22b;
|
||||
--color-primary-hover: #ea961f;
|
||||
--color-primary-soft: rgba(248, 162, 43, 0.14);
|
||||
--color-secondary: #78c38a;
|
||||
--color-secondary-soft: rgba(120, 195, 138, 0.18);
|
||||
--color-highlight: #ffcf74;
|
||||
|
||||
--color-success: #287d5a;
|
||||
--color-warning: #c9821f;
|
||||
--color-danger: #b13b35;
|
||||
|
||||
--shell-max-width: 1440px;
|
||||
--content-max-width: 1200px;
|
||||
--header-height: 62px;
|
||||
--nav-height: 52px;
|
||||
--footer-height: 46px;
|
||||
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-10: 40px;
|
||||
--space-12: 48px;
|
||||
|
||||
--radius-sm: 10px;
|
||||
--radius-md: 16px;
|
||||
--radius-lg: 24px;
|
||||
--radius-pill: 999px;
|
||||
|
||||
--shadow-soft: 0 12px 30px rgba(47, 29, 14, 0.08);
|
||||
--shadow-medium: 0 20px 50px rgba(47, 29, 14, 0.12);
|
||||
--shadow-inset: inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
||||
|
||||
--transition-fast: 140ms ease;
|
||||
--transition-base: 220ms ease;
|
||||
|
||||
--color-primary-orange: var(--color-primary);
|
||||
--color-primary-orange-hover: var(--color-primary-hover);
|
||||
--color-primary-orange-light: #f9ece1;
|
||||
--color-primary-green: #84c6a3;
|
||||
--color-primary-green-hover: #95d1b0;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(255, 255, 255, 0.85), transparent 30%),
|
||||
linear-gradient(180deg, #f8f2e8 0%, #f3ebdd 100%);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f4f4;
|
||||
font-family: var(--font-body);
|
||||
color: var(--color-text-primary);
|
||||
background: transparent;
|
||||
line-height: 1.55;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 10px;
|
||||
padding: 5px 12px;
|
||||
cursor: pointer;
|
||||
background: var(--color-primary-orange);
|
||||
color: var(--color-text-on-orange);
|
||||
border: 1px solid var(--color-primary-orange);
|
||||
border-radius: 4px;
|
||||
transition: background 0.05s;
|
||||
a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
.button,
|
||||
span.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
min-height: 42px;
|
||||
padding: 0 18px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
box-shadow: 0 6px 14px rgba(248, 162, 43, 0.2);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform var(--transition-fast),
|
||||
box-shadow var(--transition-fast),
|
||||
background var(--transition-fast),
|
||||
border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--color-primary-orange-light);
|
||||
color: var(--color-text-secondary);
|
||||
border: 1px solid var(--color-text-secondary);
|
||||
button:hover,
|
||||
.button:hover,
|
||||
span.button:hover {
|
||||
transform: translateY(-1px);
|
||||
background: var(--color-primary-hover);
|
||||
box-shadow: 0 10px 18px rgba(248, 162, 43, 0.24);
|
||||
}
|
||||
|
||||
button:active,
|
||||
.button:active,
|
||||
span.button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
button:focus-visible,
|
||||
input:focus-visible,
|
||||
select:focus-visible,
|
||||
textarea:focus-visible,
|
||||
a:focus-visible {
|
||||
outline: 3px solid rgba(120, 195, 138, 0.32);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]):not([type="radio"]),
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: 46px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: var(--color-text-primary);
|
||||
box-shadow: var(--shadow-inset);
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast);
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 120px;
|
||||
padding: 14px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]):not([type="radio"]):hover,
|
||||
select:hover,
|
||||
textarea:hover {
|
||||
border-color: var(--color-border-strong);
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]):not([type="radio"]):focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
border-color: rgba(120, 195, 138, 0.65);
|
||||
box-shadow: 0 0 0 4px rgba(120, 195, 138, 0.16);
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
width: auto;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
accent-color: var(--color-primary);
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
inline-size: 16px;
|
||||
block-size: 16px;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
inline-size: 16px;
|
||||
block-size: 16px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 0 0 var(--space-3);
|
||||
font-family: var(--font-display);
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(2rem, 3.4vw, 3.6rem);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: clamp(1.5rem, 2vw, 2.4rem);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: clamp(1.15rem, 1.5vw, 1.5rem);
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
margin: 0 0 var(--space-4);
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
main,
|
||||
.contenthidden {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.contentscroll {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.surface-card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.link {
|
||||
color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.rc-system {
|
||||
@@ -52,25 +284,13 @@ button:hover {
|
||||
}
|
||||
|
||||
.rc-self {
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
color: #c0412c;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rc-partner {
|
||||
color: #0000ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: var(--color-primary-orange);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
margin: 0;
|
||||
display: block;
|
||||
color: #2357b5;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.multiselect__option--highlight,
|
||||
@@ -80,61 +300,42 @@ h3 {
|
||||
.multiselect__option--highlight[data-selected],
|
||||
.multiselect__option--highlight[data-deselect] {
|
||||
background: none;
|
||||
background-color: var(--color-primary-orange);
|
||||
color: var(--color-text-on-orange);
|
||||
}
|
||||
|
||||
span.button {
|
||||
padding: 2px 2px;
|
||||
margin-left: 4px;
|
||||
cursor: pointer;
|
||||
background: var(--color-primary-orange);
|
||||
color: var(--color-text-on-orange);
|
||||
border: 1px solid var(--color-primary-orange);
|
||||
border-radius: 4px;
|
||||
transition: background 0.05s;
|
||||
border: 1px solid transparent;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
span.button:hover {
|
||||
background: var(--color-primary-orange-light);
|
||||
color: var(--color-text-secondary);
|
||||
border: 1px solid var(--color-text-secondary);
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text-on-accent);
|
||||
}
|
||||
|
||||
.font-color-gender-male {
|
||||
color: #1E90FF;
|
||||
color: #1e90ff;
|
||||
}
|
||||
|
||||
.font-color-gender-female {
|
||||
color: #FF69B4;
|
||||
color: #d14682;
|
||||
}
|
||||
|
||||
.font-color-gender-transmale {
|
||||
color: #00CED1;
|
||||
color: #1f8b9b;
|
||||
}
|
||||
|
||||
.font-color-gender-transfemale {
|
||||
color: #FFB6C1;
|
||||
color: #d78398;
|
||||
}
|
||||
|
||||
.font-color-gender-nonbinary {
|
||||
color: #DAA520;
|
||||
color: #ba7c1f;
|
||||
}
|
||||
|
||||
main,
|
||||
.contenthidden {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
@media (max-width: 960px) {
|
||||
:root {
|
||||
--header-height: 56px;
|
||||
--nav-height: auto;
|
||||
--footer-height: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(1.8rem, 8vw, 2.8rem);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: clamp(1.35rem, 5vw, 2rem);
|
||||
}
|
||||
}
|
||||
.contentscroll {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
<template>
|
||||
<main class="contenthidden">
|
||||
<div class="contentscroll">
|
||||
<main class="app-content contenthidden">
|
||||
<div class="app-content__scroll contentscroll">
|
||||
<div class="app-content__inner">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
@@ -12,15 +14,31 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
main {
|
||||
padding: 0;
|
||||
background-color: #ffffff;
|
||||
flex: 1;
|
||||
<style scoped>
|
||||
.app-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-content__scroll {
|
||||
background: transparent;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.app-content__inner {
|
||||
max-width: var(--shell-max-width);
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
margin: 0 auto;
|
||||
padding: 14px 18px;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.app-content__inner {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
.contentscroll {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
<template>
|
||||
<footer>
|
||||
<div class="logo" @click="showFalukantDaemonStatus"><img src="/images/icons/logo_color.png"></div>
|
||||
<div class="window-bar">
|
||||
<footer class="app-footer">
|
||||
<div class="app-footer__inner">
|
||||
<button class="footer-brand" type="button" @click="showFalukantDaemonStatus">
|
||||
<img src="/images/icons/logo_color.png" alt="YourPart" />
|
||||
<span>System</span>
|
||||
</button>
|
||||
<div class="window-bar">
|
||||
<button v-for="dialog in openDialogs" :key="dialog.dialog.name" class="dialog-button"
|
||||
@click="toggleDialogMinimize(dialog.dialog.name)" :title="dialog.dialog.localTitle">
|
||||
<img v-if="dialog.dialog.icon" :src="'/images/icons/' + dialog.dialog.icon" />
|
||||
<span class="button-text">{{ dialog.dialog.isTitleTranslated ? $t(dialog.dialog.localTitle) :
|
||||
dialog.dialog.localTitle }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="static-block">
|
||||
<a href="#" @click.prevent="openImprintDialog">{{ $t('imprint.button') }}</a>
|
||||
<a href="#" @click.prevent="openDataPrivacyDialog">{{ $t('dataPrivacy.button') }}</a>
|
||||
<a href="#" @click.prevent="openContactDialog">{{ $t('contact.button') }}</a>
|
||||
</div>
|
||||
<div class="static-block">
|
||||
<a href="#" @click.prevent="openImprintDialog">{{ $t('imprint.button') }}</a>
|
||||
<a href="#" @click.prevent="openDataPrivacyDialog">{{ $t('dataPrivacy.button') }}</a>
|
||||
<a href="#" @click.prevent="openContactDialog">{{ $t('contact.button') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
@@ -63,18 +68,47 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
footer {
|
||||
display: flex;
|
||||
background-color: var(--color-primary-green);
|
||||
height: 38px;
|
||||
width: 100%;
|
||||
color: #1F2937; /* Dunkles Grau für besseren Kontrast auf hellem Grün */
|
||||
.app-footer {
|
||||
flex: 0 0 auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.logo,
|
||||
.window-bar,
|
||||
.static-block {
|
||||
text-align: center;
|
||||
.app-footer__inner {
|
||||
max-width: var(--shell-max-width);
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
min-height: 44px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 0;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(242, 248, 243, 0.96) 0%, rgba(224, 238, 227, 0.98) 100%);
|
||||
border-top: 1px solid rgba(120, 195, 138, 0.28);
|
||||
box-shadow: 0 -6px 18px rgba(93, 64, 55, 0.06);
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
min-height: 32px;
|
||||
padding: 0 10px 0 8px;
|
||||
background: rgba(120, 195, 138, 0.12);
|
||||
border: 1px solid rgba(120, 195, 138, 0.22);
|
||||
color: #24523a;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.footer-brand:hover {
|
||||
background: rgba(120, 195, 138, 0.18);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.footer-brand img {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.footer-brand span {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.window-bar {
|
||||
@@ -83,24 +117,25 @@ footer {
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
padding-left: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.dialog-button {
|
||||
max-width: 12em;
|
||||
max-width: 15em;
|
||||
min-height: 30px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: none;
|
||||
height: 1.8em;
|
||||
border: 1px solid #0a4337;
|
||||
box-shadow: 1px 1px 2px #484949;
|
||||
padding: 0 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid rgba(120, 195, 138, 0.18);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dialog-button:hover {
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
.dialog-button>img {
|
||||
@@ -111,16 +146,35 @@ footer {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.logo>img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.static-block {
|
||||
line-height: 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.static-block>a {
|
||||
padding-right: 1.5em;
|
||||
color: #42634e;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
.static-block > a:hover {
|
||||
color: #24523a;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.app-footer__inner {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.window-bar,
|
||||
.static-block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.static-block {
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
<template>
|
||||
<header>
|
||||
<div class="logo"><img src="/images/logos/logo.png" /></div>
|
||||
<div class="advertisement">Advertisement</div>
|
||||
<div class="connection-status" v-if="isLoggedIn">
|
||||
<div class="status-indicator" :class="backendStatusClass">
|
||||
<span class="status-dot"></span>
|
||||
<span class="status-text">B</span>
|
||||
<header class="app-header">
|
||||
<div class="app-header__inner">
|
||||
<div class="brand">
|
||||
<div class="logo"><img src="/images/logos/logo.png" alt="YourPart" /></div>
|
||||
<div class="brand-copy">
|
||||
<strong>YourPart</strong>
|
||||
<span>Community, Spiele und Lernen auf einer Plattform</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-indicator" :class="daemonStatusClass">
|
||||
<span class="status-dot"></span>
|
||||
<span class="status-text">D</span>
|
||||
<div class="header-meta">
|
||||
<div class="header-pill">Beta</div>
|
||||
<div class="connection-status" v-if="isLoggedIn">
|
||||
<div class="status-indicator" :class="backendStatusClass">
|
||||
<span class="status-dot"></span>
|
||||
<span class="status-text">Backend</span>
|
||||
</div>
|
||||
<div class="status-indicator" :class="daemonStatusClass">
|
||||
<span class="status-dot"></span>
|
||||
<span class="status-text">Daemon</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -43,43 +53,107 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
.app-header {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
padding: 8px 14px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 248, 236, 0.94) 0%, rgba(247, 235, 216, 0.98) 100%);
|
||||
color: #2b1f14;
|
||||
border-bottom: 1px solid rgba(93, 64, 55, 0.12);
|
||||
box-shadow: 0 6px 18px rgba(93, 64, 55, 0.08);
|
||||
}
|
||||
|
||||
.app-header__inner {
|
||||
max-width: var(--shell-max-width);
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
background-color: #f8a22b;
|
||||
gap: 16px;
|
||||
}
|
||||
.logo, .title, .advertisement {
|
||||
text-align: center;
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.advertisement {
|
||||
flex: 1;
|
||||
|
||||
.logo {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
padding: 6px;
|
||||
border-radius: 14px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(248, 162, 43, 0.2) 0%, rgba(255, 255, 255, 0.7) 100%);
|
||||
border: 1px solid rgba(248, 162, 43, 0.22);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
|
||||
.logo > img {
|
||||
max-height: 50px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.brand-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.brand-copy strong {
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.1;
|
||||
color: #3a2a1b;
|
||||
}
|
||||
|
||||
.brand-copy span {
|
||||
font-size: 0.8rem;
|
||||
color: rgba(95, 75, 57, 0.88);
|
||||
}
|
||||
|
||||
.header-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.header-pill {
|
||||
padding: 5px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(248, 162, 43, 0.12);
|
||||
border: 1px solid rgba(248, 162, 43, 0.24);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #8a5411;
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
gap: 5px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 6pt;
|
||||
font-weight: 500;
|
||||
gap: 8px;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
border: 1px solid rgba(93, 64, 55, 0.1);
|
||||
background: rgba(255, 255, 255, 0.62);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 4px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@@ -100,23 +174,23 @@ header {
|
||||
}
|
||||
|
||||
.status-connected {
|
||||
background-color: rgba(76, 175, 80, 0.1);
|
||||
color: #2e7d32;
|
||||
background-color: rgba(76, 175, 80, 0.12);
|
||||
color: #245b2c;
|
||||
}
|
||||
|
||||
.status-connecting {
|
||||
background-color: rgba(255, 152, 0, 0.1);
|
||||
color: #f57c00;
|
||||
background-color: rgba(255, 152, 0, 0.12);
|
||||
color: #8b5e0d;
|
||||
}
|
||||
|
||||
.status-disconnected {
|
||||
background-color: rgba(244, 67, 54, 0.1);
|
||||
color: #d32f2f;
|
||||
background-color: rgba(244, 67, 54, 0.12);
|
||||
color: #8f2c27;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
background-color: rgba(244, 67, 54, 0.1);
|
||||
color: #d32f2f;
|
||||
background-color: rgba(244, 67, 54, 0.12);
|
||||
color: #8f2c27;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
@@ -124,4 +198,24 @@ header {
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.app-header {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.app-header__inner {
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header-meta {
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.brand-copy span {
|
||||
font-size: 0.76rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
> </span>
|
||||
<span>{{ $t(`navigation.${key}`) }}</span>
|
||||
|
||||
<!-- Untermenü Ebene 1 -->
|
||||
<ul v-if="item.children" class="submenu1">
|
||||
<!-- Untermenü Ebene 1 -->
|
||||
<ul v-if="hasTopLevelSubmenu(item)" class="submenu1">
|
||||
<li
|
||||
v-for="(subitem, subkey) in item.children"
|
||||
:key="subkey"
|
||||
@@ -29,7 +29,7 @@
|
||||
> </span>
|
||||
<span>{{ subitem?.label || $t(`navigation.m-${key}.${subkey}`) }}</span>
|
||||
<span
|
||||
v-if="subkey === 'forum' || subkey === 'vocabtrainer' || subitem.children"
|
||||
v-if="hasSecondLevelSubmenu(subitem, subkey)"
|
||||
class="subsubmenu"
|
||||
>▶</span>
|
||||
|
||||
@@ -183,6 +183,34 @@ export default {
|
||||
methods: {
|
||||
...mapActions(['loadMenu', 'logout']),
|
||||
|
||||
hasChildren(item) {
|
||||
if (!item?.children) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(item.children)) {
|
||||
return item.children.length > 0;
|
||||
}
|
||||
|
||||
return Object.keys(item.children).length > 0;
|
||||
},
|
||||
|
||||
hasTopLevelSubmenu(item) {
|
||||
return this.hasChildren(item) || (item?.showLoggedinFriends === 1 && this.friendsList.length > 0);
|
||||
},
|
||||
|
||||
hasSecondLevelSubmenu(subitem, subkey) {
|
||||
if (subkey === 'forum') {
|
||||
return this.forumList.length > 0;
|
||||
}
|
||||
|
||||
if (subkey === 'vocabtrainer') {
|
||||
return this.vocabLanguagesList.length > 0;
|
||||
}
|
||||
|
||||
return this.hasChildren(subitem);
|
||||
},
|
||||
|
||||
openMultiChat() {
|
||||
// Räume können später dynamisch geladen werden, hier als Platzhalter ein Beispiel:
|
||||
const exampleRooms = [
|
||||
@@ -254,7 +282,7 @@ export default {
|
||||
event.stopPropagation();
|
||||
|
||||
// 1) nur aufklappen, wenn es echte Untermenüs gibt (nicht bei leerem children wie bei Startseite)
|
||||
if (item.children && Object.keys(item.children).length > 0) return;
|
||||
if (this.hasChildren(item)) return;
|
||||
|
||||
// 2) view → Dialog/Window
|
||||
if (item.view) {
|
||||
@@ -295,8 +323,6 @@ nav,
|
||||
nav > ul {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: #f8a22b;
|
||||
color: #000;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
@@ -304,6 +330,28 @@ nav > ul {
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
nav {
|
||||
max-width: var(--shell-max-width);
|
||||
margin: 0 auto;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 0;
|
||||
background: var(--color-primary-orange-light);
|
||||
border-top: 1px solid rgba(93, 64, 55, 0.08);
|
||||
border-bottom: 1px solid rgba(93, 64, 55, 0.12);
|
||||
box-shadow: none;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
nav > ul {
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: transparent;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
@@ -311,18 +359,23 @@ ul {
|
||||
}
|
||||
|
||||
nav > ul > li {
|
||||
padding: 0 1em;
|
||||
line-height: 2.5em;
|
||||
transition: background-color 0.25s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 36px;
|
||||
padding: 0 12px;
|
||||
line-height: 1;
|
||||
border-radius: 999px;
|
||||
transition: background-color 0.25s, color 0.25s, transform 0.2s;
|
||||
}
|
||||
|
||||
nav > ul > li:hover {
|
||||
background-color: #f8a22b;
|
||||
background-color: rgba(248, 162, 43, 0.16);
|
||||
white-space: nowrap;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
nav > ul > li:hover > span {
|
||||
color: #000;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
nav > ul > li:hover > ul {
|
||||
@@ -335,17 +388,22 @@ a {
|
||||
|
||||
.right-block {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.logoutblock {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.menuitem {
|
||||
cursor: pointer;
|
||||
color: #5D4037;
|
||||
color: var(--color-primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mailbox {
|
||||
@@ -353,20 +411,29 @@ a {
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
padding-left: 24px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
text-align: left;
|
||||
border-radius: 999px;
|
||||
background-color: rgba(120, 195, 138, 0.12);
|
||||
border: 1px solid rgba(93, 64, 55, 0.1);
|
||||
}
|
||||
|
||||
.mainmenuitem {
|
||||
position: relative;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.submenu1 {
|
||||
position: absolute;
|
||||
border: 1px solid #5D4037;
|
||||
background-color: #f8a22b;
|
||||
border: 1px solid rgba(93, 64, 55, 0.12);
|
||||
background: rgba(255, 252, 247, 0.98);
|
||||
left: 0;
|
||||
top: 2.5em;
|
||||
top: calc(100% + 10px);
|
||||
min-width: 220px;
|
||||
padding: 8px;
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 10px 18px rgba(93, 64, 55, 0.12);
|
||||
max-height: 0;
|
||||
overflow: visible;
|
||||
opacity: 0;
|
||||
@@ -386,15 +453,16 @@ a {
|
||||
}
|
||||
|
||||
.submenu1 > li {
|
||||
padding: 0.5em;
|
||||
padding: 0.75em 0.9em;
|
||||
line-height: 1em;
|
||||
color: #5D4037;
|
||||
color: var(--color-text-secondary);
|
||||
position: relative;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.submenu1 > li:hover {
|
||||
color: #000;
|
||||
background-color: #f8a22b;
|
||||
color: var(--color-text-primary);
|
||||
background-color: rgba(248, 162, 43, 0.12);
|
||||
}
|
||||
|
||||
.menu-icon,
|
||||
@@ -407,7 +475,7 @@ a {
|
||||
.menu-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 3px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.submenu-icon {
|
||||
@@ -419,10 +487,14 @@ a {
|
||||
|
||||
.submenu2 {
|
||||
position: absolute;
|
||||
background-color: #f8a22b;
|
||||
left: 100%;
|
||||
background: rgba(255, 252, 247, 0.98);
|
||||
left: calc(100% + 8px);
|
||||
top: 0;
|
||||
border: 1px solid #5D4037;
|
||||
min-width: 220px;
|
||||
padding: 8px;
|
||||
border-radius: 18px;
|
||||
border: 1px solid rgba(71, 52, 35, 0.12);
|
||||
box-shadow: 0 10px 18px rgba(93, 64, 55, 0.12);
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
@@ -442,14 +514,15 @@ a {
|
||||
}
|
||||
|
||||
.submenu2 > li {
|
||||
padding: 0.5em;
|
||||
padding: 0.75em 0.9em;
|
||||
line-height: 1em;
|
||||
color: #5D4037;
|
||||
color: var(--color-text-secondary);
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.submenu2 > li:hover {
|
||||
color: #000;
|
||||
background-color: #f8a22b;
|
||||
color: var(--color-text-primary);
|
||||
background-color: rgba(120, 195, 138, 0.14);
|
||||
}
|
||||
|
||||
.subsubmenu {
|
||||
@@ -457,4 +530,37 @@ a {
|
||||
font-size: 8pt;
|
||||
margin-right: -4px;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
nav {
|
||||
margin: 0;
|
||||
flex-direction: column;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
nav > ul,
|
||||
.right-block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.right-block {
|
||||
justify-content: space-between;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.logoutblock {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.submenu1,
|
||||
.submenu2 {
|
||||
position: static;
|
||||
min-width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<template>
|
||||
<div ref="container" class="character-3d-container"></div>
|
||||
<div class="character-3d-shell">
|
||||
<div v-show="!showFallback" ref="container" class="character-3d-container"></div>
|
||||
<img
|
||||
v-if="showFallback"
|
||||
class="character-fallback"
|
||||
:src="fallbackImageSrc"
|
||||
:alt="`Character ${actualGender}`"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -41,7 +49,8 @@ export default {
|
||||
animationId: null,
|
||||
mixer: null,
|
||||
clock: markRaw(new THREE.Clock()),
|
||||
baseYPosition: 0 // Basis-Y-Position für Animation
|
||||
baseYPosition: 0,
|
||||
showFallback: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -93,6 +102,11 @@ export default {
|
||||
const base = getApiBaseURL();
|
||||
const prefix = base ? `${base}${MODELS_API_PATH}` : MODELS_API_PATH;
|
||||
return `${prefix}/${this.actualGender}_${age}y.glb`;
|
||||
},
|
||||
fallbackImageSrc() {
|
||||
return this.actualGender === 'female'
|
||||
? '/images/mascot/mascot_female.png'
|
||||
: '/images/mascot/mascot_male.png';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -115,6 +129,7 @@ export default {
|
||||
init3D() {
|
||||
const container = this.$refs.container;
|
||||
if (!container) return;
|
||||
this.showFallback = false;
|
||||
|
||||
// Scene erstellen - markRaw verwenden, um Vue's Reactivity zu vermeiden
|
||||
this.scene = markRaw(new THREE.Scene());
|
||||
@@ -301,6 +316,7 @@ export default {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading 3D model:', error);
|
||||
this.showFallback = true;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -375,10 +391,25 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.character-3d-shell {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.character-3d-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.character-fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
object-position: center bottom;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,11 +23,19 @@
|
||||
<img :src="'/images/icons/falukant/relationship-' + item.image + '.png'" class="relationship-icon" :title="$t(`falukant.statusbar.${item.key}`)" />
|
||||
</div>
|
||||
</template>
|
||||
<span v-if="statusItems.length > 0 && menu.falukant && menu.falukant.children">
|
||||
<div
|
||||
v-if="statusItems.length > 0 && menu.falukant && menu.falukant.children"
|
||||
class="quick-access"
|
||||
>
|
||||
<template v-for="(menuItem, key) in menu.falukant.children" :key="menuItem.id" >
|
||||
<img :src="'/images/icons/falukant/shortmap/' + key + '.png'" class="menu-icon" @click="openPage(menuItem)" :title="$t(`navigation.m-falukant.${key}`)" />
|
||||
<img
|
||||
:src="'/images/icons/falukant/shortmap/' + key + '.png'"
|
||||
class="menu-icon"
|
||||
@click="openPage(menuItem)"
|
||||
:title="$t(`navigation.m-falukant.${key}`)"
|
||||
/>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<MessagesDialog ref="msgs" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -220,13 +228,18 @@ export default {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
background-color: #f4f4f4;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
width: calc(100% + 40px);
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
gap: 1.2em;
|
||||
margin: -21px -20px 1.5em -20px;
|
||||
position: fixed;
|
||||
padding: 0.4rem 0.75rem;
|
||||
margin: 0 0 1.5em 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
@@ -237,6 +250,14 @@ export default {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.quick-access {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.status-icon-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -254,6 +275,8 @@ export default {
|
||||
.menu-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: block;
|
||||
flex: 0 0 auto;
|
||||
cursor: pointer;
|
||||
padding: 4px 2px 0 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import BlogListView from '@/views/blog/BlogListView.vue';
|
||||
import BlogView from '@/views/blog/BlogView.vue';
|
||||
import BlogEditorView from '@/views/blog/BlogEditorView.vue';
|
||||
import { buildAbsoluteUrl } from '@/utils/seo.js';
|
||||
|
||||
export default [
|
||||
{ path: '/blogs/create', name: 'BlogCreate', component: BlogEditorView, meta: { requiresAuth: true } },
|
||||
@@ -46,7 +47,7 @@ export default [
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: 'Blogs auf YourPart',
|
||||
url: 'https://www.your-part.de/blogs',
|
||||
url: buildAbsoluteUrl('/blogs'),
|
||||
description: 'Oeffentliche Blogs und Community-Beitraege auf YourPart.',
|
||||
inLanguage: 'de',
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ import blogRoutes from './blogRoutes';
|
||||
import minigamesRoutes from './minigamesRoutes';
|
||||
import personalRoutes from './personalRoutes';
|
||||
import marketingRoutes from './marketingRoutes';
|
||||
import { applyRouteSeo } from '../utils/seo';
|
||||
import { applyRouteSeo, buildAbsoluteUrl } from '../utils/seo';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -28,12 +28,12 @@ const routes = [
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: 'YourPart',
|
||||
url: 'https://www.your-part.de/',
|
||||
url: buildAbsoluteUrl('/'),
|
||||
inLanguage: 'de',
|
||||
description: 'Community-Plattform mit Chat, Forum, Blogs, Vokabeltrainer, Falukant und Browser-Minispielen.',
|
||||
potentialAction: {
|
||||
'@type': 'SearchAction',
|
||||
target: 'https://www.your-part.de/blogs?q={search_term_string}',
|
||||
target: `${buildAbsoluteUrl('/blogs')}?q={search_term_string}`,
|
||||
'query-input': 'required name=search_term_string',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { buildAbsoluteUrl } from '../utils/seo';
|
||||
|
||||
const FalukantLandingView = () => import('../views/public/FalukantLandingView.vue');
|
||||
const MinigamesLandingView = () => import('../views/public/MinigamesLandingView.vue');
|
||||
const VocabLandingView = () => import('../views/public/VocabLandingView.vue');
|
||||
@@ -18,7 +20,7 @@ const marketingRoutes = [
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'VideoGame',
|
||||
name: 'Falukant',
|
||||
url: 'https://www.your-part.de/falukant',
|
||||
url: buildAbsoluteUrl('/falukant'),
|
||||
description: 'Mittelalterliches Browser-Aufbauspiel mit Handel, Politik, Familie und Charakterentwicklung.',
|
||||
gamePlatform: 'Web Browser',
|
||||
applicationCategory: 'Game',
|
||||
@@ -47,7 +49,7 @@ const marketingRoutes = [
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: 'YourPart Minispiele',
|
||||
url: 'https://www.your-part.de/minigames',
|
||||
url: buildAbsoluteUrl('/minigames'),
|
||||
description: 'Browser-Minispiele auf YourPart mit Match 3 und Taxi.',
|
||||
inLanguage: 'de',
|
||||
},
|
||||
@@ -70,7 +72,7 @@ const marketingRoutes = [
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'YourPart Vokabeltrainer',
|
||||
url: 'https://www.your-part.de/vokabeltrainer',
|
||||
url: buildAbsoluteUrl('/vokabeltrainer'),
|
||||
description: 'Interaktiver Vokabeltrainer mit Kursen, Lektionen und Übungen zum Sprachenlernen.',
|
||||
applicationCategory: 'EducationalApplication',
|
||||
operatingSystem: 'Web',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { getChatWsUrlFromEnv } from '@/utils/appConfig.js';
|
||||
|
||||
// Small helper to resolve the Chat WebSocket URL from env or sensible defaults
|
||||
export function getChatWsUrl() {
|
||||
// Prefer explicit env var
|
||||
@@ -5,24 +7,7 @@ export function getChatWsUrl() {
|
||||
if (override && typeof override === 'string' && override.trim()) {
|
||||
return override.trim();
|
||||
}
|
||||
const envUrl = import.meta?.env?.VITE_CHAT_WS_URL;
|
||||
if (envUrl && typeof envUrl === 'string' && envUrl.trim()) {
|
||||
return envUrl.trim();
|
||||
}
|
||||
// Fallback: use current origin host with ws/wss and default port/path if provided by backend
|
||||
const isHttps = typeof window !== 'undefined' && window.location.protocol === 'https:';
|
||||
const proto = isHttps ? 'wss' : 'ws';
|
||||
// If a reverse proxy exposes the chat at a path, you can change '/chat' here.
|
||||
const host = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
|
||||
const port = (typeof window !== 'undefined' && window.location.port) ? `:${window.location.port}` : '';
|
||||
// On localhost, prefer dedicated chat port 1235 by default
|
||||
// Prefer IPv4 for localhost to avoid browsers resolving to ::1 (IPv6) where the server may not listen
|
||||
if (host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]') {
|
||||
return `${proto}://127.0.0.1:1235`;
|
||||
}
|
||||
// Default to same origin with chat port for production
|
||||
const defaultUrl = `${proto}://${host}:1235`;
|
||||
return defaultUrl;
|
||||
return getChatWsUrlFromEnv();
|
||||
}
|
||||
|
||||
// Provide a list of candidate WS URLs to try, in order of likelihood.
|
||||
@@ -31,37 +16,8 @@ export function getChatWsCandidates() {
|
||||
if (override && typeof override === 'string' && override.trim()) {
|
||||
return [override.trim()];
|
||||
}
|
||||
const envUrl = import.meta?.env?.VITE_CHAT_WS_URL;
|
||||
if (envUrl && typeof envUrl === 'string' && envUrl.trim()) {
|
||||
return [envUrl.trim()];
|
||||
}
|
||||
const isHttps = typeof window !== 'undefined' && window.location.protocol === 'https:';
|
||||
const proto = isHttps ? 'wss' : 'ws';
|
||||
const host = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
|
||||
const port = (typeof window !== 'undefined' && window.location.port) ? `:${window.location.port}` : '';
|
||||
const candidates = [];
|
||||
// Common local setups: include IPv4 and IPv6 loopback variants (root only)
|
||||
if (host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]') {
|
||||
// Prefer IPv6 loopback first when available
|
||||
const localHosts = ['[::1]', '127.0.0.1', 'localhost'];
|
||||
for (const h of localHosts) {
|
||||
const base = `${proto}://${h}:1235`;
|
||||
candidates.push(base);
|
||||
candidates.push(`${base}/`);
|
||||
}
|
||||
}
|
||||
// Same-origin with chat port
|
||||
const sameOriginBases = [`${proto}://${host}:1235`];
|
||||
// If localhost-ish, also try 127.0.0.1 for chat port
|
||||
if (host === 'localhost' || host === '::1' || host === '[::1]') {
|
||||
sameOriginBases.push(`${proto}://[::1]:1235`);
|
||||
sameOriginBases.push(`${proto}://127.0.0.1:1235`);
|
||||
}
|
||||
for (const base of sameOriginBases) {
|
||||
candidates.push(base);
|
||||
candidates.push(`${base}/`);
|
||||
}
|
||||
return candidates;
|
||||
const resolved = getChatWsUrlFromEnv();
|
||||
return [resolved, `${resolved}/`];
|
||||
}
|
||||
|
||||
// Return optional subprotocols for the WebSocket handshake.
|
||||
@@ -84,4 +40,4 @@ export function getChatWsProtocols() {
|
||||
// Default to the 'chat' subprotocol so the server can gate connections accordingly
|
||||
return ['chat'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import loadMenu from '../utils/menuLoader.js';
|
||||
import router from '../router';
|
||||
import apiClient from '../utils/axios.js';
|
||||
import { io } from 'socket.io-client';
|
||||
import { getDaemonSocketUrl, getSocketIoUrl } from '../utils/appConfig.js';
|
||||
|
||||
const store = createStore({
|
||||
state: {
|
||||
@@ -180,38 +181,7 @@ const store = createStore({
|
||||
|
||||
commit('setConnectionStatus', 'connecting');
|
||||
|
||||
// Socket.io URL für lokale Entwicklung und Produktion
|
||||
let socketIoUrl = import.meta.env.VITE_SOCKET_IO_URL || import.meta.env.VITE_API_BASE_URL;
|
||||
|
||||
// Für lokale Entwicklung: direkte Backend-Verbindung
|
||||
if (!socketIoUrl && (import.meta.env.DEV || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
|
||||
socketIoUrl = 'http://localhost:3001';
|
||||
}
|
||||
|
||||
// Direkte Verbindung zu Socket.io (ohne Apache-Proxy)
|
||||
// In Produktion: direkte Verbindung zu Port 4443 (verschlüsselt)
|
||||
const hostname = window.location.hostname;
|
||||
const isProduction = hostname === 'www.your-part.de' || hostname.includes('your-part.de');
|
||||
|
||||
if (isProduction) {
|
||||
// Produktion: direkte Verbindung zu Port 4443 (verschlüsselt)
|
||||
const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
|
||||
socketIoUrl = `${protocol}//${hostname}:4443`;
|
||||
} else {
|
||||
// Lokale Entwicklung: direkte Backend-Verbindung
|
||||
if (!socketIoUrl && (import.meta.env.DEV || hostname === 'localhost' || hostname === '127.0.0.1')) {
|
||||
socketIoUrl = 'http://localhost:3001';
|
||||
} else if (socketIoUrl) {
|
||||
try {
|
||||
const parsed = new URL(socketIoUrl, window.location.origin);
|
||||
socketIoUrl = parsed.origin;
|
||||
} catch (e) {
|
||||
socketIoUrl = window.location.origin;
|
||||
}
|
||||
} else {
|
||||
socketIoUrl = window.location.origin;
|
||||
}
|
||||
}
|
||||
let socketIoUrl = getSocketIoUrl();
|
||||
|
||||
// Socket.io-Konfiguration: In Produktion mit HTTPS verwenden wir wss://
|
||||
const socketOptions = {
|
||||
@@ -287,29 +257,7 @@ const store = createStore({
|
||||
|
||||
// Daemon URL für lokale Entwicklung und Produktion
|
||||
// Vite bindet Umgebungsvariablen zur Build-Zeit ein, daher Fallback-Logik basierend auf Hostname
|
||||
const hostname = window.location.hostname;
|
||||
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '[::1]';
|
||||
const isProduction = hostname === 'www.your-part.de' || hostname.includes('your-part.de');
|
||||
|
||||
// Versuche Umgebungsvariable zu lesen (kann undefined sein, wenn nicht zur Build-Zeit gesetzt)
|
||||
let daemonUrl = import.meta.env?.VITE_DAEMON_SOCKET;
|
||||
|
||||
console.log('[Daemon] Umgebungsvariable VITE_DAEMON_SOCKET:', daemonUrl);
|
||||
console.log('[Daemon] DEV-Modus:', import.meta.env?.DEV);
|
||||
console.log('[Daemon] Hostname:', hostname);
|
||||
console.log('[Daemon] IsLocalhost:', isLocalhost);
|
||||
console.log('[Daemon] IsProduction:', isProduction);
|
||||
|
||||
// Wenn Umgebungsvariable nicht gesetzt ist oder leer, verwende Fallback-Logik
|
||||
if (!daemonUrl || (typeof daemonUrl === 'string' && daemonUrl.trim() === '')) {
|
||||
// Immer direkte Verbindung zum Daemon-Port 4551 (verschlüsselt)
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
daemonUrl = `${protocol}//${hostname}:4551/`;
|
||||
console.log('[Daemon] Verwende direkte Verbindung zu Port 4551');
|
||||
} else {
|
||||
// Wenn Umgebungsvariable gesetzt ist, verwende sie direkt
|
||||
console.log('[Daemon] Verwende Umgebungsvariable:', daemonUrl);
|
||||
}
|
||||
let daemonUrl = getDaemonSocketUrl();
|
||||
|
||||
console.log('[Daemon] Finale Daemon-URL:', daemonUrl);
|
||||
|
||||
|
||||
61
frontend/src/utils/appConfig.js
Normal file
61
frontend/src/utils/appConfig.js
Normal file
@@ -0,0 +1,61 @@
|
||||
function trimTrailingSlash(value) {
|
||||
return value ? value.replace(/\/$/, '') : value;
|
||||
}
|
||||
|
||||
function getWindowOrigin() {
|
||||
if (typeof window === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return window.location.origin;
|
||||
}
|
||||
|
||||
function toWsOrigin(value) {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value
|
||||
.replace(/^http:\/\//i, 'ws://')
|
||||
.replace(/^https:\/\//i, 'wss://');
|
||||
}
|
||||
|
||||
export function getApiBaseUrl() {
|
||||
return trimTrailingSlash(import.meta.env.VITE_API_BASE_URL || getWindowOrigin() || '');
|
||||
}
|
||||
|
||||
export function getSocketIoUrl() {
|
||||
return trimTrailingSlash(import.meta.env.VITE_SOCKET_IO_URL || getApiBaseUrl() || getWindowOrigin() || '');
|
||||
}
|
||||
|
||||
export function getDaemonSocketUrl() {
|
||||
const configured = import.meta.env.VITE_DAEMON_SOCKET;
|
||||
if (configured) {
|
||||
return configured;
|
||||
}
|
||||
|
||||
return toWsOrigin(getWindowOrigin());
|
||||
}
|
||||
|
||||
export function getPublicBaseUrl() {
|
||||
return trimTrailingSlash(import.meta.env.VITE_PUBLIC_BASE_URL || getWindowOrigin() || 'https://www.your-part.de');
|
||||
}
|
||||
|
||||
export function getChatWsUrlFromEnv() {
|
||||
const directUrl = import.meta.env.VITE_CHAT_WS_URL;
|
||||
if (directUrl) {
|
||||
return directUrl.trim();
|
||||
}
|
||||
|
||||
const host = import.meta.env.VITE_CHAT_WS_HOST;
|
||||
const port = import.meta.env.VITE_CHAT_WS_PORT;
|
||||
const protocol = import.meta.env.VITE_CHAT_WS_PROTOCOL || (typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss' : 'ws');
|
||||
|
||||
if (host || port) {
|
||||
const resolvedHost = host || (typeof window !== 'undefined' ? window.location.hostname : 'localhost');
|
||||
const resolvedPort = port ? `:${port}` : '';
|
||||
return `${protocol}://${resolvedHost}${resolvedPort}`;
|
||||
}
|
||||
|
||||
return toWsOrigin(getWindowOrigin());
|
||||
}
|
||||
@@ -1,20 +1,10 @@
|
||||
import axios from 'axios';
|
||||
import store from '../store';
|
||||
import { getApiBaseUrl } from './appConfig.js';
|
||||
|
||||
// API-Basis-URL - Apache-Proxy für Produktion, direkte Verbindung für lokale Entwicklung
|
||||
const getApiBaseURL = () => {
|
||||
// Wenn explizite Umgebungsvariable gesetzt ist, diese verwenden
|
||||
if (import.meta.env.VITE_API_BASE_URL) {
|
||||
return import.meta.env.VITE_API_BASE_URL;
|
||||
}
|
||||
|
||||
// Für lokale Entwicklung: direkte Backend-Verbindung
|
||||
if (import.meta.env.DEV || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
||||
return 'http://localhost:3001';
|
||||
}
|
||||
|
||||
// Für Produktion: Root-Pfad, da API-Endpunkte bereits mit /api beginnen
|
||||
return '';
|
||||
return getApiBaseUrl();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
// Centralized config for YourChat protocol mapping and WS endpoint
|
||||
// Override via .env (VITE_* variables)
|
||||
import { getChatWsUrlFromEnv } from './appConfig.js';
|
||||
|
||||
const env = import.meta.env || {};
|
||||
|
||||
export const CHAT_WS_URL = env.VITE_CHAT_WS_URL
|
||||
|| (env.VITE_CHAT_WS_HOST || env.VITE_CHAT_WS_PORT
|
||||
? `ws://${env.VITE_CHAT_WS_HOST || 'localhost'}:${env.VITE_CHAT_WS_PORT || '1235'}`
|
||||
: (typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host + '/socket.io/');
|
||||
export const CHAT_WS_URL = getChatWsUrlFromEnv();
|
||||
|
||||
// Event/type keys
|
||||
export const CHAT_EVENT_KEY = env.VITE_CHAT_EVENT_KEY || 'type';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { getPublicBaseUrl } from './appConfig.js';
|
||||
|
||||
const DEFAULT_BASE_URL = 'https://www.your-part.de';
|
||||
const DEFAULT_SITE_NAME = 'YourPart';
|
||||
const DEFAULT_TITLE = 'YourPart - Community, Chat, Forum, Vokabeltrainer, Falukant und Minispiele';
|
||||
@@ -21,7 +23,7 @@ const MANAGED_META_KEYS = [
|
||||
];
|
||||
|
||||
function getBaseUrl() {
|
||||
return (import.meta.env.VITE_PUBLIC_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, '');
|
||||
return getPublicBaseUrl().replace(/\/$/, '') || DEFAULT_BASE_URL;
|
||||
}
|
||||
|
||||
function upsertMeta(attr, key, content) {
|
||||
|
||||
@@ -51,8 +51,11 @@
|
||||
:title="$t('home.nologin.login.passworddescription')" @keydown.enter="doLogin"
|
||||
ref="passwordInput">
|
||||
</div>
|
||||
<div>
|
||||
<label><input type="checkbox"><span>{{ $t('home.nologin.login.stayLoggedIn') }}</span></label>
|
||||
<div class="stay-logged-in-row">
|
||||
<label class="stay-logged-in-label">
|
||||
<input class="stay-logged-in-checkbox" type="checkbox">
|
||||
<span>{{ $t('home.nologin.login.stayLoggedIn') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -125,7 +128,8 @@ export default {
|
||||
const response = await apiClient.post('/api/auth/login', { username: this.username, password: this.password });
|
||||
this.login(response.data);
|
||||
} catch (error) {
|
||||
this.$root.$refs.errorDialog.open(`tr:error.${error.response.data.error}`);
|
||||
const errorKey = error?.response?.data?.error || 'network';
|
||||
this.$root.$refs.errorDialog.open(`tr:error.${errorKey}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,34 +150,45 @@ export default {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
gap: 2em;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.home-structure>div {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.mascot {
|
||||
flex: 0 0 clamp(180px, 22%, 280px);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #fdf1db;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
min-height: 400px;
|
||||
align-items: stretch;
|
||||
background: linear-gradient(180deg, #fff5e8 0%, #fce7ca 100%);
|
||||
border: 1px solid rgba(248, 162, 43, 0.16);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 24px rgba(93, 64, 55, 0.08);
|
||||
overflow: hidden;
|
||||
align-self: center;
|
||||
height: clamp(320px, 68vh, 560px);
|
||||
min-height: 320px;
|
||||
max-height: 560px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2em;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.actions>div {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background-color: #FFF4F0;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
@@ -188,6 +203,33 @@ export default {
|
||||
color: var(--color-primary-orange);
|
||||
}
|
||||
|
||||
.stay-logged-in-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
|
||||
.stay-logged-in-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.stay-logged-in-checkbox {
|
||||
width: 16px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
min-height: 16px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex: 0 0 16px;
|
||||
accent-color: var(--color-primary-orange);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.seo-content {
|
||||
max-width: 1000px;
|
||||
margin: 24px auto 0 auto;
|
||||
@@ -233,7 +275,32 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.home-structure {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.mascot {
|
||||
min-height: 260px;
|
||||
height: 260px;
|
||||
flex: 0 0 260px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.actions>div {
|
||||
min-height: 260px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user