From 465aba4e47ae2997bbd0b88f18a86215b22b0b8c Mon Sep 17 00:00:00 2001 From: Arne Baeumler Date: Tue, 9 Jun 2026 21:00:59 +0200 Subject: [PATCH] fix: update timeline --- src/server/request-handler.ts | 917 +++++++++++++++++----------------- 1 file changed, 447 insertions(+), 470 deletions(-) diff --git a/src/server/request-handler.ts b/src/server/request-handler.ts index 3048760..ca33461 100644 --- a/src/server/request-handler.ts +++ b/src/server/request-handler.ts @@ -102,8 +102,10 @@ function htmlPage(): string { --bg-accent-a: rgba(71, 85, 105, 0.1); --bg-accent-b: rgba(30, 41, 59, 0.08); --panel: rgba(15, 23, 42, 0.84); + --panel-float: rgba(15, 23, 42, 0.52); --panel-strong: rgba(15, 23, 42, 0.96); --surface: rgba(15, 23, 42, 0.6); + --surface-float: rgba(15, 23, 42, 0.42); --surface-strong: rgba(30, 41, 59, 0.88); --border: rgba(148, 163, 184, 0.14); --border-strong: rgba(148, 163, 184, 0.2); @@ -123,15 +125,22 @@ function htmlPage(): string { --photo-active-bg: rgba(71, 85, 105, 0.22); --photo-active-border: rgba(148, 163, 184, 0.28); --empty-border: rgba(148, 163, 184, 0.18); - --map-label-bg: rgba(15, 23, 42, 0.94); - --map-label-border: rgba(148, 163, 184, 0.14); --overlay-bg: rgba(2, 6, 23, 0.84); --card-border: rgba(148, 163, 184, 0.12); --leaflet-popup-bg: rgba(15, 23, 42, 0.96); --leaflet-popup-text: #e2e8f0; - --timeline-bg: - linear-gradient(180deg, rgba(15, 23, 42, 0.96), rgba(15, 23, 42, 0.9)), - radial-gradient(circle at top left, rgba(71, 85, 105, 0.14), transparent 42%); + --timeline-bg: #f0f0f0c0; + --timeline-bar-border: rgba(55, 65, 81, 0.92); + --timeline-bar-fill: rgba(156, 163, 175, 0.78); + --timeline-bar-hover: rgba(203, 213, 225, 0.9); + --timeline-bar-selected: rgba(209, 213, 219, 0.92); + --timeline-axis-color: rgba(156, 163, 175, 0.55); + --timeline-axis-label: rgba(156, 163, 175, 0.72); + --timeline-meta: rgba(156, 163, 175, 0.68); + --timeline-selection-border: rgba(107, 114, 128, 0.42); + --timeline-selection-bg: rgba(107, 114, 128, 0.08); + --timeline-brush-border: rgba(107, 114, 128, 0.62); + --timeline-brush-bg: rgba(107, 114, 128, 0.12); --chart-grid: rgba(255, 255, 255, 0.04); } @@ -141,8 +150,10 @@ function htmlPage(): string { --bg-accent-a: rgba(100, 116, 139, 0.08); --bg-accent-b: rgba(148, 163, 184, 0.06); --panel: rgba(255, 255, 255, 0.86); + --panel-float: rgba(255, 255, 255, 0.52); --panel-strong: rgba(255, 255, 255, 0.96); --surface: rgba(255, 255, 255, 0.8); + --surface-float: rgba(255, 255, 255, 0.46); --surface-strong: rgba(241, 245, 249, 0.96); --border: rgba(15, 23, 42, 0.1); --border-strong: rgba(15, 23, 42, 0.16); @@ -162,15 +173,22 @@ function htmlPage(): string { --photo-active-bg: rgba(100, 116, 139, 0.12); --photo-active-border: rgba(100, 116, 139, 0.3); --empty-border: rgba(15, 23, 42, 0.16); - --map-label-bg: rgba(255, 255, 255, 0.94); - --map-label-border: rgba(15, 23, 42, 0.08); --overlay-bg: rgba(8, 12, 18, 0.78); --card-border: rgba(15, 23, 42, 0.11); --leaflet-popup-bg: rgba(255, 255, 255, 0.97); --leaflet-popup-text: #0f172a; - --timeline-bg: - linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(248, 250, 252, 0.92)), - radial-gradient(circle at top left, rgba(100, 116, 139, 0.08), transparent 42%); + --timeline-bg: #f0f0f0c0; + --timeline-bar-border: rgba(75, 85, 99, 0.9); + --timeline-bar-fill: rgba(156, 163, 175, 0.74); + --timeline-bar-hover: rgba(209, 213, 219, 0.88); + --timeline-bar-selected: rgba(220, 224, 230, 0.92); + --timeline-axis-color: rgba(107, 114, 128, 0.52); + --timeline-axis-label: rgba(107, 114, 128, 0.7); + --timeline-meta: rgba(107, 114, 128, 0.66); + --timeline-selection-border: rgba(107, 114, 128, 0.3); + --timeline-selection-bg: rgba(107, 114, 128, 0.08); + --timeline-brush-border: rgba(107, 114, 128, 0.52); + --timeline-brush-bg: rgba(107, 114, 128, 0.12); --chart-grid: rgba(15, 23, 42, 0.06); } @@ -185,8 +203,7 @@ function htmlPage(): string { } body { - display: grid; - grid-template-rows: auto minmax(0, 1fr); + position: relative; height: 100dvh; overflow: hidden; font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; @@ -197,49 +214,12 @@ function htmlPage(): string { var(--bg); } - header { - display: flex; - justify-content: space-between; - align-items: end; - gap: 16px; - padding: 20px 24px 12px; - } - - .header-actions { - display: flex; - align-items: center; - gap: 10px; - } - - .theme-toggle { - min-width: 128px; - justify-content: center; - } - h1, h2, p { margin: 0; } - .brand h1 { - font-size: clamp(1.6rem, 2.4vw, 2.2rem); - } - - .brand p, - .meta, - .muted { - color: var(--muted); - } - - .layout { - min-height: 0; - display: grid; - grid-template-columns: minmax(300px, 360px) minmax(0, 1fr); - gap: 14px; - padding: 0 14px 14px; - } - aside, main, .overlay-card { @@ -250,22 +230,62 @@ function htmlPage(): string { box-shadow: var(--shadow); } + .muted { + color: var(--muted); + } + aside { + position: absolute; + top: 16px; + left: 16px; + bottom: 128px; + width: min(280px, calc(100vw - 32px)); min-height: 0; min-width: 0; - padding: 16px; + padding: 8px; display: grid; grid-template-rows: auto minmax(0, 1fr); - gap: 14px; - overflow: auto; + gap: 8px; + overflow: hidden; + z-index: 700; + backdrop-filter: blur(16px); + background: var(--panel-float); + border-color: rgba(255, 255, 255, 0.08); + transition: width 180ms ease, transform 180ms ease, opacity 180ms ease, padding 180ms ease; + } + + aside.sidebar-collapsed { + width: 44px; + padding: 6px; + } + + aside.sidebar-collapsed .panel-stack { + padding: 0; + background: transparent; + border-color: transparent; + backdrop-filter: none; + } + + aside.sidebar-collapsed .panel-stack > :not(.panel-toolbar), + aside.sidebar-collapsed .photo-card { + display: none; + } + + aside.sidebar-collapsed .panel-toolbar { + justify-content: center; + } + + aside.sidebar-collapsed .theme-toggle { + display: none; } .card { min-width: 0; - padding: 15px; + padding: 10px; border-radius: var(--radius-lg); border: 1px solid var(--card-border); - background: var(--surface); + background: var(--surface-float); + backdrop-filter: blur(16px); } .photo-card { @@ -275,8 +295,8 @@ function htmlPage(): string { } .card h2 { - font-size: 1rem; - margin-bottom: 8px; + font-size: 0.92rem; + margin-bottom: 6px; } .card p, @@ -286,16 +306,16 @@ function htmlPage(): string { label { display: grid; - gap: 8px; - margin-top: 12px; - font-size: 0.92rem; + gap: 6px; + margin-top: 10px; + font-size: 0.84rem; } input { width: 100%; border: 1px solid var(--border-strong); border-radius: var(--radius-md); - padding: 11px 13px; + padding: 9px 11px; font: inherit; color: var(--text); background: var(--input-bg); @@ -303,15 +323,41 @@ function htmlPage(): string { .button-row { display: flex; - gap: 10px; - margin-top: 12px; + gap: 8px; + margin-top: 8px; flex-wrap: wrap; } + .panel-stack { + display: grid; + gap: 8px; + } + + .panel-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + } + + .theme-toggle { + min-width: 112px; + justify-content: center; + } + + .panel-collapse { + width: 32px; + min-width: 32px; + height: 32px; + padding: 0; + justify-content: center; + flex: 0 0 auto; + } + button { border: 0; border-radius: var(--radius-md); - padding: 10px 13px; + padding: 8px 11px; font: inherit; cursor: pointer; display: inline-flex; @@ -363,8 +409,8 @@ function htmlPage(): string { } .status { - margin-top: 10px; - font-size: 0.9rem; + margin-top: 6px; + font-size: 0.8rem; color: var(--muted); overflow-wrap: anywhere; } @@ -372,8 +418,8 @@ function htmlPage(): string { .status-row { display: flex; align-items: center; - gap: 10px; - margin-top: 10px; + gap: 8px; + margin-top: 6px; min-width: 0; } @@ -406,12 +452,12 @@ function htmlPage(): string { .progress { display: grid; - gap: 8px; - margin-top: 12px; + gap: 6px; + margin-top: 8px; } .progress-bar { - height: 10px; + height: 8px; border-radius: var(--radius-md); overflow: hidden; background: var(--progress-track); @@ -429,7 +475,7 @@ function htmlPage(): string { display: flex; justify-content: space-between; gap: 12px; - font-size: 0.82rem; + font-size: 0.76rem; color: var(--muted); min-width: 0; } @@ -441,7 +487,7 @@ function htmlPage(): string { .list { display: grid; - gap: 10px; + gap: 6px; min-height: 0; overflow: auto; padding-right: 2px; @@ -449,10 +495,10 @@ function htmlPage(): string { .photo { display: grid; - grid-template-columns: 52px 1fr; - gap: 12px; + grid-template-columns: 38px 1fr; + gap: 8px; align-items: center; - padding: 11px; + padding: 8px; border-radius: var(--radius-md); background: var(--photo-bg); border: 1px solid var(--card-border); @@ -460,15 +506,15 @@ function htmlPage(): string { } .photo img { - width: 52px; - height: 52px; + width: 38px; + height: 38px; object-fit: cover; border-radius: 0; } .photo strong { display: block; - font-size: 0.95rem; + font-size: 0.8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -476,7 +522,7 @@ function htmlPage(): string { .photo span { color: var(--muted); - font-size: 0.82rem; + font-size: 0.7rem; display: block; overflow: hidden; text-overflow: ellipsis; @@ -489,7 +535,7 @@ function htmlPage(): string { } .empty-state { - padding: 14px; + padding: 10px; border-radius: var(--radius-md); border: 1px dashed var(--empty-border); color: var(--muted); @@ -498,43 +544,28 @@ function htmlPage(): string { } main { - position: relative; + position: absolute; + inset: 0; min-height: 0; min-width: 0; overflow: hidden; - display: grid; - grid-template-rows: minmax(460px, 1fr) auto; - gap: 16px; } .map-panel { - position: relative; + position: absolute; + inset: 0; min-height: 0; overflow: hidden; - border-radius: var(--radius-xl); - border: 1px solid var(--border); - background: var(--panel); - backdrop-filter: blur(14px); - box-shadow: var(--shadow); + border-radius: 0; + border: 0; + background: transparent; + backdrop-filter: none; + box-shadow: none; } #map { width: 100%; height: 100%; - min-height: 460px; - } - - .map-label { - position: absolute; - z-index: 500; - top: 16px; - left: 16px; - padding: 10px 12px; - border-radius: 0; - background: var(--map-label-bg); - border: 1px solid var(--map-label-border); - box-shadow: var(--shadow-soft); - font-size: 0.88rem; } .overlay { @@ -638,63 +669,74 @@ function htmlPage(): string { } .timeline { - padding: 15px; + position: absolute; + left: 8px; + right: 8px; + bottom: 6px; + padding: 4px; display: grid; - gap: 12px; + gap: 4px; + z-index: 650; + backdrop-filter: none; + background: #f0f0f0c0; + opacity: 1; + border-color: rgba(255, 255, 255, 0.08); + transition: height 180ms ease, transform 180ms ease, opacity 180ms ease, padding 180ms ease; } - .timeline-header, - .timeline-footer { - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; + .timeline.timeline-collapsed { + height: 34px; + padding: 4px; + overflow: hidden; + } + + .timeline.timeline-collapsed .timeline-summary, + .timeline.timeline-collapsed .timeline-chart, + .timeline.timeline-collapsed .timeline-body { + display: none; } .timeline-header h2 { margin: 0 0 4px; - font-size: 1rem; + font-size: 0.84rem; } .timeline-summary { color: var(--muted); - font-size: 0.88rem; + font-size: 0.72rem; + } + + .timeline-body { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 4px; + align-items: stretch; } .timeline-chart { position: relative; - min-height: 170px; - padding: 14px 12px 10px; + min-height: 64px; + padding: 4px 6px 4px; border-radius: 0; - border: 1px solid rgba(148, 163, 184, 0.18); + border: 0; background: var(--timeline-bg); overflow: hidden; touch-action: none; user-select: none; - box-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.05), - inset 0 -1px 0 rgba(255, 255, 255, 0.03); + box-shadow: none; } .timeline-chart::before { - content: ""; - position: absolute; - inset: 0; - background: - linear-gradient(to top, var(--chart-grid) 1px, transparent 1px), - linear-gradient(to right, var(--chart-grid) 1px, transparent 1px); - background-size: 100% 33.333%, 10% 100%; - opacity: 0.35; - pointer-events: none; + content: none; } .timeline-bars { - height: 124px; + height: 36px; display: grid; grid-auto-flow: column; - grid-auto-columns: minmax(10px, 1fr); + grid-auto-columns: minmax(8px, 1fr); align-items: end; - gap: 2px; + gap: 0; position: relative; z-index: 1; } @@ -706,27 +748,30 @@ function htmlPage(): string { align-items: end; cursor: pointer; border-radius: 0; + background: transparent; + padding: 0; + border: 0; } .timeline-bar-fill { width: 100%; border-radius: inherit; - min-height: 4px; - background: linear-gradient(180deg, rgba(96, 165, 250, 0.98), rgba(59, 130, 246, 0.36)); + min-height: 3px; + background: #808080; + border: 1px solid #404040; transition: transform 140ms ease, background 140ms ease, opacity 140ms ease, box-shadow 140ms ease; transform-origin: bottom; - box-shadow: 0 0 0 1px rgba(30, 64, 175, 0.28); + box-shadow: none; + box-sizing: border-box; } .timeline-bar:hover .timeline-bar-fill, .timeline-bar.active .timeline-bar-fill { - background: linear-gradient(180deg, #38bdf8, #0ea5e9); - box-shadow: 0 0 0 1px rgba(14, 165, 233, 0.38), 0 0 18px rgba(14, 165, 233, 0.18); + background: #909090; } .timeline-bar.selected .timeline-bar-fill { - background: linear-gradient(180deg, #22c55e, #86efac); - box-shadow: 0 0 0 1px rgba(34, 197, 94, 0.35), 0 0 18px rgba(34, 197, 94, 0.18); + background: #a0a0a0; } .timeline-bar.active .timeline-bar-fill { @@ -734,84 +779,38 @@ function htmlPage(): string { } .timeline-bar.selected.active .timeline-bar-fill { - background: linear-gradient(180deg, #34d399, #10b981); - box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.38), 0 0 22px rgba(16, 185, 129, 0.24); + background: #b0b0b0; } .timeline-bar-meta { position: absolute; left: 50%; - bottom: -18px; + bottom: -12px; transform: translateX(-50%); white-space: nowrap; - font-size: 0.72rem; - color: rgba(226, 232, 240, 0.82); + font-size: 0.62rem; + color: var(--timeline-meta); font-variant-numeric: tabular-nums; pointer-events: none; } - .timeline-axis { - position: relative; - height: 20px; - margin-top: -2px; - color: rgba(226, 232, 240, 0.72); + .timeline-actions { + display: flex; + align-items: flex-start; + gap: 4px; + padding-top: 4px; } - .timeline-axis-label { - position: absolute; - top: 0; - transform: translateX(-50%); - font-size: 0.72rem; - color: rgba(226, 232, 240, 0.78); - font-variant-numeric: tabular-nums; - white-space: nowrap; - } - - .timeline-selection { - position: absolute; - top: 14px; - bottom: 34px; - border-radius: 0; - border: 1px solid rgba(56, 189, 248, 0.55); - background: - linear-gradient(180deg, rgba(56, 189, 248, 0.18), rgba(14, 165, 233, 0.08)), - rgba(14, 165, 233, 0.12); - pointer-events: none; - opacity: 0; - transition: opacity 120ms ease; - box-shadow: - inset 0 0 0 1px rgba(255, 255, 255, 0.06), - 0 0 22px rgba(14, 165, 233, 0.12); - } - - .timeline-selection.visible { - opacity: 1; - } - - .timeline-brush { - position: absolute; - top: 14px; - bottom: 34px; - border-radius: 0; - border: 1px dashed rgba(56, 189, 248, 0.9); - background: rgba(56, 189, 248, 0.16); - pointer-events: none; - opacity: 0; - box-shadow: 0 0 20px rgba(56, 189, 248, 0.12); - } - - .timeline-brush.visible { - opacity: 1; - } - - .timeline-footer { - font-size: 0.84rem; - color: var(--muted); - font-variant-numeric: tabular-nums; - } - - .timeline-footer span:last-child { + .timeline-actions .panel-collapse { + width: 28px; + min-width: 28px; + height: 28px; + padding: 0; + justify-content: center; + background: rgba(255, 255, 255, 0.42); color: var(--text); + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08); + border: 1px solid rgba(75, 85, 99, 0.24); } @media (max-width: 920px) { @@ -821,12 +820,13 @@ function htmlPage(): string { overflow: auto; } - .layout { - grid-template-columns: 1fr; - min-height: 0; - } - aside { + position: static; + width: auto; + margin: 12px; + bottom: auto; + right: auto; + top: auto; overflow: visible; } @@ -835,125 +835,131 @@ function htmlPage(): string { } main { - grid-template-rows: minmax(400px, 1fr) auto; + position: static; + inset: auto; + min-height: 60vh; } #map { - min-height: 460px; + min-height: 60vh; } - .timeline-header, - .timeline-footer { - flex-direction: column; - align-items: flex-start; + .timeline { + position: static; + left: auto; + right: auto; + bottom: auto; + margin: 0 12px 12px; } } -
-
-

mapy-mg

-

Browser-based photo geolocation on OpenStreetMap.

+
+
+
+
+
+ + + +
+
No photos imported yet.
+
+
+
+
+
+
+
+ + +
-
-
Client-side processing, no server-side storage
- -
-
- -
- - -
-
-
Hover for thumbnail · click for fullscreen · route sorted by time
-
-
-
-
-
-

Timeline

-
No photos imported yet.
-
- -
-
-
-
-
-
-
- -
-
-
+