diff options
| author | 2025-08-18 19:28:18 +0700 | |
|---|---|---|
| committer | 2025-08-18 19:28:18 +0700 | |
| commit | 6401cf603f598a8008c69a14fe58f460009180a7 (patch) | |
| tree | e275408d111e0fd98f46826ca6cabba6aafdd763 /views/pages | |
| parent | 188d24d6cb7a1d753898b340359e3fcfaf426433 (diff) | |
| parent | f3b5c8136217fd57f7ae948a153f3510558a139d (diff) | |
| download | web-0.6.0.tar.gz web-0.6.0.zip | |
v0.6.0v0.6.0
- feat: add map page
- feat: use webp of various sizes (320/560/800/1200/1600w) as thumbnails
- feat: fixate gallery divs to viewport measures
- feat: make thumbnails square
- feat: remove recreating gallery on window resize
- refactor: replace gap with margins
- refactor: more line height in gallery captions
- refactor: have routes in separate files
Diffstat (limited to 'views/pages')
| -rw-r--r-- | views/pages/blog-catalogue.html | 20 | ||||
| -rw-r--r-- | views/pages/blog-page.html | 10 | ||||
| -rw-r--r-- | views/pages/global-map.html | 241 |
3 files changed, 257 insertions, 14 deletions
diff --git a/views/pages/blog-catalogue.html b/views/pages/blog-catalogue.html index 2b248c3..9fa8ea7 100644 --- a/views/pages/blog-catalogue.html +++ b/views/pages/blog-catalogue.html @@ -4,12 +4,12 @@ class="tags-list ar-gt-0.8:min-w-[10vh] ar-gt-0.8:max-w-[20vh] ar-gt-0.8:w-fit ar-lt-0.8:w-[100%] flex shrink-0 flex-col overflow-y-auto ar-gt-0.8:mr-[1.2vmin]"> <fieldset> <legend class="font-bold w-[100%] text-center">{{ .L.BlogSearch.TagsHeader }}</legend> - <div class="flex flex-col ar-lt-0.8:grid grid-cols-5 grid-flow-row-dense gap-[5%]"> - <div> + <div class="flex flex-col ar-lt-0.8:grid grid-cols-5 grid-flow-row-dense"> + <div class="m-[0.4vmin]"> <label class="font-bold"><input type="checkbox" id="tagsAllCheckbox" onclick="selectAll();"> {{ .L.BlogSearch.ChooseAllTags }}</label> </div> {{- range .Tags }} - <div> + <div class="m-[0.4vmin]"> <label><input type="checkbox" id="tag{{ .Name }}Checkbox" name="tags[]" value="{{ .Name }}" onclick="contextAll();" {{ if contains $.QueryTags .Name }}checked{{ end }}> {{ .Name }} <span class="text-secondary italic">{{ .Count }}</span></label> </div> {{- end }} @@ -17,23 +17,23 @@ </fieldset> <fieldset class="mt-[0.8vmin]"> <legend class="font-bold w-[100%] text-center">{{ .L.BlogSearch.OrderByHeader }}</legend> - <div class="flex flex-col ar-lt-0.8:grid grid-cols-3 grid-rows-2 grid-flow-col gap-[5%]"> - <div> + <div class="flex flex-col ar-lt-0.8:grid grid-cols-3 grid-rows-2 grid-flow-col"> + <div class="m-[0.4vmin]"> <label><input type="radio" id="sortTitleAsc" name="sort" value="titleAsc" {{ if eq .QuerySort "titleAsc" }}checked{{ end }}> {{ .L.BlogSearch.TitleOrdered }} <i class="fas fa-arrow-down-a-z"></i></label> </div> - <div> + <div class="m-[0.4vmin]"> <label><input type="radio" id="sortTitleDesc" name="sort" value="titleDesc" {{ if eq .QuerySort "titleDesc" }}checked{{ end }}> {{ .L.BlogSearch.TitleOrdered }} <i class="fas fa-arrow-down-z-a"></i></label> </div> - <div> + <div class="m-[0.4vmin]"> <label><input type="radio" id="sortActionDateAsc" name="sort" value="actionDateAsc" {{ if eq .QuerySort "actionDateAsc" }}checked{{ end }}> {{ .L.BlogSearch.ActionDateOrdered }} <i class="fas fa-arrow-down-1-9"></i></label> </div> - <div> + <div class="m-[0.4vmin]"> <label><input type="radio" id="sortActionDateDesc" name="sort" value="actionDateDesc" {{ if eq .QuerySort "actionDateDesc" }}checked{{ end }}> {{ .L.BlogSearch.ActionDateOrdered }} <i class="fas fa-arrow-down-9-1"></i></label> </div> - <div> + <div class="m-[0.4vmin]"> <label><input type="radio" id="sortPublicationDateAsc" name="sort" value="publicationDateAsc" {{ if eq .QuerySort "publicationDateAsc" }}checked{{ end }}> {{ .L.BlogSearch.PublicationDateOrdered }} <i class="fas fa-arrow-down-1-9"></i></label> </div> - <div> + <div class="m-[0.4vmin]"> <label><input type="radio" id="sortPublicationDateDesc" name="sort" value="publicationDateDesc" {{ if eq .QuerySort "publicationDateDesc" }}checked{{ end }}> {{ .L.BlogSearch.PublicationDateOrdered }} <i class="fas fa-arrow-down-9-1"></i></label> </div> </div> diff --git a/views/pages/blog-page.html b/views/pages/blog-page.html index 330a1ac..755744d 100644 --- a/views/pages/blog-page.html +++ b/views/pages/blog-page.html @@ -19,7 +19,7 @@ {{ .ParsedMarkdown }} {{- if .MapLocationX }} <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto"> - <div id="location-map" class="relative p-[0.8vmin] flex-none ml-auto mr-auto w-[60%] ar-lt-0.8:w-[80%] h-[40vh] ar-lt-0.8:h-[30vh]"></div> + <div id="map-container" class="relative p-[0.8vmin] flex-none ml-auto mr-auto w-[60%] ar-lt-0.8:w-[80%] h-[40vh] ar-lt-0.8:h-[30vh]"></div> <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto"> {{- end }} </div> @@ -33,8 +33,10 @@ <script src="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.js"></script> <script> + var map; + function initLocationMap(id, x, y, relativeErrorMeters = 0) { - var map = L.map(id).setView([x, y], 8); + map = L.map(id).setView([x, y], 8); L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, @@ -58,7 +60,7 @@ } document.addEventListener('DOMContentLoaded', () => {initLocationMap( - 'location-map', + 'map-container', {{ .MapLocationX }}, {{ .MapLocationY }}, {{ .MapLocationAreaMeters }} @@ -66,7 +68,7 @@ window.addEventListener('resize', function() { setTimeout(() => { - if (window.location-map) window.location-map.invalidateSize(); + map.invalidateSize(); }, 100); }); </script> diff --git a/views/pages/global-map.html b/views/pages/global-map.html new file mode 100644 index 0000000..09d7ab2 --- /dev/null +++ b/views/pages/global-map.html @@ -0,0 +1,241 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>SAYA TODAY // {{ .L.GlobalMap.Header }}</title> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,200;1,300;1,400;1,500;1,600;1,700;1,800&family=Patua+One&display=swap" rel="stylesheet"> + {{ block "header" . }}{{ end }} + <link href="/output.css" rel="stylesheet"> + <link rel="stylesheet" href="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.css"> +</head> + +<body data-theme="ram" class="font-spectral text-main-dark overflow-hidden bg-interlocked-hexagons h-screen flex flex-row ar-lt-0.8:flex-col"> + + <script> + function switchTheme(theme) { + if (theme == "" || typeof theme == "undefined") { + const currentTheme = document.body.getAttribute('data-theme'); + switch (currentTheme) { + case "olive": theme = "lettuce"; break; + case "lettuce": theme = "night"; break; + case "night": theme = "ram"; break; + case "ram": theme = "olive"; break; + } + } + localStorage.setItem('theme', theme); + document.body.setAttribute('data-theme', theme); + } + + const savedTheme = localStorage.getItem('theme') || 'ram'; + switchTheme(savedTheme); + </script> + + <script> + const clientTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + </script> + + <div id="sidebar" class="bg-sidebar flex sticky flex-col ar-gt-0.8:items-center shadow-2xl z-20 w-[5vw] ar-lt-0.8:w-screen h-screen ar-lt-0.8:h-[8vh]"> + <div class="logo-custom ar-lt-0.8:hidden text-sidebar font-patua font-extrabold text-center relative z-20"> + saya.today + </div> + <div class="text-[3vw] ar-lt-0.8:text-[5vh] text-stroke-(--sidebar-stroke-color) text-stroke-[0.1vw] flex flex-col ar-lt-0.8:flex-row gap-0 relative z-20"> + <i onclick="location.href='/';" class="fas fa-home w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> + <i onclick="location.href='/{{ .Lang }}/blog';" class="fas fa-search w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> + <i onclick="location.href='/{{ .Lang }}/map';" class="fas fa-map w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> + <i onclick="toggleThemeWheel();" id="theme-button" class="fas fa-swatchbook w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> + </div> + <div class="theme-wheel" id="theme-wheel"> + <div class="theme-icon" data-theme="olive"></div> + <div class="theme-icon" data-theme="lettuce"></div> + <div class="theme-icon" data-theme="night"></div> + <div class="theme-icon" data-theme="ram"></div> + </div> + </div> + + <div id="map-container" class="bg-background-dark flex z-10 w-[95vw] ar-lt-0.8:w-screen h-screen ar-lt-0.8:h-[92vh]"></div> + + <script src="https://f003.backblazeb2.com/file/sayana-static/libs/htmx/2.0.6/htmx.min.js"></script> + <script src="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.js"></script> + + <script> + const themeWheel = document.getElementById('theme-wheel'); + const themeButton = document.getElementById('theme-button'); + const themeIcons = document.querySelectorAll('.theme-icon'); + + themeIcons.forEach(icon => { + if (icon.getAttribute('data-theme') == savedTheme) { + icon.classList.add('active'); + } + }); + + function calculateVmax(percent) { + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + const largerDimension = Math.max(viewportWidth, viewportHeight); + + const vmaxValue = (largerDimension / 100) * percent; + + return vmaxValue; + } + + function positionThemeIcons() { + const icons = themeWheel.querySelectorAll('.theme-icon'); + const iconCount = icons.length; + const vmax = calculateVmax(1); + const wheelRadius = 7.5*vmax; // Radius of the wheel + const iconRadius = 4*vmax; // Distance from center to place icons + const iconSize = 3*vmax; // Half of icon size for centering + + icons.forEach((icon, index) => { + const angle = (index * 360 / iconCount) - 90; + const angleRad = (angle * Math.PI) / 180; + + const x = Math.cos(angleRad) * iconRadius + wheelRadius - iconSize; + const y = Math.sin(angleRad) * iconRadius + wheelRadius - iconSize; + + icon.style.left = x + 'px'; + icon.style.top = y + 'px'; + }); + } + + function positionThemeWheel() { + const buttonRect = themeButton.getBoundingClientRect(); + const buttonCenterX = buttonRect.left + buttonRect.width / 2; + const buttonCenterY = buttonRect.top + buttonRect.height / 2; + + const wheelRadius = calculateVmax(7.5); + const margin = calculateVmax(1); + + let wheelX = buttonCenterX - wheelRadius; + let wheelY = buttonCenterY - wheelRadius; + + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + if (wheelX < margin) { + wheelX = margin; + } else if (wheelX + (wheelRadius * 2) > viewportWidth - margin) { + wheelX = viewportWidth - (wheelRadius * 2) - margin; + } + + if (wheelY < margin) { + wheelY = margin; + } else if (wheelY + (wheelRadius * 2) > viewportHeight - margin) { + wheelY = viewportHeight - (wheelRadius * 2) - margin; + } + + themeWheel.style.left = wheelX + 'px'; + themeWheel.style.top = wheelY + 'px'; + } + + function hideThemeWheel() { + themeWheel.classList.remove('active'); + } + + function showThemeWheel() { + positionThemeWheel(); + positionThemeIcons(); + themeWheel.classList.add('active'); + } + + function toggleThemeWheel() { + if (themeWheel.classList.contains('active')) { + hideThemeWheel(); + } else { + showThemeWheel(); + } + } + + themeIcons.forEach(icon => { + icon.addEventListener('click', (e) => { + e.stopPropagation(); + const theme = icon.getAttribute('data-theme'); + switchTheme(theme); + themeIcons.forEach(icon => { + icon.classList.remove('active'); + }) + icon.classList.add('active'); + }); + }); + + themeWheel.addEventListener('mouseleave', hideThemeWheel); + + document.addEventListener('click', (e) => { + if (!themeWheel.contains(e.target) && e.target !== themeButton) { + hideThemeWheel(); + } + }); + </script> + + <script> + var map; + + function initLocationMap() { + map = L.map('map-container').setView([{{ .MapLocationLat }}, {{ .MapLocationLong }}], 4); + + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 18, + attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' + }).addTo(map); + } + + function stringToColor(str) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + + let color = '#'; + for (let i = 0; i < 3; i++) { + const value = (hash >> (i * 8)) & 0xFF; + color += ('00' + value.toString(16)).substr(-2); + } + + return color; + } + + function mapAddMarker(x, y, relativeErrorMeters = 0, title = "", link = "", thumbnail = "") { + const titleColor = stringToColor(title); + + if (relativeErrorMeters != 0) { + var circle = L.circle([x, y], { + color: titleColor, + fillColor: titleColor, + fillOpacity: 0.15, + radius: relativeErrorMeters + }).addTo(map); + } + + var marker = L.marker([x, y], { + title: title, + }).addTo(map) + .bindPopup(` + <div class="flex flex-row justify-center justify-items-center"> + <img onclick="location.href='${link}';" class="cursor-pointer object-cover rounded-[10%] select-none w-[3vmax] h-[3vmax] mr-[0.5vmax]" src="https://f003.backblazeb2.com/file/sayana-photos/webp-320p/${thumbnail}.webp"> + <span><b><a href="${link}">${title}</a></b><br><a href="https://www.openstreetmap.org/#map=13/${x}/${y}">${x}, ${y}</a></span> + </div>`); + } + + document.addEventListener('DOMContentLoaded', () => { + initLocationMap(); + {{- range .MapMarkers }} + mapAddMarker({{ .Lat }}, {{ .Long }}, {{ .AccuracyMeters }}, {{ .Title }}, {{ .PageLink }}, {{ .Thumbnail }}); + {{- end }} + }); + + window.addEventListener('resize', function() { + setTimeout(() => { + map.invalidateSize(); + }, 100); + }); + </script> + + {{ block "footer" . }}{{ end }} + +</body> +</html> |