Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
2420 lines
108 KiB
HTML
2420 lines
108 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Dating – xXx Sphere</title>
|
||
<link rel="stylesheet" href="/css/variables.css">
|
||
<link rel="stylesheet" href="/css/style.css">
|
||
<style>
|
||
/* ── Toolbar ── */
|
||
.results-count { font-size: 0.85rem; color: var(--color-muted); }
|
||
.filter-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--color-primary);
|
||
color: #fff;
|
||
border-radius: 50%;
|
||
width: 1rem;
|
||
height: 1rem;
|
||
font-size: 0.62rem;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
}
|
||
|
||
/* ── Filter-Overlay ── */
|
||
.filter-overlay-bg {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.55);
|
||
z-index: 200;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.2s;
|
||
}
|
||
.filter-overlay-bg.open { opacity: 1; pointer-events: all; }
|
||
|
||
.filter-drawer {
|
||
position: fixed;
|
||
top: 0; right: 0; bottom: 0;
|
||
width: min(320px, 92vw);
|
||
background: var(--color-card);
|
||
border-left: 1px solid var(--color-secondary);
|
||
z-index: 201;
|
||
display: flex;
|
||
flex-direction: column;
|
||
transform: translateX(100%);
|
||
transition: transform 0.25s ease;
|
||
overflow: hidden;
|
||
}
|
||
.filter-drawer.open { transform: translateX(0); }
|
||
|
||
.filter-drawer-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 1rem 1.25rem 0.75rem;
|
||
border-bottom: 1px solid var(--color-secondary);
|
||
flex-shrink: 0;
|
||
}
|
||
.filter-drawer-header h2 {
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
color: var(--color-primary);
|
||
margin: 0;
|
||
}
|
||
.filter-close-btn {
|
||
background: none;
|
||
border: none;
|
||
color: var(--color-muted);
|
||
font-size: 1.3rem;
|
||
cursor: pointer;
|
||
padding: 0.2rem 0.4rem;
|
||
line-height: 1;
|
||
border-radius: 4px;
|
||
}
|
||
.filter-close-btn:hover { background: var(--color-secondary); color: var(--color-text); }
|
||
|
||
.filter-drawer-body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 1rem 1.25rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
}
|
||
.filter-drawer-footer {
|
||
padding: 0.75rem 1.25rem;
|
||
border-top: 1px solid var(--color-secondary);
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
flex-shrink: 0;
|
||
}
|
||
.apply-btn { flex: 1; padding: 0.65rem; font-size: 0.9rem; }
|
||
.reset-btn {
|
||
padding: 0.65rem 1rem;
|
||
font-size: 0.9rem;
|
||
background: var(--color-secondary);
|
||
color: var(--color-muted);
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
}
|
||
.reset-btn:hover { color: var(--color-text); }
|
||
|
||
/* ── Filter-Elemente ── */
|
||
.filter-group { display: flex; flex-direction: column; gap: 0.35rem; }
|
||
.filter-group label {
|
||
font-size: 0.78rem;
|
||
color: var(--color-muted);
|
||
margin: 0;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
.range-val { color: var(--color-text); }
|
||
.filter-group input[type="range"] {
|
||
padding: 0; background: none; border: none;
|
||
accent-color: var(--color-primary); width: 100%;
|
||
}
|
||
|
||
/* ── Dual-Handle Alters-Slider ── */
|
||
.dual-slider {
|
||
position: relative;
|
||
height: 20px;
|
||
margin: 0.5rem 0 0.25rem;
|
||
}
|
||
.dual-slider-track {
|
||
position: absolute;
|
||
top: 50%; left: 0; right: 0;
|
||
height: 4px;
|
||
background: var(--color-secondary);
|
||
border-radius: 2px;
|
||
transform: translateY(-50%);
|
||
}
|
||
.dual-slider-range {
|
||
position: absolute;
|
||
top: 0; height: 100%;
|
||
background: var(--color-primary);
|
||
border-radius: 2px;
|
||
}
|
||
.dual-slider-thumb {
|
||
position: absolute;
|
||
top: 50%;
|
||
width: 18px; height: 18px;
|
||
background: var(--color-primary);
|
||
border: 2px solid #fff;
|
||
border-radius: 50%;
|
||
transform: translate(-50%, -50%);
|
||
cursor: grab;
|
||
box-shadow: 0 1px 4px rgba(0,0,0,0.4);
|
||
transition: box-shadow 0.1s;
|
||
touch-action: none;
|
||
}
|
||
.dual-slider-thumb:active { cursor: grabbing; box-shadow: 0 2px 8px rgba(0,0,0,0.5); }
|
||
.dual-slider-thumb:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
|
||
.filter-divider { border: none; border-top: 1px solid var(--color-secondary); margin: 0; }
|
||
.chip-group { display: flex; flex-wrap: wrap; gap: 0.4rem; }
|
||
.chip {
|
||
padding: 0.25rem 0.65rem;
|
||
border-radius: 20px;
|
||
font-size: 0.78rem;
|
||
cursor: pointer;
|
||
border: 1px solid var(--color-secondary);
|
||
background: var(--color-secondary);
|
||
color: var(--color-muted);
|
||
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
||
user-select: none;
|
||
}
|
||
.chip.active { background: var(--color-primary); color: #fff; border-color: var(--color-primary); }
|
||
.logic-toggle { display: flex; gap: 0.4rem; }
|
||
.logic-btn {
|
||
flex: 1; padding: 0.3rem; font-size: 0.78rem; font-weight: 600;
|
||
text-align: center; border-radius: 6px; cursor: pointer;
|
||
border: 1px solid var(--color-secondary);
|
||
background: var(--color-secondary); color: var(--color-muted);
|
||
transition: background 0.15s, color 0.15s;
|
||
}
|
||
.logic-btn.active { background: var(--color-primary); color: #fff; border-color: var(--color-primary); }
|
||
.vorlieben-search { position: relative; }
|
||
.vorlieben-search input { padding: 0.4rem 0.7rem; font-size: 0.82rem; }
|
||
.vorlieben-dropdown {
|
||
position: absolute; top: calc(100% + 4px); left: 0; right: 0;
|
||
background: var(--color-card); border: 1px solid var(--color-secondary);
|
||
border-radius: 8px; max-height: 180px; overflow-y: auto; z-index: 210; display: none;
|
||
}
|
||
.vorlieben-dropdown.open { display: block; }
|
||
.vl-item { padding: 0.45rem 0.75rem; font-size: 0.82rem; cursor: pointer; color: var(--color-text); }
|
||
.vl-item:hover { background: var(--color-secondary); }
|
||
.selected-vorlieben { display: flex; flex-wrap: wrap; gap: 0.35rem; }
|
||
.vl-tag {
|
||
display: inline-flex; align-items: center; gap: 0.3rem;
|
||
padding: 0.2rem 0.55rem; background: var(--color-primary);
|
||
color: #fff; border-radius: 20px; font-size: 0.75rem;
|
||
}
|
||
.vl-tag button {
|
||
background: none; border: none; color: #fff; padding: 0;
|
||
font-size: 0.75rem; cursor: pointer; line-height: 1; min-width: unset;
|
||
}
|
||
.vl-tag button:hover { background: none; opacity: 0.7; }
|
||
|
||
/* ── Profilkarten-Grid ── */
|
||
.profiles-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
gap: 1rem;
|
||
}
|
||
.profile-card {
|
||
background: var(--color-card);
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
transition: border-color 0.15s, box-shadow 0.15s;
|
||
text-decoration: none;
|
||
color: var(--color-text);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.profile-card:hover {
|
||
border-color: var(--color-primary);
|
||
box-shadow: 0 4px 18px rgba(0,0,0,0.35);
|
||
}
|
||
.profile-card-img { display: none; } /* ersetzt durch .profile-card-img-wrap */
|
||
.profile-card-body {
|
||
padding: 0.75rem; display: flex; flex-direction: column; gap: 0.3rem; flex: 1;
|
||
}
|
||
.profile-card-name {
|
||
font-weight: 700; font-size: 1rem;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.profile-card-meta { display: flex; flex-wrap: wrap; gap: 0.3rem; }
|
||
.meta-chip {
|
||
padding: 0.1rem 0.45rem; border-radius: 20px;
|
||
background: var(--color-secondary); font-size: 0.73rem; color: var(--color-muted);
|
||
}
|
||
.meta-chip.dist { color: var(--color-primary); }
|
||
.profile-card-desc {
|
||
font-size: 0.78rem; color: var(--color-muted); line-height: 1.4;
|
||
overflow: hidden; display: -webkit-box;
|
||
-webkit-line-clamp: 2; -webkit-box-orient: vertical; margin-top: 0.15rem;
|
||
}
|
||
|
||
/* ── Skeleton-Karten beim Nachladen ── */
|
||
.profile-card-skeleton {
|
||
background: var(--color-card);
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.skeleton-img {
|
||
width: 100%; aspect-ratio: 1;
|
||
background: linear-gradient(90deg, var(--color-secondary) 25%, var(--color-card) 50%, var(--color-secondary) 75%);
|
||
background-size: 200% 100%;
|
||
animation: shimmer 1.2s infinite;
|
||
}
|
||
.skeleton-body { padding: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem; }
|
||
.skeleton-line {
|
||
height: 0.75rem; border-radius: 4px;
|
||
background: linear-gradient(90deg, var(--color-secondary) 25%, var(--color-card) 50%, var(--color-secondary) 75%);
|
||
background-size: 200% 100%;
|
||
animation: shimmer 1.2s infinite;
|
||
}
|
||
@keyframes shimmer { to { background-position: -200% 0; } }
|
||
|
||
/* ── Like-Button auf Karten ── */
|
||
.profile-card-like {
|
||
position: absolute;
|
||
bottom: 0.5rem;
|
||
right: 0.5rem;
|
||
background: rgba(0,0,0,0.55);
|
||
border: none;
|
||
border-radius: 50%;
|
||
width: 2.2rem;
|
||
height: 2.2rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
font-size: 1.1rem;
|
||
transition: background 0.15s, transform 0.1s;
|
||
padding: 0;
|
||
color: #fff;
|
||
flex-shrink: 0;
|
||
}
|
||
.profile-card-like:hover { background: rgba(0,0,0,0.75); transform: scale(1.1); }
|
||
.profile-card-like.liked { background: var(--color-primary); }
|
||
.profile-card-img-wrap {
|
||
position: relative;
|
||
width: 100%;
|
||
aspect-ratio: 1;
|
||
flex-shrink: 0;
|
||
overflow: hidden;
|
||
background: var(--color-secondary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 3rem;
|
||
}
|
||
.profile-card-img-wrap img { width: 100%; height: 100%; object-fit: cover; }
|
||
|
||
/* ── Sentinel & Leer/Fehler ── */
|
||
#sentinel { height: 1px; margin-top: 1rem; }
|
||
.empty-state {
|
||
text-align: center; padding: 3rem 1rem;
|
||
color: var(--color-muted); grid-column: 1 / -1;
|
||
}
|
||
.empty-state .icon { font-size: 2.5rem; margin-bottom: 0.75rem; }
|
||
|
||
@media (max-width: 500px) {
|
||
.profiles-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
|
||
}
|
||
|
||
/* ── Discovery (Match-Tab) ── */
|
||
.discovery-wrap {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 1rem 0 2rem;
|
||
gap: 1.25rem;
|
||
}
|
||
.discovery-card {
|
||
width: 100%;
|
||
max-width: 380px;
|
||
background: var(--color-card);
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
box-shadow: 0 6px 28px rgba(0,0,0,0.35);
|
||
cursor: pointer;
|
||
transition: transform 0.15s, box-shadow 0.15s;
|
||
position: relative;
|
||
}
|
||
.discovery-card:hover { transform: translateY(-2px); box-shadow: 0 10px 36px rgba(0,0,0,0.45); }
|
||
.discovery-card-img {
|
||
width: 100%;
|
||
aspect-ratio: 3 / 4;
|
||
background: var(--color-secondary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 5rem;
|
||
color: var(--color-muted);
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
.discovery-card-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||
.disc-nav-btn {
|
||
position: absolute;
|
||
top: 50%; transform: translateY(-50%);
|
||
background: rgba(0,0,0,0.45);
|
||
border: none; color: #fff;
|
||
width: 2rem; height: 2rem; border-radius: 50%;
|
||
font-size: 1.5rem; line-height: 1;
|
||
cursor: pointer; z-index: 3;
|
||
display: flex; align-items: center; justify-content: center;
|
||
padding: 0; backdrop-filter: blur(2px);
|
||
transition: background 0.15s;
|
||
}
|
||
.disc-nav-btn:hover { background: rgba(0,0,0,0.7); }
|
||
.disc-nav-prev { left: 0.45rem; }
|
||
.disc-nav-next { right: 0.45rem; }
|
||
.disc-img-dots {
|
||
position: absolute; bottom: 0.45rem; left: 50%; transform: translateX(-50%);
|
||
display: flex; gap: 0.3rem; z-index: 3; pointer-events: none;
|
||
}
|
||
.disc-img-dot {
|
||
width: 6px; height: 6px; border-radius: 50%;
|
||
background: rgba(255,255,255,0.45);
|
||
transition: background 0.15s;
|
||
}
|
||
.disc-img-dot.active { background: #fff; }
|
||
.discovery-card-gradient {
|
||
position: absolute;
|
||
bottom: 0; left: 0; right: 0;
|
||
background: linear-gradient(transparent, rgba(0,0,0,0.75));
|
||
padding: 2rem 1.1rem 1rem;
|
||
pointer-events: none;
|
||
}
|
||
.discovery-card-name {
|
||
font-size: 1.35rem;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
text-shadow: 0 1px 4px rgba(0,0,0,0.5);
|
||
}
|
||
.discovery-card-meta {
|
||
font-size: 0.85rem;
|
||
color: rgba(255,255,255,0.82);
|
||
margin-top: 0.2rem;
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
.disc-match-chips {
|
||
position: absolute;
|
||
top: 0.6rem;
|
||
right: 0.6rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
gap: 0.3rem;
|
||
pointer-events: none;
|
||
z-index: 2;
|
||
}
|
||
.disc-match-chip {
|
||
font-size: 0.7rem;
|
||
font-weight: 600;
|
||
padding: 0.2rem 0.55rem;
|
||
border-radius: 20px;
|
||
background: rgba(0,0,0,0.55);
|
||
color: #fff;
|
||
border: 1px solid rgba(255,255,255,0.25);
|
||
backdrop-filter: blur(4px);
|
||
white-space: nowrap;
|
||
max-width: 140px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.discovery-card-desc {
|
||
padding: 0.75rem 1.1rem;
|
||
font-size: 0.88rem;
|
||
color: var(--color-muted);
|
||
line-height: 1.45;
|
||
overflow: hidden;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
}
|
||
.discovery-actions {
|
||
display: flex;
|
||
gap: 2rem;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.discovery-btn {
|
||
width: 3.6rem;
|
||
height: 3.6rem;
|
||
border-radius: 50%;
|
||
border: none;
|
||
font-size: 1.5rem;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 3px 12px rgba(0,0,0,0.35);
|
||
transition: transform 0.12s, box-shadow 0.12s;
|
||
flex-shrink: 0;
|
||
padding: 0;
|
||
margin: 0;
|
||
line-height: 1;
|
||
}
|
||
.discovery-btn:hover { transform: scale(1.1); box-shadow: 0 5px 18px rgba(0,0,0,0.45); }
|
||
.discovery-btn:active { transform: scale(0.95); }
|
||
.discovery-btn-no { background: #c0392b; color: #fff; }
|
||
.discovery-btn-yes { background: #27ae60; color: #fff; }
|
||
.discovery-counter { font-size: 0.8rem; color: var(--color-muted); }
|
||
|
||
/* ── Match-Overlay ── */
|
||
.match-overlay {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.82);
|
||
z-index: 500;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-direction: column;
|
||
gap: 1.25rem;
|
||
text-align: center;
|
||
padding: 2rem;
|
||
}
|
||
.match-overlay.open { display: flex; }
|
||
.match-overlay-title {
|
||
font-size: 2rem;
|
||
font-weight: 900;
|
||
color: #fff;
|
||
text-shadow: 0 2px 12px rgba(0,0,0,0.5);
|
||
letter-spacing: 0.03em;
|
||
}
|
||
.match-overlay-sub { font-size: 1rem; color: rgba(255,255,255,0.75); }
|
||
.match-overlay-avatars {
|
||
display: flex;
|
||
gap: 1rem;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.match-avatar {
|
||
width: 90px; height: 90px;
|
||
border-radius: 50%;
|
||
border: 3px solid var(--color-primary);
|
||
background: var(--color-secondary);
|
||
overflow: hidden;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 2.5rem; color: var(--color-muted);
|
||
}
|
||
.match-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||
.match-heart { font-size: 2rem; color: var(--color-primary); }
|
||
.match-overlay-btn { margin-top: 0.5rem; padding: 0.75rem 2rem; font-size: 1rem; }
|
||
|
||
/* ── Discovery-Popup (Vollprofil) ── */
|
||
.discovery-popup-bg {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.7);
|
||
z-index: 300;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 1rem;
|
||
}
|
||
.discovery-popup-bg.open { display: flex; }
|
||
.discovery-popup {
|
||
background: var(--color-card);
|
||
border-radius: 16px;
|
||
width: 100%;
|
||
max-width: 420px;
|
||
max-height: 90vh;
|
||
overflow-y: auto;
|
||
padding: 1.5rem 1.25rem 2rem;
|
||
position: relative;
|
||
}
|
||
.discovery-popup-close {
|
||
position: absolute;
|
||
top: 0.85rem; right: 1rem;
|
||
background: var(--color-secondary);
|
||
border: none;
|
||
color: var(--color-text);
|
||
width: 2rem; height: 2rem;
|
||
border-radius: 50%;
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
display: flex; align-items: center; justify-content: center;
|
||
padding: 0; margin: 0;
|
||
}
|
||
.pd-layout { display: flex; flex-direction: column; }
|
||
.pd-info { /* Info-Bereich – Layout-neutral auf kleinen Screens */ }
|
||
.pd-photo-box {
|
||
position: relative; width: 100%; aspect-ratio: 1 / 1; max-height: 280px;
|
||
background: var(--color-secondary); border-radius: 12px; overflow: hidden;
|
||
display: flex; align-items: center; justify-content: center;
|
||
margin-bottom: 0.75rem; font-size: 3rem; color: var(--color-muted);
|
||
}
|
||
.pd-photo-box img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||
/* ── Breites Layout: Bild links (bis 1024px), Info rechts ── */
|
||
@media (min-width: 900px) {
|
||
.profile-dialog-bg { align-items: center; }
|
||
.profile-dialog {
|
||
border-radius: 16px;
|
||
max-width: min(1340px, calc(100vw - 2rem));
|
||
max-height: 90vh;
|
||
overflow: hidden;
|
||
padding: 0;
|
||
}
|
||
.discovery-popup {
|
||
max-width: min(1340px, calc(100vw - 2rem));
|
||
max-height: 90vh;
|
||
overflow: hidden;
|
||
padding: 0;
|
||
}
|
||
.pd-layout {
|
||
flex-direction: row;
|
||
height: min(90vh, 1024px);
|
||
}
|
||
.pd-photo-box {
|
||
flex: 1 1 0; min-width: 0; max-width: 1024px;
|
||
height: 100%; max-height: none; aspect-ratio: auto;
|
||
border-radius: 0; margin: 0;
|
||
}
|
||
.pd-info {
|
||
width: 300px; flex-shrink: 0; overflow-y: auto;
|
||
padding: 1.5rem 1.25rem 2rem;
|
||
border-left: 1px solid var(--color-secondary);
|
||
}
|
||
}
|
||
.pd-nav-btn {
|
||
position: absolute; top: 50%; transform: translateY(-50%);
|
||
background: rgba(0,0,0,0.45); border: 1px solid rgba(255,255,255,0.2); color: #fff;
|
||
width: 2rem; height: 2rem; border-radius: 50%; font-size: 1.3rem;
|
||
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
||
padding: 0; margin: 0; line-height: 1; z-index: 2; transition: background 0.15s;
|
||
}
|
||
.pd-nav-btn:hover { background: rgba(0,0,0,0.75); }
|
||
.pd-nav-btn:disabled { opacity: 0.25; cursor: default; }
|
||
.pd-nav-prev { left: 0.4rem; }
|
||
.pd-nav-next { right: 0.4rem; }
|
||
.pd-photo-dots {
|
||
position: absolute; bottom: 0.4rem; left: 0; right: 0;
|
||
display: flex; justify-content: center; gap: 0.3rem; pointer-events: none;
|
||
}
|
||
.pd-photo-dot { width: 6px; height: 6px; border-radius: 50%; background: rgba(255,255,255,0.45); }
|
||
.pd-photo-dot.active { background: #fff; }
|
||
.dp-name { text-align: center; font-size: 1.25rem; font-weight: 700; margin-bottom: 0.5rem; }
|
||
.dp-tags { display: flex; flex-wrap: wrap; gap: 0.4rem; justify-content: center; margin-bottom: 0.75rem; }
|
||
.dp-tag {
|
||
background: var(--color-secondary);
|
||
border-radius: 20px;
|
||
padding: 0.25rem 0.7rem;
|
||
font-size: 0.78rem;
|
||
color: var(--color-muted);
|
||
}
|
||
.dp-desc {
|
||
background: var(--color-secondary);
|
||
border-radius: 8px;
|
||
padding: 0.85rem 1rem;
|
||
font-size: 0.9rem;
|
||
line-height: 1.55;
|
||
color: var(--color-text);
|
||
white-space: pre-wrap;
|
||
margin-top: 0.75rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
.dp-actions { display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap; }
|
||
.dp-link { padding: 0.55rem 1.25rem; font-size: 0.9rem; }
|
||
|
||
/* ── Tabs ── */
|
||
.dating-tabs {
|
||
display: flex;
|
||
gap: 0;
|
||
align-items: center;
|
||
border-bottom: 1px solid var(--color-secondary);
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
.dating-tab-btn {
|
||
background: none;
|
||
border: none;
|
||
border-bottom: 3px solid transparent;
|
||
border-radius: 0;
|
||
padding: 0.6rem 1.4rem;
|
||
font-size: 0.95rem;
|
||
font-weight: 600;
|
||
color: var(--color-muted);
|
||
cursor: pointer;
|
||
margin-bottom: -1px;
|
||
transition: color 0.15s, border-color 0.15s;
|
||
width: auto;
|
||
margin-top: 0;
|
||
}
|
||
.dating-tab-btn:hover { color: var(--color-text); background: none; }
|
||
.dating-tab-btn.active { color: var(--color-primary); border-bottom-color: var(--color-primary); }
|
||
.dating-tab-panel { display: none; }
|
||
.dating-tab-panel.active { display: block; }
|
||
|
||
/* ── Dates-Tab ── */
|
||
.dates-section-title {
|
||
font-size: 0.78rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
color: var(--color-muted);
|
||
margin: 0 0 0.6rem;
|
||
}
|
||
.dates-section { margin-bottom: 1.75rem; }
|
||
.dates-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 1rem;
|
||
gap: 0.75rem;
|
||
}
|
||
.dates-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||
gap: 1rem;
|
||
}
|
||
.date-card {
|
||
background: var(--color-card);
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
transition: border-color 0.15s, box-shadow 0.15s;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.date-card:hover {
|
||
border-color: var(--color-primary);
|
||
box-shadow: 0 4px 18px rgba(0,0,0,0.35);
|
||
}
|
||
.date-card-img {
|
||
width: 100%;
|
||
aspect-ratio: 16/9;
|
||
background: var(--color-secondary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 2.5rem;
|
||
color: var(--color-muted);
|
||
overflow: hidden;
|
||
flex-shrink: 0;
|
||
}
|
||
.date-card-img img { width: 100%; height: 100%; object-fit: cover; }
|
||
.date-card-body { padding: 0.75rem; display: flex; flex-direction: column; gap: 0.4rem; flex: 1; }
|
||
.date-card-title { font-weight: 700; font-size: 0.95rem; line-height: 1.3; }
|
||
.date-card-desc {
|
||
font-size: 0.78rem; color: var(--color-muted); line-height: 1.4;
|
||
overflow: hidden; display: -webkit-box;
|
||
-webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
||
}
|
||
.date-card-footer {
|
||
display: flex; align-items: center; gap: 0.5rem;
|
||
padding: 0.5rem 0.75rem;
|
||
border-top: 1px solid var(--color-secondary);
|
||
font-size: 0.76rem;
|
||
flex-shrink: 0;
|
||
}
|
||
.date-card-avatar {
|
||
width: 24px; height: 24px; border-radius: 50%;
|
||
background: var(--color-secondary);
|
||
overflow: hidden; display: flex; align-items: center; justify-content: center;
|
||
font-size: 0.9rem; flex-shrink: 0;
|
||
}
|
||
.date-card-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||
.date-card-creator { color: var(--color-muted); flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
.date-card-meta { color: var(--color-muted); white-space: nowrap; }
|
||
.date-card-interest { color: var(--color-primary); font-weight: 600; }
|
||
.date-card-date { color: var(--color-muted); font-size: 0.73rem; }
|
||
|
||
/* ── Date-Dialog (Details / Eigenes Date) ── */
|
||
.date-dialog-bg {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.65);
|
||
z-index: 400;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 1rem;
|
||
}
|
||
.date-dialog-bg.open { display: flex; }
|
||
.date-dialog {
|
||
background: var(--color-card);
|
||
border-radius: 16px;
|
||
width: 100%;
|
||
max-width: 520px;
|
||
max-height: 92vh;
|
||
overflow-y: auto;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.date-dialog-img {
|
||
width: 100%; aspect-ratio: 16/9;
|
||
background: var(--color-secondary);
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 3rem; color: var(--color-muted);
|
||
overflow: hidden; flex-shrink: 0;
|
||
border-radius: 16px 16px 0 0;
|
||
}
|
||
.date-dialog-img img { width: 100%; height: 100%; object-fit: cover; }
|
||
.date-dialog-body { padding: 1.25rem; display: flex; flex-direction: column; gap: 0.75rem; }
|
||
.date-dialog-close {
|
||
position: absolute; top: 0.75rem; right: 0.85rem;
|
||
background: rgba(0,0,0,0.45); border: none; color: #fff;
|
||
width: 2rem; height: 2rem; border-radius: 50%;
|
||
font-size: 1rem; cursor: pointer;
|
||
display: flex; align-items: center; justify-content: center;
|
||
padding: 0; z-index: 1;
|
||
}
|
||
.date-dialog-close:hover { background: rgba(0,0,0,0.65); }
|
||
.date-dialog-title { font-size: 1.2rem; font-weight: 700; line-height: 1.3; }
|
||
.date-dialog-creator {
|
||
display: flex; align-items: center; gap: 0.6rem;
|
||
cursor: pointer; width: fit-content;
|
||
}
|
||
.date-dialog-creator:hover .date-dialog-creator-name { text-decoration: underline; }
|
||
.date-dialog-creator-avatar {
|
||
width: 36px; height: 36px; border-radius: 50%;
|
||
background: var(--color-secondary); overflow: hidden;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 1.1rem; flex-shrink: 0;
|
||
}
|
||
.date-dialog-creator-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||
.date-dialog-creator-name { font-weight: 600; font-size: 0.9rem; }
|
||
.date-dialog-when {
|
||
display: flex; align-items: center; gap: 0.4rem;
|
||
font-size: 0.85rem; color: var(--color-muted);
|
||
}
|
||
.date-dialog-desc {
|
||
background: var(--color-secondary); border-radius: 8px;
|
||
padding: 0.85rem 1rem; font-size: 0.9rem; line-height: 1.55;
|
||
white-space: pre-wrap;
|
||
}
|
||
.date-dialog-stats {
|
||
font-size: 0.82rem; color: var(--color-muted);
|
||
}
|
||
.date-dialog-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; }
|
||
.date-interest-btn { flex: 1; min-width: 120px; }
|
||
.date-interest-btn.active { background: var(--color-primary); color: #fff; }
|
||
|
||
/* ── Interessenten-Liste (eigenes Date) ── */
|
||
.interest-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
||
.interest-item {
|
||
display: flex; align-items: center; gap: 0.75rem;
|
||
padding: 0.6rem 0.75rem;
|
||
background: var(--color-secondary); border-radius: 8px;
|
||
}
|
||
.interest-item-avatar {
|
||
width: 38px; height: 38px; border-radius: 50%;
|
||
background: var(--color-card); overflow: hidden;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 1.2rem; flex-shrink: 0; cursor: pointer;
|
||
}
|
||
.interest-item-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||
.interest-item-name { font-weight: 600; font-size: 0.88rem; flex: 1; cursor: pointer; }
|
||
.interest-item-name:hover { text-decoration: underline; }
|
||
.interest-item-dm {
|
||
padding: 0.3rem 0.75rem; font-size: 0.78rem;
|
||
}
|
||
.interest-item-blocked {
|
||
font-size: 0.75rem; color: var(--color-muted); font-style: italic;
|
||
}
|
||
|
||
/* ── Erstellen/Bearbeiten-Dialog ── */
|
||
.date-form-bg {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.65);
|
||
z-index: 450;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 1rem;
|
||
}
|
||
.date-form-bg.open { display: flex; }
|
||
.date-form-dialog {
|
||
background: var(--color-card);
|
||
border-radius: 16px;
|
||
width: 100%;
|
||
max-width: 480px;
|
||
max-height: 92vh;
|
||
overflow-y: auto;
|
||
padding: 1.5rem 1.25rem;
|
||
position: relative;
|
||
}
|
||
.date-form-close {
|
||
position: absolute; top: 0.85rem; right: 1rem;
|
||
background: none; border: none; color: var(--color-muted);
|
||
font-size: 1.3rem; cursor: pointer; line-height: 1; padding: 0.2rem 0.4rem; border-radius: 4px;
|
||
}
|
||
.date-form-close:hover { background: var(--color-secondary); color: var(--color-text); }
|
||
.date-form-title { font-size: 1.1rem; font-weight: 700; margin-bottom: 1rem; }
|
||
.date-form-group { display: flex; flex-direction: column; gap: 0.35rem; margin-bottom: 0.85rem; }
|
||
.date-form-group label { font-size: 0.78rem; color: var(--color-muted); }
|
||
.date-form-group input, .date-form-group textarea {
|
||
padding: 0.55rem 0.75rem; font-size: 0.9rem; border-radius: 8px;
|
||
}
|
||
.date-form-group textarea { min-height: 100px; resize: vertical; }
|
||
.date-form-img-preview {
|
||
width: 100%; aspect-ratio: 16/9;
|
||
background: var(--color-secondary); border-radius: 8px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 2rem; color: var(--color-muted); overflow: hidden;
|
||
margin-bottom: 0.5rem; cursor: pointer; position: relative;
|
||
}
|
||
.date-form-img-preview img { width: 100%; height: 100%; object-fit: cover; }
|
||
.date-form-img-remove {
|
||
position: absolute; top: 0.4rem; right: 0.4rem;
|
||
background: rgba(0,0,0,0.5); border: none; color: #fff;
|
||
border-radius: 50%; width: 1.6rem; height: 1.6rem;
|
||
font-size: 0.85rem; cursor: pointer; display: none;
|
||
align-items: center; justify-content: center; padding: 0;
|
||
}
|
||
.date-form-img-remove.visible { display: flex; }
|
||
.date-form-actions { display: flex; gap: 0.6rem; margin-top: 1rem; }
|
||
.date-form-save { flex: 1; }
|
||
.date-form-limit-hint { font-size: 0.75rem; color: var(--color-muted); text-align: center; margin-top: 0.25rem; }
|
||
|
||
/* ── Profil-Dialog (Mini) ── */
|
||
.profile-dialog-bg {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.65);
|
||
z-index: 600;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
}
|
||
.profile-dialog-bg.open { display: flex; }
|
||
.profile-dialog {
|
||
background: var(--color-card);
|
||
border-radius: 18px 18px 0 0;
|
||
width: 100%; max-width: 520px;
|
||
max-height: 80vh; overflow-y: auto;
|
||
padding: 1.5rem 1.25rem 2rem;
|
||
position: relative;
|
||
}
|
||
.profile-dialog-close {
|
||
position: absolute; top: 0.85rem; right: 1rem;
|
||
background: var(--color-secondary); border: none; color: var(--color-text);
|
||
width: 2rem; height: 2rem; border-radius: 50%; font-size: 1rem;
|
||
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
||
padding: 0; margin: 0;
|
||
}
|
||
|
||
.dp-vorlieben {
|
||
display: flex; flex-wrap: wrap; gap: 0.4rem;
|
||
margin-top: 0.6rem;
|
||
}
|
||
.dp-vorliebe-chip {
|
||
font-size: 0.75rem; padding: 0.2rem 0.55rem;
|
||
border-radius: 20px; border: 1px solid currentColor;
|
||
white-space: nowrap;
|
||
}
|
||
.dp-vorliebe-chip.bw-UNBEDINGT { color: #2e7d32; }
|
||
.dp-vorliebe-chip.bw-MAG_ICH { color: #81c784; }
|
||
.dp-vorliebe-chip.bw-WILL_AUSPROBIEREN { color: #1e88e5; }
|
||
|
||
@media (max-width: 500px) {
|
||
.dates-grid { grid-template-columns: 1fr; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="app">
|
||
|
||
<!-- ── Filter-Overlay ── -->
|
||
<div class="filter-overlay-bg" id="filterBg"></div>
|
||
<div class="filter-drawer" id="filterDrawer">
|
||
<div class="filter-drawer-header">
|
||
<h2>Filter</h2>
|
||
<button class="filter-close-btn" id="filterCloseBtn" aria-label="Filter schließen">✕</button>
|
||
</div>
|
||
<div class="filter-drawer-body">
|
||
|
||
<div class="filter-group">
|
||
<label>Umkreis <span class="range-val" id="distVal">50 km</span></label>
|
||
<input type="range" id="maxDist" min="5" max="500" value="50" step="5"
|
||
oninput="document.getElementById('distVal').textContent=this.value+' km'">
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label>Alter <span class="range-val" id="ageVal">18 – 60</span></label>
|
||
<div class="dual-slider" id="ageSlider" role="group" aria-label="Altersbereich">
|
||
<div class="dual-slider-track">
|
||
<div class="dual-slider-range" id="ageRange"></div>
|
||
</div>
|
||
<div class="dual-slider-thumb" id="thumbMin" tabindex="0" role="slider"
|
||
aria-label="Mindestalter" aria-valuemin="18" aria-valuemax="99" aria-valuenow="18"></div>
|
||
<div class="dual-slider-thumb" id="thumbMax" tabindex="0" role="slider"
|
||
aria-label="Höchstalter" aria-valuemin="18" aria-valuemax="99" aria-valuenow="60"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="filter-divider">
|
||
|
||
<div class="filter-group">
|
||
<label>Geschlecht</label>
|
||
<div class="chip-group" id="geschlechtChips">
|
||
<span class="chip" data-val="WEIBLICH">weiblich</span>
|
||
<span class="chip" data-val="MAENNLICH">männlich</span>
|
||
<span class="chip" data-val="DIVERS">divers</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label>Neigung</label>
|
||
<div class="chip-group" id="neigungChips">
|
||
<span class="chip" data-val="DEVOT">devot</span>
|
||
<span class="chip" data-val="EHER_DEVOT">eher devot</span>
|
||
<span class="chip" data-val="SWITCHER">Switcher</span>
|
||
<span class="chip" data-val="EHER_DOMINANT">eher dominant</span>
|
||
<span class="chip" data-val="DOMINANT">dominant</span>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="filter-divider">
|
||
|
||
<div class="filter-group">
|
||
<label>Vorlieben</label>
|
||
<div class="logic-toggle" id="logicToggle">
|
||
<span class="logic-btn active" data-val="false">ODER</span>
|
||
<span class="logic-btn" data-val="true">UND</span>
|
||
</div>
|
||
<div class="vorlieben-search" style="margin-top:0.4rem;">
|
||
<input type="text" id="vlSearch" placeholder="Vorliebe suchen …" autocomplete="off">
|
||
<div class="vorlieben-dropdown" id="vlDropdown"></div>
|
||
</div>
|
||
<div class="selected-vorlieben" id="selectedVorlieben"></div>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="filter-drawer-footer">
|
||
<button class="btn apply-btn" id="applyBtn">Anwenden</button>
|
||
<button class="reset-btn" id="resetBtn">Zurücksetzen</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="main">
|
||
<div class="content">
|
||
<div class="dating-tabs">
|
||
<button class="dating-tab-btn active" data-tab="match">Match</button>
|
||
<button class="dating-tab-btn" data-tab="entdecken">Entdecken</button>
|
||
<button class="dating-tab-btn" data-tab="dates">Dates</button>
|
||
<div style="margin-left:auto;display:flex;align-items:center;gap:0.4rem;padding-bottom:1px;">
|
||
<a href="/konto/einstellungen.html#sec-dating" title="Dating-Einstellungen"
|
||
style="display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:50%;background:var(--color-secondary);color:var(--color-muted);text-decoration:none;font-size:1.1rem;transition:background 0.15s,color 0.15s;"
|
||
onmouseover="this.style.background='var(--color-primary)';this.style.color='#fff';"
|
||
onmouseout="this.style.background='var(--color-secondary)';this.style.color='var(--color-muted)';">⚙</a>
|
||
<button id="filterOpenBtn" title="Filter"
|
||
style="display:flex;align-items:center;justify-content:center;position:relative;width:2rem;height:2rem;border-radius:50%;background:var(--color-secondary);color:var(--color-muted);border:none;cursor:pointer;transition:background 0.15s,color 0.15s;padding:0;"
|
||
onmouseover="this.style.background='var(--color-primary)';this.style.color='#fff';"
|
||
onmouseout="this.style.background='var(--color-secondary)';this.style.color='var(--color-muted)';">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
||
<line x1="4" y1="6" x2="20" y2="6"/><line x1="8" y1="12" x2="16" y2="12"/><line x1="11" y1="18" x2="13" y2="18"/>
|
||
</svg>
|
||
<span class="filter-badge" id="filterBadge" style="display:none;position:absolute;top:-3px;right:-3px;"></span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dating-tab-panel" id="tab-entdecken">
|
||
<span class="results-count" id="resultsCount" style="display:block;margin-bottom:0.75rem;"></span>
|
||
<div class="profiles-grid" id="profilesGrid"></div>
|
||
<div id="sentinel"></div>
|
||
</div>
|
||
|
||
<div class="dating-tab-panel active" id="tab-match">
|
||
<div class="discovery-wrap">
|
||
<div id="discoveryCard" class="discovery-card" style="display:none;" onclick="openDiscoveryPopup()">
|
||
<div class="discovery-card-img" id="discCardImg">
|
||
<span id="discNoPic" style="display:none;">👤</span>
|
||
<img id="discCardPhoto" alt="" style="display:none;width:100%;height:100%;object-fit:cover;">
|
||
<div class="discovery-card-gradient">
|
||
<div class="discovery-card-name" id="discCardName"></div>
|
||
<div class="discovery-card-meta" id="discCardMeta"></div>
|
||
</div>
|
||
<div class="disc-img-dots" id="discImgDots"></div>
|
||
<button class="disc-nav-btn disc-nav-prev" id="discNavPrev"
|
||
onclick="discNavStep(-1,event)" style="display:none;">‹</button>
|
||
<button class="disc-nav-btn disc-nav-next" id="discNavNext"
|
||
onclick="discNavStep(1,event)" style="display:none;">›</button>
|
||
</div>
|
||
<div class="disc-match-chips" id="discMatchChips"></div>
|
||
<div class="discovery-card-desc" id="discCardDesc"></div>
|
||
</div>
|
||
<div id="discoveryEmpty" style="display:none; text-align:center; padding:3rem 1rem; color:var(--color-muted);">
|
||
<div style="font-size:2.5rem; margin-bottom:0.75rem;">🎉</div>
|
||
<p>Keine weiteren Profile gefunden.<br>Schau später wieder vorbei!</p>
|
||
</div>
|
||
<div id="discoveryLoading" style="text-align:center; padding:3rem 1rem; color:var(--color-muted);">
|
||
Wird geladen…
|
||
</div>
|
||
<div class="discovery-actions" id="discoveryActions" style="display:none;">
|
||
<button class="discovery-btn discovery-btn-no" title="Nein" onclick="discardProfile()">✕</button>
|
||
<button class="discovery-btn discovery-btn-yes" title="Gefällt mir" onclick="likeProfile()">♥</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dating-tab-panel" id="tab-dates">
|
||
<div class="dates-toolbar">
|
||
<span class="results-count" id="datesCount"></span>
|
||
<button class="btn" id="createDateBtn" style="padding:0.5rem 1rem;font-size:0.88rem;">
|
||
+ Date erstellen
|
||
</button>
|
||
</div>
|
||
<div class="dates-section" id="myDatesSection" style="display:none;">
|
||
<p class="dates-section-title">Meine Dates</p>
|
||
<div class="dates-grid" id="myDatesGrid"></div>
|
||
</div>
|
||
<div class="dates-section">
|
||
<p class="dates-section-title">Verfügbare Dates</p>
|
||
<div class="dates-grid" id="availableDatesGrid"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Date-Detail-Dialog ── -->
|
||
<div class="date-dialog-bg" id="dateBg" onclick="closeDateDialog(event)">
|
||
<div class="date-dialog" id="dateDialog">
|
||
<button class="date-dialog-close" onclick="closeDateDialogBtn()">✕</button>
|
||
<div class="date-dialog-img" id="ddImg"></div>
|
||
<div class="date-dialog-body">
|
||
<div class="date-dialog-title" id="ddTitle"></div>
|
||
<div class="date-dialog-creator" id="ddCreator" style="cursor:pointer;">
|
||
<div class="date-dialog-creator-avatar" id="ddCreatorAvatar"></div>
|
||
<div class="date-dialog-creator-name" id="ddCreatorName"></div>
|
||
</div>
|
||
<div class="date-dialog-when" id="ddWhen" style="display:none;">
|
||
🗓️ <span id="ddWhenText"></span>
|
||
</div>
|
||
<div class="date-dialog-desc" id="ddDesc"></div>
|
||
<div class="date-dialog-stats" id="ddStats"></div>
|
||
<div class="date-dialog-actions" id="ddActions"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Eigenes-Date-Dialog ── -->
|
||
<div class="date-dialog-bg" id="myDateBg" onclick="closeMyDateDialog(event)">
|
||
<div class="date-dialog" id="myDateDialog">
|
||
<button class="date-dialog-close" onclick="closeMyDateDialogBtn()">✕</button>
|
||
<div class="date-dialog-img" id="mdImg"></div>
|
||
<div class="date-dialog-body">
|
||
<div class="date-dialog-title" id="mdTitle"></div>
|
||
<div class="date-dialog-when" id="mdWhen" style="display:none;">
|
||
🗓️ <span id="mdWhenText"></span>
|
||
</div>
|
||
<div class="date-dialog-desc" id="mdDesc"></div>
|
||
<div class="date-dialog-actions">
|
||
<button class="btn" style="flex:1;" id="mdEditBtn" onclick="openEditDate()">Bearbeiten</button>
|
||
<button class="btn" style="flex:1;background:var(--color-secondary);color:var(--color-text);"
|
||
id="mdDeleteBtn" onclick="deleteMyDate()">Löschen</button>
|
||
</div>
|
||
<hr style="border:none;border-top:1px solid var(--color-secondary);margin:0.25rem 0;">
|
||
<p class="dates-section-title" style="margin:0.25rem 0 0.5rem;">Interessenten</p>
|
||
<div class="interest-list" id="mdInterestList">
|
||
<div style="color:var(--color-muted);font-size:0.85rem;">Wird geladen…</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Date Erstellen/Bearbeiten-Dialog ── -->
|
||
<div class="date-form-bg" id="dateFormBg" onclick="closeDateForm(event)">
|
||
<div class="date-form-dialog" id="dateFormDialog">
|
||
<button class="date-form-close" onclick="closeDateFormBtn()">✕</button>
|
||
<div class="date-form-title" id="dateFormTitle">Date erstellen</div>
|
||
<div class="date-form-group">
|
||
<label>Bild (optional)</label>
|
||
<div class="date-form-img-preview" id="dfImgPreview" onclick="triggerImgPick()">
|
||
<span id="dfImgPlaceholder">📷 Bild hinzufügen</span>
|
||
<button class="date-form-img-remove" id="dfImgRemove" onclick="removeFormImg(event)">✕</button>
|
||
</div>
|
||
<input type="file" id="dfImgInput" accept="image/*" style="display:none;" onchange="onImgSelected(event)">
|
||
</div>
|
||
<div class="date-form-group">
|
||
<label>Titel *</label>
|
||
<input type="text" id="dfTitle" maxlength="200" placeholder="z.B. Spaziergang am See">
|
||
</div>
|
||
<div class="date-form-group">
|
||
<label>Beschreibung *</label>
|
||
<textarea id="dfDesc" maxlength="2000" placeholder="Was planst du? Wen suchst du?"></textarea>
|
||
</div>
|
||
<div class="date-form-group">
|
||
<label>Datum & Uhrzeit (optional)</label>
|
||
<input type="datetime-local" id="dfDateTime">
|
||
</div>
|
||
<div class="date-form-actions">
|
||
<button class="btn date-form-save" id="dfSaveBtn" onclick="saveDate()">Speichern</button>
|
||
</div>
|
||
<div class="date-form-limit-hint" id="dfLimitHint"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Profil-Mini-Dialog ── -->
|
||
<div class="profile-dialog-bg" id="profileDialogBg" onclick="closeProfileDialog(event)">
|
||
<div class="profile-dialog">
|
||
<button class="profile-dialog-close" onclick="closeProfileDialogBtn()">✕</button>
|
||
<div class="pd-layout">
|
||
<div class="pd-photo-box" id="pdPhotoBox">
|
||
<span id="pdNoPic">👤</span>
|
||
<img id="pdPhoto" alt="" style="display:none;">
|
||
<div class="pd-photo-dots" id="pdPhotoDots"></div>
|
||
<button class="pd-nav-btn pd-nav-prev" id="pdNavPrev" style="display:none;" onclick="pdNavStep(-1)">‹</button>
|
||
<button class="pd-nav-btn pd-nav-next" id="pdNavNext" style="display:none;" onclick="pdNavStep(1)">›</button>
|
||
</div>
|
||
<div class="pd-info">
|
||
<div class="dp-name" id="pdName"></div>
|
||
<div class="dp-tags" id="pdTags"></div>
|
||
<div class="dp-vorlieben" id="pdVorlieben"></div>
|
||
<div class="dp-desc" id="pdDesc" style="display:none;"></div>
|
||
<div class="dp-actions">
|
||
<a id="pdProfileLink" href="#" class="btn dp-link">Profil ansehen</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Match-Overlay ── -->
|
||
<div class="match-overlay" id="matchOverlay">
|
||
<div class="match-overlay-title">💕 Match! 💕</div>
|
||
<div class="match-overlay-avatars">
|
||
<div class="match-avatar" id="matchAvatarMe"></div>
|
||
<div class="match-heart">♥</div>
|
||
<div class="match-avatar" id="matchAvatarPartner"></div>
|
||
</div>
|
||
<div class="match-overlay-sub" id="matchOverlaySub"></div>
|
||
<button class="btn match-overlay-btn" onclick="closeMatchOverlay()">Weiter swipen</button>
|
||
<a id="matchChatLink" href="#" class="btn match-overlay-btn" style="background:var(--color-secondary);color:var(--color-text);">Nachricht schreiben</a>
|
||
</div>
|
||
|
||
<!-- ── Discovery-Popup ── -->
|
||
<div class="discovery-popup-bg" id="discoveryPopupBg" onclick="closeDiscoveryPopup(event)">
|
||
<div class="discovery-popup" id="discoveryPopup">
|
||
<button class="discovery-popup-close" onclick="closeDiscoveryPopup(null)">✕</button>
|
||
<div class="pd-layout">
|
||
<div class="pd-photo-box" id="dpPhotoBox">
|
||
<span id="dpNoPic">👤</span>
|
||
<img id="dpPhoto" alt="" style="display:none;">
|
||
<div class="pd-photo-dots" id="dpPhotoDots"></div>
|
||
<button class="pd-nav-btn pd-nav-prev" id="dpNavPrev" style="display:none;" onclick="dpNavStep(-1)">‹</button>
|
||
<button class="pd-nav-btn pd-nav-next" id="dpNavNext" style="display:none;" onclick="dpNavStep(1)">›</button>
|
||
</div>
|
||
<div class="pd-info">
|
||
<div class="dp-name" id="dpName"></div>
|
||
<div class="dp-tags" id="dpTags"></div>
|
||
<div class="dp-vorlieben" id="dpVorlieben"></div>
|
||
<div class="dp-desc" id="dpDesc" style="display:none;"></div>
|
||
<div class="dp-actions">
|
||
<a id="dpProfileLink" href="#" target="_blank" rel="noopener" class="btn dp-link">Profil ansehen</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/js/icons.js"></script>
|
||
<script src="/js/sidebar.js"></script>
|
||
<script src="/js/social-sidebar.js"></script>
|
||
<script>
|
||
(function () {
|
||
const BATCH_SIZE = 20;
|
||
|
||
let allIds = []; // vollständig gefilterte, nach Entfernung sortierte ID-Liste
|
||
let loadedCount = 0;
|
||
let loading = false;
|
||
let vorliebenUnd = false;
|
||
const selectedVorlieben = new Map();
|
||
let allVorlieben = [];
|
||
// Default-Werte aus Einstellungen (werden beim Login-Fetch gesetzt)
|
||
let defaultDistKm = 50, defaultAgeFrom = 18, defaultAgeTo = 60;
|
||
let defaultGeschlechter = [];
|
||
|
||
// ── Profilbild-Galerie in Dialog/Popup ────────────────────────────────────
|
||
let pdImages = [], pdImageIdx = 0;
|
||
let dpImages = [], dpImageIdx = 0;
|
||
|
||
function renderPopupImage(prefix, images, idx) {
|
||
const photo = document.getElementById(prefix + 'Photo');
|
||
const noPic = document.getElementById(prefix + 'NoPic');
|
||
const prev = document.getElementById(prefix + 'NavPrev');
|
||
const next = document.getElementById(prefix + 'NavNext');
|
||
const dots = document.getElementById(prefix + 'PhotoDots');
|
||
if (!images.length) {
|
||
photo.style.display = 'none'; noPic.style.display = '';
|
||
prev.style.display = next.style.display = 'none';
|
||
dots.innerHTML = '';
|
||
} else {
|
||
photo.src = images[idx]; photo.style.display = ''; noPic.style.display = 'none';
|
||
const multi = images.length > 1;
|
||
prev.style.display = next.style.display = multi ? '' : 'none';
|
||
if (multi) { prev.disabled = idx === 0; next.disabled = idx === images.length - 1; }
|
||
dots.innerHTML = multi
|
||
? images.map((_, i) => `<span class="pd-photo-dot${i === idx ? ' active' : ''}"></span>`).join('')
|
||
: '';
|
||
}
|
||
}
|
||
|
||
window.pdNavStep = function(dir) {
|
||
pdImageIdx = Math.max(0, Math.min(pdImages.length - 1, pdImageIdx + dir));
|
||
renderPopupImage('pd', pdImages, pdImageIdx);
|
||
};
|
||
window.dpNavStep = function(dir) {
|
||
dpImageIdx = Math.max(0, Math.min(dpImages.length - 1, dpImageIdx + dir));
|
||
renderPopupImage('dp', dpImages, dpImageIdx);
|
||
};
|
||
|
||
// ── Tab-Navigation ────────────────────────────────────────────────────────
|
||
function activateTab(tabName) {
|
||
document.querySelectorAll('.dating-tab-btn').forEach(b => b.classList.remove('active'));
|
||
document.querySelectorAll('.dating-tab-panel').forEach(p => p.classList.remove('active'));
|
||
const btn = document.querySelector(`.dating-tab-btn[data-tab="${tabName}"]`);
|
||
if (btn) btn.classList.add('active');
|
||
const panel = document.getElementById('tab-' + tabName);
|
||
if (panel) panel.classList.add('active');
|
||
document.getElementById('filterOpenBtn').style.display = 'flex';
|
||
const url = new URL(window.location);
|
||
url.searchParams.set('tab', tabName);
|
||
history.replaceState(null, '', url);
|
||
if (tabName === 'match' && !discoveryLoaded) {
|
||
discoveryLoaded = true;
|
||
loadDiscovery();
|
||
}
|
||
if (tabName === 'entdecken' && !entdeckenLoaded) {
|
||
entdeckenLoaded = true;
|
||
runSearch();
|
||
}
|
||
if (tabName === 'dates' && !datesLoaded) {
|
||
datesLoaded = true;
|
||
loadDates();
|
||
}
|
||
}
|
||
|
||
document.querySelectorAll('.dating-tab-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => activateTab(btn.dataset.tab));
|
||
});
|
||
|
||
// ── Auth-Check ────────────────────────────────────────────────────────────
|
||
fetch('/login/me').then(r => {
|
||
if (r.status === 401) { window.location.href = '/login.html'; return null; }
|
||
return r.ok ? r.json() : null;
|
||
}).then(user => {
|
||
if (!user) return;
|
||
if (!user.datingAktiv) { window.location.href = '/konto/einstellungen.html#sec-dating'; return; }
|
||
myPicture = user.profilePicture || null;
|
||
myUserId = user.userId || null;
|
||
loadVorlieben();
|
||
if (user.datingGeschlechter && user.datingGeschlechter.length > 0) {
|
||
defaultGeschlechter = user.datingGeschlechter;
|
||
document.querySelectorAll('#geschlechtChips .chip').forEach(chip => {
|
||
chip.classList.toggle('active', defaultGeschlechter.includes(chip.dataset.val));
|
||
});
|
||
}
|
||
// Standard-Filterwerte aus den Einstellungen übernehmen
|
||
if (user.datingMaxDistanzKm != null) defaultDistKm = user.datingMaxDistanzKm;
|
||
if (user.datingMinAlter != null) defaultAgeFrom = user.datingMinAlter;
|
||
if (user.datingMaxAlter != null) defaultAgeTo = user.datingMaxAlter;
|
||
document.getElementById('maxDist').value = defaultDistKm;
|
||
document.getElementById('distVal').textContent = defaultDistKm + ' km';
|
||
ageFrom = defaultAgeFrom;
|
||
ageTo = defaultAgeTo;
|
||
updateAgeSlider();
|
||
// Tab aus URL-Parameter aktivieren, sonst Default (Match)
|
||
const urlTab = new URLSearchParams(window.location.search).get('tab');
|
||
activateTab(urlTab || 'match');
|
||
}).catch(() => { window.location.href = '/login.html'; });
|
||
|
||
// ── IntersectionObserver für Infinite Scroll ──────────────────────────────
|
||
const sentinel = document.getElementById('sentinel');
|
||
const observer = new IntersectionObserver(entries => {
|
||
if (entries[0].isIntersecting) loadNextBatch();
|
||
}, { rootMargin: '300px' });
|
||
observer.observe(sentinel);
|
||
|
||
// ── Overlay öffnen/schließen ──────────────────────────────────────────────
|
||
const filterDrawer = document.getElementById('filterDrawer');
|
||
const filterBg = document.getElementById('filterBg');
|
||
|
||
function openFilter() {
|
||
filterDrawer.classList.add('open');
|
||
filterBg.classList.add('open');
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
function closeFilter() {
|
||
filterDrawer.classList.remove('open');
|
||
filterBg.classList.remove('open');
|
||
document.body.style.overflow = '';
|
||
}
|
||
|
||
document.getElementById('filterOpenBtn').addEventListener('click', openFilter);
|
||
document.getElementById('filterCloseBtn').addEventListener('click', closeFilter);
|
||
filterBg.addEventListener('click', closeFilter);
|
||
|
||
// ── Vorlieben laden ───────────────────────────────────────────────────────
|
||
function loadVorlieben() {
|
||
fetch('/vorlieben/items').then(r => r.json()).then(kategorien => {
|
||
allVorlieben = kategorien.flatMap(k => k.items.map(i => ({ itemId: i.itemId, name: i.name })));
|
||
}).catch(() => {});
|
||
}
|
||
|
||
// ── Chip-Toggle ───────────────────────────────────────────────────────────
|
||
document.querySelectorAll('.chip-group').forEach(group => {
|
||
group.addEventListener('click', e => {
|
||
const chip = e.target.closest('.chip');
|
||
if (chip) chip.classList.toggle('active');
|
||
});
|
||
});
|
||
|
||
// ── Logik-Toggle ─────────────────────────────────────────────────────────
|
||
document.querySelectorAll('#logicToggle .logic-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
document.querySelectorAll('#logicToggle .logic-btn').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
vorliebenUnd = btn.dataset.val === 'true';
|
||
});
|
||
});
|
||
|
||
// ── Dual-Handle Alters-Slider ─────────────────────────────────────────────
|
||
const AGE_MIN = 18, AGE_MAX = 99;
|
||
let ageFrom = 18, ageTo = 60;
|
||
|
||
const thumbMin = document.getElementById('thumbMin');
|
||
const thumbMax = document.getElementById('thumbMax');
|
||
const ageRange = document.getElementById('ageRange');
|
||
const ageSlider = document.getElementById('ageSlider');
|
||
|
||
function pct(val) { return (val - AGE_MIN) / (AGE_MAX - AGE_MIN) * 100; }
|
||
|
||
function updateAgeSlider() {
|
||
const lo = pct(ageFrom), hi = pct(ageTo);
|
||
thumbMin.style.left = lo + '%';
|
||
thumbMax.style.left = hi + '%';
|
||
ageRange.style.left = lo + '%';
|
||
ageRange.style.width = (hi - lo) + '%';
|
||
thumbMin.setAttribute('aria-valuenow', ageFrom);
|
||
thumbMax.setAttribute('aria-valuenow', ageTo);
|
||
document.getElementById('ageVal').textContent = ageFrom + ' – ' + ageTo;
|
||
}
|
||
|
||
function makeDraggable(thumb, isMin) {
|
||
function onMove(clientX) {
|
||
const rect = ageSlider.getBoundingClientRect();
|
||
const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
||
const raw = AGE_MIN + Math.round(ratio * (AGE_MAX - AGE_MIN));
|
||
if (isMin) {
|
||
ageFrom = Math.min(raw, ageTo - 1);
|
||
} else {
|
||
ageTo = Math.max(raw, ageFrom + 1);
|
||
}
|
||
updateAgeSlider();
|
||
}
|
||
|
||
// Mouse
|
||
thumb.addEventListener('mousedown', e => {
|
||
e.preventDefault();
|
||
const move = ev => onMove(ev.clientX);
|
||
const up = () => { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up); };
|
||
document.addEventListener('mousemove', move);
|
||
document.addEventListener('mouseup', up);
|
||
});
|
||
|
||
// Touch
|
||
thumb.addEventListener('touchstart', e => {
|
||
e.preventDefault();
|
||
const move = ev => onMove(ev.touches[0].clientX);
|
||
const end = () => { document.removeEventListener('touchmove', move); document.removeEventListener('touchend', end); };
|
||
document.addEventListener('touchmove', move, { passive: false });
|
||
document.addEventListener('touchend', end);
|
||
}, { passive: false });
|
||
|
||
// Keyboard
|
||
thumb.addEventListener('keydown', e => {
|
||
if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
|
||
if (isMin) ageFrom = Math.max(AGE_MIN, ageFrom - 1);
|
||
else ageTo = Math.max(ageFrom + 1, ageTo - 1);
|
||
updateAgeSlider(); e.preventDefault();
|
||
} else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
|
||
if (isMin) ageFrom = Math.min(ageTo - 1, ageFrom + 1);
|
||
else ageTo = Math.min(AGE_MAX, ageTo + 1);
|
||
updateAgeSlider(); e.preventDefault();
|
||
}
|
||
});
|
||
}
|
||
|
||
makeDraggable(thumbMin, true);
|
||
makeDraggable(thumbMax, false);
|
||
updateAgeSlider();
|
||
|
||
// ── Vorlieben-Suche ───────────────────────────────────────────────────────
|
||
const vlSearchEl = document.getElementById('vlSearch');
|
||
const vlDropdown = document.getElementById('vlDropdown');
|
||
|
||
vlSearchEl.addEventListener('input', () => {
|
||
const q = vlSearchEl.value.trim().toLowerCase();
|
||
if (!q) { vlDropdown.classList.remove('open'); return; }
|
||
const matches = allVorlieben
|
||
.filter(v => v.name.toLowerCase().includes(q) && !selectedVorlieben.has(v.itemId))
|
||
.slice(0, 15);
|
||
vlDropdown.innerHTML = matches.map(v =>
|
||
`<div class="vl-item" data-id="${v.itemId}" data-name="${escHtml(v.name)}">${escHtml(v.name)}</div>`
|
||
).join('');
|
||
vlDropdown.classList.toggle('open', matches.length > 0);
|
||
});
|
||
|
||
vlDropdown.addEventListener('click', e => {
|
||
const item = e.target.closest('.vl-item');
|
||
if (!item) return;
|
||
selectedVorlieben.set(item.dataset.id, item.dataset.name);
|
||
renderSelectedVorlieben();
|
||
vlSearchEl.value = '';
|
||
vlDropdown.classList.remove('open');
|
||
});
|
||
|
||
document.addEventListener('click', e => {
|
||
if (!vlSearchEl.contains(e.target) && !vlDropdown.contains(e.target))
|
||
vlDropdown.classList.remove('open');
|
||
});
|
||
|
||
function renderSelectedVorlieben() {
|
||
document.getElementById('selectedVorlieben').innerHTML =
|
||
[...selectedVorlieben.entries()].map(([id, name]) =>
|
||
`<span class="vl-tag">${escHtml(name)}<button onclick="removeVorliebe('${id}')" title="Entfernen">✕</button></span>`
|
||
).join('');
|
||
}
|
||
|
||
window.removeVorliebe = function (id) {
|
||
selectedVorlieben.delete(id);
|
||
renderSelectedVorlieben();
|
||
updateFilterBadge();
|
||
};
|
||
|
||
// ── Reset ─────────────────────────────────────────────────────────────────
|
||
document.getElementById('resetBtn').addEventListener('click', () => {
|
||
document.getElementById('maxDist').value = defaultDistKm;
|
||
document.getElementById('distVal').textContent = defaultDistKm + ' km';
|
||
ageFrom = defaultAgeFrom; ageTo = defaultAgeTo;
|
||
updateAgeSlider();
|
||
// Geschlecht-Chips auf gespeicherte Einstellungswerte zurücksetzen
|
||
document.querySelectorAll('#geschlechtChips .chip').forEach(c =>
|
||
c.classList.toggle('active', defaultGeschlechter.includes(c.dataset.val)));
|
||
document.querySelectorAll('#neigungChips .chip.active').forEach(c => c.classList.remove('active'));
|
||
document.querySelectorAll('#logicToggle .logic-btn').forEach((b, i) => b.classList.toggle('active', i === 0));
|
||
vorliebenUnd = false;
|
||
selectedVorlieben.clear();
|
||
renderSelectedVorlieben();
|
||
updateFilterBadge();
|
||
});
|
||
|
||
// ── Suchen-Button ─────────────────────────────────────────────────────────
|
||
document.getElementById('applyBtn').addEventListener('click', () => {
|
||
closeFilter();
|
||
updateFilterBadge();
|
||
saveFilterToDb();
|
||
const activeTab = document.querySelector('.dating-tab-btn.active')?.dataset?.tab;
|
||
if (activeTab === 'dates') {
|
||
loadDates();
|
||
} else if (activeTab === 'match') {
|
||
discoveryLoaded = false;
|
||
loadDiscovery();
|
||
} else {
|
||
runSearch();
|
||
}
|
||
});
|
||
|
||
function saveFilterToDb() {
|
||
const geschlechter = [...document.querySelectorAll('#geschlechtChips .chip.active')]
|
||
.map(c => c.dataset.val);
|
||
fetch('/user/me/dating-filter', {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
datingGeschlechter: geschlechter,
|
||
datingMaxDistanzKm: parseInt(document.getElementById('maxDist').value),
|
||
datingMinAlter: ageFrom,
|
||
datingMaxAlter: ageTo
|
||
})
|
||
}).then(r => {
|
||
if (r.ok) {
|
||
defaultDistKm = parseInt(document.getElementById('maxDist').value);
|
||
defaultAgeFrom = ageFrom;
|
||
defaultAgeTo = ageTo;
|
||
defaultGeschlechter = geschlechter;
|
||
}
|
||
}).catch(() => {});
|
||
}
|
||
|
||
// ── Filter-Badge ──────────────────────────────────────────────────────────
|
||
function updateFilterBadge() {
|
||
let count = 0;
|
||
if (parseInt(document.getElementById('maxDist').value) !== 50) count++;
|
||
if (ageFrom !== 18 || ageTo !== 60) count++;
|
||
if (document.querySelectorAll('#geschlechtChips .chip.active').length > 0) count++;
|
||
if (document.querySelectorAll('#neigungChips .chip.active').length > 0) count++;
|
||
if (selectedVorlieben.size > 0) count++;
|
||
const badge = document.getElementById('filterBadge');
|
||
badge.textContent = count;
|
||
badge.style.display = count > 0 ? 'inline-flex' : 'none';
|
||
}
|
||
|
||
// ── Schritt 1: IDs laden ──────────────────────────────────────────────────
|
||
async function runSearch() {
|
||
allIds = [];
|
||
loadedCount = 0;
|
||
loading = false;
|
||
|
||
document.getElementById('profilesGrid').innerHTML = skeletons(BATCH_SIZE);
|
||
document.getElementById('resultsCount').textContent = '';
|
||
|
||
try {
|
||
const res = await fetch('/dating/profile-ids?' + buildParams());
|
||
if (res.status === 403) { window.location.href = '/konto/einstellungen.html#sec-dating'; return; }
|
||
if (!res.ok) throw new Error();
|
||
const data = await res.json();
|
||
allIds = data.ids;
|
||
document.getElementById('resultsCount').textContent =
|
||
data.total === 0
|
||
? 'Keine Ergebnisse'
|
||
: data.total + ' Person' + (data.total === 1 ? '' : 'en') + ' gefunden';
|
||
|
||
document.getElementById('profilesGrid').innerHTML = '';
|
||
|
||
if (allIds.length === 0) {
|
||
document.getElementById('profilesGrid').innerHTML =
|
||
'<div class="empty-state"><div class="icon">🔍</div><p>Niemand in deinem Umkreis mit diesen Filtern gefunden.</p></div>';
|
||
return;
|
||
}
|
||
loadNextBatch();
|
||
} catch {
|
||
document.getElementById('profilesGrid').innerHTML =
|
||
'<div class="empty-state"><div class="icon">⚠️</div><p>Fehler beim Laden.</p></div>';
|
||
}
|
||
}
|
||
|
||
// ── Schritt 2: Profildetails batchweise nachladen ─────────────────────────
|
||
async function loadNextBatch() {
|
||
if (loading || loadedCount >= allIds.length) return;
|
||
loading = true;
|
||
|
||
const batchIds = allIds.slice(loadedCount, loadedCount + BATCH_SIZE);
|
||
showSkeletons(batchIds.length);
|
||
|
||
try {
|
||
const res = await fetch('/dating/profiles/batch', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(batchIds)
|
||
});
|
||
if (!res.ok) throw new Error();
|
||
const profiles = await res.json();
|
||
removeSkeletons();
|
||
appendProfiles(profiles);
|
||
loadedCount += batchIds.length;
|
||
} catch {
|
||
removeSkeletons();
|
||
}
|
||
|
||
loading = false;
|
||
}
|
||
|
||
// ── Render ────────────────────────────────────────────────────────────────
|
||
function appendProfiles(profiles) {
|
||
const grid = document.getElementById('profilesGrid');
|
||
profiles.forEach(p => {
|
||
const card = document.createElement('div');
|
||
card.className = 'profile-card';
|
||
|
||
const picSrc = p.profilePictureHq || p.profilePicture;
|
||
const img = picSrc
|
||
? `<img src="data:image/png;base64,${picSrc}" alt="${escHtml(p.name)}" loading="lazy">`
|
||
: `<span>👤</span>`;
|
||
|
||
const chips = [
|
||
p.alter ? `<span class="meta-chip">${p.alter} J.</span>` : '',
|
||
p.distanzKm != null ? `<span class="meta-chip dist">${p.distanzKm < 1 ? '< 1' : p.distanzKm} km</span>` : '',
|
||
p.geschlecht ? `<span class="meta-chip">${escHtml(p.geschlecht)}</span>` : '',
|
||
p.neigung ? `<span class="meta-chip">${escHtml(p.neigung)}</span>` : '',
|
||
p.datingStadt ? `<span class="meta-chip">${escHtml(p.datingStadt)}</span>` : '',
|
||
].filter(Boolean).join('');
|
||
|
||
const desc = p.beschreibung
|
||
? `<div class="profile-card-desc">${escHtml(p.beschreibung)}</div>` : '';
|
||
|
||
card.innerHTML = `
|
||
<a href="/community/benutzer.html?userId=${p.userId}" style="text-decoration:none;color:inherit;display:contents;">
|
||
<div class="profile-card-img-wrap">${img}
|
||
<button class="profile-card-like${p.likedByMe ? ' liked' : ''}"
|
||
data-user-id="${p.userId}"
|
||
title="${p.likedByMe ? 'Unlike' : 'Like'}">♥</button>
|
||
</div>
|
||
<div class="profile-card-body">
|
||
<div class="profile-card-name">${escHtml(p.name)}</div>
|
||
<div class="profile-card-meta">${chips}</div>
|
||
${desc}
|
||
</div>
|
||
</a>`;
|
||
|
||
card.querySelector('.profile-card-like').addEventListener('click', e => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
toggleLike(e.currentTarget, p.userId);
|
||
});
|
||
grid.appendChild(card);
|
||
});
|
||
}
|
||
|
||
async function toggleLike(btn, userId) {
|
||
btn.disabled = true;
|
||
try {
|
||
const res = await fetch('/dating/like/' + userId, { method: 'POST' });
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
btn.classList.toggle('liked', data.liked);
|
||
btn.title = data.liked ? 'Unlike' : 'Like';
|
||
if (data.newMatch) showMatchToast();
|
||
} finally {
|
||
btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
function showMatchToast() {
|
||
const t = document.createElement('div');
|
||
t.textContent = '🎉 Es ist ein Match!';
|
||
Object.assign(t.style, {
|
||
position:'fixed', bottom:'2rem', left:'50%', transform:'translateX(-50%)',
|
||
background:'var(--color-primary)', color:'#fff', padding:'0.75rem 1.5rem',
|
||
borderRadius:'8px', fontWeight:'700', zIndex:'999', boxShadow:'0 4px 16px rgba(0,0,0,0.4)'
|
||
});
|
||
document.body.appendChild(t);
|
||
setTimeout(() => t.remove(), 3500);
|
||
}
|
||
|
||
// ── Skeleton-Helpers ──────────────────────────────────────────────────────
|
||
function skeletons(n) {
|
||
return Array.from({ length: n }, () => `
|
||
<div class="profile-card-skeleton" data-skeleton>
|
||
<div class="skeleton-img"></div>
|
||
<div class="skeleton-body">
|
||
<div class="skeleton-line" style="width:60%"></div>
|
||
<div class="skeleton-line" style="width:80%"></div>
|
||
</div>
|
||
</div>`).join('');
|
||
}
|
||
|
||
function showSkeletons(n) {
|
||
const grid = document.getElementById('profilesGrid');
|
||
grid.insertAdjacentHTML('beforeend', skeletons(n));
|
||
}
|
||
|
||
function removeSkeletons() {
|
||
document.querySelectorAll('[data-skeleton]').forEach(el => el.remove());
|
||
}
|
||
|
||
// ── Params-Builder ────────────────────────────────────────────────────────
|
||
function buildParams() {
|
||
const p = new URLSearchParams();
|
||
p.set('maxDistanceKm', document.getElementById('maxDist').value);
|
||
p.set('minAge', ageFrom);
|
||
p.set('maxAge', ageTo);
|
||
p.set('vorliebenUnd', vorliebenUnd);
|
||
document.querySelectorAll('#geschlechtChips .chip.active').forEach(c => p.append('geschlechter', c.dataset.val));
|
||
document.querySelectorAll('#neigungChips .chip.active').forEach(c => p.append('neigungen', c.dataset.val));
|
||
selectedVorlieben.forEach((_, id) => p.append('vorliebenIds', id));
|
||
return p;
|
||
}
|
||
|
||
// ── Utility ───────────────────────────────────────────────────────────────
|
||
function escHtml(s) {
|
||
if (!s) return '';
|
||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||
}
|
||
|
||
// ── Discovery (Match-Tab) ─────────────────────────────────────────────────
|
||
let discoveryQueue = [];
|
||
let discoveryIdx = 0;
|
||
let discoveryProfile = null;
|
||
let myPicture = null;
|
||
let discoveryLoaded = false;
|
||
let entdeckenLoaded = false;
|
||
let discImages = []; // Bildquellen (volle data-URL) für aktuelle Karte
|
||
let discImageIdx = 0;
|
||
|
||
// (Tab-Wechsel-Logik ist oben in der Tab-Navigation)
|
||
|
||
async function loadDiscovery() {
|
||
document.getElementById('discoveryLoading').style.display = '';
|
||
document.getElementById('discoveryCard').style.display = 'none';
|
||
document.getElementById('discoveryActions').style.display = 'none';
|
||
document.getElementById('discoveryEmpty').style.display = 'none';
|
||
try {
|
||
const res = await fetch('/dating/discovery');
|
||
if (res.status === 403) return;
|
||
if (!res.ok) throw new Error();
|
||
discoveryQueue = await res.json();
|
||
discoveryIdx = 0;
|
||
} catch { discoveryQueue = []; }
|
||
document.getElementById('discoveryLoading').style.display = 'none';
|
||
showDiscoveryCard();
|
||
}
|
||
|
||
async function showDiscoveryCard() {
|
||
if (discoveryIdx >= discoveryQueue.length) {
|
||
document.getElementById('discoveryCard').style.display = 'none';
|
||
document.getElementById('discoveryActions').style.display = 'none';
|
||
document.getElementById('discoveryEmpty').style.display = '';
|
||
return;
|
||
}
|
||
const userId = discoveryQueue[discoveryIdx];
|
||
try {
|
||
const [profRes, galleryRes] = await Promise.all([
|
||
fetch('/dating/profiles/batch', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify([userId])
|
||
}),
|
||
fetch('/social/profile-images?userId=' + userId)
|
||
]);
|
||
if (!profRes.ok) { discoveryIdx++; showDiscoveryCard(); return; }
|
||
const profiles = await profRes.json();
|
||
if (!profiles || !profiles.length) { discoveryIdx++; showDiscoveryCard(); return; }
|
||
discoveryProfile = profiles[0];
|
||
|
||
// Bildersammlung aufbauen: Profilfoto + Galerie
|
||
discImages = [];
|
||
const pic = discoveryProfile.profilePictureHq || discoveryProfile.profilePicture;
|
||
if (pic) discImages.push('data:image/png;base64,' + pic);
|
||
if (galleryRes.ok) {
|
||
const gallery = await galleryRes.json();
|
||
gallery.forEach(img => {
|
||
if (img.imageData) discImages.push('data:image/jpeg;base64,' + img.imageData);
|
||
});
|
||
}
|
||
discImageIdx = 0;
|
||
} catch { discoveryIdx++; showDiscoveryCard(); return; }
|
||
|
||
// Texte setzen
|
||
document.getElementById('discCardName').textContent = discoveryProfile.name || '';
|
||
const meta = [];
|
||
if (discoveryProfile.alter) meta.push(discoveryProfile.alter + ' J.');
|
||
if (discoveryProfile.geschlecht) meta.push({ WEIBLICH:'weiblich', MAENNLICH:'männlich', DIVERS:'divers' }[discoveryProfile.geschlecht] || discoveryProfile.geschlecht);
|
||
if (discoveryProfile.neigung) meta.push({ DEVOT:'devot', EHER_DEVOT:'eher devot', SWITCHER:'Switcher', EHER_DOMINANT:'eher dominant', DOMINANT:'dominant' }[discoveryProfile.neigung] || discoveryProfile.neigung);
|
||
document.getElementById('discCardMeta').textContent = meta.join(' · ');
|
||
|
||
const descEl = document.getElementById('discCardDesc');
|
||
if (discoveryProfile.beschreibung) { descEl.textContent = discoveryProfile.beschreibung; descEl.style.display = ''; }
|
||
else { descEl.style.display = 'none'; }
|
||
|
||
// Matching Vorlieben
|
||
const chipsEl = document.getElementById('discMatchChips');
|
||
const matchIds = discoveryProfile.matchingVorliebenIds || [];
|
||
if (matchIds.length > 0 && allVorlieben.length > 0) {
|
||
const nameMap = new Map(allVorlieben.map(v => [v.itemId, v.name]));
|
||
chipsEl.innerHTML = matchIds.filter(id => nameMap.has(id))
|
||
.map(id => `<span class="disc-match-chip">${escHtml(nameMap.get(id))}</span>`).join('');
|
||
} else {
|
||
chipsEl.innerHTML = '';
|
||
}
|
||
|
||
renderDiscImage();
|
||
document.getElementById('discoveryCard').style.display = '';
|
||
document.getElementById('discoveryActions').style.display = '';
|
||
}
|
||
|
||
function renderDiscImage() {
|
||
const src = discImages[discImageIdx] || null;
|
||
const photoEl = document.getElementById('discCardPhoto');
|
||
const noPicEl = document.getElementById('discNoPic');
|
||
if (src) {
|
||
photoEl.src = src;
|
||
photoEl.style.display = '';
|
||
noPicEl.style.display = 'none';
|
||
} else {
|
||
photoEl.style.display = 'none';
|
||
noPicEl.style.display = '';
|
||
}
|
||
const hasMany = discImages.length > 1;
|
||
document.getElementById('discNavPrev').style.display = hasMany && discImageIdx > 0 ? '' : 'none';
|
||
document.getElementById('discNavNext').style.display = hasMany && discImageIdx < discImages.length - 1 ? '' : 'none';
|
||
// Dots
|
||
const dotsEl = document.getElementById('discImgDots');
|
||
if (hasMany) {
|
||
dotsEl.innerHTML = discImages.map((_, i) =>
|
||
`<span class="disc-img-dot${i === discImageIdx ? ' active' : ''}"></span>`).join('');
|
||
} else {
|
||
dotsEl.innerHTML = '';
|
||
}
|
||
}
|
||
|
||
window.discNavStep = function(dir, e) {
|
||
e.stopPropagation();
|
||
discImageIdx = Math.max(0, Math.min(discImages.length - 1, discImageIdx + dir));
|
||
renderDiscImage();
|
||
};
|
||
|
||
window.discardProfile = async function () {
|
||
if (!discoveryProfile) return;
|
||
fetch('/dating/pass/' + discoveryProfile.userId, { method: 'POST' }).catch(() => {});
|
||
discoveryIdx++;
|
||
showDiscoveryCard();
|
||
};
|
||
|
||
window.likeProfile = async function () {
|
||
if (!discoveryProfile) return;
|
||
const userId = discoveryProfile.userId;
|
||
const name = discoveryProfile.name;
|
||
const pic = discoveryProfile.profilePicture || null;
|
||
discoveryIdx++;
|
||
showDiscoveryCard();
|
||
try {
|
||
const res = await fetch('/dating/like/' + userId, { method: 'POST' });
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
if (data.newMatch) showMatchOverlay(userId, name, pic);
|
||
} catch { /* ignore */ }
|
||
};
|
||
|
||
window.openDiscoveryPopup = async function () {
|
||
if (!discoveryProfile) return;
|
||
const p = discoveryProfile;
|
||
|
||
// Bilder aus der Match-Karte übernehmen
|
||
dpImages = [...discImages]; dpImageIdx = discImageIdx;
|
||
renderPopupImage('dp', dpImages, dpImageIdx);
|
||
|
||
document.getElementById('dpName').textContent = p.name || '';
|
||
document.getElementById('dpProfileLink').href = '/community/benutzer.html?userId=' + p.userId;
|
||
document.getElementById('dpVorlieben').innerHTML = '';
|
||
|
||
const tags = document.getElementById('dpTags');
|
||
tags.innerHTML = '';
|
||
const addTag = v => { if (!v) return; const s = document.createElement('span'); s.className = 'dp-tag'; s.textContent = v; tags.appendChild(s); };
|
||
if (p.alter) addTag(p.alter + ' J.');
|
||
if (p.geschlecht) addTag({ WEIBLICH:'weiblich', MAENNLICH:'männlich', DIVERS:'divers' }[p.geschlecht] || p.geschlecht);
|
||
if (p.neigung) addTag({ DEVOT:'devot', EHER_DEVOT:'eher devot', SWITCHER:'Switcher', EHER_DOMINANT:'eher dominant', DOMINANT:'dominant' }[p.neigung] || p.neigung);
|
||
if (p.beziehungsstatus) addTag({ SINGLE:'single', IN_EINER_BEZIEHUNG:'in einer Beziehung', VERHEIRATET:'verheiratet', IN_EINER_OFFENEN_BEZIEHUNG:'offene Beziehung', IN_EINER_OFFENEN_EHE:'offene Ehe' }[p.beziehungsstatus] || p.beziehungsstatus);
|
||
|
||
const descEl = document.getElementById('dpDesc');
|
||
if (p.beschreibung) { descEl.textContent = p.beschreibung; descEl.style.display = ''; }
|
||
else { descEl.style.display = 'none'; }
|
||
|
||
document.getElementById('discoveryPopupBg').classList.add('open');
|
||
|
||
try {
|
||
const vlRes = await fetch('/dating/vorlieben/' + p.userId);
|
||
if (vlRes.ok) {
|
||
const chips = await vlRes.json();
|
||
renderVorliebenChips(document.getElementById('dpVorlieben'), chips);
|
||
}
|
||
} catch {}
|
||
};
|
||
|
||
window.closeDiscoveryPopup = function (e) {
|
||
if (e && e.target !== document.getElementById('discoveryPopupBg')) return;
|
||
document.getElementById('discoveryPopupBg').classList.remove('open');
|
||
};
|
||
|
||
function showMatchOverlay(partnerId, partnerName, partnerPic) {
|
||
const meAv = document.getElementById('matchAvatarMe');
|
||
meAv.innerHTML = myPicture
|
||
? `<img src="data:image/png;base64,${myPicture}" alt="">`
|
||
: '👤';
|
||
const theirAv = document.getElementById('matchAvatarPartner');
|
||
theirAv.innerHTML = partnerPic
|
||
? `<img src="data:image/png;base64,${partnerPic}" alt="">`
|
||
: '👤';
|
||
document.getElementById('matchOverlaySub').textContent = 'Du und ' + (partnerName || 'jemand') + ' mögt euch gegenseitig!';
|
||
document.getElementById('matchChatLink').href = '/community/nachrichten.html?userId=' + partnerId;
|
||
document.getElementById('matchOverlay').classList.add('open');
|
||
}
|
||
|
||
window.closeMatchOverlay = function () {
|
||
document.getElementById('matchOverlay').classList.remove('open');
|
||
};
|
||
|
||
// SSE-Match-Events (auch aus social-sidebar SSE-Handler aufrufbar)
|
||
window._onSseMatch = function (data) {
|
||
showMatchOverlay(data.partnerId, data.partnerName, data.partnerPicture || null);
|
||
};
|
||
|
||
// ── Dates-Tab ─────────────────────────────────────────────────────────────
|
||
let datesLoaded = false;
|
||
let currentDate = null; // aktuell geöffnetes Date (Detail- oder Eigenes-Dialog)
|
||
let editingDateId = null; // UUID wenn Bearbeitung, null wenn Neu-Erstellen
|
||
let formImgData = null; // base64 des gewählten Formular-Bildes
|
||
let myUserId = null; // eigene User-ID (wird beim Login-Fetch gesetzt)
|
||
|
||
document.getElementById('createDateBtn').addEventListener('click', () => openCreateDate());
|
||
|
||
async function loadDates() {
|
||
document.getElementById('datesCount').textContent = '';
|
||
document.getElementById('myDatesSection').style.display = 'none';
|
||
document.getElementById('myDatesGrid').innerHTML = '';
|
||
document.getElementById('availableDatesGrid').innerHTML =
|
||
'<div style="color:var(--color-muted);font-size:0.85rem;padding:1rem 0;">Wird geladen…</div>';
|
||
|
||
try {
|
||
const res = await fetch('/dating/dates?' + buildParams());
|
||
if (res.status === 403) return;
|
||
if (!res.ok) throw new Error();
|
||
const data = await res.json();
|
||
|
||
renderMyDates(data.mine || []);
|
||
renderAvailableDates(data.available || []);
|
||
|
||
const total = (data.available || []).length;
|
||
document.getElementById('datesCount').textContent =
|
||
total === 0 ? 'Keine Dates gefunden' : total + ' Date' + (total === 1 ? '' : 's') + ' gefunden';
|
||
} catch {
|
||
document.getElementById('availableDatesGrid').innerHTML =
|
||
'<div style="color:var(--color-muted);font-size:0.85rem;">Fehler beim Laden.</div>';
|
||
}
|
||
}
|
||
|
||
function renderMyDates(dates) {
|
||
const section = document.getElementById('myDatesSection');
|
||
const grid = document.getElementById('myDatesGrid');
|
||
if (dates.length === 0) { section.style.display = 'none'; return; }
|
||
section.style.display = '';
|
||
grid.innerHTML = '';
|
||
dates.forEach(d => grid.appendChild(buildDateCard(d, true)));
|
||
}
|
||
|
||
function renderAvailableDates(dates) {
|
||
const grid = document.getElementById('availableDatesGrid');
|
||
grid.innerHTML = '';
|
||
if (dates.length === 0) {
|
||
grid.innerHTML = '<div style="color:var(--color-muted);font-size:0.85rem;padding:1rem 0;">Keine Dates in deinem Umkreis gefunden.</div>';
|
||
return;
|
||
}
|
||
dates.forEach(d => grid.appendChild(buildDateCard(d, false)));
|
||
}
|
||
|
||
function buildDateCard(d, isMine) {
|
||
const card = document.createElement('div');
|
||
card.className = 'date-card';
|
||
|
||
const imgHtml = d.imageData
|
||
? `<img src="${escHtml(d.imageData)}" alt="" loading="lazy">`
|
||
: '📅';
|
||
|
||
const footerRight = isMine
|
||
? `<span class="date-card-interest">♥ ${d.interestCount}</span>`
|
||
: (d.myInterest
|
||
? `<span class="date-card-interest">♥ Interesse</span>`
|
||
: `<span class="date-card-meta">♥ ${d.interestCount}</span>`);
|
||
|
||
const avatarHtml = d.creatorProfilePicture
|
||
? `<img src="data:image/png;base64,${d.creatorProfilePicture}" alt="">`
|
||
: '👤';
|
||
|
||
const dateHint = d.scheduledAt
|
||
? `<span class="date-card-date">${formatDateTime(d.scheduledAt)}</span>` : '';
|
||
|
||
card.innerHTML = `
|
||
<div class="date-card-img">${imgHtml}</div>
|
||
<div class="date-card-body">
|
||
<div class="date-card-title">${escHtml(d.title)}</div>
|
||
<div class="date-card-desc">${escHtml(d.description)}</div>
|
||
${dateHint ? '<div class="date-card-date">' + escHtml(formatDateTime(d.scheduledAt)) + '</div>' : ''}
|
||
</div>
|
||
<div class="date-card-footer">
|
||
<div class="date-card-avatar" data-creator-id="${d.creatorId}">${avatarHtml}</div>
|
||
<span class="date-card-creator">${escHtml(d.creatorName || '')}</span>
|
||
${footerRight}
|
||
</div>`;
|
||
|
||
card.addEventListener('click', () => {
|
||
if (isMine) openMyDateDialog(d);
|
||
else openDateDialog(d);
|
||
});
|
||
|
||
return card;
|
||
}
|
||
|
||
// ── Detail-Dialog (fremdes Date) ──────────────────────────────────────────
|
||
function openDateDialog(d) {
|
||
currentDate = d;
|
||
|
||
const imgEl = document.getElementById('ddImg');
|
||
imgEl.innerHTML = d.imageData ? `<img src="${escHtml(d.imageData)}" alt="">` : '📅';
|
||
|
||
document.getElementById('ddTitle').textContent = d.title;
|
||
|
||
const avatarEl = document.getElementById('ddCreatorAvatar');
|
||
avatarEl.innerHTML = d.creatorProfilePicture
|
||
? `<img src="data:image/png;base64,${d.creatorProfilePicture}" alt="">` : '👤';
|
||
document.getElementById('ddCreatorName').textContent = d.creatorName || '';
|
||
document.getElementById('ddCreator').onclick =
|
||
() => window.open('/community/benutzer.html?userId=' + d.creatorId, '_blank');
|
||
|
||
const whenEl = document.getElementById('ddWhen');
|
||
if (d.scheduledAt) {
|
||
document.getElementById('ddWhenText').textContent = formatDateTime(d.scheduledAt);
|
||
whenEl.style.display = 'flex';
|
||
} else {
|
||
whenEl.style.display = 'none';
|
||
}
|
||
|
||
document.getElementById('ddDesc').textContent = d.description;
|
||
document.getElementById('ddStats').textContent =
|
||
d.interestCount + ' Person' + (d.interestCount === 1 ? '' : 'en') + ' interessiert';
|
||
|
||
const actionsEl = document.getElementById('ddActions');
|
||
const interested = d.myInterest;
|
||
actionsEl.innerHTML = `
|
||
<button class="btn date-interest-btn${interested ? ' active' : ''}" id="ddInterestBtn"
|
||
onclick="toggleInterest()">
|
||
${interested ? '♥ Interesse bekundet' : '♥ Interesse bekunden'}
|
||
</button>`;
|
||
|
||
document.getElementById('dateBg').classList.add('open');
|
||
}
|
||
|
||
window.closeDateDialog = function(e) {
|
||
if (e && e.target !== document.getElementById('dateBg')) return;
|
||
document.getElementById('dateBg').classList.remove('open');
|
||
currentDate = null;
|
||
};
|
||
window.closeDateDialogBtn = function() {
|
||
document.getElementById('dateBg').classList.remove('open');
|
||
currentDate = null;
|
||
};
|
||
|
||
window.toggleInterest = async function() {
|
||
if (!currentDate) return;
|
||
const btn = document.getElementById('ddInterestBtn');
|
||
btn.disabled = true;
|
||
try {
|
||
const res = await fetch('/dating/dates/' + currentDate.dateId + '/interest', { method: 'POST' });
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
currentDate = { ...currentDate, myInterest: data.myInterest, interestCount: data.interestCount };
|
||
btn.className = 'btn date-interest-btn' + (data.myInterest ? ' active' : '');
|
||
btn.textContent = data.myInterest ? '♥ Interesse bekundet' : '♥ Interesse bekunden';
|
||
document.getElementById('ddStats').textContent =
|
||
data.interestCount + ' Person' + (data.interestCount === 1 ? '' : 'en') + ' interessiert';
|
||
// Karte aktualisieren
|
||
datesLoaded = false;
|
||
} finally {
|
||
btn.disabled = false;
|
||
}
|
||
};
|
||
|
||
// ── Eigenes-Date-Dialog ───────────────────────────────────────────────────
|
||
function openMyDateDialog(d) {
|
||
currentDate = d;
|
||
|
||
const imgEl = document.getElementById('mdImg');
|
||
imgEl.innerHTML = d.imageData ? `<img src="${escHtml(d.imageData)}" alt="">` : '📅';
|
||
|
||
document.getElementById('mdTitle').textContent = d.title;
|
||
|
||
const whenEl = document.getElementById('mdWhen');
|
||
if (d.scheduledAt) {
|
||
document.getElementById('mdWhenText').textContent = formatDateTime(d.scheduledAt);
|
||
whenEl.style.display = 'flex';
|
||
} else {
|
||
whenEl.style.display = 'none';
|
||
}
|
||
document.getElementById('mdDesc').textContent = d.description;
|
||
|
||
// Interessenten laden
|
||
loadInterests(d.dateId);
|
||
|
||
document.getElementById('myDateBg').classList.add('open');
|
||
}
|
||
|
||
async function loadInterests(dateId) {
|
||
const list = document.getElementById('mdInterestList');
|
||
list.innerHTML = '<div style="color:var(--color-muted);font-size:0.85rem;">Wird geladen…</div>';
|
||
try {
|
||
const res = await fetch('/dating/dates/' + dateId + '/interests');
|
||
if (!res.ok) throw new Error();
|
||
const interests = await res.json();
|
||
if (interests.length === 0) {
|
||
list.innerHTML = '<div style="color:var(--color-muted);font-size:0.85rem;">Noch keine Interessenten.</div>';
|
||
return;
|
||
}
|
||
list.innerHTML = '';
|
||
interests.forEach(i => {
|
||
const item = document.createElement('div');
|
||
item.className = 'interest-item';
|
||
const avatarHtml = i.profilePicture
|
||
? `<img src="${escHtml(i.profilePicture)}" alt="">` : '👤';
|
||
const actionHtml = i.blocked
|
||
? `<span class="interest-item-blocked">blockiert</span>`
|
||
: `<button class="btn interest-item-dm" onclick="openDm('${i.userId}')">Nachricht</button>`;
|
||
item.innerHTML = `
|
||
<div class="interest-item-avatar" onclick="loadAndOpenProfile('${i.userId}','${escHtml(i.name || '')}', '${i.profilePicture || ''}')">${avatarHtml}</div>
|
||
<span class="interest-item-name" onclick="loadAndOpenProfile('${i.userId}','${escHtml(i.name || '')}','${i.profilePicture || ''}')">${escHtml(i.name || 'Unbekannt')}</span>
|
||
${actionHtml}`;
|
||
list.appendChild(item);
|
||
});
|
||
} catch {
|
||
list.innerHTML = '<div style="color:var(--color-muted);font-size:0.85rem;">Fehler beim Laden.</div>';
|
||
}
|
||
}
|
||
|
||
window.closeMyDateDialog = function(e) {
|
||
if (e && e.target !== document.getElementById('myDateBg')) return;
|
||
document.getElementById('myDateBg').classList.remove('open');
|
||
currentDate = null;
|
||
};
|
||
window.closeMyDateDialogBtn = function() {
|
||
document.getElementById('myDateBg').classList.remove('open');
|
||
currentDate = null;
|
||
};
|
||
|
||
window.deleteMyDate = async function() {
|
||
if (!currentDate) return;
|
||
if (!confirm('Date wirklich löschen?')) return;
|
||
try {
|
||
const res = await fetch('/dating/dates/' + currentDate.dateId, { method: 'DELETE' });
|
||
if (!res.ok) return;
|
||
closeMyDateDialogBtn();
|
||
datesLoaded = false;
|
||
loadDates();
|
||
} catch {}
|
||
};
|
||
|
||
window.openDm = function(userId) {
|
||
window.location.href = '/community/nachrichten.html?userId=' + userId;
|
||
};
|
||
|
||
// ── Erstellen/Bearbeiten-Dialog ───────────────────────────────────────────
|
||
function openCreateDate() {
|
||
editingDateId = null;
|
||
formImgData = null;
|
||
document.getElementById('dateFormTitle').textContent = 'Date erstellen';
|
||
document.getElementById('dfTitle').value = '';
|
||
document.getElementById('dfDesc').value = '';
|
||
document.getElementById('dfDateTime').value = '';
|
||
resetFormImg();
|
||
document.getElementById('dfLimitHint').textContent = '';
|
||
document.getElementById('dateFormBg').classList.add('open');
|
||
}
|
||
|
||
window.openEditDate = function() {
|
||
if (!currentDate) return;
|
||
editingDateId = currentDate.dateId;
|
||
formImgData = currentDate.imageData || null;
|
||
document.getElementById('dateFormTitle').textContent = 'Date bearbeiten';
|
||
document.getElementById('dfTitle').value = currentDate.title || '';
|
||
document.getElementById('dfDesc').value = currentDate.description || '';
|
||
document.getElementById('dfDateTime').value = currentDate.scheduledAt
|
||
? currentDate.scheduledAt.substring(0, 16) : '';
|
||
|
||
const preview = document.getElementById('dfImgPreview');
|
||
const removeBtn = document.getElementById('dfImgRemove');
|
||
const placeholder = document.getElementById('dfImgPlaceholder');
|
||
if (formImgData) {
|
||
preview.style.backgroundImage = '';
|
||
let imgTag = preview.querySelector('img');
|
||
if (!imgTag) { imgTag = document.createElement('img'); preview.appendChild(imgTag); }
|
||
imgTag.src = formImgData;
|
||
placeholder.style.display = 'none';
|
||
removeBtn.classList.add('visible');
|
||
} else {
|
||
resetFormImg();
|
||
}
|
||
|
||
document.getElementById('myDateBg').classList.remove('open');
|
||
document.getElementById('dateFormBg').classList.add('open');
|
||
};
|
||
|
||
window.closeDateForm = function(e) {
|
||
if (e && e.target !== document.getElementById('dateFormBg')) return;
|
||
document.getElementById('dateFormBg').classList.remove('open');
|
||
};
|
||
window.closeDateFormBtn = function() {
|
||
document.getElementById('dateFormBg').classList.remove('open');
|
||
};
|
||
|
||
window.triggerImgPick = function() {
|
||
document.getElementById('dfImgInput').click();
|
||
};
|
||
|
||
window.onImgSelected = function(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
const reader = new FileReader();
|
||
reader.onload = e => {
|
||
formImgData = e.target.result;
|
||
const preview = document.getElementById('dfImgPreview');
|
||
let imgTag = preview.querySelector('img');
|
||
if (!imgTag) { imgTag = document.createElement('img'); preview.appendChild(imgTag); }
|
||
imgTag.src = formImgData;
|
||
document.getElementById('dfImgPlaceholder').style.display = 'none';
|
||
document.getElementById('dfImgRemove').classList.add('visible');
|
||
};
|
||
reader.readAsDataURL(file);
|
||
event.target.value = '';
|
||
};
|
||
|
||
window.removeFormImg = function(e) {
|
||
e.stopPropagation();
|
||
formImgData = null;
|
||
resetFormImg();
|
||
};
|
||
|
||
function resetFormImg() {
|
||
const preview = document.getElementById('dfImgPreview');
|
||
const imgTag = preview.querySelector('img');
|
||
if (imgTag) imgTag.remove();
|
||
document.getElementById('dfImgPlaceholder').style.display = '';
|
||
document.getElementById('dfImgRemove').classList.remove('visible');
|
||
}
|
||
|
||
window.saveDate = async function() {
|
||
const title = document.getElementById('dfTitle').value.trim();
|
||
const desc = document.getElementById('dfDesc').value.trim();
|
||
const dtVal = document.getElementById('dfDateTime').value;
|
||
const saveBtn = document.getElementById('dfSaveBtn');
|
||
const hintEl = document.getElementById('dfLimitHint');
|
||
|
||
if (!title) { hintEl.textContent = 'Bitte Titel eingeben.'; return; }
|
||
if (!desc) { hintEl.textContent = 'Bitte Beschreibung eingeben.'; return; }
|
||
|
||
hintEl.textContent = '';
|
||
saveBtn.disabled = true;
|
||
|
||
const body = {
|
||
title,
|
||
description: desc,
|
||
imageData: formImgData || null,
|
||
scheduledAt: dtVal ? dtVal + ':00' : null
|
||
};
|
||
|
||
try {
|
||
const url = editingDateId ? '/dating/dates/' + editingDateId : '/dating/dates';
|
||
const method = editingDateId ? 'PUT' : 'POST';
|
||
const res = await fetch(url, {
|
||
method,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body)
|
||
});
|
||
if (res.status === 422) {
|
||
const data = await res.json().catch(() => ({}));
|
||
hintEl.textContent = data.message || 'Limit erreicht.';
|
||
return;
|
||
}
|
||
if (!res.ok) { hintEl.textContent = 'Fehler beim Speichern.'; return; }
|
||
closeDateFormBtn();
|
||
datesLoaded = false;
|
||
loadDates();
|
||
} catch {
|
||
hintEl.textContent = 'Fehler beim Speichern.';
|
||
} finally {
|
||
saveBtn.disabled = false;
|
||
}
|
||
};
|
||
|
||
// ── Profil-Mini-Dialog ────────────────────────────────────────────────────
|
||
let profileDialogCreatorId = null;
|
||
|
||
window.openProfileDialog = function() {
|
||
if (!currentDate) return;
|
||
loadAndOpenProfile(currentDate.creatorId, currentDate.creatorName, currentDate.creatorProfilePicture);
|
||
};
|
||
|
||
function renderVorliebenChips(container, chips) {
|
||
container.innerHTML = chips.map(c =>
|
||
`<span class="dp-vorliebe-chip bw-${c.bewertung}">${escHtml(c.name)}</span>`
|
||
).join('');
|
||
}
|
||
|
||
window.loadAndOpenProfile = async function(userId, name, picture) {
|
||
profileDialogCreatorId = userId;
|
||
|
||
pdImages = []; pdImageIdx = 0;
|
||
renderPopupImage('pd', pdImages, pdImageIdx);
|
||
document.getElementById('pdName').textContent = name || '';
|
||
document.getElementById('pdProfileLink').href = '/community/benutzer.html?userId=' + userId;
|
||
document.getElementById('pdTags').innerHTML = '';
|
||
document.getElementById('pdVorlieben').innerHTML = '';
|
||
document.getElementById('pdDesc').style.display = 'none';
|
||
document.getElementById('profileDialogBg').classList.add('open');
|
||
|
||
// Weitere Details nachladen
|
||
try {
|
||
const [profRes, vlRes, galleryRes] = await Promise.all([
|
||
fetch('/social/users/' + userId),
|
||
fetch('/dating/vorlieben/' + userId),
|
||
fetch('/social/profile-images?userId=' + userId)
|
||
]);
|
||
if (profRes.ok) {
|
||
const p = await profRes.json();
|
||
const tags = document.getElementById('pdTags');
|
||
const addTag = v => {
|
||
if (!v) return;
|
||
const s = document.createElement('span');
|
||
s.className = 'dp-tag';
|
||
s.textContent = v;
|
||
tags.appendChild(s);
|
||
};
|
||
if (p.alter) addTag(p.alter + ' J.');
|
||
if (p.geschlecht) addTag({ WEIBLICH:'weiblich', MAENNLICH:'männlich', DIVERS:'divers' }[p.geschlecht] || p.geschlecht);
|
||
if (p.neigung) addTag({ DEVOT:'devot', EHER_DEVOT:'eher devot', SWITCHER:'Switcher', EHER_DOMINANT:'eher dominant', DOMINANT:'dominant' }[p.neigung] || p.neigung);
|
||
if (p.beziehungsstatus) addTag({ SINGLE:'single', IN_EINER_BEZIEHUNG:'in einer Beziehung', VERHEIRATET:'verheiratet', IN_EINER_OFFENEN_BEZIEHUNG:'offene Beziehung', IN_EINER_OFFENEN_EHE:'offene Ehe' }[p.beziehungsstatus] || p.beziehungsstatus);
|
||
const descEl = document.getElementById('pdDesc');
|
||
if (p.beschreibung) { descEl.textContent = p.beschreibung; descEl.style.display = ''; }
|
||
const hqPic = p.profilePictureHq || p.profilePicture;
|
||
if (hqPic) pdImages.push('data:image/png;base64,' + hqPic);
|
||
}
|
||
if (galleryRes.ok) {
|
||
const gallery = await galleryRes.json();
|
||
gallery.forEach(img => { if (img.imageData) pdImages.push('data:image/jpeg;base64,' + img.imageData); });
|
||
}
|
||
renderPopupImage('pd', pdImages, pdImageIdx);
|
||
if (vlRes.ok) {
|
||
const chips = await vlRes.json();
|
||
renderVorliebenChips(document.getElementById('pdVorlieben'), chips);
|
||
}
|
||
} catch {}
|
||
};
|
||
|
||
window.closeProfileDialog = function(e) {
|
||
if (e && e.target !== document.getElementById('profileDialogBg')) return;
|
||
document.getElementById('profileDialogBg').classList.remove('open');
|
||
profileDialogCreatorId = null;
|
||
};
|
||
window.closeProfileDialogBtn = function() {
|
||
document.getElementById('profileDialogBg').classList.remove('open');
|
||
profileDialogCreatorId = null;
|
||
};
|
||
|
||
// ── Tastatur-Steuerung ────────────────────────────────────────────────────
|
||
document.addEventListener('keydown', e => {
|
||
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
||
const dir = e.key === 'ArrowLeft' ? -1 : 1;
|
||
if (document.getElementById('profileDialogBg').classList.contains('open')) {
|
||
pdNavStep(dir); return;
|
||
}
|
||
if (document.getElementById('discoveryPopupBg').classList.contains('open')) {
|
||
dpNavStep(dir); return;
|
||
}
|
||
// Pfeiltasten: Bilder-Navigation in der Match-Karte
|
||
if (document.getElementById('discoveryCard').style.display !== 'none' &&
|
||
!document.getElementById('filterDrawer').classList.contains('open')) {
|
||
discNavStep(dir, { stopPropagation: () => {} });
|
||
return;
|
||
}
|
||
}
|
||
// Escape schließt Dialoge (höchster z-index zuerst)
|
||
if (e.key !== 'Escape') return;
|
||
if (document.getElementById('profileDialogBg').classList.contains('open')) { closeProfileDialogBtn(); return; }
|
||
if (document.getElementById('matchOverlay').classList.contains('open')) { closeMatchOverlay(); return; }
|
||
if (document.getElementById('dateFormBg').classList.contains('open')) { closeDateFormBtn(); return; }
|
||
if (document.getElementById('dateBg').classList.contains('open')) { closeDateDialogBtn(); return; }
|
||
if (document.getElementById('myDateBg').classList.contains('open')) { closeMyDateDialogBtn(); return; }
|
||
if (document.getElementById('discoveryPopupBg').classList.contains('open')) { closeDiscoveryPopup(null); return; }
|
||
if (document.getElementById('filterDrawer').classList.contains('open')) { closeFilter(); return; }
|
||
});
|
||
|
||
// ── Datum formatieren ─────────────────────────────────────────────────────
|
||
function formatDateTime(isoStr) {
|
||
if (!isoStr) return '';
|
||
try {
|
||
const d = new Date(isoStr);
|
||
return d.toLocaleString('de-DE', { day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit' });
|
||
} catch { return isoStr; }
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|