mobile ui

This commit is contained in:
2025-11-26 11:17:12 +01:00
parent d5486417e2
commit 8a1f078435
5 changed files with 668 additions and 402 deletions

View File

@ -10,275 +10,128 @@ body {
overflow: hidden; overflow: hidden;
background: #1a1a1a; background: #1a1a1a;
color: #e0e0e0; color: #e0e0e0;
-webkit-tap-highlight-color: transparent;
} }
.container { .container {
display: grid;
grid-template-columns: 380px 1fr;
grid-template-rows: auto 1fr;
height: calc(100vh - 60px);
}
.search-bar {
grid-column: 1 / -1;
background: #242424;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
display: grid;
grid-template-columns: 1fr auto auto auto auto;
gap: 12px;
align-items: end;
}
.form-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px; height: calc(100vh - 45px);
} }
.form-group label { /* Compact Search Bar (Mobile First) */
font-size: 12px; .search-bar {
font-weight: 600; background: #242424;
color: #b0b0b0; padding: 10px;
text-transform: uppercase; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
letter-spacing: 0.5px; z-index: 1000;
display: flex;
flex-direction: column;
gap: 8px;
} }
.search-bar input, .search-bar select { .search-compact {
padding: 12px; display: flex;
flex-direction: column;
gap: 8px;
}
.search-compact input[type="text"] {
width: 100%;
padding: 10px;
border: 1px solid #3a3a3a; border: 1px solid #3a3a3a;
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
background: #2a2a2a; background: #2a2a2a;
color: #e0e0e0; color: #e0e0e0;
transition: all 0.2s;
} }
.search-bar input:focus, .search-bar select:focus { .search-row-compact {
outline: none; display: grid;
border-color: #0ea5e9; grid-template-columns: 1fr 1fr 70px;
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1); gap: 6px;
} }
.price-inputs { .search-row-compact input,
display: flex; .search-row-compact select {
gap: 8px; padding: 8px 6px;
align-items: center; border: 1px solid #3a3a3a;
border-radius: 6px;
font-size: 12px;
background: #2a2a2a;
color: #e0e0e0;
min-width: 0;
} }
.price-inputs input { .search-actions-compact {
width: 120px; display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
} }
.price-separator { .search-actions-compact button {
color: #666; padding: 10px 16px;
font-weight: 600;
padding: 0 4px;
}
.search-bar button {
padding: 12px 24px;
background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%); background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);
color: white; color: white;
border: none; border: none;
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 13px;
transition: all 0.2s; white-space: nowrap;
box-shadow: 0 2px 8px rgba(14, 165, 233, 0.3);
} }
.search-bar button:hover:not(:disabled) { .search-actions-compact button:disabled {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.4);
}
.search-bar button:disabled {
background: #3a3a3a; background: #3a3a3a;
cursor: not-allowed; cursor: not-allowed;
box-shadow: none;
} }
.search-bar button.cancel { .search-actions-compact button.cancel {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
} }
.search-bar button.cancel:hover { /* Mobile Progress Bar */
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4); .progress-info-mobile {
} background: rgba(14, 165, 233, 0.15);
padding: 6px 8px;
.results-panel {
background: #1e1e1e;
overflow-y: auto;
border-right: 1px solid #2a2a2a;
}
.results-panel::-webkit-scrollbar {
width: 8px;
}
.results-panel::-webkit-scrollbar-track {
background: #1a1a1a;
}
.results-panel::-webkit-scrollbar-thumb {
background: #3a3a3a;
border-radius: 4px;
}
.results-panel::-webkit-scrollbar-thumb:hover {
background: #4a4a4a;
}
.results-header {
background: #242424;
padding: 20px;
border-bottom: 1px solid #2a2a2a;
position: sticky;
top: 0;
z-index: 10;
}
.results-count {
font-weight: 700;
color: #e0e0e0;
margin-bottom: 12px;
font-size: 16px;
}
.progress-info {
background: rgba(14, 165, 233, 0.1);
padding: 14px;
border-radius: 6px;
margin-bottom: 12px;
display: none; display: none;
border: 1px solid rgba(14, 165, 233, 0.2); border-bottom: 1px solid rgba(14, 165, 233, 0.3);
} }
.progress-info.active { .progress-info-mobile.active {
display: block; display: block;
} }
.progress-bar { .progress-text-mobile {
width: 100%; font-size: 11px;
height: 6px; color: #0ea5e9;
background: rgba(255, 255, 255, 0.1); font-weight: 600;
border-radius: 3px; margin-bottom: 4px;
overflow: hidden;
margin-top: 10px;
} }
.progress-fill { .progress-bar-mobile {
height: 3px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
overflow: hidden;
}
.progress-fill-mobile {
height: 100%; height: 100%;
background: linear-gradient(90deg, #0ea5e9 0%, #06b6d4 100%); background: linear-gradient(90deg, #0ea5e9 0%, #06b6d4 100%);
width: 0%; width: 0%;
transition: width 0.3s; transition: width 0.3s;
box-shadow: 0 0 10px rgba(14, 165, 233, 0.5);
} }
.progress-text { /* Desktop Results Panel - Hidden on Mobile */
font-size: 13px; .results-panel {
color: #0ea5e9; display: none;
margin-bottom: 6px;
font-weight: 600;
}
.eta-text {
font-size: 11px;
color: #888;
margin-top: 6px;
}
.sort-control {
display: flex;
gap: 8px;
align-items: center;
}
.sort-control label {
font-size: 12px;
color: #888;
font-weight: 600;
}
.sort-control select {
padding: 8px 12px;
border: 1px solid #3a3a3a;
border-radius: 6px;
font-size: 13px;
background: #2a2a2a;
color: #e0e0e0;
}
.result-item {
background: #242424;
margin: 12px;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: all 0.2s;
border: 1px solid #2a2a2a;
}
.result-item:hover {
border-color: #0ea5e9;
box-shadow: 0 4px 16px rgba(14, 165, 233, 0.2);
transform: translateY(-2px);
}
.result-item.selected {
border-color: #0ea5e9;
box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.3);
}
.result-image {
width: 100%;
height: 180px;
object-fit: cover;
background: #1a1a1a;
}
.result-content {
padding: 14px;
}
.result-title {
font-weight: 600;
color: #e0e0e0;
margin-bottom: 10px;
font-size: 14px;
line-height: 1.4;
}
.result-price {
color: #0ea5e9;
font-weight: 700;
font-size: 20px;
margin-bottom: 10px;
}
.result-meta {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #888;
}
.result-location {
display: flex;
align-items: center;
gap: 4px;
}
.result-date {
font-style: italic;
} }
/* Map Container */
.map-container { .map-container {
flex: 1;
position: relative; position: relative;
height: 100%;
background: #1a1a1a; background: #1a1a1a;
} }
@ -290,16 +143,18 @@ body {
.status-bar { .status-bar {
position: absolute; position: absolute;
top: 20px; top: 10px;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
background: #242424; background: #242424;
padding: 14px 24px; padding: 8px 12px;
border-radius: 8px; border-radius: 6px;
box-shadow: 0 4px 16px rgba(0,0,0,0.4); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
z-index: 1000; z-index: 999;
display: none; display: none;
border: 1px solid #3a3a3a; border: 1px solid #3a3a3a;
max-width: 90%;
font-size: 12px;
} }
.status-bar.visible { .status-bar.visible {
@ -324,64 +179,157 @@ body {
border-color: rgba(239, 68, 68, 0.3); border-color: rgba(239, 68, 68, 0.3);
} }
.no-results {
text-align: center;
padding: 60px 20px;
color: #666;
font-size: 14px;
}
.loading-spinner { .loading-spinner {
display: inline-block; display: inline-block;
width: 14px; width: 12px;
height: 14px; height: 12px;
border: 2px solid #0ea5e9; border: 2px solid #0ea5e9;
border-radius: 50%; border-radius: 50%;
border-top-color: transparent; border-top-color: transparent;
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
margin-right: 10px; margin-right: 8px;
} }
@keyframes spin { @keyframes spin {
to { transform: rotate(360deg); } to {
transform: rotate(360deg);
}
} }
/* Mobile Fullscreen Popup */
.mobile-popup {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #1a1a1a;
z-index: 10000;
overflow-y: auto;
}
.mobile-popup.show {
display: flex;
flex-direction: column;
}
.mobile-popup-header {
background: #242424;
padding: 12px;
display: flex;
justify-content: flex-end;
border-bottom: 1px solid #3a3a3a;
}
.mobile-popup-close {
background: none;
border: none;
color: #888;
font-size: 28px;
cursor: pointer;
padding: 0;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.mobile-popup-content {
flex: 1;
padding: 16px;
overflow-y: auto;
}
.popup-image {
width: 100%;
height: 250px;
object-fit: cover;
border-radius: 8px;
margin-bottom: 16px;
}
.popup-title {
font-size: 20px;
font-weight: 700;
color: #e0e0e0;
margin-bottom: 12px;
line-height: 1.4;
}
.popup-price {
font-size: 28px;
font-weight: 700;
color: #0ea5e9;
margin-bottom: 16px;
}
.popup-meta {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 20px;
}
.popup-meta-item {
display: flex;
align-items: center;
gap: 8px;
color: #ccc;
font-size: 14px;
}
.popup-meta-icon {
font-size: 18px;
}
.popup-link {
display: block;
padding: 14px;
background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);
color: white;
text-align: center;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
font-size: 16px;
}
/* Footer */
.footer { .footer {
background: #242424; background: #242424;
border-top: 1px solid #2a2a2a; border-top: 1px solid #2a2a2a;
padding: 15px 20px; padding: 10px 12px;
height: 60px; height: 45px;
display: flex;
align-items: center;
} }
.footer-content { .footer-content {
display: flex; display: flex;
justify-content: space-between; justify-content: center;
align-items: center; align-items: center;
max-width: 1400px; width: 100%;
margin: 0 auto; font-size: 11px;
} }
.footer-links { .footer-links {
display: flex; display: flex;
gap: 20px; gap: 12px;
} }
.footer-links a { .footer-links a {
color: #888; color: #888;
text-decoration: none; text-decoration: none;
font-size: 13px; font-size: 11px;
transition: color 0.2s;
}
.footer-links a:hover {
color: #0ea5e9;
} }
.footer-info { .footer-info {
color: #666; display: none;
font-size: 12px;
} }
/* Privacy Modal */
.modal { .modal {
display: none; display: none;
position: fixed; position: fixed;
@ -391,7 +339,7 @@ body {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
background-color: rgba(0,0,0,0.8); background-color: rgba(0, 0, 0, 0.85);
} }
.modal.show { .modal.show {
@ -405,7 +353,7 @@ body {
border: 1px solid #3a3a3a; border: 1px solid #3a3a3a;
border-radius: 8px; border-radius: 8px;
width: 90%; width: 90%;
max-width: 800px; max-width: 600px;
max-height: 80vh; max-height: 80vh;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
@ -414,60 +362,48 @@ body {
.modal-content h2 { .modal-content h2 {
background: #2a2a2a; background: #2a2a2a;
padding: 20px; padding: 16px;
margin: 0; margin: 0;
color: #e0e0e0; color: #e0e0e0;
border-bottom: 1px solid #3a3a3a; border-bottom: 1px solid #3a3a3a;
font-size: 18px;
} }
.modal-body { .modal-body {
padding: 20px; padding: 16px;
overflow-y: auto; overflow-y: auto;
flex: 1; flex: 1;
} font-size: 13px;
line-height: 1.6;
.modal-body::-webkit-scrollbar {
width: 8px;
}
.modal-body::-webkit-scrollbar-track {
background: #1a1a1a;
}
.modal-body::-webkit-scrollbar-thumb {
background: #3a3a3a;
border-radius: 4px;
} }
.modal-body h3 { .modal-body h3 {
color: #0ea5e9; color: #0ea5e9;
margin-top: 20px; margin-top: 16px;
margin-bottom: 10px; margin-bottom: 8px;
font-size: 18px; font-size: 16px;
} }
.modal-body h4 { .modal-body h4 {
color: #888; color: #888;
margin-top: 15px; margin-top: 12px;
margin-bottom: 8px; margin-bottom: 6px;
font-size: 14px; font-size: 13px;
} }
.modal-body p { .modal-body p {
margin-bottom: 10px; margin-bottom: 10px;
line-height: 1.6;
color: #ccc; color: #ccc;
} }
.modal-body ul { .modal-body ul {
margin-left: 20px; margin-left: 20px;
margin-bottom: 15px; margin-bottom: 12px;
} }
.modal-body li { .modal-body li {
margin-bottom: 8px; margin-bottom: 6px;
color: #ccc; color: #ccc;
line-height: 1.6;
} }
.modal-body a { .modal-body a {
@ -475,52 +411,290 @@ body {
text-decoration: none; text-decoration: none;
} }
.modal-body a:hover {
text-decoration: underline;
}
.modal-close { .modal-close {
color: #888; color: #888;
position: absolute; position: absolute;
right: 20px; right: 16px;
top: 20px; top: 16px;
font-size: 28px; font-size: 24px;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
transition: color 0.2s;
} }
.modal-close:hover, /* Desktop Styles */
.modal-close:focus { @media (min-width: 768px) {
color: #fff;
}
@media (max-width: 1024px) {
.container { .container {
grid-template-columns: 320px 1fr; display: grid;
grid-template-columns: 380px 1fr;
grid-template-rows: auto 1fr;
height: calc(100vh - 50px);
} }
.search-bar { .search-bar {
grid-template-columns: 1fr; grid-column: 1 / -1;
padding: 16px;
gap: 12px;
flex-direction: row;
align-items: end;
} }
.price-inputs input { .search-compact {
width: 100px; flex-direction: row;
} flex: 0 0 auto;
} max-width: 800px;
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
grid-template-rows: auto auto 1fr;
} }
.search-compact input[type="text"] {
width: 250px;
padding: 12px;
}
.search-row-compact {
width: auto;
display: flex;
gap: 8px;
}
.search-row-compact input {
width: 110px;
padding: 12px;
font-size: 14px;
}
.search-row-compact select {
width: 90px;
padding: 12px;
font-size: 14px;
}
.search-actions-compact {
flex-direction: row;
flex: 0 0 auto;
}
.search-actions-compact button {
padding: 12px 24px;
font-size: 14px;
width: auto;
}
/* Hide mobile progress */
.progress-info-mobile {
display: none !important;
}
/* Show desktop results panel */
.results-panel { .results-panel {
max-height: 300px; display: flex;
flex-direction: column;
background: #1e1e1e;
overflow-y: auto;
border-right: 1px solid #2a2a2a;
}
.results-panel::-webkit-scrollbar {
width: 8px;
}
.results-panel::-webkit-scrollbar-track {
background: #1a1a1a;
}
.results-panel::-webkit-scrollbar-thumb {
background: #3a3a3a;
border-radius: 4px;
}
.results-header {
background: #242424;
padding: 16px;
border-bottom: 1px solid #2a2a2a;
position: sticky;
top: 0;
z-index: 10;
}
.results-count {
font-weight: 700;
color: #e0e0e0;
margin-bottom: 12px;
font-size: 15px;
}
.progress-info {
background: rgba(14, 165, 233, 0.1);
padding: 12px;
border-radius: 6px;
margin-bottom: 12px;
display: none;
border: 1px solid rgba(14, 165, 233, 0.2);
}
.progress-info.active {
display: block;
}
.progress-bar {
width: 100%;
height: 5px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
overflow: hidden;
margin-top: 8px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #0ea5e9 0%, #06b6d4 100%);
width: 0%;
transition: width 0.3s;
}
.progress-text {
font-size: 12px;
color: #0ea5e9;
font-weight: 600;
margin-bottom: 4px;
}
.eta-text {
font-size: 11px;
color: #888;
margin-top: 4px;
}
.sort-control {
display: flex;
gap: 8px;
align-items: center;
}
.sort-control label {
font-size: 12px;
color: #888;
font-weight: 600;
}
.sort-control select {
flex: 1;
padding: 8px 12px;
border: 1px solid #3a3a3a;
border-radius: 6px;
font-size: 13px;
background: #2a2a2a;
color: #e0e0e0;
}
.result-item {
background: #242424;
margin: 10px;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: all 0.2s;
border: 1px solid #2a2a2a;
}
.result-item:hover {
border-color: #0ea5e9;
box-shadow: 0 4px 12px rgba(14, 165, 233, 0.2);
transform: translateY(-2px);
}
.result-item.selected {
border-color: #0ea5e9;
box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.3);
}
.result-image {
width: 100%;
height: 160px;
object-fit: cover;
background: #1a1a1a;
}
.result-content {
padding: 12px;
}
.result-title {
font-weight: 600;
color: #e0e0e0;
margin-bottom: 8px;
font-size: 13px;
line-height: 1.4;
}
.result-price {
color: #0ea5e9;
font-weight: 700;
font-size: 18px;
margin-bottom: 8px;
}
.result-meta {
display: flex;
justify-content: space-between;
font-size: 11px;
color: #888;
}
.result-location {
display: flex;
align-items: center;
gap: 4px;
}
.result-date {
font-style: italic;
}
.no-results {
text-align: center;
padding: 40px 20px;
color: #666;
font-size: 13px;
}
.map-container {
grid-column: 2;
grid-row: 2;
}
/* Hide mobile popup on desktop */
.mobile-popup {
display: none !important;
}
.footer {
padding: 12px 20px;
height: 50px;
} }
.footer-content { .footer-content {
flex-direction: column; justify-content: space-between;
gap: 10px; max-width: 1400px;
margin: 0 auto;
font-size: 12px;
}
.footer-links {
gap: 16px;
}
.footer-links a {
font-size: 12px;
}
.footer-info {
display: block;
color: #666;
font-size: 12px;
}
.status-bar {
top: 20px;
padding: 12px 20px;
font-size: 13px;
} }
} }

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Kleinanzeigen Karten-Suche</title> <title>Kleinanzeigen Karten-Suche</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" />
<link rel="stylesheet" href="./css/style.css"> <link rel="stylesheet" href="./css/style.css">
@ -11,76 +11,84 @@
<body> <body>
<div class="container"> <div class="container">
<!-- Search Bar -->
<div class="search-bar"> <div class="search-bar">
<div class="form-group"> <div class="search-compact">
<label>Suchbegriff</label>
<input type="text" id="searchTerm" placeholder="z.B. Gravelbike"> <input type="text" id="searchTerm" placeholder="z.B. Gravelbike">
</div> <div class="search-row-compact">
<input type="number" id="minPrice" placeholder="Min €" value="" min="0" max="1000000000">
<div class="form-group"> <input type="number" id="maxPrice" placeholder="Max €" value="" min="0" max="1000000000">
<label>Preisspanne</label>
<div class="price-inputs">
<input type="number" id="minPrice" placeholder="0 €" value="0" min="0" max="1000000000">
<span class="price-separator"></span>
<input type="number" id="maxPrice" placeholder="∞ €" value="" min="0" max="1000000000">
</div>
</div>
<div class="form-group">
<label>Anzahl Inserate</label>
<select id="numListings"> <select id="numListings">
<option value="25" selected>25 Inserate</option> <option value="25" selected>25</option>
<option value="50">50 Inserate</option> <option value="50">50</option>
<option value="100">100 Inserate</option> <option value="100">100</option>
<option value="250">250 Inserate</option> <option value="250">250</option>
</select> </select>
</div> </div>
</div>
<div class="search-actions-compact">
<button id="searchBtn">Suchen</button> <button id="searchBtn">Suchen</button>
<button id="cancelBtn" class="cancel" style="display: none;">Abbrechen</button> <button id="cancelBtn" class="cancel" style="display: none;">Stop</button>
</div>
</div> </div>
<!-- Progress Bar (Mobile) -->
<div id="progressInfo" class="progress-info-mobile">
<div class="progress-text-mobile">Lädt...</div>
<div class="progress-bar-mobile">
<div class="progress-fill-mobile"></div>
</div>
</div>
<!-- Desktop Results Panel -->
<div class="results-panel"> <div class="results-panel">
<div class="results-header"> <div class="results-header">
<div class="results-count">Keine Ergebnisse</div> <div class="results-count">Keine Ergebnisse</div>
<div id="progressInfoDesktop" class="progress-info">
<div id="progressInfo" class="progress-info">
<div class="progress-text">Inserate werden geladen...</div> <div class="progress-text">Inserate werden geladen...</div>
<div class="progress-bar"> <div class="progress-bar">
<div class="progress-fill"></div> <div class="progress-fill"></div>
</div> </div>
<div class="eta-text"></div> <div class="eta-text"></div>
</div> </div>
<div class="sort-control"> <div class="sort-control">
<label>Sortierung:</label> <label>Sortierung:</label>
<select id="sortSelect"> <select id="sortSelect">
<option value="date-desc">Datum (neueste)</option> <option value="date-desc">Neueste</option>
<option value="date-asc">Datum (älteste)</option> <option value="date-asc">Älteste</option>
<option value="price-asc">Preis (niedrig → hoch)</option> <option value="price-asc">Preis </option>
<option value="price-desc">Preis (hoch → niedrig)</option> <option value="price-desc">Preis </option>
</select> </select>
</div> </div>
</div> </div>
<div id="resultsList"></div> <div id="resultsList"></div>
</div> </div>
<!-- Map Container -->
<div class="map-container"> <div class="map-container">
<div id="statusBar" class="status-bar"></div> <div id="statusBar" class="status-bar"></div>
<div id="map"></div> <div id="map"></div>
</div> </div>
</div> </div>
<!-- Mobile Fullscreen Popup -->
<div id="mobilePopup" class="mobile-popup">
<div class="mobile-popup-header">
<button class="mobile-popup-close" id="mobilePopupClose"></button>
</div>
<div class="mobile-popup-content" id="mobilePopupContent">
<!-- Content will be dynamically inserted -->
</div>
</div>
<footer class="footer"> <footer class="footer">
<div class="footer-content"> <div class="footer-content">
<div class="footer-links"> <div class="footer-links">
<a href="https://www.mosad.xyz/html/privacy.html" target="_blank">Impressum</a> <a href="https://www.mosad.xyz/html/privacy.html" target="_blank">Impressum</a>
<a href="#" id="privacyLink">Datenschutz</a> <a href="#" id="privacyLink">Datenschutz</a>
<a href="https://git.mosad.xyz/localhorst/kleinanzeigen-boosted" target="_blank">Source Code</a> <a href="https://git.mosad.xyz/localhorst/kleinanzeigen-boosted" target="_blank">Source</a>
</div>
<div class="footer-info">
kleinanzeigen.mosad.xyz © 2025
</div> </div>
<div class="footer-info">kleinanzeigen.mosad.xyz © 2025</div>
</div> </div>
</footer> </footer>
@ -96,51 +104,29 @@
<p>Diese Anwendung verarbeitet folgende Daten:</p> <p>Diese Anwendung verarbeitet folgende Daten:</p>
<ul> <ul>
<li><strong>Suchanfragen:</strong> Ihre Suchbegriffe und Filter werden temporär auf dem Server <li><strong>Suchanfragen:</strong> Ihre Suchbegriffe und Filter werden temporär auf dem Server
verarbeitet, um Ergebnisse von Kleinanzeigen.de abzurufen.</li> verarbeitet.</li>
<li><strong>Standortdaten:</strong> Wenn Sie die Standortfreigabe in Ihrem Browser erlauben, wird <li><strong>Standortdaten:</strong> Wenn Sie die Standortfreigabe erlauben, wird Ihre Position auf
Ihre Position verwendet, um sie auf der Karte anzuzeigen. Diese Daten werden nicht gespeichert der Karte angezeigt. Diese Daten werden nicht gespeichert.</li>
oder übertragen.</li> <li><strong>Technische Daten:</strong> IP-Adresse und Zeitstempel werden in Server-Logs gespeichert.
<li><strong>Technische Daten:</strong> IP-Adresse und Zeitstempel werden in Server-Logs zu </li>
Debugging-Zwecken gespeichert.</li>
</ul> </ul>
<h3>3. Externe Dienste</h3> <h3>3. Externe Dienste</h3>
<p>Diese Anwendung nutzt folgende externe Dienste:</p>
<h4>3.1 Kleinanzeigen.de</h4> <h4>3.1 Kleinanzeigen.de</h4>
<p>Die Anwendung ruft öffentlich verfügbare Inserate von kleinanzeigen.de ab. Es gelten die <p>Die Anwendung ruft öffentlich verfügbare Inserate von kleinanzeigen.de ab.</p>
Datenschutzbestimmungen von <a href="https://www.kleinanzeigen.de/datenschutzerklaerung/"
target="_blank">Kleinanzeigen.de</a>.</p>
<h4>3.2 OpenStreetMap (Leaflet.js v1.9.4)</h4> <h4>3.2 OpenStreetMap (Leaflet.js v1.9.4)</h4>
<p>Für die Kartendarstellung wird Leaflet.js verwendet. Kartenkacheln werden von OpenStreetMap <p>Für die Kartendarstellung wird Leaflet.js verwendet. Kartenkacheln werden von OpenStreetMap
bereitgestellt. Beim Laden der Karte werden Anfragen an diese Server gesendet, die Ihre IP-Adresse bereitgestellt.</p>
enthalten können.</p>
<ul>
<li>OpenStreetMap Datenschutz: <a href="https://wiki.osmfoundation.org/wiki/Privacy_Policy"
target="_blank">Privacy Policy</a></li>
</ul>
<h4>3.3 Nominatim Geocoding API</h4> <h4>3.3 Nominatim Geocoding API</h4>
<p>Postleitzahlen werden über die Nominatim-API von OpenStreetMap in Koordinaten umgewandelt. Die <p>Postleitzahlen werden über die Nominatim-API in Koordinaten umgewandelt.</p>
Anfragen werden zwischengespeichert, um die Anzahl der API-Aufrufe zu minimieren.</p>
<ul>
<li>Nominatim Nutzungsbedingungen: <a
href="https://operations.osmfoundation.org/policies/nominatim/" target="_blank">Usage
Policy</a></li>
</ul>
<h4>3.4 CDN-Dienste (cdnjs.cloudflare.com)</h4> <h4>3.4 CDN-Dienste (cdnjs.cloudflare.com)</h4>
<p>JavaScript- und CSS-Bibliotheken werden über Cloudflare CDN geladen. Cloudflare kann dabei Ihre <p>JavaScript- und CSS-Bibliotheken werden über Cloudflare CDN geladen.</p>
IP-Adresse verarbeiten.</p>
<ul>
<li>Cloudflare Datenschutz: <a href="https://www.cloudflare.com/privacypolicy/"
target="_blank">Privacy Policy</a></li>
</ul>
<h3>4. Browser-Speicher</h3> <h3>4. Browser-Speicher</h3>
<p>Diese Anwendung nutzt <strong>keine</strong> Cookies, localStorage oder sessionStorage. Alle Daten <p>Diese Anwendung nutzt <strong>keine</strong> Cookies, localStorage oder sessionStorage.</p>
werden nur während der aktiven Sitzung im Arbeitsspeicher gehalten.</p>
<h3>5. Kontakt</h3> <h3>5. Kontakt</h3>
<p>Bei Fragen zum Datenschutz wenden Sie sich bitte an die im <a <p>Bei Fragen zum Datenschutz wenden Sie sich bitte an die im <a

View File

@ -19,6 +19,10 @@ document.getElementById('sortSelect').addEventListener('change', (e) => {
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initMap(); initMap();
initPrivacyModal(); initPrivacyModal();
initMobilePopup();
initPriceInputs();
console.log('Kleinanzeigen Karten-Suche initialized'); console.log('Kleinanzeigen Karten-Suche initialized');
console.log('API Base URL:', API_BASE_URL); console.log('API Base URL:', API_BASE_URL);
console.log('Mobile view:', window.innerWidth < 768);
}); });

View File

@ -46,6 +46,16 @@ function addMarker(listing) {
const marker = L.marker([listing.lat, listing.lon]).addTo(AppState.map); const marker = L.marker([listing.lat, listing.lon]).addTo(AppState.map);
// Check if mobile
const isMobile = window.innerWidth < 768;
if (isMobile) {
// On mobile: open fullscreen popup
marker.on('click', () => {
showMobilePopup(listing);
});
} else {
// On desktop: use regular leaflet popup
const imageHtml = listing.image const imageHtml = listing.image
? `<img src="${listing.image}" style="width: 100%; max-height: 150px; object-fit: cover; margin: 8px 0;" alt="${listing.title}">` ? `<img src="${listing.image}" style="width: 100%; max-height: 150px; object-fit: cover; margin: 8px 0;" alt="${listing.title}">`
: ''; : '';
@ -65,6 +75,37 @@ function addMarker(listing) {
AppState.selectedListingId = listing.id; AppState.selectedListingId = listing.id;
highlightSelectedListing(); highlightSelectedListing();
}); });
}
AppState.markers.push(marker); AppState.markers.push(marker);
} }
function showMobilePopup(listing) {
const popup = document.getElementById('mobilePopup');
const content = document.getElementById('mobilePopupContent');
const imageHtml = listing.image
? `<img src="${listing.image}" class="popup-image" alt="${listing.title}">`
: '';
const dateStr = listing.date_added ? new Date(listing.date_added).toLocaleDateString('de-DE') : 'Unbekannt';
content.innerHTML = `
${imageHtml}
<div class="popup-title">${listing.title}</div>
<div class="popup-price">${listing.price} €</div>
<div class="popup-meta">
<div class="popup-meta-item">
<span class="popup-meta-icon">📍</span>
<span>${listing.address || listing.zip_code}</span>
</div>
<div class="popup-meta-item">
<span class="popup-meta-icon">📅</span>
<span>${dateStr}</span>
</div>
</div>
<a href="${listing.url}" target="_blank" class="popup-link">Inserat auf Kleinanzeigen.de öffnen</a>
`;
popup.classList.add('show');
}

View File

@ -18,7 +18,26 @@ function showStatus(message, type = 'loading') {
} }
function updateProgress(current, total) { function updateProgress(current, total) {
const isMobile = window.innerWidth < 768;
if (isMobile) {
// Mobile progress bar
const progressInfo = document.getElementById('progressInfo'); const progressInfo = document.getElementById('progressInfo');
const progressFill = progressInfo.querySelector('.progress-fill-mobile');
const progressText = progressInfo.querySelector('.progress-text-mobile');
if (total === 0) {
progressInfo.classList.remove('active');
return;
}
progressInfo.classList.add('active');
const percentage = (current / total) * 100;
progressFill.style.width = percentage + '%';
progressText.textContent = `${current}/${total} Inserate`;
} else {
// Desktop progress bar
const progressInfo = document.getElementById('progressInfoDesktop');
const progressFill = progressInfo.querySelector('.progress-fill'); const progressFill = progressInfo.querySelector('.progress-fill');
const progressText = progressInfo.querySelector('.progress-text'); const progressText = progressInfo.querySelector('.progress-text');
const etaText = progressInfo.querySelector('.eta-text'); const etaText = progressInfo.querySelector('.eta-text');
@ -49,6 +68,7 @@ function updateProgress(current, total) {
etaText.textContent = `Noch ca. ${seconds}s`; etaText.textContent = `Noch ca. ${seconds}s`;
} }
} }
}
} }
function highlightSelectedListing() { function highlightSelectedListing() {
@ -70,6 +90,9 @@ function formatDate(dateString) {
} }
function renderResults(listings) { function renderResults(listings) {
// Only render on desktop
if (window.innerWidth < 768) return;
const resultsList = document.getElementById('resultsList'); const resultsList = document.getElementById('resultsList');
const resultsCount = document.querySelector('.results-count'); const resultsCount = document.querySelector('.results-count');
@ -166,3 +189,41 @@ function initPrivacyModal() {
} }
}); });
} }
// Mobile popup
function initMobilePopup() {
const closeBtn = document.getElementById('mobilePopupClose');
const popup = document.getElementById('mobilePopup');
closeBtn.addEventListener('click', () => {
popup.classList.remove('show');
});
}
// Format price inputs with € suffix
function initPriceInputs() {
const minPrice = document.getElementById('minPrice');
const maxPrice = document.getElementById('maxPrice');
function formatPriceInput(input) {
input.addEventListener('blur', (e) => {
if (e.target.value) {
// Store the numeric value
const value = e.target.value.replace(/[^\d]/g, '');
if (value) {
e.target.dataset.value = value;
}
}
});
input.addEventListener('focus', (e) => {
// Show only numeric value when focused
if (e.target.dataset.value) {
e.target.value = e.target.dataset.value;
}
});
}
formatPriceInput(minPrice);
formatPriceInput(maxPrice);
}