Phase 2: Detailansicht

Modal, das sich bei Marker- oder Listen-Auswahl (state.selectedPlaceId)
oeffnet: Titelbild/Platzhalter, vollstaendige Beschreibung, Adresse,
Mini-Karte mit Positionsmarker, Oeffnungszeiten, Kosten, Altersempfehlung,
Ausstattung (inkl. klar markierter negativer Werte), Hinweise fuer
Familien, Website-Link. Navigation-Button zu OSM Directions. Loeschen ist
bereits voll funktional (mit Bestaetigung), Bearbeiten ist als Platzhalter
fuer Phase 3 markiert.

Neuer Helper isSafeHttpUrl() in dom.js: nur http/https als Link- oder
Bildquelle zulaessig, schuetzt vor javascript:-URLs in Fremddaten
(Vorbereitung fuer KI-JSON-Import). Rendering weiterhin ausschliesslich
ueber textContent/el(), kein innerHTML mit Place-Daten.
This commit is contained in:
2026-07-24 11:32:32 +02:00
parent ee55fb1cf9
commit 2812e03530
6 changed files with 707 additions and 1 deletions
+219
View File
@@ -0,0 +1,219 @@
/* Detailansicht (Modal): Vollbild auf Mobile, zentriertes Panel ab 1024px
(siehe Breakpoint in css/responsive.css). Struktur wird von js/ui/detail.js
in #detail-modal eingehängt. */
.detail-modal {
position: fixed;
inset: 0;
z-index: 2000;
}
.detail-modal[hidden] {
display: none;
}
.detail-modal__backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.55);
}
.detail-modal__panel {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background: var(--color-surface);
overflow-y: auto;
}
.detail-modal__close {
position: absolute;
top: var(--space-sm);
right: var(--space-sm);
z-index: 1;
width: 36px;
height: 36px;
border: none;
border-radius: 50%;
background: rgba(0, 0, 0, 0.55);
color: #fff;
font-size: 1rem;
line-height: 1;
cursor: pointer;
}
.detail-image {
width: 100%;
height: 200px;
flex-shrink: 0;
background: var(--color-border);
}
.detail-image__img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.detail-image__placeholder {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-xs);
color: #fff;
}
.detail-image__placeholder-icon {
font-size: 2.5rem;
}
.detail-image__placeholder-text {
font-size: 0.85rem;
}
.detail-body {
padding: var(--space-lg);
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.detail-name {
font-size: 1.3rem;
font-weight: 700;
}
.detail-category {
align-self: flex-start;
color: #fff;
font-size: 0.8rem;
font-weight: 600;
padding: 2px var(--space-sm);
border-radius: 999px;
}
.detail-section__label {
font-size: 0.85rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.02em;
color: var(--color-text-muted);
margin-bottom: var(--space-xs);
}
.detail-description {
line-height: 1.5;
white-space: pre-line;
}
.detail-address {
font-style: normal;
line-height: 1.4;
}
.detail-value {
line-height: 1.4;
}
.detail-value--muted {
color: var(--color-text-muted);
}
.detail-minimap {
width: 100%;
height: 220px;
border-radius: var(--radius-md);
overflow: hidden;
background: var(--color-border);
}
.detail-minimap__note {
color: var(--color-text-muted);
font-size: 0.875rem;
}
.detail-facilities {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.detail-facility {
display: flex;
align-items: center;
gap: var(--space-sm);
font-size: 0.9rem;
}
.detail-facility--no {
color: var(--color-text-muted);
opacity: 0.7;
}
.detail-facility--no .detail-facility__icon {
filter: grayscale(1);
}
.detail-facility--no .detail-facility__label {
text-decoration: line-through;
}
.detail-facility__state {
font-size: 0.75rem;
color: var(--color-text-muted);
}
.detail-family-notes {
display: flex;
flex-direction: column;
gap: var(--space-xs);
margin-bottom: var(--space-sm);
line-height: 1.4;
}
.detail-tags {
display: flex;
flex-wrap: wrap;
gap: var(--space-xs);
}
.detail-tag {
background: var(--color-background);
border: 1px solid var(--color-border);
border-radius: 999px;
padding: 2px var(--space-sm);
font-size: 0.75rem;
color: var(--color-text-muted);
}
.detail-website-link {
color: var(--color-primary-dark);
word-break: break-word;
}
.detail-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-sm);
padding-top: var(--space-sm);
border-top: 1px solid var(--color-border);
}
@media (min-width: 1024px) {
.detail-modal__panel {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: min(720px, 90vw);
height: min(85vh, 900px);
border-radius: var(--radius-md);
box-shadow: var(--shadow-card);
}
}