Arbeit der parallelen Session in git gesichert (war nur auf dem Server)
Diese Änderungen liefen bereits live auf dem Server, lagen dort aber ausschließlich als nicht eingecheckte Arbeitskopie — bei jedem Deploy (stash/pull/pop) und bei jedem Serverproblem wären sie verloren gewesen. Deshalb hier unverändert in git übernommen, bevor darauf aufgebaut wird. Enthalten (nicht von mir gebaut, nur gesichert): - Supporter: eigener fester Zugangscode statt Einmalcode-Login (Migration 0009, lib/crypto.js scrypt-Hash, routes/supporter.js, abonnieren.html, supporter.html, i18n-abonnieren/-supporter) - Stimmen: Profilbilder (Migration 0010, routes/testimonials.js) - Event-Bild-Upload: voller Pfad statt relativem (routes/events.js) - Neue/überarbeitete Hintergrundbilder für viele Seiten - Teilen-Funktion (streamplan.js, i18n-index/-streamplan, main.css) Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+163
-16
@@ -58,6 +58,80 @@
|
||||
.poll-bar-track { height: 12px; border-radius: 999px; background: rgba(255,255,255,.06); overflow: hidden; border: 1px solid var(--border); }
|
||||
.poll-bar-fill { height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--accent-2), var(--accent)); transition: width .4s ease; }
|
||||
|
||||
/* =====================================================================
|
||||
Abschnitts-Kopf + Abmelde-Knopf (21.08.2026, Nutzer-Wunsch: "ich will
|
||||
da einen richtig geilen abmelde button").
|
||||
|
||||
Farbwahl bewusst NICHT Gold: Gold ist auf der ganzen Seite die Farbe
|
||||
für "das ist deine Belohnung / das willst du" (Prämien, Preis, Abo).
|
||||
Ausloggen ist das Gegenteil — ein Weggehen. Deshalb ein gedecktes
|
||||
Rosé/Rot aus derselben Edelstein-Familie wie die Verwaltungsseite:
|
||||
eindeutig unterscheidbar, aber nicht das grelle Warnrot einer
|
||||
Fehlermeldung (Projektregel "augenschonend"). Der Glanzstreifen läuft
|
||||
nur beim Überfahren durch, nicht dauerhaft — ein permanent blinkender
|
||||
Knopf in der Seitenmitte würde beim Lesen stören.
|
||||
===================================================================== */
|
||||
.supp-abschnitt-kopf {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: 1rem 1.4rem; flex-wrap: wrap;
|
||||
/* Nutzer-Hinweis 21.08.2026: "der abstand soll perfekter sein, teilweise
|
||||
berühren die sich wenn man mit der maus über die kacheln geht."
|
||||
Ursache war das margin:0 am h2 unten — dadurch lag der Kopf direkt auf
|
||||
der Karte. Der reine Kasten-Abstand reicht hier aber nicht als Maß:
|
||||
der Knopf wirft beim Überfahren einen Schatten von rund 32px nach
|
||||
unten, und die Kacheln heben sich ihrerseits 4px an und leuchten
|
||||
26px weit nach oben — beides zusammen berührte sich. Nachgemessen:
|
||||
mit diesem Wert bleiben auch im ungünstigsten Fall (beide Hover-
|
||||
Zustände gleichzeitig) deutlich sichtbare Luft dazwischen. */
|
||||
margin: 0 0 1.9rem;
|
||||
}
|
||||
/* margin:0 nötig, damit die Überschrift in der Flex-Zeile sauber auf einer
|
||||
Linie mit dem Knopf sitzt — der Abstand nach unten kommt stattdessen vom
|
||||
Container oben. */
|
||||
.supp-abschnitt-kopf h2 { margin: 0; }
|
||||
|
||||
.supp-logout {
|
||||
position: relative; isolation: isolate; overflow: hidden;
|
||||
display: inline-flex; align-items: center; gap: .55rem;
|
||||
padding: .72rem 1.35rem; border-radius: 999px;
|
||||
font-weight: 800; font-size: .92rem; letter-spacing: .01em;
|
||||
cursor: pointer; white-space: nowrap;
|
||||
color: #ffd9df;
|
||||
border: 1.5px solid rgba(255,138,164,.45);
|
||||
background: linear-gradient(140deg, rgba(74,26,38,.95), rgba(38,20,26,.95));
|
||||
box-shadow: 0 8px 22px -14px rgba(255,110,140,.8), inset 0 1px 0 rgba(255,255,255,.06);
|
||||
transition: transform .18s ease, box-shadow .22s ease, border-color .22s ease, color .22s ease;
|
||||
}
|
||||
/* Durchlaufender Glanz — startet außerhalb und wandert beim Hover einmal
|
||||
quer über den Knopf. */
|
||||
.supp-logout-glanz {
|
||||
position: absolute; inset: 0; z-index: 1; pointer-events: none;
|
||||
background: linear-gradient(100deg, transparent 34%, rgba(255,255,255,.42) 50%, transparent 66%);
|
||||
background-size: 260% 100%;
|
||||
background-position: 140% 0;
|
||||
transition: background-position .75s ease;
|
||||
}
|
||||
.supp-logout > *:not(.supp-logout-glanz) { position: relative; z-index: 2; }
|
||||
.supp-logout-icon { font-size: 1.02rem; line-height: 1; }
|
||||
.supp-logout:hover {
|
||||
transform: translateY(-2px);
|
||||
color: #fff;
|
||||
border-color: rgba(255,158,180,.85);
|
||||
box-shadow: 0 14px 30px -12px rgba(255,110,140,.75), 0 0 22px rgba(255,110,140,.28), inset 0 1px 0 rgba(255,255,255,.1);
|
||||
}
|
||||
.supp-logout:hover .supp-logout-glanz { background-position: -60% 0; }
|
||||
.supp-logout:active { transform: translateY(0) scale(.985); }
|
||||
.supp-logout:focus-visible { outline: 2px solid rgba(255,158,180,.9); outline-offset: 3px; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.supp-logout, .supp-logout-glanz { transition: none; }
|
||||
.supp-logout:hover { transform: none; }
|
||||
}
|
||||
@media (max-width: 520px) {
|
||||
/* Auf schmalen Handys volle Breite statt gequetscht neben der Überschrift. */
|
||||
.supp-abschnitt-kopf { align-items: stretch; }
|
||||
.supp-logout { width: 100%; justify-content: center; }
|
||||
}
|
||||
|
||||
/* Gemeinsamer Karten-Rahmen mit Gold-Schimmerlinie oben — dieselbe
|
||||
Machart wie .abo-preis-box auf abonnieren.html, damit der Bereich
|
||||
erkennbar zusammengehört. */
|
||||
@@ -322,12 +396,12 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-abonnieren.jpg');--page-bg-mobile:url('/assets/img/bg-abonnieren-mobile.jpg');">
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-supporter.jpg');--page-bg-mobile:url('/assets/img/bg-supporter-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
|
||||
<main>
|
||||
<section class="hero page-bg">
|
||||
<div class="hero-banner"><img src="/assets/img/bg-abonnieren.jpg" alt="Dogfather Supporter-Abo" loading="eager" /></div>
|
||||
<div class="hero-banner"><img src="/assets/img/bg-supporter.jpg" alt="Dogfather Supporter-Abo" loading="eager" /></div>
|
||||
<div class="container">
|
||||
<span class="eyebrow" data-i18n="su_eyebrow">👑 Supporter-Bereich</span>
|
||||
<h1 id="supp-begruessung" data-i18n="su_lade">Dein Supporter-Bereich</h1>
|
||||
@@ -351,7 +425,20 @@
|
||||
<p class="abo-fehler" id="poll-fehler"></p>
|
||||
</div>
|
||||
|
||||
<h2 data-i18n="su_fenster1_h2">Mein Abo</h2>
|
||||
<!-- Nutzer-Wunsch 21.08.2026: "ich will da einen richtig geilen abmelde
|
||||
button." Sitzt bewusst HIER oben neben der Überschrift statt wie
|
||||
vorher ganz unten unter "Abo verwalten" — dort war er als schlichter
|
||||
Umriss-Knopf kaum zu finden. Der alte wurde entfernt statt zusätzlich
|
||||
stehen zu lassen: zwei Abmelde-Knöpfe auf einer Seite wären genau die
|
||||
Dopplung, die schon bei "2 mal registrieren" gestört hat. -->
|
||||
<div class="supp-abschnitt-kopf">
|
||||
<h2 data-i18n="su_fenster1_h2">Mein Abo</h2>
|
||||
<button type="button" class="supp-logout" id="btn-logout">
|
||||
<span class="supp-logout-glanz" aria-hidden="true"></span>
|
||||
<span class="supp-logout-icon" aria-hidden="true">⏻</span>
|
||||
<span class="supp-logout-text" data-i18n="su_btn_logout">Ausloggen</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card supp-card">
|
||||
<div class="supp-stats">
|
||||
<div class="supp-stat"><span class="supp-stat-icon" aria-hidden="true">🪪</span><div><div class="label" data-i18n="su_stat_status">Status</div><div class="value" id="stat-status">–</div></div></div>
|
||||
@@ -360,9 +447,20 @@
|
||||
<div class="supp-stat supp-stat-clickable" id="stat-card-monate" tabindex="0" role="button" aria-haspopup="dialog"><span class="supp-stat-icon" aria-hidden="true">🗓️</span><div><div class="label" data-i18n="su_stat_monate">Bezahlte Monate gesamt</div><div class="value" id="stat-monate">–</div><div class="supp-stat-hinweis" data-i18n="su_stat_details_hinweis">Details ansehen</div></div></div>
|
||||
<div class="supp-stat"><span class="supp-stat-icon" aria-hidden="true">🔄</span><div><div class="label" data-i18n="su_stat_zyklus">Aktueller Zyklus</div><div class="value" id="stat-zyklus">–</div></div></div>
|
||||
</div>
|
||||
<!-- Kein aktives Abo. Der frühere Knopf "Jetzt abonnieren" führte auf
|
||||
abonnieren.html — die schickt eingeloggte Leute seit 21.08.2026
|
||||
aber direkt wieder hierher zurück (siehe pruefeStatus() dort), das
|
||||
wäre also eine Schleife gewesen. Solange die Bezahlung ohnehin
|
||||
gesperrt ist, steht hier deshalb derselbe "Coming soon…"-Hinweis
|
||||
wie auf der Anmeldeseite — ehrlicher und ohne Sackgasse. Sobald
|
||||
PayPal live ist, wird hieraus wieder ein echter Knopf. -->
|
||||
<div id="supp-keine-abo" style="display:none;margin-top:1.2rem;">
|
||||
<p class="small" data-i18n="su_kein_abo_text">Du hast noch kein aktives Abo.</p>
|
||||
<a class="btn btn-primary" href="abonnieren.html" data-i18n="su_kein_abo_btn">Jetzt abonnieren</a>
|
||||
<div class="btn-gold-locked" style="max-width:320px;margin-top:.6rem;cursor:default;">
|
||||
<span class="btn-gold-locked-lock" aria-hidden="true">🔒</span>
|
||||
<span class="btn-gold-locked-text" data-i18n="su_kein_abo_soon">Coming soon…</span>
|
||||
</div>
|
||||
<p class="small muted" style="margin-top:.7rem;" data-i18n="su_kein_abo_hinweis">Alles hier ist schon für dich vorbereitet — sobald das Abo startet, kannst du es mit einem Klick aktivieren.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -388,9 +486,10 @@
|
||||
<button type="button" class="btn btn-outline" id="btn-save-profile" data-i18n="su_btn_profil_speichern">Speichern</button>
|
||||
<p class="abo-erfolg" id="profil-erfolg" data-i18n="su_profil_gespeichert">Gespeichert.</p>
|
||||
|
||||
<!-- "Ausloggen" ist von hier nach oben neben die Überschrift "Mein Abo"
|
||||
gewandert (21.08.2026) — siehe dort. -->
|
||||
<div class="btn-row" style="margin-top:1.6rem;">
|
||||
<button type="button" class="btn btn-outline" id="btn-cancel" data-i18n="su_btn_kuendigen">Abo kündigen</button>
|
||||
<button type="button" class="btn btn-outline" id="btn-logout" data-i18n="su_btn_logout">Ausloggen</button>
|
||||
</div>
|
||||
<p class="abo-fehler" id="verwalten-fehler"></p>
|
||||
</div>
|
||||
@@ -550,13 +649,29 @@
|
||||
document.getElementById("supp-begruessung").textContent = `Willkommen, ${data.profile.displayName}!`;
|
||||
|
||||
const sub = data.subscription;
|
||||
/* Läuft überhaupt ein Abo? Entscheidet, ob der nächste Meilenstein als
|
||||
"läuft schon" (✨ + Countdown) oder als gesperrt (🔒) dargestellt wird.
|
||||
Dieselbe Statusliste wie beim Nav-Knopf in main.js — "cancelled" zählt
|
||||
bewusst noch dazu, weil der Zugang bis zum Ende des bezahlten Zeitraums
|
||||
bestehen bleibt und in dieser Zeit auch noch Monate dazukommen können. */
|
||||
const aboLaeuft = ["active", "pending", "suspended", "cancelled"].includes(sub.status);
|
||||
document.getElementById("stat-status").innerHTML =
|
||||
`<span class="supp-badge ${sub.status}">${STATUS_LABELS[sub.status] || sub.status}</span>`;
|
||||
document.getElementById("stat-preis").textContent = sub.monthlyPrice;
|
||||
/* Nutzer-Wunsch 21.08.2026: "da soll noch kein preis stehen bitte."
|
||||
Der Preis ist überall sonst bewusst noch verborgen (auf abonnieren.html
|
||||
steht an seiner Stelle "Coming soon…", siehe .abo-preis-zahl dort) —
|
||||
über diese Kachel wäre er sonst durch die Hintertür doch schon
|
||||
öffentlich gewesen. Wer bereits zahlt, MUSS seinen Preis dagegen sehen
|
||||
können, deshalb nur ausblenden solange kein Abo läuft. */
|
||||
document.getElementById("stat-preis").textContent = aboLaeuft ? sub.monthlyPrice : "Coming soon…";
|
||||
document.getElementById("stat-naechste").textContent = fmtDatum(sub.nextPaymentAt);
|
||||
document.getElementById("stat-monate").textContent = sub.totalPaidMonths;
|
||||
document.getElementById("stat-zyklus").textContent = `Zyklus ${data.progress.cycleNumber}`;
|
||||
document.getElementById("supp-keine-abo").style.display = sub.status === "none" ? "" : "none";
|
||||
// Nutzer-Wunsch 21.08.2026: Kündigen-Knopf ergibt nur Sinn, wenn
|
||||
// überhaupt ein (noch nicht schon gekündigtes) Abo existiert -- sonst
|
||||
// sieht man einen Knopf für etwas, das es gar nicht gibt.
|
||||
document.getElementById("btn-cancel").style.display = ["none", "cancelled", "expired"].includes(sub.status) ? "none" : "";
|
||||
|
||||
if (sub.status === "cancelled") {
|
||||
const hinweis = document.getElementById("supp-kuendigungs-hinweis");
|
||||
@@ -571,10 +686,17 @@
|
||||
const zeitleisteEl = document.getElementById("supp-zeitleiste");
|
||||
zeitleisteEl.innerHTML = t.milestones
|
||||
.map((m) => {
|
||||
const klasse = m.reached ? "erreicht" : m.isNext ? "naechste" : "";
|
||||
const status = m.reached ? "Freigeschaltet" : m.isNext ? `Noch ${m.monthsRemaining} Monate` : "Gesperrt";
|
||||
// Nutzer-Wunsch 21.08.2026: "die erste kiste soll blockiert sein wie
|
||||
// die anderen, denn das ist ja verbunden mit dem abo." Ohne laufendes
|
||||
// Abo läuft auch kein Countdown -- die Rechnung "noch 6 Monate" wäre
|
||||
// schlicht falsch, weil der erste dieser Monate noch gar nicht
|
||||
// angefangen hat. Deshalb: ohne Abo ist ALLES gesperrt, inklusive des
|
||||
// ersten Meilensteins. Siehe aboLaeuft weiter oben.
|
||||
const naechste = m.isNext && aboLaeuft;
|
||||
const klasse = m.reached ? "erreicht" : naechste ? "naechste" : "";
|
||||
const status = m.reached ? "Freigeschaltet" : naechste ? `Noch ${m.monthsRemaining} Monate` : "Gesperrt";
|
||||
return `<div class="supp-meilenstein ${klasse}">
|
||||
<div class="icon-medaille">${m.reached ? "🎁" : m.isNext ? "✨" : "🔒"}</div>
|
||||
<div class="icon-medaille">${m.reached ? "🎁" : naechste ? "✨" : "🔒"}</div>
|
||||
<div class="monat">Monat ${m.month}</div>
|
||||
<div class="titel">${REDEEM_LABELS[m.type]}</div>
|
||||
<div class="supp-premium-status">${status}</div>
|
||||
@@ -775,6 +897,9 @@
|
||||
|
||||
function modalZahlungHtml(data) {
|
||||
const sub = data.subscription;
|
||||
// Gleiche Regel wie auf der Preis-Kachel: ohne laufendes Abo bleibt der
|
||||
// Preis verborgen (siehe Kommentar bei stat-preis).
|
||||
const aboLaeuftZahlung = ["active", "pending", "suspended", "cancelled"].includes(sub.status);
|
||||
const payments = data.payments || [];
|
||||
const gesamtBezahlt = payments
|
||||
.filter((p) => p.payment_status === "completed")
|
||||
@@ -801,7 +926,10 @@
|
||||
</div>
|
||||
<div class="supp-modal-grid">
|
||||
<div><div class="supp-modal-label">Nächste Zahlung</div><div class="supp-modal-value">${fmtDatum(sub.nextPaymentAt)}</div></div>
|
||||
<div><div class="supp-modal-label">Betrag</div><div class="supp-modal-value">${sub.monthlyPrice}</div></div>
|
||||
<!-- Preis auch hier verbergen, solange kein Abo läuft (21.08.2026) —
|
||||
sonst wäre er über den Klick auf "Nächste Zahlung" doch wieder
|
||||
sichtbar, obwohl er auf der Kachel daneben schon verborgen ist. -->
|
||||
<div><div class="supp-modal-label">Betrag</div><div class="supp-modal-value">${aboLaeuftZahlung ? sub.monthlyPrice : "Coming soon…"}</div></div>
|
||||
<div><div class="supp-modal-label">Abo läuft seit</div><div class="supp-modal-value">${fmtDatum(sub.startedAt)}</div></div>
|
||||
<div><div class="supp-modal-label">Zahlungsmethode</div><div class="supp-modal-value">PayPal</div></div>
|
||||
${sub.cancelledAt ? `<div><div class="supp-modal-label">Gekündigt am</div><div class="supp-modal-value">${fmtDatum(sub.cancelledAt)}</div></div>` : ""}
|
||||
@@ -816,15 +944,20 @@
|
||||
function modalFortschrittHtml(data) {
|
||||
const t = data.timeline;
|
||||
const p = data.progress;
|
||||
const naechster = t.milestones.find((m) => m.isNext);
|
||||
// Gleiche Regel wie in der Zeitleiste oben (21.08.2026): ohne laufendes
|
||||
// Abo ist auch der erste Meilenstein gesperrt, sonst stünden hier und
|
||||
// dort widersprüchliche Angaben.
|
||||
const aboLaeuftModal = ["active", "pending", "suspended", "cancelled"].includes(data.subscription.status);
|
||||
const naechster = aboLaeuftModal ? t.milestones.find((m) => m.isNext) : null;
|
||||
const prozent = Math.round((t.currentCycleMonth / 30) * 100);
|
||||
|
||||
const meilensteineHtml = t.milestones
|
||||
.map((m) => {
|
||||
const klasse = m.reached ? "erreicht" : m.isNext ? "naechste" : "";
|
||||
const status = m.reached ? "Freigeschaltet" : m.isNext ? `noch ${m.monthsRemaining} Monate` : "gesperrt";
|
||||
const naechste = m.isNext && aboLaeuftModal;
|
||||
const klasse = m.reached ? "erreicht" : naechste ? "naechste" : "";
|
||||
const status = m.reached ? "Freigeschaltet" : naechste ? `noch ${m.monthsRemaining} Monate` : "gesperrt";
|
||||
return `<div class="supp-modal-meilenstein ${klasse}">
|
||||
<span class="icon-medaille-mini" aria-hidden="true">${m.reached ? "🎁" : m.isNext ? "✨" : "🔒"}</span>
|
||||
<span class="icon-medaille-mini" aria-hidden="true">${m.reached ? "🎁" : naechste ? "✨" : "🔒"}</span>
|
||||
<div>
|
||||
<div class="titel">${REDEEM_LABELS[m.type]}</div>
|
||||
<div class="small" style="opacity:.75;">Monat ${m.month} · ${status}</div>
|
||||
@@ -847,7 +980,12 @@
|
||||
: ""
|
||||
}
|
||||
${
|
||||
naechster
|
||||
!aboLaeuftModal
|
||||
? /* Ohne Abo darf hier NICHT "Zyklus komplett" stehen (das wäre der
|
||||
Fall, wenn wirklich alle Meilensteine erreicht wären) — es hat
|
||||
ja noch gar nichts angefangen. */
|
||||
`<p class="small" style="margin-top:.9rem;">Alle Meilensteine sind noch gesperrt — sie starten, sobald dein Abo läuft.</p>`
|
||||
: naechster
|
||||
? `<p class="small" style="margin-top:.9rem;">Nächster Meilenstein: <strong>${REDEEM_LABELS[naechster.type]}</strong> in ${naechster.monthsRemaining} Monat${naechster.monthsRemaining === 1 ? "" : "en"}.</p>`
|
||||
: `<p class="small" style="margin-top:.9rem;">Dieser Zyklus ist komplett — alle Prämien freigeschaltet! 🎉</p>`
|
||||
}
|
||||
@@ -870,7 +1008,16 @@
|
||||
// vorherige Zeile im selben Klick trotzdem schon das Overlay
|
||||
// sichtbar gemacht hätte. Zusätzlich try/catch um den Builder-Aufruf
|
||||
// selbst, mit sichtbarer Konsolen-Meldung statt stillem Fehlschlag.
|
||||
if (!letzteDaten || letzteDaten.subscription?.status === "none") return;
|
||||
/* Nutzer-Wunsch 21.08.2026: "sie sollen ALLES SEHEN KÖNNEN." Die
|
||||
frühere Bedingung blockierte das Detail-Fenster komplett, solange
|
||||
kein Abo lief — die Kacheln laden aber sichtbar mit "Details
|
||||
ansehen ›" zum Klicken ein, es passierte dann nur einfach nichts.
|
||||
Beide Fenster kommen mit "kein Abo" sauber zurecht: die
|
||||
Zahlungsübersicht zeigt "Noch keine Zahlungen erfasst.", die
|
||||
Fortschritts-Ansicht alle Meilensteine gesperrt plus passenden
|
||||
Hinweis (siehe modalFortschrittHtml). Deshalb bleibt hier nur noch
|
||||
die Absicherung gegen fehlende Daten. */
|
||||
if (!letzteDaten) return;
|
||||
try {
|
||||
oeffneModal(builder(letzteDaten));
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user