Zugangsseite: zwei eigene Fenster für qciga und VanVan statt einer gemeinsamen Box

Auf ausdrücklichen Wunsch im Stil einer bereits bestehenden Zwei-Personen-Zugangsseite umgebaut,
aber komplett an Van's eigene Marke angepasst (Farben/Schrift/Wasserzeichen unverändert: Lila/
Anthrazit/Hellblau/Gold statt fremder Projektfarben). Jede Person bekommt ihr eigenes Fenster mit
Avatar (VanVan: ihr echtes Porträtfoto; qciga: passendes Icon, da noch kein Foto in diesem Projekt
hinterlegt ist), eigenem Codefeld und eigenem "Öffnen"-Button in unterschiedlicher Akzentfarbe
(Cyan/Violett für qciga, Gold/Violett für VanVan). Technisch unverändert: /gate-auth erkennt die
Rolle weiterhin selbst anhand des eingegebenen Codes, unabhängig vom benutzten Fenster.

Dabei gefundener und behobener Bug: /img/vanvan-portrait.png wurde von der Zugangs-Schranke selbst
blockiert (302) — das für den Avatar gebrauchte Foto zeigte deshalb nur einen leeren Kreis. Gezielt
nur diese eine Datei freigegeben (kein Shop-/Kundendatum, ohnehin VanVans öffentliches Porträtfoto).

Per echtem Browsertest verifiziert: beide Fenster funktional getrennt (falscher Code im einen
Fenster zeigt den Fehler nur dort, richtiger Code im anderen Fenster loggt korrekt ein), Layout auf
Desktop nebeneinander und auf schmalen Bildschirmen sauber gestapelt.
This commit is contained in:
qcigano
2026-08-04 16:28:05 +02:00
parent 2efba64d4f
commit c6f8465e64
2 changed files with 211 additions and 81 deletions
+7 -1
View File
@@ -75,11 +75,17 @@ const OEFFENTLICHE_SEITEN = new Set([
von der Schranke blockiert (302 statt der Datei) — dadurch schlug u.a. die Service-Worker-
Registrierung auf /admin/ mit einem echten Browserfehler fehl, sobald jemand /admin/ ohne
gültige Haupt-Gate-Sitzung aufruft (z.B. direkt per Lesezeichen, ohne vorher die Haupt-Seite
besucht/eingeloggt zu haben). */
besucht/eingeloggt zu haben).
/img/vanvan-portrait.png (04.08.2026 ergänzt): wird als Avatar-Foto direkt AUF der
Zugangscode-Seite selbst gezeigt (siehe gate.html) — ohne Freigabe zeigt das Foto dort nur einen
leeren Kreis, weil selbst dieses eine Bild noch hinter der Schranke lag, die es doch gerade
anzeigen soll. Kein Shop-/Kundendatum, sondern VanVans eigenes öffentliches Porträtfoto (wird
nach dem Öffentlich-Schalten ohnehin überall sichtbar sein, z.B. Startseite). */
function istOeffentlichErreichbar(pfad) {
const ohneSlash = pfad.endsWith("/") && pfad.length > 1 ? pfad.slice(0, -1) : pfad;
if (OEFFENTLICHE_SEITEN.has(ohneSlash)) return true;
if (pfad.startsWith("/_astro/") && pfad.endsWith(".css")) return true;
if (pfad === "/img/vanvan-portrait.png") return true;
if (/^\/(logo\.png|favicon\.ico|favicon-\d+x\d+\.png|apple-touch-icon\.png|icon-\d+(-maskable)?\.png|sw\.js|manifest\.webmanifest)$/.test(pfad)) return true;
return false;
}
+181 -57
View File
@@ -16,6 +16,8 @@
--c-accent: #5fd3ec;
--c-accent-hover: #3ca9c2;
--c-purple-glow: #9b7fd6;
--c-gold: #e3b23c;
--c-gold-hover: #c99527;
--radius-l: 22px;
--radius-m: 16px;
--ease: cubic-bezier(0.22, 1, 0.36, 1);
@@ -73,26 +75,82 @@
to { transform: translate(-50%, -50%) rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) { .gate-logo-bg { animation: none; } }
.gate-box {
/* 04.08.2026, auf ausdrücklichen Wunsch: statt EINER gemeinsamen Zugangscode-Box (in der beide
einfach irgendeinen der beiden Codes eintippen konnten) jetzt zwei eigene "Fenster" — eins für
qciga, eins für VanVan — im Stil einer anderen, bereits bestehenden Zwei-Personen-Zugangsseite,
aber komplett an Van's eigene Marke angepasst (Farben/Schrift/Wasserzeichen unverändert, siehe
Branding.md: kein Babyblau/Rot-Look von anderen Projekten, sondern die eigene Lila/Anthrazit/
Hellblau/Gold-Welt). Technisch ändert sich nichts: /gate-auth erkennt die Rolle ohnehin selbst
anhand DES CODES, nicht danach, welches Fenster benutzt wurde — die Aufteilung ist rein dafür
da, dass jede/r sofort das eigene Feld findet, statt raten zu müssen. */
.gate-wrap {
position: relative;
max-width: 430px;
z-index: 1;
max-width: 700px;
width: 100%;
text-align: center;
padding: 2.4rem 2rem;
}
.gate-header { margin-bottom: 1.6rem; }
.gate-eyebrow {
display: inline-block;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--c-gold);
background: color-mix(in srgb, var(--c-gold) 16%, transparent);
border: 1px solid color-mix(in srgb, var(--c-gold) 40%, transparent);
border-radius: 999px;
padding: 0.3rem 0.9rem;
margin-bottom: 1rem;
}
.gate-header h1 {
font-family: "Fraunces", "Playfair Display", Georgia, serif;
font-size: 1.7rem;
font-weight: 600;
margin: 0 0 0.6rem;
line-height: 1.2;
}
.gate-header p {
margin: 0;
color: var(--c-text-muted);
font-size: 0.96rem;
}
.gate-cards {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 1.1rem;
}
.gate-card {
position: relative;
overflow: hidden;
flex: 1 1 250px;
max-width: 300px;
padding: 1.8rem 1.5rem;
border-radius: var(--radius-l);
border: 1.5px solid transparent;
background:
linear-gradient(165deg, var(--c-anthracite-light), var(--c-anthracite)) padding-box,
linear-gradient(120deg, #9b7fd6, var(--c-accent), #e3b23c, #9b7fd6) border-box;
background-size: 100% 100%, 300% 100%;
animation: gate-border-shift 9s ease-in-out infinite;
box-shadow: 0 20px 50px -20px rgba(0, 0, 0, 0.7);
}
.gate-card-qciga {
background:
linear-gradient(165deg, var(--c-anthracite-light), var(--c-anthracite)) padding-box,
linear-gradient(120deg, var(--c-accent), var(--c-accent-hover), var(--c-purple-glow), var(--c-accent)) border-box;
}
.gate-card-vanvan {
background:
linear-gradient(165deg, var(--c-anthracite-light), var(--c-anthracite)) padding-box,
linear-gradient(120deg, var(--c-gold), var(--c-purple-glow), var(--c-accent), var(--c-gold)) border-box;
}
@keyframes gate-border-shift {
0%, 100% { background-position: 0 0, 0% 50%; }
50% { background-position: 0 0, 100% 50%; }
}
.gate-box::after {
.gate-card::after {
content: "";
position: absolute;
top: 0;
@@ -105,75 +163,105 @@
pointer-events: none;
border-radius: inherit;
}
.gate-card-vanvan::after { animation-delay: 1.2s; }
@keyframes gate-shine {
0% { left: -60%; }
35%, 100% { left: 130%; }
}
.gate-box > * { position: relative; z-index: 1; }
@media (prefers-reduced-motion: reduce) { .gate-box, .gate-box::after { animation: none; } }
.gate-eyebrow {
display: inline-block;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #e3b23c;
background: color-mix(in srgb, #e3b23c 16%, transparent);
border: 1px solid color-mix(in srgb, #e3b23c 40%, transparent);
border-radius: 999px;
padding: 0.3rem 0.9rem;
margin-bottom: 1rem;
.gate-card > * { position: relative; z-index: 1; }
@media (prefers-reduced-motion: reduce) { .gate-card, .gate-card::after { animation: none; } }
/* Avatar je Karte: dasselbe "Glow-Ring"-Prinzip wie beim .portrait-frame-Baustein auf der
restlichen Seite (siehe Branding.md), nur kompakt für ein kleines Zugangs-Fenster. VanVan
bekommt ihr echtes Porträtfoto, für qciga gibt es (noch) kein Foto in diesem Projekt — deshalb
ein passendes Icon in derselben Bildsprache statt eines geratenen/fremden Fotos. */
.gate-avatar {
position: relative;
width: 84px;
height: 84px;
margin: 0 auto 1rem;
}
.gate-box h1 {
font-family: "Fraunces", "Playfair Display", Georgia, serif;
font-size: 1.7rem;
font-weight: 600;
margin: 0 0 0.6rem;
line-height: 1.2;
.gate-avatar-glow {
position: absolute;
inset: -12px;
border-radius: 50%;
filter: blur(13px);
opacity: 0.6;
animation: gate-avatar-pulse 3.2s ease-in-out infinite;
}
.gate-box p {
margin: 0 0 1.4rem;
color: var(--c-text-muted);
font-size: 0.96rem;
.gate-card-qciga .gate-avatar-glow { background: radial-gradient(circle, color-mix(in srgb, var(--c-accent) 70%, transparent), transparent 72%); }
.gate-card-vanvan .gate-avatar-glow { background: radial-gradient(circle, color-mix(in srgb, var(--c-gold) 70%, transparent), transparent 72%); }
@keyframes gate-avatar-pulse {
0%, 100% { opacity: 0.45; transform: scale(1); }
50% { opacity: 0.85; transform: scale(1.12); }
}
.gate-box input {
@media (prefers-reduced-motion: reduce) { .gate-avatar-glow { animation: none; } }
.gate-avatar-photo,
.gate-avatar-icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 0.9rem 1rem;
height: 100%;
border-radius: 50%;
background: linear-gradient(160deg, var(--c-anthracite-light), var(--c-anthracite));
}
.gate-avatar-photo { object-fit: cover; object-position: top center; }
.gate-card-qciga .gate-avatar-photo,
.gate-card-qciga .gate-avatar-icon { box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-accent) 50%, transparent), 0 10px 26px -10px rgba(0, 0, 0, 0.7); }
.gate-card-vanvan .gate-avatar-photo,
.gate-card-vanvan .gate-avatar-icon { box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-gold) 50%, transparent), 0 10px 26px -10px rgba(0, 0, 0, 0.7); }
.gate-avatar-icon { font-size: 2.1rem; }
.gate-card h2 {
font-family: "Fraunces", "Playfair Display", Georgia, serif;
font-size: 1.2rem;
font-weight: 600;
margin: 0 0 1.1rem;
}
.gate-card-qciga h2 { color: var(--c-accent); }
.gate-card-vanvan h2 { color: var(--c-gold); }
.gate-card input {
width: 100%;
padding: 0.85rem 0.9rem;
border-radius: 12px;
text-align: center;
letter-spacing: 0.08em;
font-size: 1.05rem;
margin-bottom: 0.9rem;
font-size: 1rem;
margin-bottom: 0.8rem;
background: rgba(244, 241, 247, 0.05);
border: 1.5px solid rgba(244, 241, 247, 0.16);
color: var(--c-text);
font-family: inherit;
}
.gate-box input:focus {
outline: none;
border-color: var(--c-accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-accent) 25%, transparent);
}
.gate-box button {
.gate-card-qciga input:focus { outline: none; border-color: var(--c-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-accent) 25%, transparent); }
.gate-card-vanvan input:focus { outline: none; border-color: var(--c-gold); box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-gold) 25%, transparent); }
.gate-card button {
width: 100%;
padding: 0.85rem 1rem;
padding: 0.8rem 1rem;
border-radius: 12px;
border: none;
background: linear-gradient(120deg, var(--c-accent), var(--c-accent-hover));
color: var(--c-black);
font-weight: 800;
font-size: 1rem;
font-size: 0.96rem;
cursor: pointer;
transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.gate-box button:hover { transform: translateY(-2px); box-shadow: 0 10px 24px -10px color-mix(in srgb, var(--c-accent) 60%, transparent); }
.gate-card-qciga button { background: linear-gradient(120deg, var(--c-accent), var(--c-accent-hover)); color: var(--c-black); }
.gate-card-qciga button:hover { transform: translateY(-2px); box-shadow: 0 10px 24px -10px color-mix(in srgb, var(--c-accent) 60%, transparent); }
.gate-card-vanvan button { background: linear-gradient(120deg, var(--c-gold), var(--c-gold-hover)); color: var(--c-black); }
.gate-card-vanvan button:hover { transform: translateY(-2px); box-shadow: 0 10px 24px -10px color-mix(in srgb, var(--c-gold) 60%, transparent); }
.gate-fehler {
color: #ff9a9a;
font-size: 0.88rem;
margin: 0.9rem 0 0;
font-size: 0.85rem;
margin: 0.8rem 0 0;
display: none;
}
.gate-fehler.zeige { display: block; }
.gate-logo {
font-family: "Fraunces", "Playfair Display", Georgia, serif;
font-weight: 700;
@@ -181,32 +269,67 @@
color: var(--c-text-muted);
margin: 1.6rem 0 0;
}
@media (max-width: 560px) {
.gate-card { max-width: 100%; flex-basis: 100%; }
}
</style>
</head>
<body>
<div class="gate-logo-bg" aria-hidden="true"></div>
<div class="gate-box">
<div class="gate-wrap">
<div class="gate-header">
<span class="gate-eyebrow">🔒 Noch nicht öffentlich</span>
<h1>Van's DIY &amp; Bastelbedarf</h1>
<p>Diese Seite ist gerade im Aufbau und noch nicht für die Öffentlichkeit freigegeben.<br />Zugangscode eingeben, um weiterzukommen.</p>
<form id="gate-form">
<label class="sr-only" for="gate-code">Zugangscode</label>
<input type="text" id="gate-code" placeholder="Zugangscode" autocomplete="off" autocapitalize="off" spellcheck="false" autofocus />
<button type="submit">Weiter</button>
</div>
<div class="gate-cards">
<div class="gate-card gate-card-qciga">
<div class="gate-avatar" aria-hidden="true">
<span class="gate-avatar-glow"></span>
<span class="gate-avatar-icon">🧑‍💻</span>
</div>
<h2>qciga</h2>
<form class="gate-form" data-role="qciga">
<label class="sr-only" for="gate-code-qciga">Zugangscode für qciga</label>
<input type="text" id="gate-code-qciga" placeholder="Zugangscode" autocomplete="off" autocapitalize="off" spellcheck="false" />
<button type="submit">🔑 Öffnen</button>
</form>
<p class="gate-fehler" id="gate-fehler" role="alert" aria-live="assertive"></p>
<p class="gate-fehler" data-fehler-for="qciga" role="alert" aria-live="assertive"></p>
</div>
<div class="gate-card gate-card-vanvan">
<div class="gate-avatar" aria-hidden="true">
<span class="gate-avatar-glow"></span>
<img class="gate-avatar-photo" src="/img/vanvan-portrait.png" alt="" />
</div>
<h2>VanVan</h2>
<form class="gate-form" data-role="vanvan">
<label class="sr-only" for="gate-code-vanvan">Zugangscode für VanVan</label>
<input type="text" id="gate-code-vanvan" placeholder="Zugangscode" autocomplete="off" autocapitalize="off" spellcheck="false" />
<button type="submit">💛 Öffnen</button>
</form>
<p class="gate-fehler" data-fehler-for="vanvan" role="alert" aria-live="assertive"></p>
</div>
</div>
<p class="gate-logo">🧶 Handgemacht mit Liebe</p>
</div>
<script>
(function () {
const params = new URLSearchParams(location.search);
const next = params.get("next") || "/";
const form = document.getElementById("gate-form");
const fehlerEl = document.getElementById("gate-fehler");
// Ein gemeinsamer Handler für beide Fenster — /gate-auth erkennt die Rolle ohnehin selbst
// anhand des eingegebenen Codes, unabhängig davon, welches der beiden Fenster benutzt wurde.
document.querySelectorAll(".gate-form").forEach(function (form) {
const role = form.dataset.role;
const input = form.querySelector("input");
const fehlerEl = document.querySelector('[data-fehler-for="' + role + '"]');
form.addEventListener("submit", async function (e) {
e.preventDefault();
fehlerEl.classList.remove("zeige");
const code = document.getElementById("gate-code").value.trim();
const code = input.value.trim();
if (!code) return;
try {
const res = await fetch("/gate-auth", {
@@ -226,6 +349,7 @@
fehlerEl.classList.add("zeige");
}
});
});
})();
</script>
</body>