new listing layout

This commit is contained in:
2026-02-01 11:28:08 +01:00
parent 43a905c027
commit c58296e920
3 changed files with 147 additions and 106 deletions

View File

@@ -158,7 +158,7 @@
/* Listings Grid - responsive columns */
.listings-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(5, 1fr);
gap: var(--space-md);
}

View File

@@ -129,28 +129,29 @@ class PageListing extends HTMLElement {
this.innerHTML = /* html */`
<article class="listing-detail">
<!-- Gallery - Full Width -->
<div class="listing-gallery">
<div class="listing-image-main" id="main-image">
${firstImage
? `<img src="${firstImage}" alt="${this.escapeHtml(this.listing.title)}" id="main-img">`
: this.getPlaceholderSvg()}
</div>
${images.length > 1 ? `
<div class="listing-thumbnails">
${images.map((img, i) => `
<button class="thumbnail ${i === 0 ? 'active' : ''}" data-index="${i}">
<img src="${this.getImageUrl(img, 150)}" alt="">
</button>
`).join('')}
<!-- Two Column Layout -->
<div class="listing-layout">
<!-- Left Column: Gallery, Header, Description, More Listings -->
<div class="listing-main">
<!-- Gallery -->
<div class="listing-gallery">
<div class="listing-image-main" id="main-image">
${firstImage
? `<img src="${firstImage}" alt="${this.escapeHtml(this.listing.title)}" id="main-img">`
: this.getPlaceholderSvg()}
</div>
${images.length > 1 ? `
<div class="listing-thumbnails">
${images.map((img, i) => `
<button class="thumbnail ${i === 0 ? 'active' : ''}" data-index="${i}">
<img src="${this.getImageUrl(img, 150)}" alt="">
</button>
`).join('')}
</div>
` : ''}
</div>
` : ''}
</div>
<!-- Content Row: Info + Actions Sidebar -->
<div class="listing-content">
<!-- Main Info -->
<div class="listing-info">
<!-- Header -->
<header class="listing-header">
${categoryName ? `<a href="#/search?category=${this.listing.category?.slug}" class="badge badge-primary">${this.escapeHtml(categoryName)}</a>` : ''}
<h1>${this.escapeHtml(this.listing.title)}</h1>
@@ -161,12 +162,46 @@ class PageListing extends HTMLElement {
<span class="meta-item meta-date">${t('listing.postedOn')} ${createdDate}</span>
</div>
</header>
<!-- Sidebar Mobile (shown only on mobile, between header and description) -->
<div class="sidebar-mobile">
${this.renderSidebar()}
</div>
<!-- Description -->
<section class="listing-description">
<h2>${t('listing.description')}</h2>
<div class="description-text">${this.formatDescription(this.listing.description)}</div>
</section>
<!-- Location Mobile (shown only on mobile) -->
${this.listing.location ? `
<section class="listing-location-section location-mobile">
<h2>${t('listing.location')}</h2>
<location-map
name="${this.escapeHtml(this.listing.location.name || '')}"
postal-code="${this.escapeHtml(this.listing.location.postal_code || '')}"
country="${this.escapeHtml(this.listing.location.country || '')}"
></location-map>
</section>
` : ''}
<!-- Seller's Other Listings -->
${this.sellerListings.length > 0 ? `
<section class="seller-listings">
<h2>${t('listing.moreFromSeller')}</h2>
<div class="listings-grid">
${this.sellerListings.map(listing => this.renderListingCard(listing)).join('')}
</div>
</section>
` : ''}
</div>
<!-- Right Column: Sidebar, Location (Desktop only) -->
<aside class="listing-sidebar sidebar-desktop">
${this.renderSidebar()}
<!-- Location Desktop -->
${this.listing.location ? `
<section class="listing-location-section">
<h2>${t('listing.location')}</h2>
@@ -177,73 +212,64 @@ class PageListing extends HTMLElement {
></location-map>
</section>
` : ''}
</div>
<!-- Actions Sidebar -->
<aside class="listing-sidebar">
<div class="sidebar-card">
<button class="btn btn-primary btn-lg sidebar-btn" id="contact-btn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
${t('listing.contactSeller')}
</button>
<div class="sidebar-actions">
<button class="action-btn ${this.isFavorite ? 'active' : ''}" id="favorite-btn" title="${t('home.addFavorite')}">
${this.getFavoriteIcon()}
<span>${this.isFavorite ? t('home.removeFavorite') : t('home.addFavorite')}</span>
</button>
<button class="action-btn" id="share-btn" title="${t('listing.share') || 'Teilen'}">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="18" cy="5" r="3"></circle>
<circle cx="6" cy="12" r="3"></circle>
<circle cx="18" cy="19" r="3"></circle>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
</svg>
<span>${t('listing.share') || 'Teilen'}</span>
</button>
<button class="action-btn" id="report-btn" title="${t('listing.report') || 'Melden'}">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path>
<line x1="4" y1="22" x2="4" y2="15"></line>
</svg>
<span>${t('listing.report') || 'Melden'}</span>
</button>
</div>
</div>
<!-- Seller Card -->
<div class="sidebar-card seller-card">
<div class="seller-header">
<div class="seller-avatar">?</div>
<div class="seller-info">
<strong>${t('listing.anonymousSeller')}</strong>
<span>${t('listing.memberSince')} 2024</span>
</div>
</div>
</div>
</aside>
</div>
<!-- Seller's Other Listings -->
${this.sellerListings.length > 0 ? `
<section class="seller-listings">
<h2>${t('listing.moreFromSeller') || 'Weitere Anzeigen des Anbieters'}</h2>
<div class="listings-grid">
${this.sellerListings.map(listing => this.renderListingCard(listing)).join('')}
</div>
</section>
` : ''}
</article>
${this.renderContactDialog()}
`
}
renderSidebar() {
return /* html */`
<div class="sidebar-card">
<button class="btn btn-primary btn-lg sidebar-btn" id="contact-btn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
${t('listing.contactSeller')}
</button>
<div class="sidebar-actions">
<button class="action-btn ${this.isFavorite ? 'active' : ''}" id="favorite-btn" title="${t('home.addFavorite')}">
${this.getFavoriteIcon()}
<span>${this.isFavorite ? t('home.removeFavorite') : t('home.addFavorite')}</span>
</button>
<button class="action-btn" id="share-btn" title="${t('listing.share')}">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="18" cy="5" r="3"></circle>
<circle cx="6" cy="12" r="3"></circle>
<circle cx="18" cy="19" r="3"></circle>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
</svg>
<span>${t('listing.share')}</span>
</button>
<button class="action-btn" id="report-btn" title="${t('listing.report')}">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path>
<line x1="4" y1="22" x2="4" y2="15"></line>
</svg>
<span>${t('listing.report')}</span>
</button>
</div>
</div>
<!-- Seller Card -->
<div class="sidebar-card seller-card">
<div class="seller-header">
<div class="seller-avatar">?</div>
<div class="seller-info">
<strong>${t('listing.anonymousSeller')}</strong>
<span>${t('listing.memberSince')} 2024</span>
</div>
</div>
</div>
`
}
renderListingCard(listing) {
const imageId = listing.images?.[0]?.directus_files_id?.id || listing.images?.[0]?.directus_files_id
const imageUrl = imageId ? directus.getThumbnailUrl(imageId, 300) : ''
@@ -500,22 +526,43 @@ style.textContent = /* css */`
object-fit: cover;
}
/* Content Row */
page-listing .listing-content {
/* Two Column Layout */
page-listing .listing-layout {
display: grid;
grid-template-columns: 1fr 320px;
gap: var(--space-xl);
align-items: start;
}
page-listing .listing-main {
min-width: 0;
}
/* Mobile: Hide desktop sidebar, show mobile elements */
page-listing .sidebar-mobile {
display: none;
}
page-listing .location-mobile {
display: none;
}
@media (max-width: 768px) {
page-listing .listing-content {
page-listing .listing-layout {
grid-template-columns: 1fr;
}
page-listing .listing-sidebar {
position: static;
order: -1;
page-listing .listing-sidebar.sidebar-desktop {
display: none;
}
page-listing .sidebar-mobile {
display: flex;
margin-bottom: var(--space-xl);
}
page-listing .location-mobile {
display: block;
}
page-listing .sidebar-card {
@@ -523,11 +570,6 @@ style.textContent = /* css */`
}
}
/* Main Info */
page-listing .listing-info {
min-width: 0;
}
page-listing .listing-header {
margin-bottom: var(--space-xl);
}
@@ -581,17 +623,16 @@ style.textContent = /* css */`
/* Sidebar */
page-listing .listing-sidebar {
position: sticky;
top: var(--space-lg);
display: flex;
flex-direction: column;
gap: var(--space-md);
position: sticky;
top: var(--space-lg);
}
@media (max-width: 768px) {
page-listing .listing-sidebar {
position: static;
}
page-listing .sidebar-mobile {
flex-direction: column;
gap: var(--space-md);
}
page-listing .sidebar-card {

View File

@@ -1,7 +1,7 @@
{
"name": "dgray.io - Marktplatz",
"name": "dgray.io - marketplace",
"short_name": "dgray.io",
"description": "Anonymer Marktplatz mit Monero-Bezahlung",
"description": "Anonymous marketplace with Monero payment",
"start_url": "/",
"display": "standalone",
"background_color": "#FAFAFA",
@@ -56,14 +56,14 @@
"categories": ["shopping", "lifestyle"],
"shortcuts": [
{
"name": "Anzeige erstellen",
"short_name": "Erstellen",
"name": "create ad",
"short_name": "create",
"url": "/#/create",
"icons": [{ "src": "assets/icons/icon-96.png", "sizes": "96x96" }]
},
{
"name": "Suchen",
"short_name": "Suche",
"name": "search",
"short_name": "search",
"url": "/#/search",
"icons": [{ "src": "assets/icons/icon-96.png", "sizes": "96x96" }]
}