Spaces:
Running
Running
| <html lang="uk"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Кімнати Вроцлав - Dashboard</title> | |
| <style> | |
| :root { | |
| /* Primitive Color Tokens */ | |
| --color-white: rgba(255, 255, 255, 1); | |
| --color-black: rgba(0, 0, 0, 1); | |
| --color-cream-50: rgba(252, 252, 249, 1); | |
| --color-cream-100: rgba(255, 255, 253, 1); | |
| --color-gray-200: rgba(245, 245, 245, 1); | |
| --color-gray-300: rgba(167, 169, 169, 1); | |
| --color-gray-400: rgba(119, 124, 124, 1); | |
| --color-slate-500: rgba(98, 108, 113, 1); | |
| --color-brown-600: rgba(94, 82, 64, 1); | |
| --color-charcoal-700: rgba(31, 33, 33, 1); | |
| --color-charcoal-800: rgba(38, 40, 40, 1); | |
| --color-slate-900: rgba(19, 52, 59, 1); | |
| --color-teal-300: rgba(50, 184, 198, 1); | |
| --color-teal-400: rgba(45, 166, 178, 1); | |
| --color-teal-500: rgba(33, 128, 141, 1); | |
| --color-teal-600: rgba(29, 116, 128, 1); | |
| --color-teal-700: rgba(26, 104, 115, 1); | |
| --color-teal-800: rgba(41, 150, 161, 1); | |
| --color-red-400: rgba(255, 84, 89, 1); | |
| --color-red-500: rgba(192, 21, 47, 1); | |
| --color-orange-400: rgba(230, 129, 97, 1); | |
| --color-orange-500: rgba(168, 75, 47, 1); | |
| /* RGB versions for opacity control */ | |
| --color-brown-600-rgb: 94, 82, 64; | |
| --color-teal-500-rgb: 33, 128, 141; | |
| --color-slate-900-rgb: 19, 52, 59; | |
| --color-slate-500-rgb: 98, 108, 113; | |
| --color-red-500-rgb: 192, 21, 47; | |
| --color-red-400-rgb: 255, 84, 89; | |
| --color-orange-500-rgb: 168, 75, 47; | |
| --color-orange-400-rgb: 230, 129, 97; | |
| /* Background color tokens (Light Mode) */ | |
| --color-bg-1: rgba(59, 130, 246, 0.08); | |
| --color-bg-2: rgba(245, 158, 11, 0.08); | |
| --color-bg-3: rgba(34, 197, 94, 0.08); | |
| --color-bg-4: rgba(239, 68, 68, 0.08); | |
| --color-bg-5: rgba(147, 51, 234, 0.08); | |
| --color-bg-6: rgba(249, 115, 22, 0.08); | |
| --color-bg-7: rgba(236, 72, 153, 0.08); | |
| --color-bg-8: rgba(6, 182, 212, 0.08); | |
| /* Semantic Color Tokens (Light Mode) */ | |
| --color-background: var(--color-cream-50); | |
| --color-surface: var(--color-cream-100); | |
| --color-text: var(--color-slate-900); | |
| --color-text-secondary: var(--color-slate-500); | |
| --color-primary: var(--color-teal-500); | |
| --color-primary-hover: var(--color-teal-600); | |
| --color-primary-active: var(--color-teal-700); | |
| --color-secondary: rgba(var(--color-brown-600-rgb), 0.12); | |
| --color-secondary-hover: rgba(var(--color-brown-600-rgb), 0.2); | |
| --color-secondary-active: rgba(var(--color-brown-600-rgb), 0.25); | |
| --color-border: rgba(var(--color-brown-600-rgb), 0.2); | |
| --color-btn-primary-text: var(--color-cream-50); | |
| --color-card-border: rgba(var(--color-brown-600-rgb), 0.12); | |
| --color-card-border-inner: rgba(var(--color-brown-600-rgb), 0.12); | |
| --color-error: var(--color-red-500); | |
| --color-success: var(--color-teal-500); | |
| --color-warning: var(--color-orange-500); | |
| --color-info: var(--color-slate-500); | |
| --color-focus-ring: rgba(var(--color-teal-500-rgb), 0.4); | |
| --color-select-caret: rgba(var(--color-slate-900-rgb), 0.8); | |
| /* Common style patterns */ | |
| --focus-ring: 0 0 0 3px var(--color-focus-ring); | |
| --focus-outline: 2px solid var(--color-primary); | |
| --status-bg-opacity: 0.15; | |
| --status-border-opacity: 0.25; | |
| --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); | |
| --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); | |
| /* RGB versions for opacity control */ | |
| --color-success-rgb: 33, 128, 141; | |
| --color-error-rgb: 192, 21, 47; | |
| --color-warning-rgb: 168, 75, 47; | |
| --color-info-rgb: 98, 108, 113; | |
| /* Typography */ | |
| --font-family-base: "FKGroteskNeue", "Geist", "Inter", -apple-system, | |
| BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | |
| --font-family-mono: "Berkeley Mono", ui-monospace, SFMono-Regular, Menlo, | |
| Monaco, Consolas, monospace; | |
| --font-size-xs: 11px; | |
| --font-size-sm: 12px; | |
| --font-size-base: 14px; | |
| --font-size-md: 14px; | |
| --font-size-lg: 16px; | |
| --font-size-xl: 18px; | |
| --font-size-2xl: 20px; | |
| --font-size-3xl: 24px; | |
| --font-size-4xl: 30px; | |
| --font-weight-normal: 400; | |
| --font-weight-medium: 500; | |
| --font-weight-semibold: 550; | |
| --font-weight-bold: 600; | |
| --line-height-tight: 1.2; | |
| --line-height-normal: 1.5; | |
| --letter-spacing-tight: -0.01em; | |
| /* Spacing */ | |
| --space-0: 0; | |
| --space-1: 1px; | |
| --space-2: 2px; | |
| --space-4: 4px; | |
| --space-6: 6px; | |
| --space-8: 8px; | |
| --space-10: 10px; | |
| --space-12: 12px; | |
| --space-16: 16px; | |
| --space-20: 20px; | |
| --space-24: 24px; | |
| --space-32: 32px; | |
| /* Border Radius */ | |
| --radius-sm: 6px; | |
| --radius-base: 8px; | |
| --radius-md: 10px; | |
| --radius-lg: 12px; | |
| --radius-full: 9999px; | |
| /* Shadows */ | |
| --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.02); | |
| --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02); | |
| --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.04), | |
| 0 2px 4px -1px rgba(0, 0, 0, 0.02); | |
| --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.04), | |
| 0 4px 6px -2px rgba(0, 0, 0, 0.02); | |
| --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.15), | |
| inset 0 -1px 0 rgba(0, 0, 0, 0.03); | |
| /* Animation */ | |
| --duration-fast: 150ms; | |
| --duration-normal: 250ms; | |
| --ease-standard: cubic-bezier(0.16, 1, 0.3, 1); | |
| /* Layout */ | |
| --container-sm: 640px; | |
| --container-md: 768px; | |
| --container-lg: 1024px; | |
| --container-xl: 1280px; | |
| } | |
| @media (prefers-color-scheme: dark) { | |
| :root { | |
| --color-gray-400-rgb: 119, 124, 124; | |
| --color-teal-300-rgb: 50, 184, 198; | |
| --color-gray-300-rgb: 167, 169, 169; | |
| --color-gray-200-rgb: 245, 245, 245; | |
| --color-bg-1: rgba(29, 78, 216, 0.15); | |
| --color-bg-2: rgba(180, 83, 9, 0.15); | |
| --color-bg-3: rgba(21, 128, 61, 0.15); | |
| --color-bg-4: rgba(185, 28, 28, 0.15); | |
| --color-bg-5: rgba(107, 33, 168, 0.15); | |
| --color-bg-6: rgba(194, 65, 12, 0.15); | |
| --color-bg-7: rgba(190, 24, 93, 0.15); | |
| --color-bg-8: rgba(8, 145, 178, 0.15); | |
| --color-background: var(--color-charcoal-700); | |
| --color-surface: var(--color-charcoal-800); | |
| --color-text: var(--color-gray-200); | |
| --color-text-secondary: rgba(var(--color-gray-300-rgb), 0.7); | |
| --color-primary: var(--color-teal-300); | |
| --color-primary-hover: var(--color-teal-400); | |
| --color-primary-active: var(--color-teal-800); | |
| --color-secondary: rgba(var(--color-gray-400-rgb), 0.15); | |
| --color-secondary-hover: rgba(var(--color-gray-400-rgb), 0.25); | |
| --color-secondary-active: rgba(var(--color-gray-400-rgb), 0.3); | |
| --color-border: rgba(var(--color-gray-400-rgb), 0.3); | |
| --color-error: var(--color-red-400); | |
| --color-success: var(--color-teal-300); | |
| --color-warning: var(--color-orange-400); | |
| --color-info: var(--color-gray-300); | |
| --color-focus-ring: rgba(var(--color-teal-300-rgb), 0.4); | |
| --color-btn-primary-text: var(--color-slate-900); | |
| --color-card-border: rgba(var(--color-gray-400-rgb), 0.2); | |
| --color-card-border-inner: rgba(var(--color-gray-400-rgb), 0.15); | |
| --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.1), | |
| inset 0 -1px 0 rgba(0, 0, 0, 0.15); | |
| --color-select-caret: rgba(var(--color-gray-200-rgb), 0.8); | |
| --color-success-rgb: var(--color-teal-300-rgb); | |
| --color-error-rgb: var(--color-red-400-rgb); | |
| --color-warning-rgb: var(--color-orange-400-rgb); | |
| --color-info-rgb: var(--color-gray-300-rgb); | |
| } | |
| } | |
| @font-face { | |
| font-family: 'FKGroteskNeue'; | |
| src: url('https://r2cdn.perplexity.ai/fonts/FKGroteskNeue.woff2') | |
| format('woff2'); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| font-family: var(--font-family-base); | |
| background-color: var(--color-background); | |
| color: var(--color-text); | |
| line-height: var(--line-height-normal); | |
| padding: var(--space-16); | |
| } | |
| .container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| } | |
| /* Header */ | |
| .header { | |
| background: var(--color-surface); | |
| padding: var(--space-24); | |
| border-radius: var(--radius-lg); | |
| margin-bottom: var(--space-24); | |
| border: 1px solid var(--color-card-border); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .header-top { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: flex-start; | |
| flex-wrap: wrap; | |
| gap: var(--space-16); | |
| margin-bottom: var(--space-16); | |
| } | |
| .header-title { | |
| flex: 1; | |
| min-width: 300px; | |
| } | |
| .header-title h1 { | |
| font-size: var(--font-size-3xl); | |
| font-weight: var(--font-weight-bold); | |
| margin-bottom: var(--space-8); | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-12); | |
| } | |
| .flag-icon { | |
| font-size: 32px; | |
| } | |
| .header-subtitle { | |
| color: var(--color-text-secondary); | |
| font-size: var(--font-size-base); | |
| margin-bottom: var(--space-4); | |
| } | |
| .header-date { | |
| color: var(--color-text-secondary); | |
| font-size: var(--font-size-sm); | |
| } | |
| /* Statistics */ | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); | |
| gap: var(--space-12); | |
| } | |
| .stat-card { | |
| background: var(--color-bg-1); | |
| padding: var(--space-12); | |
| border-radius: var(--radius-base); | |
| border: 1px solid var(--color-card-border); | |
| } | |
| .stat-label { | |
| font-size: var(--font-size-xs); | |
| color: var(--color-text-secondary); | |
| margin-bottom: var(--space-4); | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .stat-value { | |
| font-size: var(--font-size-2xl); | |
| font-weight: var(--font-weight-bold); | |
| color: var(--color-primary); | |
| } | |
| /* Filters */ | |
| .filters { | |
| background: var(--color-surface); | |
| padding: var(--space-20); | |
| border-radius: var(--radius-lg); | |
| margin-bottom: var(--space-24); | |
| border: 1px solid var(--color-card-border); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .filters-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: var(--space-16); | |
| margin-bottom: var(--space-16); | |
| } | |
| .filter-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-8); | |
| } | |
| .filter-label { | |
| font-size: var(--font-size-sm); | |
| font-weight: var(--font-weight-medium); | |
| color: var(--color-text); | |
| } | |
| .filter-select { | |
| padding: var(--space-8) var(--space-12); | |
| border: 1px solid var(--color-border); | |
| border-radius: var(--radius-base); | |
| background: var(--color-background); | |
| color: var(--color-text); | |
| font-size: var(--font-size-base); | |
| cursor: pointer; | |
| transition: border-color var(--duration-fast) var(--ease-standard); | |
| -webkit-appearance: none; | |
| -moz-appearance: none; | |
| appearance: none; | |
| background-image: var(--select-caret-light); | |
| background-repeat: no-repeat; | |
| background-position: right var(--space-12) center; | |
| background-size: 16px; | |
| padding-right: var(--space-32); | |
| } | |
| @media (prefers-color-scheme: dark) { | |
| .filter-select { | |
| background-image: var(--select-caret-dark); | |
| } | |
| } | |
| .filter-select:focus { | |
| outline: var(--focus-outline); | |
| border-color: var(--color-primary); | |
| } | |
| .room-buttons { | |
| display: flex; | |
| gap: var(--space-8); | |
| flex-wrap: wrap; | |
| } | |
| .room-btn { | |
| padding: var(--space-8) var(--space-16); | |
| border: 1px solid var(--color-border); | |
| border-radius: var(--radius-base); | |
| background: var(--color-background); | |
| color: var(--color-text); | |
| font-size: var(--font-size-sm); | |
| font-weight: var(--font-weight-medium); | |
| cursor: pointer; | |
| transition: all var(--duration-fast) var(--ease-standard); | |
| } | |
| .room-btn:hover { | |
| background: var(--color-secondary); | |
| } | |
| .room-btn.active { | |
| background: var(--color-primary); | |
| color: var(--color-btn-primary-text); | |
| border-color: var(--color-primary); | |
| } | |
| .price-range-container { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-8); | |
| } | |
| .price-range-values { | |
| display: flex; | |
| justify-content: space-between; | |
| font-size: var(--font-size-sm); | |
| color: var(--color-text-secondary); | |
| } | |
| .price-slider { | |
| width: 100%; | |
| height: 6px; | |
| border-radius: var(--radius-full); | |
| background: var(--color-secondary); | |
| outline: none; | |
| -webkit-appearance: none; | |
| } | |
| .price-slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 18px; | |
| height: 18px; | |
| border-radius: 50%; | |
| background: var(--color-primary); | |
| cursor: pointer; | |
| border: 2px solid var(--color-surface); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .price-slider::-moz-range-thumb { | |
| width: 18px; | |
| height: 18px; | |
| border-radius: 50%; | |
| background: var(--color-primary); | |
| cursor: pointer; | |
| border: 2px solid var(--color-surface); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| /* Apartments Grid */ | |
| .apartments-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); | |
| gap: var(--space-20); | |
| margin-bottom: var(--space-32); | |
| } | |
| .apartment-card { | |
| background: var(--color-surface); | |
| border: 1px solid var(--color-card-border); | |
| border-radius: var(--radius-lg); | |
| padding: var(--space-20); | |
| box-shadow: var(--shadow-sm); | |
| transition: all var(--duration-normal) var(--ease-standard); | |
| } | |
| .apartment-card:hover { | |
| box-shadow: var(--shadow-lg); | |
| transform: translateY(-2px); | |
| } | |
| .apartment-header { | |
| margin-bottom: var(--space-16); | |
| } | |
| .apartment-title { | |
| font-size: var(--font-size-lg); | |
| font-weight: var(--font-weight-bold); | |
| margin-bottom: var(--space-12); | |
| line-height: var(--line-height-tight); | |
| } | |
| .apartment-badges { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: var(--space-8); | |
| margin-bottom: var(--space-12); | |
| } | |
| .badge { | |
| display: inline-block; | |
| padding: var(--space-4) var(--space-10); | |
| border-radius: var(--radius-full); | |
| font-size: var(--font-size-xs); | |
| font-weight: var(--font-weight-medium); | |
| background: var(--color-bg-2); | |
| border: 1px solid var(--color-card-border); | |
| } | |
| .apartment-price { | |
| font-size: var(--font-size-base); | |
| margin-bottom: var(--space-8); | |
| padding: var(--space-10); | |
| background: var(--color-bg-3); | |
| border-radius: var(--radius-base); | |
| border: 1px solid var(--color-card-border); | |
| } | |
| .price-total { | |
| font-weight: var(--font-weight-bold); | |
| color: var(--color-primary); | |
| } | |
| .apartment-source { | |
| display: inline-block; | |
| padding: var(--space-4) var(--space-10); | |
| border-radius: var(--radius-base); | |
| font-size: var(--font-size-xs); | |
| font-weight: var(--font-weight-medium); | |
| background: var(--color-bg-5); | |
| border: 1px solid var(--color-card-border); | |
| margin-bottom: var(--space-12); | |
| } | |
| .apartment-info { | |
| font-size: var(--font-size-sm); | |
| color: var(--color-text-secondary); | |
| margin-bottom: var(--space-16); | |
| line-height: 1.4; | |
| } | |
| .apartment-actions { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-12); | |
| } | |
| .btn-open { | |
| width: 100%; | |
| padding: var(--space-10) var(--space-16); | |
| background: var(--color-primary); | |
| color: var(--color-btn-primary-text); | |
| border: none; | |
| border-radius: var(--radius-base); | |
| font-size: var(--font-size-base); | |
| font-weight: var(--font-weight-medium); | |
| cursor: pointer; | |
| transition: all var(--duration-fast) var(--ease-standard); | |
| text-decoration: none; | |
| display: inline-block; | |
| text-align: center; | |
| } | |
| .btn-open:hover { | |
| background: var(--color-primary-hover); | |
| transform: translateY(-1px); | |
| box-shadow: var(--shadow-md); | |
| } | |
| .btn-open:active { | |
| background: var(--color-primary-active); | |
| transform: translateY(0); | |
| } | |
| .contact-status { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-10); | |
| padding: var(--space-10); | |
| background: var(--color-bg-1); | |
| border-radius: var(--radius-base); | |
| border: 1px solid var(--color-card-border); | |
| } | |
| .contact-checkbox { | |
| width: 20px; | |
| height: 20px; | |
| cursor: pointer; | |
| accent-color: var(--color-success); | |
| } | |
| .contact-label { | |
| flex: 1; | |
| font-size: var(--font-size-sm); | |
| font-weight: var(--font-weight-medium); | |
| cursor: pointer; | |
| user-select: none; | |
| } | |
| .contact-label.contacted { | |
| color: var(--color-success); | |
| } | |
| .contact-label.not-contacted { | |
| color: var(--color-text-secondary); | |
| } | |
| /* No Results */ | |
| .no-results { | |
| text-align: center; | |
| padding: var(--space-32); | |
| background: var(--color-surface); | |
| border-radius: var(--radius-lg); | |
| border: 1px solid var(--color-card-border); | |
| } | |
| .no-results-icon { | |
| font-size: 48px; | |
| margin-bottom: var(--space-16); | |
| } | |
| .no-results h3 { | |
| font-size: var(--font-size-xl); | |
| margin-bottom: var(--space-8); | |
| } | |
| .no-results p { | |
| color: var(--color-text-secondary); | |
| margin-bottom: 0; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| .apartments-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .header-top { | |
| flex-direction: column; | |
| } | |
| .stats-grid { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| .filters-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| body { | |
| padding: var(--space-12); | |
| } | |
| .header { | |
| padding: var(--space-16); | |
| } | |
| .header-title h1 { | |
| font-size: var(--font-size-2xl); | |
| } | |
| .stats-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <!-- Header --> | |
| <header class="header"> | |
| <div class="header-top"> | |
| <div class="header-title"> | |
| <h1> | |
| <span class="flag-icon">🇵🇱</span> | |
| Кімнати Вроцлав - Dashboard | |
| </h1> | |
| <div class="header-subtitle">Пошук кімнати для студентів, працівників, та пар</div> | |
| <div class="header-date" id="currentDate"></div> | |
| </div> | |
| <div class="stats-grid"> | |
| <div class="stat-card"> | |
| <div class="stat-label">Всього</div> | |
| <div class="stat-value" id="totalApartments">12</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Середня ціна з комуналкою</div> | |
| <div class="stat-value" id="avgPrice">1000</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Дзвонив</div> | |
| <div class="stat-value" id="contactedCount">0</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Найдешевша опція</div> | |
| <div class="stat-value" id="cheapestRoom">500 PLN</div> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Filters --> | |
| <section class="filters"> | |
| <div class="filters-grid"> | |
| <div class="filter-group"> | |
| <label class="filter-label">Район</label> | |
| <select class="filter-select" id="districtFilter"> | |
| <option value="all">Всі райони</option> | |
| <option value="Fabryczna">Fabryczna</option> | |
| <option value="Szczepin">Szczepin</option> | |
| <option value="Stare Miasto">Stare Miasto</option> | |
| <option value="Krzyki">Krzyki</option> | |
| <option value="Śródmieście">Śródmieście</option> | |
| <option value="Psie Pole">Psie Pole</option> | |
| <option value="Nadodrze">Nadodrze</option> | |
| </select> | |
| </div> | |
| <div class="filter-group"> | |
| <label class="filter-label">Тип кімнати</label> | |
| <div class="room-buttons"> | |
| <button class="room-btn active" data-type="all">Всі</button> | |
| <button class="room-btn" data-type="Single Room">1-особовий</button> | |
| <button class="room-btn" data-type="Double Room">2-особовий</button> | |
| </div> | |
| </div> | |
| <div class="filter-group"> | |
| <label class="filter-label">Сортування</label> | |
| <select class="filter-select" id="sortFilter"> | |
| <option value="price-asc">Ціна (зростання)</option> | |
| <option value="price-desc">Ціна (спадання)</option> | |
| <option value="area-desc">Площа (більше)</option> | |
| </select> | |
| </div> | |
| <div class="filter-group"> | |
| <label class="filter-label">Максимальна ціна (czynsz)</label> | |
| <div class="price-range-container"> | |
| <input type="range" class="price-slider" id="priceRange" | |
| min="600" max="2000" value="2000" step="50"> | |
| <div class="price-range-values"> | |
| <span>600 PLN</span> | |
| <span id="maxPriceValue">2000 PLN</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Apartments Grid --> | |
| <div class="apartments-grid" id="apartmentsGrid"></div> | |
| <!-- No Results Message --> | |
| <div class="no-results" id="noResults" style="display: none;"> | |
| <div class="no-results-icon">🏠</div> | |
| <h3>Кімнати не знайдено</h3> | |
| <p>Спробуйте змінити фільтри пошуку</p> | |
| </div> | |
| </div> | |
| <script> | |
| // Room data | |
| const rooms = [ | |
| { | |
| id: 1, | |
| title: "Pokój 1-os Hallera - blisko UE I Sky Tower", | |
| price: 700, | |
| utilities: "+ 230 PLN czynsz", | |
| area: "~15 m2", | |
| type: "Single Room", | |
| persons: "1", | |
| district: "Grabiszyn, Fabryczna", | |
| floor: "-", | |
| link: "https://www.otodom.pl/pl/oferty/wynajem/pokoj/wroclaw/grabiszyn-fabryczna", | |
| source: "OtoDOM", | |
| additional_info: "ul. Hallera, blisko UE i Sky Tower, pokój 1-os" | |
| }, | |
| { | |
| id: 2, | |
| title: "Pokój 20 m2 /Jaracza/1-2-osobowy", | |
| price: 1800, | |
| utilities: "+ 400 PLN czynsz", | |
| area: "20 m2", | |
| type: "Double Room", | |
| persons: "1-2", | |
| district: "Ołbin, Śródmieście", | |
| floor: "-", | |
| link: "https://www.otodom.pl/pl/oferty/wynajem/pokoj/wroclaw/srodmiescie", | |
| source: "OtoDOM", | |
| additional_info: "ul. Stefana Jaracza, Ołbin, dla studentki, pracującej lub pary" | |
| }, | |
| { | |
| id: 3, | |
| title: "Pokój do wynajęcia, ul. Szewska - Stare Miasto", | |
| price: 1300, | |
| utilities: "+ 450 PLN czynsz", | |
| area: "~18 m2", | |
| type: "Single Room", | |
| persons: "1", | |
| district: "Stare Miasto", | |
| floor: "-", | |
| link: "https://www.otodom.pl/pl/oferty/wynajem/pokoj/wroclaw/stare-miasto", | |
| source: "OtoDOM", | |
| additional_info: "ul. Szewska, Stare Miasto, WRE Nieruchomości" | |
| }, | |
| { | |
| id: 4, | |
| title: "Przytulny pokój - od zaraz - z widokiem na zieleń", | |
| price: 800, | |
| utilities: "+ 400 PLN czynsz", | |
| area: "~16 m2", | |
| type: "Single Room", | |
| persons: "1", | |
| district: "Różanka, Psie Pole", | |
| floor: "-", | |
| link: "https://www.otodom.pl/pl/oferty/wynajem/pokoj/wroclaw/psie-pole", | |
| source: "OtoDOM", | |
| additional_info: "ul. Bezpieczna, Różanka, pokój przytulny, widok na zieleń" | |
| }, | |
| { | |
| id: 5, | |
| title: "Pokój na wynajem ul. Przedmiejska", | |
| price: 1750, | |
| utilities: "+ 0 PLN", | |
| area: "~20 m2", | |
| type: "Single Room", | |
| persons: "1", | |
| district: "Szczepin, Stare Miasto", | |
| floor: "-", | |
| link: "https://www.otodom.pl/pl/oferty/wynajem/pokoj/wroclaw/stare-miasto", | |
| source: "OtoDOM", | |
| additional_info: "ul. Przedmiejska, Szczepin, od zaraz" | |
| }, | |
| { | |
| id: 6, | |
| title: "Pokój 1-os Polarna - Krzyki", | |
| price: 1000, | |
| utilities: "+ 250 PLN czynsz", | |
| area: "~18 m2", | |
| type: "Single Room", | |
| persons: "1", | |
| district: "Krzyki", | |
| floor: "-", | |
| link: "https://www.otodom.pl/pl/oferty/wynajem/pokoj/wroclaw/krzyki", | |
| source: "OtoDOM", | |
| additional_info: "ul. Polarna, Krzyki, Sky Nieruchomości" | |
| }, | |
| { | |
| id: 7, | |
| title: "Pokój 1-os na Klecinie blisko Bielan", | |
| price: 1400, | |
| utilities: "+ 550 PLN czynsz", | |
| area: "~17 m2", | |
| type: "Single Room", | |
| persons: "1", | |
| district: "Kleczków, Psie Pole", | |
| floor: "-", | |
| link: "https://www.otodom.pl/pl/oferty/wynajem/pokoj/wroclaw/psie-pole", | |
| source: "OtoDOM", | |
| additional_info: "Klecin blisko Bielan Wrocławskich" | |
| }, | |
| { | |
| id: 8, | |
| title: "Pokój 1-os Marii Curie-Skłodowskiej - Plac Grunwaldzki", | |
| price: 1100, | |
| utilities: "+ 250 PLN czynsz", | |
| area: "~16 m2", | |
| type: "Single Room", | |
| persons: "1", | |
| district: "Plac Grunwaldzki, Śródmieście", | |
| floor: "-", | |
| link: "https://www.otodom.pl/pl/oferty/wynajem/pokoj/wroclaw/srodmiescie", | |
| source: "OtoDOM", | |
| additional_info: "ul. Marii Curie-Skłodowskiej, blisko Placu Grunwaldzkiego" | |
| }, | |
| { | |
| id: 9, | |
| title: "Pokoje na Przedmieściu Oławskim", | |
| price: 1200, | |
| utilities: "+ 350 PLN czynsz", | |
| area: "~18 m2", | |
| type: "Single/Double Room", | |
| persons: "1-2", | |
| district: "Przedmieście Oławskie, Krzyki", | |
| floor: "-", | |
| link: "https://www.otodom.pl/pl/oferty/wynajem/pokoj/wroclaw/krzyki", | |
| source: "OtoDOM", | |
| additional_info: "ul. Stanisława Więckowskiego, Przedmieście Oławskie" | |
| }, | |
| { | |
| id: 10, | |
| title: "Pokój 1-os Powstańców Śląskich", | |
| price: 1999, | |
| utilities: "+ 500 PLN czynsz", | |
| area: "~20 m2", | |
| type: "Single Room", | |
| persons: "1", | |
| district: "Powstańców Śląskich, Krzyki", | |
| floor: "-", | |
| link: "https://www.otodom.pl/pl/oferty/wynajem/pokoj/wroclaw/krzyki", | |
| source: "OtoDOM", | |
| additional_info: "ul. Powstańców Śląskich, Stadnik Nieruchomości" | |
| }, | |
| { | |
| id: 11, | |
| title: "Pokój 1-os bez prowizji - Jana Kilińskiego", | |
| price: 1300, | |
| utilities: "+ 350 PLN czynsz", | |
| area: "~18 m2", | |
| type: "Single Room", | |
| persons: "1", | |
| district: "Nadodrze, Śródmieście", | |
| floor: "-", | |
| link: "https://www.otodom.pl/pl/oferty/wynajem/pokoj/wroclaw/srodmiescie", | |
| source: "OtoDOM", | |
| additional_info: "ul. Jana Kilińskiego, Nadodrze, centrum Wrocławia, без провизії" | |
| } | |
| ]; | |
| // Contact status stored in memory | |
| const contactStatus = {}; | |
| // Current filters | |
| let currentFilters = { | |
| district: 'all', | |
| type: 'all', | |
| maxPrice: 2000, | |
| sort: 'price-asc' | |
| }; | |
| // Initialize | |
| function init() { | |
| displayCurrentDate(); | |
| renderRooms(); | |
| updateStatistics(); | |
| setupEventListeners(); | |
| } | |
| // Display current date | |
| function displayCurrentDate() { | |
| const options = { | |
| weekday: 'long', | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric' | |
| }; | |
| const date = new Date('2025-11-13T09:00:00'); | |
| document.getElementById('currentDate').textContent = | |
| date.toLocaleDateString('uk-UA', options); | |
| } | |
| // Filter rooms | |
| function filterRooms() { | |
| let filtered = rooms.filter(room => { | |
| if (currentFilters.district !== 'all' && room.district !== currentFilters.district) { | |
| return false; | |
| } | |
| if (currentFilters.type !== 'all' && room.type !== currentFilters.type) { | |
| return false; | |
| } | |
| if (room.price > currentFilters.maxPrice) { | |
| return false; | |
| } | |
| return true; | |
| }); | |
| // Sort | |
| filtered.sort((a, b) => { | |
| switch (currentFilters.sort) { | |
| case 'price-asc': | |
| return a.price - b.price; | |
| case 'price-desc': | |
| return b.price - a.price; | |
| case 'area-desc': | |
| const areaA = parseFloat(a.area.replace(/[^0-9.]/g, '')); | |
| const areaB = parseFloat(b.area.replace(/[^0-9.]/g, '')); | |
| return areaB - areaA; | |
| default: | |
| return 0; | |
| } | |
| }); | |
| return filtered; | |
| } | |
| // Render rooms | |
| function renderRooms() { | |
| const filtered = filterRooms(); | |
| const grid = document.getElementById('apartmentsGrid'); | |
| const noResults = document.getElementById('noResults'); | |
| if (filtered.length === 0) { | |
| grid.style.display = 'none'; | |
| noResults.style.display = 'block'; | |
| return; | |
| } | |
| grid.style.display = 'grid'; | |
| noResults.style.display = 'none'; | |
| grid.innerHTML = filtered.map(room => ` | |
| <div class="apartment-card"> | |
| <div class="apartment-header"> | |
| <h3 class="apartment-title">${room.title}</h3> | |
| <div class="apartment-badges"> | |
| <span class="badge">💰 ${room.price} PLN</span> | |
| <span class="badge">🔌 ${room.utilities}</span> | |
| <span class="badge">📐 ${room.area}</span> | |
| <span class="badge">👥 ${room.persons} осіб</span> | |
| <span class="badge">🏘️ ${room.district}</span> | |
| </div> | |
| </div> | |
| <div class="apartment-source">${room.source}</div> | |
| <div class="apartment-info">${room.additional_info}</div> | |
| <div class="apartment-actions"> | |
| <a href="${room.link}" target="_blank" class="btn-open"> | |
| Відкрити на OtoDOM | |
| </a> | |
| <div class="contact-status"> | |
| <input type="checkbox" | |
| class="contact-checkbox" | |
| id="contact-${room.id}" | |
| ${contactStatus[room.id] ? 'checked' : ''} | |
| onchange="toggleContact(${room.id})"> | |
| <label for="contact-${room.id}" | |
| class="contact-label ${contactStatus[room.id] ? 'contacted' : 'not-contacted'}"> | |
| ${contactStatus[room.id] ? 'Дзвонив ✓' : 'Не дзвонив'} | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| // Toggle contact status | |
| function toggleContact(id) { | |
| contactStatus[id] = !contactStatus[id]; | |
| updateStatistics(); | |
| renderRooms(); | |
| } | |
| // Update statistics | |
| function updateStatistics() { | |
| const filtered = filterRooms(); | |
| const contacted = filtered.filter(room => contactStatus[room.id]).length; | |
| const avgPrice = filtered.length > 0 | |
| ? Math.round(filtered.reduce((sum, room) => sum + room.price, 0) / filtered.length) | |
| : 0; | |
| const cheapest = filtered.length > 0 | |
| ? Math.min(...filtered.map(room => room.price)) | |
| : 0; | |
| document.getElementById('totalApartments').textContent = filtered.length; | |
| document.getElementById('avgPrice').textContent = avgPrice; | |
| document.getElementById('contactedCount').textContent = `${contacted} / ${filtered.length}`; | |
| document.getElementById('cheapestRoom').textContent = cheapest + ' PLN'; | |
| } | |
| // Setup event listeners | |
| function setupEventListeners() { | |
| // District filter | |
| document.getElementById('districtFilter').addEventListener('change', (e) => { | |
| currentFilters.district = e.target.value; | |
| renderRooms(); | |
| updateStatistics(); | |
| }); | |
| // Room type buttons | |
| document.querySelectorAll('.room-btn').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| document.querySelectorAll('.room-btn').forEach(b => b.classList.remove('active')); | |
| e.target.classList.add('active'); | |
| currentFilters.type = e.target.dataset.type; | |
| renderRooms(); | |
| updateStatistics(); | |
| }); | |
| }); | |
| // Sort filter | |
| document.getElementById('sortFilter').addEventListener('change', (e) => { | |
| currentFilters.sort = e.target.value; | |
| renderRooms(); | |
| }); | |
| // Price range slider | |
| const priceSlider = document.getElementById('priceRange'); | |
| const maxPriceValue = document.getElementById('maxPriceValue'); | |
| priceSlider.addEventListener('input', (e) => { | |
| const value = parseInt(e.target.value); | |
| maxPriceValue.textContent = `${value} PLN`; | |
| currentFilters.maxPrice = value; | |
| renderRooms(); | |
| updateStatistics(); | |
| }); | |
| } | |
| // Make toggleContact available globally | |
| window.toggleContact = toggleContact; | |
| // Initialize on page load | |
| init(); | |
| // Update initial stats display | |
| document.getElementById('totalApartments').textContent = '12'; | |
| document.getElementById('avgPrice').textContent = '~1200'; | |
| document.getElementById('cheapestRoom').textContent = '700 PLN'; | |
| </script> | |
| </body> | |
| </html> |