30 files changed, 524 insertions, 451 deletions
diff --git a/.github/workflows/build-and-deploy-prod.yml b/.github/workflows/build-and-deploy-prod.yml index 060ae32..fc44571 100644 --- a/.github/workflows/build-and-deploy-prod.yml +++ b/.github/workflows/build-and-deploy-prod.yml @@ -99,4 +99,5 @@ jobs: -e saya_today_web_mail_username="${{ secrets.MAIL_USERNAME }}" \ -e saya_today_web_mail_password="${{ secrets.MAIL_PASSWORD }}" \ -e saya_today_google_site_verification="${{ secrets.GOOGLE_SITE_VERIFICATION }}" \ - -e saya_today_yandex_verification="${{ secrets.YANDEX_VERIFICATION }}" + -e saya_today_yandex_verification="${{ secrets.YANDEX_VERIFICATION }}" \ + -e saya_today_bing_verification="${{ secrets.BING_VERIFICATION }}" @@ -20,7 +20,7 @@ COPY --from=build-stage /builddir/migrations /app/migrations COPY --from=build-stage /builddir/static /app/static COPY --from=build-stage /builddir/views /app/views COPY --from=build-stage /builddir/l10n/*.yaml /app/l10n/ -COPY --from=build-stage /builddir/config/config*.yaml /app/config/ +COPY --from=build-stage /builddir/config/config.*.yaml /app/config/ RUN apk add --no-cache tzdata sqlite diff --git a/config/config.go b/config/config.go index 0b1570a..9185e6a 100644 --- a/config/config.go +++ b/config/config.go @@ -20,9 +20,9 @@ type Config struct { Auth AuthConfig `json:"Auth" yaml:"auth" validate:"required"` Mail MailConfig `json:"Mail" yaml:"mail" validate:"required"` CanonicalEndpoint string `json:"CanonicalEndpoint" yaml:"canonicalEndpoint" validate:"required"` - Meta MetaConfig `json:"Meta" yaml:"meta"` + Meta []MetaConfig `json:"Meta" yaml:"meta"` PhotoStorage PhotoStorageConfig `json:"PhotoStorage" yaml:"photoStorage"` - StaticStorage PhotoTypeConfig `json:"StaticStorage" yaml:"staticStorage"` + StaticStorage StaticStorageConfig `json:"StaticStorage" yaml:"staticStorage" validate:"required"` AllowOrigins []string `json:"AllowOrigins" yaml:"allowOrigins"` } @@ -237,8 +237,8 @@ type TriggerConfig struct { } type MetaConfig struct { - GoogleSiteVerification string `json:"GoogleSiteVerification" yaml:"googleSiteVerification"` - YandexVerification string `json:"YandexVerification" yaml:"yandexVerification"` + Name string `json:"Name" yaml:"name"` + Value string `json:"Value" yaml:"value"` } type PhotoStorageConfig struct { @@ -261,6 +261,16 @@ type HomePageGifsConfig struct { Indexes []string `json:"Indexes" yaml:"indexes"` } +type StaticStorageConfig struct { + BaseUrl string `json:"BaseUrl" yaml:"baseUrl" validate:"url,required"` + Map MapStorageConfig `json:"Map" yaml:"map" validate:"required"` +} + +type MapStorageConfig struct { + BaseUrl string `json:"BaseUrl" yaml:"baseUrl" validate:"url,required"` + PMTiles string `json:"PMTiles" yaml:"pmTiles" validate:"required"` +} + func LoadConfig(path string, config *Config) error { fileBytes, err := os.ReadFile(path) if err != nil { diff --git a/config/config.local.yaml b/config/config.local.yaml index 80eb8f7..fefe2e7 100644 --- a/config/config.local.yaml +++ b/config/config.local.yaml @@ -30,11 +30,11 @@ localePath: ./locale/ availableLanguages: - name: ru alt: Русский - flag: https://storage.yandexcloud.kz/sayauz-static/flags/ru.svg + flag: https://cdn.saya.uz/stats/flags/ru.svg locFile: localization.ru.yaml - name: en alt: English - flag: https://storage.yandexcloud.kz/sayauz-static/flags/gb.svg + flag: https://cdn.saya.uz/stats/flags/gb.svg locFile: localization.en.yaml auth: db: @@ -56,26 +56,26 @@ mail: canonicalEndpoint: "http://127.0.0.1:3000" photoStorage: full: - baseUrl: https://storage.yandexcloud.kz/sayauz-photos/%s + baseUrl: https://cdn.saya.uz/photos/jpeg-full/%s webp: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-full/%s.webp thumbnail1600p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp1600/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-1600p/%s.webp thumbnail1200p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp1200/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-1200p/%s.webp thumbnail800p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp800/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-800p/%s.webp thumbnail560p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp560/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-560p/%s.webp thumbnail320p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp320/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-320p/%s.webp homePageGifs: - baseUrl: https://cdn.saya.uz/static/home-page-gifs/otter-%s.gif + baseUrl: https://cdn.saya.uz/stats/home-page-gifs/otter-%s.gif indexes: ["1", "2", "3"] staticStorage: - baseUrl: https://cdn.saya.uz/static -mapStorage: - baseUrl: https://cdn.saya.uz/map + baseUrl: https://cdn.saya.uz/stats + map: + baseUrl: https://cdn.saya.uz/map + pmTiles: global.pmtiles allowOrigins: - - https://storage.yandexcloud.kz - https://cdn.saya.uz diff --git a/config/config.prod.yaml b/config/config.prod.yaml index ad4864a..6ec00d8 100644 --- a/config/config.prod.yaml +++ b/config/config.prod.yaml @@ -31,11 +31,11 @@ localePath: ./locale/ availableLanguages: - name: ru alt: Русский - flag: https://storage.yandexcloud.kz/sayauz-static/flags/ru.svg + flag: https://cdn.saya.uz/stats/flags/ru.svg locFile: localization.ru.yaml - name: en alt: English - flag: https://storage.yandexcloud.kz/sayauz-static/flags/gb.svg + flag: https://cdn.saya.uz/stats/flags/gb.svg locFile: localization.en.yaml auth: db: @@ -55,30 +55,34 @@ mail: onNewPost: "0/5 * * * *" canonicalEndpoint: "https://${FQDN}" meta: - googleSiteVerification: "${GOOGLE_SITE_VERIFICATION}" - yandexVerification: "${YANDEX_VERIFICATION}" + - name: google-site-verification + value: "${GOOGLE_SITE_VERIFICATION}" + - name: yandex-verification + value: "${YANDEX_VERIFICATION}" + - name: msvalidate.01 + value: "${BING_VERIFICATION}" photoStorage: full: - baseUrl: https://storage.yandexcloud.kz/sayauz-photos/%s + baseUrl: https://cdn.saya.uz/photos/jpeg-full/%s webp: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-full/%s.webp thumbnail1600p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp1600/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-1600p/%s.webp thumbnail1200p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp1200/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-1200p/%s.webp thumbnail800p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp800/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-800p/%s.webp thumbnail560p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp560/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-560p/%s.webp thumbnail320p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp320/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-320p/%s.webp homePageGifs: - baseUrl: https://cdn.saya.uz/static/home-page-gifs/otter-%s.gif + baseUrl: https://cdn.saya.uz/stats/home-page-gifs/otter-%s.gif indexes: ["1", "2", "3"] staticStorage: - baseUrl: https://cdn.saya.uz/static -mapStorage: - baseUrl: https://cdn.saya.uz/map + baseUrl: https://cdn.saya.uz/stats + map: + baseUrl: https://cdn.saya.uz/map + pmTiles: global.pmtiles allowOrigins: - - https://storage.yandexcloud.kz - https://cdn.saya.uz diff --git a/config/config.stage.yaml b/config/config.stage.yaml index 4b46c6c..d8819ca 100644 --- a/config/config.stage.yaml +++ b/config/config.stage.yaml @@ -31,11 +31,11 @@ localePath: ./locale/ availableLanguages: - name: ru alt: Русский - flag: https://storage.yandexcloud.kz/sayauz-static/flags/ru.svg + flag: https://cdn.saya.uz/stats/flags/ru.svg locFile: localization.ru.yaml - name: en alt: English - flag: https://storage.yandexcloud.kz/sayauz-static/flags/gb.svg + flag: https://cdn.saya.uz/stats/flags/gb.svg locFile: localization.en.yaml auth: db: @@ -56,27 +56,26 @@ mail: canonicalEndpoint: "https://${FQDN}" photoStorage: full: - baseUrl: https://storage.yandexcloud.kz/sayauz-photos/%s + baseUrl: https://cdn.saya.uz/photos/jpeg-full/%s webp: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-full/%s.webp thumbnail1600p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp1600/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-1600p/%s.webp thumbnail1200p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp1200/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-1200p/%s.webp thumbnail800p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp800/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-800p/%s.webp thumbnail560p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp560/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-560p/%s.webp thumbnail320p: - baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp320/%s.webp + baseUrl: https://cdn.saya.uz/photos/webp-320p/%s.webp homePageGifs: - baseUrl: https://cdn.saya.uz/static/home-page-gifs/otter-%s.gif + baseUrl: https://cdn.saya.uz/stats/home-page-gifs/otter-%s.gif indexes: ["1", "2", "3"] staticStorage: - baseUrl: https://cdn.saya.uz/static -mapStorage: - baseUrl: https://cdn.saya.uz/map + baseUrl: https://cdn.saya.uz/stats + map: + baseUrl: https://cdn.saya.uz/map + pmTiles: global.pmtiles allowOrigins: - - https://storage.yandexcloud.kz - https://cdn.saya.uz - diff --git a/deploy/playbook.yml b/deploy/playbook.yml index ae3e936..f725335 100644 --- a/deploy/playbook.yml +++ b/deploy/playbook.yml @@ -44,6 +44,7 @@ FQDN: "{{ saya_today_web_listen_address }}" GOOGLE_SITE_VERIFICATION: "{{ saya_today_google_site_verification | default('') }}" YANDEX_VERIFICATION: "{{ saya_today_yandex_verification | default('') }}" + BING_VERIFICATION: "{{ saya_today_bing_verification | default('') }}" network_mode: caddy networks: - name: caddy diff --git a/internal/glightbox/html_renderer.go b/internal/glightbox/html_renderer.go index b2cd855..1bb606b 100644 --- a/internal/glightbox/html_renderer.go +++ b/internal/glightbox/html_renderer.go @@ -151,12 +151,12 @@ func (r *GLightboxHTMLRenderer) renderGLightbox(w util.BufWriter, source []byte, w.WriteString(strings.ReplaceAll(` <div class="items-center flex flex-col"> - <hr class="border-t-3 border-dotted border-main-hard mt-1 mb-2 w-[80%] ml-auto mr-auto"> + <hr class="border-t-[0.375rem] border-dotted border-main-hard my-2 w-24 ml-auto mr-auto"> <div class="grid masonry-grid-{id}"> <div class="grid-sizer grid-sizer-{id}"></div> `+strings.Join(elements, "\n")+` </div> - <hr class="border-t-3 border-dotted border-main-hard mt-1 mb-2 w-[80%] ml-auto mr-auto"> + <hr class="border-t-[0.375rem] border-dotted border-main-hard my-2 w-24 ml-auto mr-auto"> </div> <script> diff --git a/internal/router/handlers/api-v1-like-get.go b/internal/router/handlers/api-v1-like-get.go index 0eaf5f6..9e2c856 100644 --- a/internal/router/handlers/api-v1-like-get.go +++ b/internal/router/handlers/api-v1-like-get.go @@ -42,7 +42,7 @@ func (r *GetLikeHandler) Render(c *fiber.Ctx, supplements *router.Supplements, l return fiber.StatusBadRequest, fmt.Errorf("error getting path from referer: %w", err) } - if len(pathParts) != 3 && pathParts[1] != "blog" { + if len(pathParts) != 3 || pathParts[1] != "blog" { return fiber.StatusBadRequest, fmt.Errorf("invalid path format: expected '/:lang/blog/:page', got '%s'", path) } diff --git a/internal/router/handlers/api-v1-map-get.go b/internal/router/handlers/api-v1-map-get.go new file mode 100644 index 0000000..e19c54a --- /dev/null +++ b/internal/router/handlers/api-v1-map-get.go @@ -0,0 +1,116 @@ +package handlers + +import ( + "fmt" + "log/slog" + "slices" + "strconv" + "strings" + + "github.com/SayaAndy/saya-today-web/internal/blog" + "github.com/SayaAndy/saya-today-web/internal/router" + "github.com/gofiber/fiber/v2" +) + +type GetMapHandler struct { + router.BasicHandler +} + +func init() { + router.Routes = append(router.Routes, &GetMapHandler{}) +} + +func (r *GetMapHandler) Filter() (method string, path string) { + return "GET", "/api/v1/map" +} + +func (r *GetMapHandler) IsTemplated() bool { + return false +} + +func (r *GetMapHandler) TemplatesToInject() []string { + return []string{"views/partials/global-map-widget.html"} +} + +func (r *GetMapHandler) ToCache() router.CacheSetting { + return router.ByUrlAndQuery +} + +func (r *GetMapHandler) ToValidateLang() router.LangSetting { + return router.InReferer +} + +func (r *GetMapHandler) Render(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) { + codename := c.Query("codename") + zoom := c.QueryInt("zoom", 4) + zoomPosition := c.Query("zoomPosition") + + pages, err := supplements.BlogClient.Scan(lang + "/") + status := fiber.StatusOK + if err != nil { + slog.Error("received an error while scanning blog pages", + slog.String("error", err.Error()), + slog.String("lang", lang), + ) + status = fiber.StatusPartialContent + pages = []*blog.Page{} + } + slices.SortFunc(pages, func(a *blog.Page, b *blog.Page) int { + return a.Metadata.PublishedTime.Compare(b.Metadata.PublishedTime) + }) + + type MapMarker struct { + Index int `json:"Index"` + Title string `json:"Title"` + PageLink string `json:"PageLink"` + Lat float64 `json:"Lat"` + Long float64 `json:"Long"` + AccuracyMeters int64 `json:"AccuracyMeters"` + Thumbnail string `json:"Thumbnail"` + ToHighlight bool `json:"ToHighlight"` + } + + templateMap["MapLocationLat"] = 45.4507 + templateMap["MapLocationLong"] = 68.8319 + templateMap["MapLocationZoom"] = zoom + templateMap["ZoomPosition"] = zoomPosition + + mapMarkers := make([]*MapMarker, 0, len(pages)) + for i, page := range pages { + geolocationParts := strings.Split(page.Metadata.Geolocation, " ") + if len(geolocationParts) < 2 { + continue + } + + var x, y float64 + var areaError int64 + if len(geolocationParts) >= 2 { + x, _ = strconv.ParseFloat(geolocationParts[0], 64) + y, _ = strconv.ParseFloat(geolocationParts[1], 64) + } + if len(geolocationParts) >= 3 { + areaError, _ = strconv.ParseInt(geolocationParts[2], 10, 64) + } + + toHighlight := page.FileName == codename + if toHighlight { + templateMap["MapLocationLat"] = x + templateMap["MapLocationLong"] = y + } + + mapMarkers = append(mapMarkers, &MapMarker{ + Index: i, + Title: page.Metadata.Title, + PageLink: fmt.Sprintf("/%s/blog/%s", lang, page.FileName), + Lat: x, + Long: y, + AccuracyMeters: areaError, + Thumbnail: page.Metadata.Thumbnail, + ToHighlight: toHighlight, + }) + } + + templateMap["MapMarkers"] = mapMarkers + + return status, nil +} diff --git a/internal/router/handlers/lang-blog-title.go b/internal/router/handlers/lang-blog-title.go index eac317a..76dc335 100644 --- a/internal/router/handlers/lang-blog-title.go +++ b/internal/router/handlers/lang-blog-title.go @@ -10,6 +10,7 @@ import ( "github.com/SayaAndy/saya-today-web/internal/blog" "github.com/SayaAndy/saya-today-web/internal/frontmatter" "github.com/SayaAndy/saya-today-web/internal/router" + "github.com/SayaAndy/saya-today-web/l10n" "github.com/gofiber/fiber/v2" "github.com/yuin/goldmark" ) @@ -70,11 +71,15 @@ func (r *BlogPageHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, if err != nil { return nil, fmt.Errorf("failed to read frontmatter of the desired blog post: %w", err) } + title := metadata.Title + if metadata.Medley != "" { + title += " // " + l10n.T.GetPath(lang, "Medleys", metadata.Medley).(string) + } return []router.MetaField{ - {Property: "og:title", Content: metadata.Title}, + {Property: "og:title", Content: title}, {Property: "og:description", Content: fmt.Sprintf("%s [%s]", metadata.ShortDescription, metadata.ActionDate)}, - {Property: "og:image", Content: fmt.Sprintf(supplements.PhotoStorage.Thumbnail320p.BaseUrl, metadata.Thumbnail)}, + {Property: "og:image", Content: fmt.Sprintf(supplements.PhotoStorage.Thumbnail560p.BaseUrl, metadata.Thumbnail)}, {Property: "og:url", Content: fmt.Sprintf("%s/%s/blog/%s", templateMap["CanonicalEndpoint"], lang, c.Params("title"))}, {Property: "og:type", Content: "website"}, {Name: "twitter:card", Content: "summary_large_image"}, @@ -86,11 +91,15 @@ func (r *BlogPageHandler) AddLinkedData(c *fiber.Ctx, supplements *router.Supple if err != nil { return nil, fmt.Errorf("failed to read frontmatter of the desired blog post: %w", err) } + title := metadata.Title + if metadata.Medley != "" { + title += " // " + l10n.T.GetPath(lang, "Medleys", metadata.Medley).(string) + } return map[string]any{ "@context": "https://schema.org", "@type": "Article", - "headline": metadata.Title, + "headline": title, "description": fmt.Sprintf("%s [%s]", metadata.ShortDescription, metadata.ActionDate), "author": map[string]string{"@type": "Person", "name": "Saya Andy"}, "datePublished": metadata.PublishedTime.UTC().Format(time.RFC3339), @@ -147,7 +156,13 @@ func (r *BlogPageHandler) RenderHeader(c *fiber.Ctx, supplements *router.Supplem return fiber.StatusNotFound, fmt.Errorf("could not read '%s' for metadata: %w", path, err) } + pageTitle := metadata.Title + if metadata.Medley != "" { + pageTitle += " // " + l10n.T.GetPath(lang, "Medleys", metadata.Medley).(string) + } + templateMap["Title"] = metadata.Title + templateMap["PageTitle"] = pageTitle return fiber.StatusOK, nil } diff --git a/internal/router/handlers/lang-map.go b/internal/router/handlers/lang-map.go index d452bc7..32ab35e 100644 --- a/internal/router/handlers/lang-map.go +++ b/internal/router/handlers/lang-map.go @@ -1,13 +1,6 @@ package handlers import ( - "fmt" - "log/slog" - "slices" - "strconv" - "strings" - - "github.com/SayaAndy/saya-today-web/internal/blog" "github.com/SayaAndy/saya-today-web/internal/router" "github.com/gofiber/fiber/v2" ) @@ -49,61 +42,5 @@ func (r *MapHandler) SitemapInfo(supplements *router.Supplements) []router.Sitem } func (r *MapHandler) Render(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) { - pages, err := supplements.BlogClient.Scan(lang + "/") - slices.SortFunc(pages, func(a *blog.Page, b *blog.Page) int { - return a.Metadata.PublishedTime.Compare(b.Metadata.PublishedTime) - }) - status := fiber.StatusOK - if err != nil { - slog.Error("received an error while scanning b2 pages", - slog.String("error", err.Error()), - slog.String("lang", lang), - ) - status = fiber.StatusPartialContent - pages = []*blog.Page{} - } - - type MapMarker struct { - Index int `json:"Index"` - Title string `json:"Title"` - PageLink string `json:"PageLink"` - Lat float64 `json:"Lat"` - Long float64 `json:"Long"` - AccuracyMeters int64 `json:"AccuracyMeters"` - Thumbnail string `json:"Thumbnail"` - } - - mapMarkers := make([]*MapMarker, 0, len(pages)) - for i, page := range pages { - geolocationParts := strings.Split(page.Metadata.Geolocation, " ") - if len(geolocationParts) < 2 { - continue - } - - var x, y float64 - var areaError int64 - if len(geolocationParts) >= 2 { - x, _ = strconv.ParseFloat(geolocationParts[0], 64) - y, _ = strconv.ParseFloat(geolocationParts[1], 64) - } - if len(geolocationParts) >= 3 { - areaError, _ = strconv.ParseInt(geolocationParts[2], 10, 64) - } - - mapMarkers = append(mapMarkers, &MapMarker{ - Index: i, - Title: page.Metadata.Title, - PageLink: fmt.Sprintf("/%s/blog/%s", lang, page.FileName), - Lat: x, - Long: y, - AccuracyMeters: areaError, - Thumbnail: page.Metadata.Thumbnail, - }) - } - - templateMap["MapMarkers"] = mapMarkers - templateMap["MapLocationLat"] = 45.4507 - templateMap["MapLocationLong"] = 68.8319 - - return status, nil + return fiber.StatusOK, nil } diff --git a/internal/router/handlers/root.go b/internal/router/handlers/root.go index 6a011e4..4fc4e31 100644 --- a/internal/router/handlers/root.go +++ b/internal/router/handlers/root.go @@ -47,11 +47,8 @@ func (r *RootHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lan {Property: "og:url", Content: fmt.Sprint(templateMap["CanonicalEndpoint"]) + "/"}, {Property: "og:type", Content: "website"}, } - if supplements.Meta.GoogleSiteVerification != "" { - meta = append(meta, router.MetaField{Name: "google-site-verification", Content: supplements.Meta.GoogleSiteVerification}) - } - if supplements.Meta.YandexVerification != "" { - meta = append(meta, router.MetaField{Name: "yandex-verification", Content: supplements.Meta.YandexVerification}) + for _, field := range supplements.Meta { + meta = append(meta, router.MetaField{Name: field.Name, Content: field.Value}) } return meta, nil } diff --git a/internal/router/router.go b/internal/router/router.go index 43d6138..a11a934 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -102,9 +102,9 @@ type Supplements struct { BlogTrigger *blogtrigger.BlogTriggerScheduler TemplateManager *templatemanager.TemplateManager MarkdownRenderer goldmark.Markdown - Meta config.MetaConfig + Meta []config.MetaConfig PhotoStorage config.PhotoStorageConfig - StaticStorage config.PhotoTypeConfig + StaticStorage config.StaticStorageConfig } type Router struct { @@ -303,12 +303,12 @@ func (r *Router) InitRoutes() (err error) { } defaultMap := fiber.Map{ - "Lang": lang, - "Path": trimmedPath, - "QueryString": queryString, - "CanonicalEndpoint": r.canonicalEndpoint, - "StaticStorageBaseUrl": r.supplements.StaticStorage.BaseUrl, - "ThumbnailBaseUrl": r.supplements.PhotoStorage.Thumbnail320p.BaseUrl, + "Lang": lang, + "Path": trimmedPath, + "QueryString": queryString, + "CanonicalEndpoint": r.canonicalEndpoint, + "StaticStorage": r.supplements.StaticStorage, + "PhotoStorage": r.supplements.PhotoStorage, } statusCode, err := currentRoute.Render(c, r.supplements, lang, defaultMap) @@ -455,12 +455,12 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error { } valueMap := fiber.Map{ - "Lang": lang, - "Path": trimmedPath, - "QueryString": queryString, - "CanonicalEndpoint": r.canonicalEndpoint, - "StaticStorageBaseUrl": r.supplements.StaticStorage.BaseUrl, - "ThumbnailBaseUrl": r.supplements.PhotoStorage.Thumbnail320p.BaseUrl, + "Lang": lang, + "Path": trimmedPath, + "QueryString": queryString, + "CanonicalEndpoint": r.canonicalEndpoint, + "StaticStorage": r.supplements.StaticStorage, + "PhotoStorage": r.supplements.PhotoStorage, } var err error @@ -580,12 +580,12 @@ func (r *Router) generalPageSegment(c *fiber.Ctx, part string) error { var statusCode int defaultMap := fiber.Map{ - "Lang": lang, - "Path": strings.Trim(path, "/"), - "QueryString": queryString, - "CanonicalEndpoint": r.canonicalEndpoint, - "StaticStorageBaseUrl": r.supplements.StaticStorage.BaseUrl, - "ThumbnailBaseUrl": r.supplements.PhotoStorage.Thumbnail320p.BaseUrl, + "Lang": lang, + "Path": strings.Trim(path, "/"), + "QueryString": queryString, + "CanonicalEndpoint": r.canonicalEndpoint, + "StaticStorage": r.supplements.StaticStorage, + "PhotoStorage": r.supplements.PhotoStorage, } switch part { diff --git a/internal/tailwind/transformer.go b/internal/tailwind/transformer.go index c9da9df..f90489f 100644 --- a/internal/tailwind/transformer.go +++ b/internal/tailwind/transformer.go @@ -45,7 +45,7 @@ func (t *TailwindTransformer) Transform(node *ast.Document, reader text.Reader, node.SetAttribute([]byte("class"), []byte("text-base/[2] font-gentium tracking-[.0125rem]")) case *ast.Blockquote: - node.SetAttribute([]byte("class"), []byte("border-l-2 border-main-medium bg-paper bg-background-dark p-1 mb-2 italic font-thin")) + node.SetAttribute([]byte("class"), []byte("border-l-[0.125rem] border-main-medium bg-paper bg-background-dark p-1 mb-2 italic font-thin")) case *ast.CodeSpan: node.SetAttribute([]byte("class"), []byte("bg-paper bg-background-dark")) diff --git a/internal/templatemanager/templatemanager.go b/internal/templatemanager/templatemanager.go index 5c84418..ff1214a 100644 --- a/internal/templatemanager/templatemanager.go +++ b/internal/templatemanager/templatemanager.go @@ -43,6 +43,7 @@ var templateFuncMap = template.FuncMap{ "l": func(path ...any) any { return l10n.T.GetPath(path...) }, + "join": strings.Join, } func NewTemplateManager(templates ...TemplateManagerTemplates) (*TemplateManager, error) { diff --git a/static/input.css b/static/input.css index 602b5d9..e0c5c81 100644 --- a/static/input.css +++ b/static/input.css @@ -22,24 +22,38 @@ --color-main-dark-hard: var(--color-main-dark-hard); --color-main-dark-medium: var(--color-main-dark-medium); --color-main-dark-soft: var(--color-main-dark-soft); - --shadow-elevation-flat: 0px 1px 1px 0px rgba(0, 0, 0, 0.2); + --shadow-elevation-flat: 0px 0.0625rem 0.0625rem 0px rgba(0, 0, 0, 0.2); + --inset-shadow-elevation-flat: inset 0px 0.0625rem 0.125rem 0px rgba(0, 0, 0, 0.15); --shadow-elevation-0: 0px 0px 0px 0px rgba(0, 0, 0, 0.2), 0px 0px 0px 0px rgba(0, 0, 0, 0.14), 0px 0px 0px 0px rgba(0, 0, 0, 0.12); - --shadow-elevation-1: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12); - --shadow-elevation-2: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); - --shadow-elevation-3: 0px 3px 3px -2px rgba(0, 0, 0, 0.2), 0px 3px 4px 0px rgba(0, 0, 0, 0.14), 0px 1px 8px 0px rgba(0, 0, 0, 0.12); - --shadow-elevation-4: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); - --shadow-elevation-5: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 5px 8px 0px rgba(0, 0, 0, 0.14), 0px 1px 14px 0px rgba(0, 0, 0, 0.12); - --shadow-elevation-6: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12); - --shadow-elevation-7: 0px 4px 5px -2px rgba(0, 0, 0, 0.2), 0px 7px 10px 1px rgba(0, 0, 0, 0.14), 0px 2px 16px 1px rgba(0, 0, 0, 0.12); - --shadow-elevation-8: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12); - --shadow-elevation-9: 0px 5px 6px -3px rgba(0, 0, 0, 0.2), 0px 9px 12px 1px rgba(0, 0, 0, 0.14), 0px 3px 16px 2px rgba(0, 0, 0, 0.12); - --shadow-elevation-10: 0px 6px 6px -3px rgba(0, 0, 0, 0.2), 0px 10px 14px 1px rgba(0, 0, 0, 0.14), 0px 4px 18px 3px rgba(0, 0, 0, 0.12); - --shadow-elevation-11: 0px 6px 7px -4px rgba(0, 0, 0, 0.2), 0px 11px 15px 1px rgba(0, 0, 0, 0.14), 0px 4px 20px 3px rgba(0, 0, 0, 0.12); - --shadow-elevation-12: 0px 7px 8px -4px rgba(0, 0, 0, 0.2), 0px 12px 17px 2px rgba(0, 0, 0, 0.14), 0px 5px 22px 4px rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-0: inset 0px 0px 0px 0px rgba(0, 0, 0, 0); + --shadow-elevation-1: 0px 0.125rem 0.0625rem -0.0625rem rgba(0, 0, 0, 0.2), 0px 0.0625rem 0.0625rem 0px rgba(0, 0, 0, 0.14), 0px 0.0625rem 0.1875rem 0px rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-1: inset 0px 0.125rem 0.25rem 0px rgba(0, 0, 0, 0.2); + --shadow-elevation-2: 0px 0.1875rem 0.0625rem -0.125rem rgba(0, 0, 0, 0.2), 0px 0.125rem 0.125rem 0px rgba(0, 0, 0, 0.14), 0px 0.0625rem 0.3125rem 0px rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-2: inset 0px 0.1875rem 0.375rem 0px rgba(0, 0, 0, 0.22); + --shadow-elevation-3: 0px 0.1875rem 0.1875rem -0.125rem rgba(0, 0, 0, 0.2), 0px 0.1875rem 0.25rem 0px rgba(0, 0, 0, 0.14), 0px 0.0625rem 0.5rem 0px rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-3: inset 0px 0.25rem 0.5rem 0px rgba(0, 0, 0, 0.25); + --shadow-elevation-4: 0px 0.125rem 0.25rem -0.0625rem rgba(0, 0, 0, 0.2), 0px 0.25rem 0.3125rem 0px rgba(0, 0, 0, 0.14), 0px 0.0625rem 0.625rem 0px rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-4: inset 0px 0.3125rem 0.625rem 0px rgba(0, 0, 0, 0.27); + --shadow-elevation-5: 0px 0.1875rem 0.3125rem -0.0625rem rgba(0, 0, 0, 0.2), 0px 0.3125rem 0.5rem 0px rgba(0, 0, 0, 0.14), 0px 0.0625rem 0.875rem 0px rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-5: inset 0px 0.375rem 0.75rem 0px rgba(0, 0, 0, 0.3); + --shadow-elevation-6: 0px 0.1875rem 0.3125rem -0.0625rem rgba(0, 0, 0, 0.2), 0px 0.375rem 0.625rem 0px rgba(0, 0, 0, 0.14), 0px 0.0625rem 1.125rem 0px rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-6: inset 0px 0.4375rem 0.875rem 0px rgba(0, 0, 0, 0.32); + --shadow-elevation-7: 0px 0.25rem 0.3125rem -0.125rem rgba(0, 0, 0, 0.2), 0px 0.4375rem 0.625rem 0.0625rem rgba(0, 0, 0, 0.14), 0px 0.125rem 1rem 0.0625rem rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-7: inset 0px 0.5rem 1rem 0px rgba(0, 0, 0, 0.35); + --shadow-elevation-8: 0px 0.3125rem 0.3125rem -0.1875rem rgba(0, 0, 0, 0.2), 0px 0.5rem 0.625rem 0.0625rem rgba(0, 0, 0, 0.14), 0px 0.1875rem 0.875rem 0.125rem rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-8: inset 0px 0.5625rem 1.125rem 0px rgba(0, 0, 0, 0.37); + --shadow-elevation-9: 0px 0.3125rem 0.375rem -0.1875rem rgba(0, 0, 0, 0.2), 0px 0.5625rem 0.75rem 0.0625rem rgba(0, 0, 0, 0.14), 0px 0.1875rem 1rem 0.125rem rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-9: inset 0px 0.625rem 1.25rem 0px rgba(0, 0, 0, 0.4); + --shadow-elevation-10: 0px 0.375rem 0.375rem -0.1875rem rgba(0, 0, 0, 0.2), 0px 0.625rem 0.875rem 0.0625rem rgba(0, 0, 0, 0.14), 0px 0.25rem 1.125rem 0.1875rem rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-10: inset 0px 0.6875rem 1.375rem 0px rgba(0, 0, 0, 0.42); + --shadow-elevation-11: 0px 0.375rem 0.4375rem -0.25rem rgba(0, 0, 0, 0.2), 0px 0.6875rem 0.9375rem 0.0625rem rgba(0, 0, 0, 0.14), 0px 0.25rem 1.25rem 0.1875rem rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-11: inset 0px 0.75rem 1.5rem 0px rgba(0, 0, 0, 0.45); + --shadow-elevation-12: 0px 0.4375rem 0.5rem -0.25rem rgba(0, 0, 0, 0.2), 0px 0.75rem 1.0625rem 0.125rem rgba(0, 0, 0, 0.14), 0px 0.3125rem 1.375rem 0.25rem rgba(0, 0, 0, 0.12); + --inset-shadow-elevation-12: inset 0px 0.8125rem 1.625rem 0px rgba(0, 0, 0, 0.47); } html { - font-size: 12px; + font-size: clamp(11px, min(1dvw, 1dvh) + 7.6px, 16px); --crossed-out: url("data:image/svg+xml,<svg id='patternId' width='100%' height='100%' xmlns='http://www.w3.org/2000/svg'><defs><pattern id='a' patternUnits='userSpaceOnUse' width='30' height='30' patternTransform='scale(1) rotate(65)'><rect x='0' y='0' width='100%' height='100%' fill='%232b2b3100' /><path d='M30 15L22.5 30 15 15 22.5 0zm-15 0L7.5 30 0 15 7.5 0z' stroke-width='0.6' stroke='%23ffffffff' fill='none' /></pattern></defs><rect width='800%' height='800%' transform='translate(0,0)' fill='url(%23a)' /></svg>"); --bg-paper: url(data:image/webp;base64,UklGRi4BAABXRUJQVlA4ICIBAABQCQCdASqAAIAAPm02lUmkIqIkIImIgA2JaW3rMFmGDAOKLAoJtP591ByFR74Tun2DP+Lygpfsz5FH7+cKgxi2Islfd03i5AJdXUfU0SDc8SXYAAD++hlzeYAJWQEF8ZdkJdlcA+5btZg0FMGaso8SHoBph0qAOQysElJ5C5SJFNoKIV1J550UyL7Y9Gh0/c/uzLnBmr8lvz7k1WeA81H4S5ViU/wBqa7aM1m8D9rDtrk8cSTqx+xWF6MS8UfZGSsks6CT/igtgv6syFN9uNA3LjnjiCJKYOvFcMkX+jB2578GWVo3nyLaM1QNPYCxn3TiUbnf7U0MdD2NsBpg1+Jd1wu6yW/U+goN+IQS6Nt4RMWltVcZHlxrneKJi0SobO0AAA==); --split-right-background: unset; @@ -73,27 +87,25 @@ body { scrollbar-color: var(--color-background-light) var(--color-main-soft); } -@media screen and (min-width: 640px) and (min-height: 640px) { - html { - font-size: 14px; - } +body.glightbox-open { + height: 100dvh; } @media screen and (min-width: 900px) and (min-height: 900px) { html { - font-size: 16px; + font-size: clamp(16px, min(1dvw, 1dvh) + 7px, 20px); } } @media screen and (min-width: 1400px) and (min-height: 1400px) { html { - font-size: 20px; + font-size: clamp(20px, min(1dvw, 1dvh) + 6px, 24px); } } @media screen and (min-width: 1920px) and (min-height: 1920px) { html { - font-size: 24px; + font-size: calc(min(1dvw, 1dvh) + 4.8px); } } @@ -169,8 +181,8 @@ body { --color-leaflet-link: var(--color-main-medium); --color-leaflet-link-hover: var(--color-main-soft); --interlocked-hexagons-background: - url("https://storage.yandexcloud.kz/sayauz-static/themes/lettuce/linen-fabric.webp"), - url("https://storage.yandexcloud.kz/sayauz-static/themes/lettuce/lettuce-tiles.svg"); + url("https://cdn.saya.uz/stats/themes/lettuce/linen-fabric.webp"), + url("https://cdn.saya.uz/stats/themes/lettuce/lettuce-tiles.svg"); --interlocked-hexagons-background-repeat: repeat, repeat; --interlocked-hexagons-background-size: 11dvh, 20dvh; --squares-and-triangles-background: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%23bbd095%22%2F%3E%3Cpath%20fill%3D%22%23f5ede1%22%20d%3D%22M0%200v6.77L6.72%200zm8.91%200L0%208.96v3.86L12.83%200zm6.11%200L0%2015.06v3.87L18.9%200zm6.11%200L0%2021.12v3.92L25%200zM25%200l25%2024.99v-3.87L28.87%200zm25%2024.99%2025%2024.98L100%2025%2075%200zm-50%20.05v3.87l21.13%2021.12H25l-25-25zm25%2024.99h3.87L50%2028.9v-3.87zm0%200L0%2075l25%2025%2025-24.99zM50%2075l25-24.98h-3.87L50%2071.14v3.87zm0%200v3.87L71.13%20100H75L50%2075.01zm25%2025h3.87L100%2078.88v-3.87zm25-24.99v-3.87L78.87%2050.03H75L100%2075zM31.1%200%2050%2018.93v-3.87L34.98%200h-3.87zm6.07%200L50%2012.82V8.96L41.04%200zm6.1%200L50%206.77V0h-6.72zM25%202.19%202.14%2025.04H25zm0%2022.85v22.8l22.81-22.8zM75%203.92l21.13%2021.12L75%2046.16%2053.87%2025.04zm0%202.19L56.06%2025.04l18.89%2018.88%2018.94-18.88L75%206.1zm0%203.92%2015.07%2015L75%2040.06%2059.98%2025.04zm0%202.18L62.17%2025.04%2075%2037.86l12.83-12.82L75%2012.2zm0%203.82%208.96%209L75%2033.95%2066.04%2025zm0%202.24-6.72%206.77L75%2031.76l6.77-6.72h-.05zM0%2031.09v3.87l15.02%2015.07h3.87zm50%200L31.1%2050.03h3.88L50%2034.96V31.1zM0%2037.2v3.87l8.91%208.96h3.92zm50%200L37.17%2050.03h3.87L50%2041.07zM0%2043.3v6.73h6.72zm50%200-6.72%206.73H50zm0%206.73v6.71l6.72-6.71zm8.96%200L50%2058.93v3.92l12.83-12.82zm6.06%200L50%2065.03v3.88l18.94-18.88zm16.09%200L100%2068.9v-3.87l-15.02-15zm6.11%200L100%2062.85v-3.92l-8.91-8.9zm6.06%200%206.72%206.71v-6.71zM75%2052.2%2052.19%2075H75zM75%2075v22.8L97.81%2075zM25%2053.9l21.13%2021.12L25%2096.13%203.87%2075.01zm0%202.2L6.1%2074.95l18.95%2018.88%2018.9-18.88L25%2056.08zm0%203.9%2015.02%2015.01L25%2090.03%209.98%2075zm0%202.19L12.17%2075%2025%2087.84%2037.83%2075%2025%2062.2zm0%203.87L33.96%2075%2025%2083.97%2016.04%2075zm0%202.23-6.72%206.72L25%2081.73l6.72-6.72L25%2068.3zm25%2012.83v3.87L65.02%20100h3.87zm50%200L81.1%20100h3.88L100%2084.99zm-50%206.06v3.86l8.96%208.96h3.87zm50%200L87.22%20100h3.87l8.91-8.96zm-50%206.1V100h6.72zm50%200L93.28%20100H100z%22%2F%3E%3C%2Fsvg%3E"); @@ -335,10 +347,10 @@ body { --color-leaflet-text: var(--color-background-dark); --color-leaflet-link: var(--color-background-dark); --color-leaflet-link-hover: var(--color-background-light); - --interlocked-hexagons-background: url("https://storage.yandexcloud.kz/sayauz-static/themes/ram/ram-background.webp"); + --interlocked-hexagons-background: url("https://cdn.saya.uz/stats/themes/ram/ram-background.webp"); --interlocked-hexagons-background-repeat: repeat; --interlocked-hexagons-background-size: auto; - --squares-and-triangles-background: url("https://storage.yandexcloud.kz/sayauz-static/themes/ram/ram-sidebar.webp"); + --squares-and-triangles-background: url("https://cdn.saya.uz/stats/themes/ram/ram-sidebar.webp"); --squares-and-triangles-background-repeat: repeat; --squares-and-triangles-background-width-coef: 5; --squares-and-triangles-background-height-coef: 15; @@ -390,7 +402,7 @@ body { --squares-and-triangles-background-repeat: repeat; --squares-and-triangles-background-width-coef: 5; --squares-and-triangles-background-height-coef: 5; - --split-right-background: url("https://storage.yandexcloud.kz/sayauz-static/themes/lilac/split-right-light.webp"); + --split-right-background: url("https://cdn.saya.uz/stats/themes/lilac/split-right-light.webp"); } @media (prefers-color-scheme: dark) { @@ -406,7 +418,7 @@ body { --color-background-medium: var(--color-main-dark-medium); --color-background-light: var(--color-main-dark-soft); --color-sidebar-stroke: var(--color-main-dark-hard); - --split-right-background: url("https://storage.yandexcloud.kz/sayauz-static/themes/lilac/split-right-dark.webp"); + --split-right-background: url("https://cdn.saya.uz/stats/themes/lilac/split-right-dark.webp"); } } @@ -527,14 +539,6 @@ form.crossable.htmx-request input { calc(var(--squares-and-triangles-background-height-coef) * 1rem); } -.inset-shadow { - box-shadow: - inset 0dvh 1dvh 1dvh -1dvh rgba(75, 81, 58, 0.5), - inset 1dvh 0dvh 1dvh -1dvh rgba(75, 81, 58, 0.5), - inset -1dvh 0dvh 1dvh -1dvh rgba(75, 81, 58, 0.5), - inset 0dvh -0.5dvh rgba(75, 81, 58, 0.5); -} - .text-stroke-\(--color-sidebar-stroke\) { -webkit-text-stroke-color: var(--color-sidebar-stroke); } @@ -547,7 +551,7 @@ form.crossable.htmx-request input { position: fixed; width: 12rem; height: 12rem; - box-shadow: 0.25rem 0.25rem 0.25rem var(--color-main-soft); + box-shadow: 0px 0.3125rem 0.3125rem -0.1875rem rgba(0, 0, 0, 0.2), 0px 0.5rem 0.625rem 0.0625rem rgba(0, 0, 0, 0.14), 0px 0.1875rem 0.875rem 0.125rem rgba(0, 0, 0, 0.12); border-radius: 50%; opacity: 0; visibility: hidden; @@ -582,7 +586,7 @@ form.crossable.htmx-request input { .theme-icon i { position: absolute; - filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3)); + filter: drop-shadow(0 0.125rem 0.25rem rgba(0, 0, 0, 0.3)); z-index: 1; } @@ -608,7 +612,7 @@ form.crossable.htmx-request input { box-shadow: 0rem 0rem 1rem var(--color-main-hard); border: 0.5rem solid color-mix(in srgb, var(--color-main-soft) 90%, transparent); - backdrop-filter: blur(2px); + backdrop-filter: blur(0.125rem); top: 35%; left: 35%; width: 30%; @@ -633,6 +637,10 @@ form.crossable.htmx-request input { transition-timing-function: var(--ease-in-out); } +.accent-button { + box-shadow: 0px 0.1875rem 0.1875rem -0.125rem rgba(0, 0, 0, 0.2), 0px 0.1875rem 0.25rem 0px rgba(0, 0, 0, 0.14), 0px 0.0625rem 0.5rem 0px rgba(0, 0, 0, 0.12); +} + .accent-button:hover, .accent-button.active:active:hover { cursor: pointer; @@ -646,6 +654,7 @@ form.crossable.htmx-request input { --tw-gradient-position: from 45deg in oklab; --tw-gradient-to: color-mix(in oklab, var(--color-accent-dark) 30%, transparent); --tw-gradient-via: color-mix(in oklab, var(--color-accent-light) 50%, transparent); + box-shadow: inset 0px 0.25rem 0.5rem 0px rgba(0, 0, 0, 0.25); } .accent-button.active:hover, @@ -661,7 +670,7 @@ form.crossable.htmx-request input { } .custom-svg-marker svg { - filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.3)); + filter: drop-shadow(0.125rem 0.125rem 0.125rem rgba(0, 0, 0, 0.3)); transition: transform 0.2s; } @@ -717,6 +726,15 @@ form.crossable.htmx-request input { width: 10rem; } +.grid-item picture { + mask-image: + linear-gradient(to right, #FFFFFF calc(100% - 0.5rem), transparent 100%), + linear-gradient(to left, #FFFFFF calc(100% - 0.5rem), transparent 100%), + linear-gradient(to top, #FFFFFF calc(100% - 0.5rem), transparent 100%), + linear-gradient(to bottom, #FFFFFF calc(100% - 0.5rem), transparent 100%); + mask-composite: intersect; +} + .grid-sizer { width: 10.5rem; } @@ -740,6 +758,10 @@ form.crossable.htmx-request input { opacity: 0; } +.grid-item:hover picture { + mask-image: none; +} + .grid-item-2x { width: 20rem; } @@ -855,6 +877,15 @@ form.crossable.htmx-request input { color: var(--color-main-medium) !important; } +.leaflet-marker-icon.marker-cluster { + color: var(--color-main-dark-hard) !important; +} + +.leaflet-attribution-flag { + visibility: hidden; + width: 0; +} + .home-page-heart { position: absolute; color: var(--color-pink-700); @@ -873,8 +904,8 @@ form.crossable.htmx-request input { } .ram-frame { - width: 20vmin; - height: 20vmin; + width: 8rem; + height: 8rem; user-select: none; -webkit-user-select: none; } diff --git a/views/layouts/general-page.html b/views/layouts/general-page.html index e380ef3..06ef625 100644 --- a/views/layouts/general-page.html +++ b/views/layouts/general-page.html @@ -14,7 +14,7 @@ {{end}} /> {{- if eq .Property "og:title" }} - <title>{{.Content}} // SAYA.UZ</title> + <title>{{.Content}}</title> {{- end }} {{- if eq .Property "og:description" }} <meta name="description" content="{{.Content}}" /> {{- end }} {{- if eq .Property "og:url" }} @@ -22,15 +22,23 @@ {{- end }} {{- end }} <link rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/fonts/fonts.css" + href="{{ .StaticStorage.BaseUrl }}/fonts/fonts.css" /> <link rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/libs/leaflet/1.9.4/leaflet.css" + href="{{ .StaticStorage.BaseUrl }}/libs/leaflet/1.9.4/leaflet.css" /> <link rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/libs/glightbox/3.3.0/css/glightbox.min.css" + href="{{ .StaticStorage.BaseUrl }}/libs/leaflet.markercluster/1.4.1/MarkerCluster.css" + /> + <link + rel="stylesheet" + href="{{ .StaticStorage.BaseUrl }}/libs/leaflet.markercluster/1.4.1/MarkerCluster.Default.css" + /> + <link + rel="stylesheet" + href="{{ .StaticStorage.BaseUrl }}/libs/glightbox/3.3.0/css/glightbox.min.css" /> <link rel="stylesheet" href="/output.css" /> <link @@ -55,6 +63,11 @@ {{ . }} </script> {{- end }} + <script src="{{ .StaticStorage.BaseUrl }}/libs/htmx/2.0.6/htmx.min.js"></script> + <script src="{{ .StaticStorage.BaseUrl }}/libs/leaflet/1.9.4/leaflet.js"></script> + <script src="{{ .StaticStorage.BaseUrl }}/libs/protomaps-leaflet/5.0.0/dist/protomaps-leaflet.js"></script> + <script src="{{ .StaticStorage.BaseUrl }}/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.js"></script> + <script src="{{ .StaticStorage.BaseUrl }}/libs/glightbox/3.3.0/js/glightbox.min.js"></script> </head> <body @@ -160,8 +173,6 @@ } </script> - <script src="{{ .StaticStorageBaseUrl }}/libs/glightbox/3.3.0/js/glightbox.min.js"></script> - <script> function sfc32(a, b, c, d) { return function () { @@ -202,18 +213,8 @@ var seed = cyrb128("{{ .Title }}"); var rand = sfc32(seed[0], seed[1], seed[2], seed[3]); - function calculateVmin(percent) { - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - - const smallerDimension = Math.min( - viewportWidth, - viewportHeight, - ); - - const vminValue = (smallerDimension / 100) * percent; - - return vminValue; + function calculateRem(remNum) { + return remNum * parseFloat(getComputedStyle(document.documentElement).fontSize); } const _probe = document.createElement('div'); @@ -308,7 +309,7 @@ theme = document.body.getAttribute("data-theme"); } - vmin = calculateVmin(1); + rem = calculateRem(1); var clWidth = document.documentElement.clientWidth; var clHeight = document.documentElement.clientHeight; @@ -327,11 +328,11 @@ "z-0", "absolute", ); - const x = Math.floor(rand() * clWidth); - const y = Math.floor(rand() * clHeight * 0.8); const rot = rand() * 90; const animDuration = rand() * 10 + 3; const sz = Math.floor(rand() * 8) + 12; + const x = Math.floor(rand() * clWidth - 1.42 * sz); // 1.42 ~ sqrt(2) + const y = Math.floor(rand() * clHeight * 0.8); star.style.transform = `translateX(${x}px) translateY(${y}px) rotate(${rot}deg)`; star.style.animationDuration = `${animDuration}s`; star.style.fontSize = `${sz}px`; @@ -342,7 +343,7 @@ const frameCenters = new Array(4); for (i = 1; i <= 4; i++) { let frame = document.createElement("img"); - frame.src = `{{ .StaticStorageBaseUrl }}/themes/ram/ram-frame${i}.webp`; + frame.src = `{{ .StaticStorage.BaseUrl }}/themes/ram/ram-frame${i}.webp`; frame.classList.add( "ram-frame", "theme-decor", @@ -351,14 +352,14 @@ ); let x, y; outerLoop: while (true) { - x = Math.floor(rand() * clWidth - 20 * vmin); - y = Math.floor(rand() * clHeight - 20 * vmin); + x = Math.floor(rand() * clWidth - 22.64 * rem) + 11.32 * rem; // 11.32 ~ 8 * sqrt(2) + y = Math.floor(rand() * clHeight - 22.64 * rem) + 11.32 * rem; for (j = 0; j < i - 1; j++) { const xd = frameCenters[j].x - x; const yd = frameCenters[j].y - y; if ( Math.sqrt(xd * xd + yd * yd) < - 30 * vmin + 23 * rem ) { continue outerLoop; } @@ -401,6 +402,7 @@ localStorage.setItem("theme", theme); document.body.setAttribute("data-theme", theme); switchThemeDecor(theme); + switchColorScheme(matchMedia('(prefers-color-scheme: dark)').matches); } const savedTheme = localStorage.getItem("theme") || "lilac"; @@ -419,11 +421,9 @@ mq.addEventListener('change', (e) => switchColorScheme(e.matches)); </script> - <script src="{{ .StaticStorageBaseUrl }}/libs/htmx/2.0.6/htmx.min.js"></script> - <script src="{{ .StaticStorageBaseUrl }}/libs/leaflet/1.9.4/leaflet.js"></script> - <script src="{{ .StaticStorageBaseUrl }}/libs/protomaps-leaflet/5.0.0/dist/protomaps-leaflet.js"></script> - <script src="{{ .StaticStorageBaseUrl }}/libs/imagesloaded/5.0.0/imagesloaded.pkgd.min.js"></script> - <script src="{{ .StaticStorageBaseUrl }}/libs/packery/3.0.0/dist/packery.pkgd.min.js"></script> + <script src="{{ .StaticStorage.BaseUrl }}/libs/htmx/2.0.6/htmx.min.js"></script> + <script src="{{ .StaticStorage.BaseUrl }}/libs/imagesloaded/5.0.0/imagesloaded.pkgd.min.js"></script> + <script src="{{ .StaticStorage.BaseUrl }}/libs/packery/3.0.0/dist/packery.pkgd.min.js"></script> <script> // Enable fitWidth-like attribute for Packery. Source: https://codepen.io/desandro/pen/kVmLYz @@ -628,15 +628,15 @@ class="flex flex-1 min-h-0 z-1 bg-paper bg-background shadow-elevation-3 @container/body" > <div - class="bg-paper bg-background-dark border-r-2 border-dashed border-main-hard" + class="bg-paper bg-background-dark border-r-[0.25rem] border-dashed border-main-hard" ></div> <div - class="max-h-full max-w-full flex flex-col grow bg-paper bg-background-dark relative pt-4 md:pr-4" + class="max-h-full max-w-full flex flex-col grow bg-paper bg-background-dark relative md:pr-4" > <header id="general-page-header" - class="max-xs:hidden [@media(max-height:32rem)]:hidden flex flex-col ml-8 basis-8" + class="max-xs:hidden [@media(max-height:32rem)]:hidden pt-4 flex flex-col ml-8 basis-8" >{{ .RenderedHeader }}</header> <div id="general-page-header-trigger" @@ -647,12 +647,12 @@ ></div> <hr - class="max-xs:hidden [@media(max-height:32rem)]:hidden border-t-4 border-dotted border-main-hard mb-2" + class="max-xs:hidden [@media(max-height:32rem)]:hidden border-t-[0.5rem] border-dotted border-main-hard mb-2" /> <main id="general-page-body" - class="relative flex-1 overflow-y-auto @container-size" + class="relative flex-1 overflow-y-auto @container-size inset-shadow-elevation-8" >{{ .RenderedBody }}</main> <div id="general-page-body-trigger" @@ -674,14 +674,10 @@ hx-target="#general-page-footer" hx-swap="innerHTML" ></div> - - <hr - class="border-t-4 border-dotted border-main-hard mb-2" - /> </div> <div - class="bg-paper bg-background-dark shadow-2xl border-l-2 border-dotted border-main-hard" + class="bg-paper bg-background-dark shadow-2xl border-l-[0.25rem] border-dotted border-main-hard" ></div> </div> </div> @@ -708,17 +704,6 @@ } }); - 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 sliceAngle = 360 / themeIcons.length; diff --git a/views/messages/new-post.html b/views/messages/new-post.html index e956253..4e586e2 100644 --- a/views/messages/new-post.html +++ b/views/messages/new-post.html @@ -2,7 +2,7 @@ <p>{{ l $.Lang "Mail" "NewPost" "Intro" }}</p> <table> <tr> - <td rowspan="3"><a href="https://{{ .ClientHost }}/{{ .Lang }}/blog/{{ .Post.FileName }}"><img src="{{ printf .ThumbnailBaseUrl .Post.Metadata.Thumbnail }}"></a></td> + <td rowspan="3"><a href="https://{{ .ClientHost }}/{{ .Lang }}/blog/{{ .Post.FileName }}"><img src="{{ printf .PhotoStorage.Thumbnail320p.BaseUrl .Post.Metadata.Thumbnail }}"></a></td> <td class="darkened" style="font-size: 24px"><a style="color: #273de1 !important;" href="https://{{ .ClientHost }}/{{ .Lang }}/blog/{{ .Post.FileName }}">{{ .Post.Metadata.Title }}</a></td> </tr> <tr> diff --git a/views/pages/blog-catalogue.html b/views/pages/blog-catalogue.html index b2bb0a3..ca8f578 100644 --- a/views/pages/blog-catalogue.html +++ b/views/pages/blog-catalogue.html @@ -45,50 +45,50 @@ </symbol> </svg> <div class="flex md:flex-row flex-col text-base h-full"> - <div class="relative flex shrink-0 w-full max-h-[30%] md:min-w-1/10 md:max-w-1/4 md:max-h-full md:h-full md:w-fit"> - <div class="absolute w-full h-full inset-shadow-[0_0_0.4rem_black] pointer-events-none z-20"></div> + <div class="relative flex shrink-0 w-full max-h-[30%] md:min-w-1/10 md:max-w-1/4 md:max-h-full md:h-full md:w-fit shadow-elevation-6"> + <div class="absolute w-full h-full pointer-events-none z-20"></div> <form hx-get="/api/v1/blog-search" hx-vals='{"lang": "{{ .Lang }}"}' hx-target=".blog-cards" hx-swap="innerHTML" - class="tags-list relative flex flex-col w-full bg-paper bg-background-light"> - <div class="flex flex-col overflow-y-auto"> + class="tags-list relative flex flex-col w-full"> + <div class="flex flex-col bg-paper bg-background-light overflow-y-auto inset-shadow-elevation-6"> <fieldset> <legend class="font-bold w-full text-center">{{ l $.Lang "BlogSearch" "OrderByHeader" }}</legend> <div class="md:flex flex-col grid grid-cols-3 grid-rows-2 grid-flow-col"> - <div> + <div class="m-1"> <label class="flex justify-start gap-1 items-center"> <input type="radio" id="sortTitleAsc" name="sort" value="titleAsc" class="bg-accent-light border-transparent text-accent-deep focus:border-transparent focus:bg-accent-light focus:ring-1 focus:ring-offset-2 focus:ring-accent-deep" {{ if eq .QuerySort "titleAsc" }}checked{{ end }}> {{ l $.Lang "BlogSearch" "TitleOrdered" }} <svg viewBox="0 0 24 24" class="h-8"><use href="#icon-arrange-letter-asc"/></svg> </label> </div> - <div> + <div class="m-1"> <label class="flex justify-start gap-1 items-center"> <input type="radio" id="sortTitleDesc" name="sort" value="titleDesc" class="bg-accent-light border-transparent text-accent-deep focus:border-transparent focus:bg-accent-light focus:ring-1 focus:ring-offset-2 focus:ring-accent-deep" {{ if eq .QuerySort "titleDesc" }}checked{{ end }}> {{ l $.Lang "BlogSearch" "TitleOrdered" }} <svg viewBox="0 0 24 24" class="h-8"><use href="#icon-arrange-letter-desc"/></svg> </label> </div> - <div> + <div class="m-1"> <label class="flex justify-start gap-1 items-center"> <input type="radio" id="sortActionDateAsc" name="sort" value="actionDateAsc" class="bg-accent-light border-transparent text-accent-deep focus:border-transparent focus:bg-accent-light focus:ring-1 focus:ring-offset-2 focus:ring-accent-deep" {{ if eq .QuerySort "actionDateAsc" }}checked{{ end }}> {{ l $.Lang "BlogSearch" "ActionDateOrdered" }} <svg viewBox="0 0 24 24" class="h-8"><use href="#icon-arrange-number-asc"/></svg> </label> </div> - <div> + <div class="m-1"> <label class="flex justify-start gap-1 items-center"> <input type="radio" id="sortActionDateDesc" name="sort" value="actionDateDesc" class="bg-accent-light border-transparent text-accent-deep focus:border-transparent focus:bg-accent-light focus:ring-1 focus:ring-offset-2 focus:ring-accent-deep" {{ if eq .QuerySort "actionDateDesc" }}checked{{ end }}> {{ l $.Lang "BlogSearch" "ActionDateOrdered" }} <svg viewBox="0 0 24 24" class="h-8"><use href="#icon-arrange-number-desc"/></svg> </label> </div> - <div> + <div class="m-1"> <label class="flex justify-start gap-1 items-center"> <input type="radio" id="sortPublicationDateAsc" name="sort" value="publicationDateAsc" class="bg-accent-light border-transparent text-accent-deep focus:border-transparent focus:bg-accent-light focus:ring-1 focus:ring-offset-2 focus:ring-accent-deep" {{ if eq .QuerySort "publicationDateAsc" }}checked{{ end }}> {{ l $.Lang "BlogSearch" "PublicationDateOrdered" }} <svg viewBox="0 0 24 24" class="h-8"><use href="#icon-arrange-number-asc"/></svg> </label> </div> - <div> + <div class="m-1"> <label class="flex justify-start gap-1 items-center"> <input type="radio" id="sortPublicationDateDesc" name="sort" value="publicationDateDesc" class="bg-accent-light border-transparent text-accent-deep focus:border-transparent focus:bg-accent-light focus:ring-1 focus:ring-offset-2 focus:ring-accent-deep" {{ if eq .QuerySort "publicationDateDesc" }}checked{{ end }}> {{ l $.Lang "BlogSearch" "PublicationDateOrdered" }} @@ -111,23 +111,20 @@ </div> </fieldset> </div> - <hr class="border-t-2 border-dotted border-main-hard my-0.5"> - <div class="flex flex-row bg-paper bg-background-dark p-0.5 rounded-1 h-12"> - <button type="button" onclick="defaultSearchParams(); cleanHrefParams();" class="flex flex-1 justify-center text-main-hard bg-paper bg-accent-light cursor-pointer hover:bg-accent-deep active:text-background-dark transition-colors duration-500"> - <svg viewBox="0 0 24 24"><use href="#icon-camera-settings-flip"/></svg> + <hr class="border-t-[0.375rem] border-dotted border-main-hard my-2 mx-auto w-24"> + <div class="flex flex-row p-0.5 rounded-1 h-12"> + <button type="button" onclick="defaultSearchParams(); cleanHrefParams();" class="accent-button grow h-full"> + <svg class="h-full mx-auto" viewBox="0 0 24 24"><use href="#icon-camera-settings-flip"/></svg> </button> - <div class="w-1 bg-paper bg-background-light grow-0"></div> - <button type="submit" onclick="setHrefParams();" class="flex flex-1 justify-center text-main-hard bg-paper bg-accent-light cursor-pointer hover:bg-accent-deep active:text-background-dark transition-colors duration-500"> - <svg viewBox="0 0 24 24"><use href="#icon-search-magnifier"/></svg> + <button type="submit" onclick="setHrefParams();" class="accent-button grow h-full"> + <svg class="h-full mx-auto" viewBox="0 0 24 24"><use href="#icon-search-magnifier"/></svg> </button> </div> </form> </div> - <div class="w-[0.5dvh] border-r-2 border-dotted border-main-hard bg-paper bg-background-dark"></div> - <div class="relative flex grow h-full"> - <div class="absolute w-full h-full inset-0 inset-shadow-[0_0_0.4rem_black] pointer-events-none z-20"></div> + <div class="absolute w-full h-full inset-0 inset-shadow-elevation-6 pointer-events-none z-20"></div> <div hx-get="/api/v1/blog-search" hx-vals='js:{lang: "{{ .Lang }}", tz: clientTimeZone, highlight: "{{ .PreviousBlogPage }}"}' hx-target="this" hx-swap="innerHTML" hx-trigger="load" hx-include=".tags-list" class="blog-cards relative grow flex flex-col overflow-y-auto bg-paper bg-background-light pr-2"> Loading... diff --git a/views/pages/blog-page.html b/views/pages/blog-page.html index 4d8ba06..3d3c623 100644 --- a/views/pages/blog-page.html +++ b/views/pages/blog-page.html @@ -1,10 +1,9 @@ {{ define "body" }} <input class="htmx-exit-trigger" type="hidden" name="codename" value="{{ .Codename }}"> -<div class="sticky top-0 left-0 w-full h-[100cqb] -mb-[100cqb] inset-shadow-[0_0_0.4rem_black] pointer-events-none z-20"></div> -<article class="relative bg-paper bg-background-light flex flex-col px-8 py-4 overflow-y-auto"> - <div class="flex flex-col sm:flex-row h-[30cqb] sm:h-[15cqb]"> - <div class="grow shrink-2 flex-1 overflow-y-auto -ml-8 -mt-4 {{if .Medley}}max-sm:-mr-8{{else}}-mr-8{{end}} z-1 shadow-elevation-2"> - <div class="multitone min-h-full" style="--multitone-bg: url({{ printf $.ThumbnailBaseUrl .Thumbnail }});"> +<div class="relative bg-paper bg-background-light flex flex-col py-4 overflow-y-auto"> + <div class="flex flex-col sm:flex-row h-[30cqb] sm:h-[15cqb] shadow-elevation-6"> + <div class="grow shrink-2 flex-1 overflow-y-auto -mt-4 z-1 shadow-elevation-3"> + <div class="multitone min-h-full" style="--multitone-bg: url({{ printf $.PhotoStorage.Thumbnail560p.BaseUrl .Thumbnail }});"> <div class="ml-8 py-4"> <h1 class="max-xs:flex flex-row content-center [@media(max-height:32rem)]:block hidden font-gentium text-2xl font-bold italic text-main-hard tracking-[.0125rem] mb-1"> <div hx-get="/api/v1/like" hx-target="this" hx-swap="outerHTML" hx-trigger="load"></div> @@ -21,13 +20,12 @@ </div> </div> </div> - <hr class="block sm:hidden border-t-3 bg-paper bg-background-dark border-dotted border-main-hard w-full"> + <hr class="block sm:hidden border-t-[0.1875rem] bg-paper bg-background-dark border-dotted border-main-hard w-full"> {{- if .Medley }} - <div class="overflow-y-auto grow-2 shrink-0 flex-1 bg-paper bg-background-dark shadow-elevation-2 -mr-8 sm:-mt-4 max-sm:-ml-8 z-2"> - <h2 class="text-2xl font-gentium font-extrabold text-main-hard tracking-[.0125rem] ml-2"> + <div class="overflow-y-auto grow-2 shrink-0 flex-1 bg-paper bg-background-dark shadow-elevation-3 sm:-mt-4 z-2"> + <h2 class="text-2xl font-gentium font-extrabold text-main-hard tracking-[.0125rem] ml-2 mb-2 border-b-[0.25rem] border-dotted w-fit"> {{ l .Lang "Medleys" .Medley }} </h2> - <hr class="block border-t-2 my-1 border-dotted border-main-hard w-full"> <div hx-get="/api/v1/blog-search" hx-vals='js:{lang: "{{ .Lang }}", tz: clientTimeZone, medley: "{{ .Medley }}", sort: "medley", hideTags: true, hidePublishedTime: true, highlight: "{{ .Codename }}"}' hx-target="this" hx-swap="innerHTML" hx-trigger="load" class="grow flex flex-col pr-2"> Loading... @@ -35,52 +33,18 @@ </div> {{- end }} </div> - <hr class="block border-t-3 mb-2 border-dotted border-main-hard w-full ml-auto mr-auto"> - {{ .ParsedMarkdown }} + <hr class="block border-t-[0.375rem] w-24 mx-auto my-4 border-dotted border-main-hard"> + <article class="px-8 flex flex-col"> + {{ .ParsedMarkdown }} + </article> {{- if .MapLocationX }} - <hr class="border-t-3 border-dotted border-main-hard mt-1 mb-2 w-[80%] ml-auto mr-auto"> - <div id="map-container" class="relative p-1 flex-none ml-auto mr-auto w-[80%] lg:w-[60%] h-[30dvh] md:h-[40dvh]"></div> - <hr class="border-t-3 border-dotted border-main-hard mt-1 mb-2 w-[80%] ml-auto mr-auto"> + <hr class="border-t-[0.375rem] border-dotted border-main-hard my-2 w-24 mx-auto"> + <div id="map-outer-container" class="relative p-1 flex-none mx-auto w-[80%] lg:w-[60%] h-[30dvh] md:h-[40dvh]" + hx-get="/api/v1/map" hx-vals='{"lang": "{{ .Lang }}", "codename": "{{ .Codename }}", "zoom": 8}' hx-target="this" hx-swap="innerHTML" hx-trigger="load"> + </div> + <hr class="border-t-[0.375rem] border-dotted border-main-hard my-2 w-24 mx-auto"> <script> - function initLocationMap(id, x, y, relativeErrorMeters = 0) { - map = L.map(id).setView([x, y], 8); - - protomapsL.leafletLayer({ - url: 'https://cdn.saya.uz/map/global.pmtiles', - maxZoom: 15, - flavor: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', - lang: '{{ .Lang }}' - }).addTo(map); - - if (relativeErrorMeters != 0) { - var circle = L.circle([x, y], { - color: '#8a9d8a', - fillColor: '#8a9d8a', - fillOpacity: 0.15, - radius: relativeErrorMeters - }).addTo(map); - } - - var marker = L.marker([x, y], { - title: '{{ .Title }}', - }).addTo(map) - .bindPopup('<span><b>{{ .Title }}</b><br><a href="https://www.openstreetmap.org/#map=13/{{ .MapLocationX }}/{{ .MapLocationY }}">{{ .MapLocationX }}, {{ .MapLocationY }}</a></span>') - .openPopup(); - } - - if (map) { - map.remove(); - map = null; - } - - setTimeout(() => initLocationMap( - 'map-container', - {{ .MapLocationX }}, - {{ .MapLocationY }}, - {{ .MapLocationAreaMeters }} - ), 1000); - var lightbox = GLightbox({ selector: '.glightbox', moreLength: 0 diff --git a/views/pages/global-map.html b/views/pages/global-map.html index f460d95..4d1853f 100644 --- a/views/pages/global-map.html +++ b/views/pages/global-map.html @@ -19,7 +19,7 @@ <link rel="canonical" href="{{ .CanonicalEndpoint }}/{{ .Lang }}/map" /> <link rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/fonts/fonts.css" + href="{{ .StaticStorage.BaseUrl }}/fonts/fonts.css" /> {{ block "header" . }}{{ end }} <link rel="stylesheet" href="/output.css" /> @@ -42,16 +42,20 @@ /> <link rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/libs/leaflet/1.9.4/leaflet.css" + href="{{ .StaticStorage.BaseUrl }}/libs/leaflet/1.9.4/leaflet.css" /> <link rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/libs/leaflet.markercluster/1.4.1/MarkerCluster.css" + href="{{ .StaticStorage.BaseUrl }}/libs/leaflet.markercluster/1.4.1/MarkerCluster.css" /> <link rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/libs/leaflet.markercluster/1.4.1/MarkerCluster.Default.css" + href="{{ .StaticStorage.BaseUrl }}/libs/leaflet.markercluster/1.4.1/MarkerCluster.Default.css" /> + <script src="{{ .StaticStorage.BaseUrl }}/libs/htmx/2.0.6/htmx.min.js"></script> + <script src="{{ .StaticStorage.BaseUrl }}/libs/leaflet/1.9.4/leaflet.js"></script> + <script src="{{ .StaticStorage.BaseUrl }}/libs/protomaps-leaflet/5.0.0/dist/protomaps-leaflet.js"></script> + <script src="{{ .StaticStorage.BaseUrl }}/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.js"></script> </head> <body @@ -216,21 +220,14 @@ </div> </nav> - <div - id="map-container" - class="bg-paper bg-background-dark flex z-10 w-full h-full" - ></div> - - <script src="{{ .StaticStorageBaseUrl }}/libs/htmx/2.0.6/htmx.min.js"></script> - <script src="{{ .StaticStorageBaseUrl }}/libs/leaflet/1.9.4/leaflet.js"></script> - <script src="{{ .StaticStorageBaseUrl }}/libs/protomaps-leaflet/5.0.0/dist/protomaps-leaflet.js"></script> - <script src="{{ .StaticStorageBaseUrl }}/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.js"></script> + <div id="map-outer-container" class="bg-paper bg-background-dark flex z-10 w-full h-full" + hx-get="/api/v1/map" hx-vals='{"lang": "{{ .Lang }}", "codename": "{{ .Codename }}", "zoom": 4, "zoomPosition": "topright"}' hx-target="this" hx-swap="innerHTML" hx-trigger="load"> + </div> <script> const themeWheel = document.getElementById("theme-wheel"); const themeButton = document.getElementById("sidebar-theme-button"); const themeIcons = document.querySelectorAll(".theme-icon"); - const THUMB_BASE = "{{ .ThumbnailBaseUrl }}"; themeIcons.forEach((icon) => { if (icon.getAttribute("data-theme") == savedTheme) { @@ -251,6 +248,7 @@ function positionThemeIcons() { const sliceAngle = 360 / themeIcons.length; + const themeWheelRadius = convertRemToPixels(6); themeIcons.forEach((icon, index) => { const startAngle = index * sliceAngle; @@ -368,96 +366,5 @@ } }); </script> - - <script> - var map; - var markers = L.markerClusterGroup(); - - function createCustomIcon(color, borderColor = "var(--color-main-soft)") { - const svgIcon = ` - <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> - <path d="M12 1C7.5 1 4 4.5 4 9c0 6.5 8 14 8 14s8-7.5 8-14c0-4.5-3.5-8-8-8zm0 11c-1.5 0-3-1.5-3-3s1.5-3 3-3 3 1.5 3 3-1.5 3-3 3z" - fill="${color}" - stroke="${borderColor}" - stroke-width="1" - stroke-linejoin="round"/> - </svg> - `; - - return L.divIcon({ - html: svgIcon, - className: 'custom-svg-marker', - iconSize: [32, 32], - iconAnchor: [16, 32], - popupAnchor: [0, -32] - }); - } - - function initLocationMap() { - map = L.map('map-container').setView([{{ .MapLocationLat }}, {{ .MapLocationLong }}], 4); - - protomapsL.leafletLayer({ - url: 'https://cdn.saya.uz/map/global.pmtiles', - maxZoom: 15, - flavor: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', - lang: '{{ .Lang }}' - }).addTo(map); - - L.control.zoom({ - position: 'topright' - }).addTo(map); - - map.addLayer(markers); - } - - function markerToHsl(str, newCoef) { - let hash = 0; - for (let i = 0; i < str.length / 2; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); - hash = str.charCodeAt(str.length - i - 1) + ((hash << 5) - hash); - hash %= 360; - } - - return [hash, (20 + newCoef * 80).toFixed(0), (20 + newCoef * 75).toFixed(0)]; - } - - function mapAddMarker(x, y, relativeErrorMeters = 0, title = "", link = "", thumbnail = "", newCoef = 1) { - const [h, s, l] = markerToHsl(title, newCoef); - const color = `hsl(${h} ${s}% ${l}%)`; - - if (relativeErrorMeters != 0) { - var circle = L.circle([x, y], { - color: color, - fillColor: color, - fillOpacity: 0.15, - radius: relativeErrorMeters - }).addTo(map); - } - - var marker = L.marker([x, y], { - icon: createCustomIcon(color), - title: title, - }).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-12 h-12 mr-1" src="${THUMB_BASE.replace('%s', thumbnail)}"> - <span><b><a href="${link}">${title}</a></b><br><a href="https://www.openstreetmap.org/#map=13/${x}/${y}">${x}, ${y}</a></span> - </div>`); - - markers.addLayer(marker); - } - - document.addEventListener('DOMContentLoaded', () => { - initLocationMap(); - {{- range .MapMarkers }} - mapAddMarker({{ .Lat }}, {{ .Long }}, {{ .AccuracyMeters }}, {{ .Title }}, {{ .PageLink }}, {{ .Thumbnail }}, {{ fdiv .Index (len $.MapMarkers) }}); - {{- end }} - }); - - window.addEventListener('resize', function() { - setTimeout(() => { - map.invalidateSize(); - }, 100); - }); - </script> </body> </html> diff --git a/views/pages/home-page.html b/views/pages/home-page.html index e89be39..d494a08 100644 --- a/views/pages/home-page.html +++ b/views/pages/home-page.html @@ -17,14 +17,13 @@ <path d="M4.59 15.35a39.94 39.94 0 0 0 -2.94 3.25 0.35 0.35 0 1 0 0.53 0.46c0.59 -0.6 1.69 -1.51 2.89 -2.58 0.63 -0.57 1.29 -1.17 1.9 -1.79s1.42 -1.52 2 -2.21 -0.14 -1 -0.82 -0.39c-0.52 0.46 -1.08 0.94 -1.67 1.47s-1.28 1.19 -1.89 1.79Z" fill="var(--color-accent-deep)" stroke-width="1"></path> </g></symbol> </svg> -<div class="absolute w-full h-full inset-0 inset-shadow-[0_0_0.4rem_black] pointer-events-none z-20"></div> <div class="relative bg-paper bg-background-light font-gentium flex flex-col sm:flex-row w-full h-full overflow-y-auto text-base/loose tracking-wide z-10"> <div class="sm:flex-3/5 justify-center content-start p-8"> <div class="flex flex-col justify-start items-start bg-paper bg-background-dark mb-8 shadow-[0.4rem_0.4rem_0.4rem_black]"> <p class="text-3xl/loose sm:text-4xl/loose lg:text-5xl/loose tracking-widest uppercase w-full font-m-plus font-thin italic text-left - border-l-4 border-solid border-background-dark + border-l-[0.25rem] border-solid border-background-dark bg-linear-to-r from-background-dark to-background-light">{{ l $.Lang "HomePage" "Hymn1" }}</p> - <hr class="w-full border-t-2 border-dotted border-main-hard mb-1"> + <hr class="w-full border-t-[0.125rem] border-dotted border-main-hard mb-1"> <p class="text-xl/loose sm:text-2xl/loose lg:text-3xl/loose -indent-8 ml-12 p-1">{{ l $.Lang "HomePage" "Hymn2" }}</p> <p class="text-xl/loose sm:text-2xl/loose lg:text-3xl/loose -indent-8 ml-12 p-1">{{ l $.Lang "HomePage" "Hymn3" }}</p> <p class="text-xl/loose sm:text-2xl/loose lg:text-3xl/loose -indent-8 ml-12 p-1">{{ l $.Lang "HomePage" "Hymn4" }}</p> @@ -32,9 +31,9 @@ <div class="flex flex-col justify-center items-center"> <p class="text-xl sm:text-2xl lg:text-3xl p-1 w-full font-m-plus font-thin italic text-left - border-l-1.5 border-solid border-background-dark + border-l-[0.125rem] border-solid border-background-dark bg-linear-to-r from-background-dark to-background-light">{{ l $.Lang "HomePage" "DidYouKnowThat" }}</p> - <hr class="w-full border-t-2 border-dotted border-main-hard mb-1"> + <hr class="w-full border-t-[0.125rem] border-dotted border-main-hard mb-1"> <ul class="w-full text-left list-[circle]"> <li class="ml-4 mb-1">{{ index .FunFacts 0 }}</li> <li class="ml-4 mb-1">{{ index .FunFacts 1 }}</li> @@ -46,9 +45,9 @@ <div class="sm:flex-2/5 justify-center content-start p-8"> <div class="flex flex-col justify-center items-start mb-4"> <p class="text-xl sm:text-2xl lg:text-3xl p-1 w-full font-m-plus font-thin italic text-left - border-l-1.5 border-solid border-background-dark + border-l-[0.125rem] border-solid border-background-dark bg-linear-to-r from-background-dark to-background-light">{{ l $.Lang "HomePage" "Contacts" }}</p> - <hr class="w-full border-t-2 border-dotted border-main-hard mb-1"> + <hr class="w-full border-t-[0.125rem] border-dotted border-main-hard mb-1"> <a href="https://t.me/EarlInisMona"> <svg class="w-4 h-4 mr-1 inline" viewBox="0 0 24 24"><use href="#icon-telegram"/></svg> <span class="font-bold">Telegram:</span> @@ -74,7 +73,7 @@ <div class="home-page-heart z-50">♡</div> {{- end }} <img src="{{ .GifUrl }}" - class="home-page-gif z-49 -rotate-12 max-w-[70%] max-h-[70%] border-4 border-background-dark border-inset"> + class="home-page-gif z-49 -rotate-12 max-w-[70%] max-h-[70%] border-[0.25rem] border-background-dark border-inset"> </div> <script> @@ -121,9 +120,9 @@ <div class="flex flex-col justify-center items-center"> <p class="text-xl sm:text-2xl lg:text-3xl p-1 w-full font-m-plus font-thin italic text-left - border-l-1.5 border-solid border-color-background-dark + border-l-[0.125rem] border-solid border-color-background-dark bg-linear-to-r from-background-dark to-background-light">{{ l $.Lang "HomePage" "SidebarDescription" }}</p> - <hr class="w-full border-t-2 border-dotted border-main-hard mb-1"> + <hr class="w-full border-t-[0.125rem] border-dotted border-main-hard mb-1"> <div class="w-full grid grid-cols-[1fr_4fr] content-center items-center [&>*:nth-child(4n+2)]:bg-background-medium/50 [&>*:nth-child(4n+3)]:bg-background-medium/50" style="container-type: inline-size;"> <svg viewBox="0 0 24 24" class="h-full w-[15cqi] context-center text-main-hard"><use href="#icon-back"/></svg> diff --git a/views/pages/language-pick.html b/views/pages/language-pick.html index f4119e4..9576a65 100644 --- a/views/pages/language-pick.html +++ b/views/pages/language-pick.html @@ -1,7 +1,7 @@ {{ define "body" }} <div class="flex flex-col justify-center text-3xl h-full"> {{- range .AvailableLanguages }} - <hr class="border-t-2 border-dotted border-main-hard"> + <hr class="border-t-[0.125rem] border-dotted border-main-hard"> <div class="nth-[2n+1_of_div]:bg-background-medium/50 nth-[2n_of_div]:bg-background-light/50 p-2.5 flex flex-row justify-center justify-items-center"> <a href="/{{ .Name }}" class="w-12 h-12 basis-12 shrink-0 mr-4"> <img onclick="return changeUrl('{{ .Name }}');" class="w-full h-full aspect-square cursor-pointer object-cover rounded-md select-none" src="{{ .Flag }}"> @@ -11,6 +11,6 @@ </a> </div> {{- end }} - <hr class="border-t-2 border-dotted border-main-hard"> + <hr class="border-t-[0.125rem] border-dotted border-main-hard"> </div> {{ end }} diff --git a/views/pages/user-page.html b/views/pages/user-page.html index dc4966b..a94dcb3 100644 --- a/views/pages/user-page.html +++ b/views/pages/user-page.html @@ -1,5 +1,5 @@ {{ define "body" }} -<div class="absolute w-full h-full inset-0 inset-shadow-[0_0_0.4rem_black] pointer-events-none z-20"></div> +<div class="absolute w-full h-full inset-0 inset-shadow-elevation-6 pointer-events-none z-20"></div> <div class="relative bg-paper bg-background-light flex flex-col px-8 py-4"> <form id="email-form" class="w-full mb-2 flex flex-col relative crossable" hx-get="/api/v1/email/is-in-verification" hx-target="#email-message" hx-swap="outerHTML" hx-trigger="load" hx-indicator="this"> @@ -8,7 +8,7 @@ <label class="block text-main-medium font-bold mb-1 z-22"> {{ l $.Lang "UserProfile" "EmailHeader" }} </label> - <input class="bg-background-medium appearance-none border-background-medium rounded ml-6 mr-4 py-2 px-4 text-main-medium leading-tight inset-shadow-[0_0_0.4rem_black] focus:outline-none focus:bg-background-light focus:text-main-hard z-22" + <input class="bg-background-medium appearance-none border-background-medium rounded ml-6 mr-4 py-2 px-4 text-main-medium leading-tight inset-shadow-elevation-6 focus:outline-none focus:bg-background-light focus:text-main-hard z-22" id="email-input" name="email" type="text" value="{{ .Email }}" placeholder="{{ l $.Lang "UserProfile" "TypeEmail" }}" maxlength="64" data-validated-email="{{ .Email }}" onchange="checkIfValidated(this);"> </div> @@ -18,9 +18,9 @@ {{ l $.Lang "UserProfile" "SendCodeButton" }} </button> </form> - <div id="email-message" class="bg-paper bg-background-dark text-main-soft z-26 w-full inset-shadow-[0_0_0.4rem_black] py-2 px-4 text-center"></div> + <div id="email-message" class="bg-paper bg-background-dark text-main-soft z-26 w-full inset-shadow-elevation-6 py-2 px-4 text-center"></div> - <hr class="my-2 border-t-4 border-dotted border-main-hard"> + <hr class="my-2 border-t-[0.25rem] border-dotted border-main-hard"> <form id="email-verification-form" class="w-full mb-2 flex flex-col relative hidden crossable" {{ if .EmailCode }}hx-post="/api/v1/email/verify" hx-target="#verification-message" hx-swap="outerHTML" hx-trigger="load" hx-indicator="this"{{ end }}> @@ -29,7 +29,7 @@ <label class="block text-main-medium font-bold ml-1 z-22"> {{ l $.Lang "UserProfile" "VerificationCodeHeader" }} </label> - <input class="bg-background-medium appearance-none border-background-medium rounded ml-6 mr-4 py-2 px-4 text-main-medium leading-tight inset-shadow-[0_0_0.4rem_black] focus:outline-none focus:bg-background-light focus:text-main-hard z-22" + <input class="bg-background-medium appearance-none border-background-medium rounded ml-6 mr-4 py-2 px-4 text-main-medium leading-tight inset-shadow-elevation-6 focus:outline-none focus:bg-background-light focus:text-main-hard z-22" name="email_code" type="text" value="{{ .EmailCode }}" placeholder="0123456789ABCDEF" maxlength="16" autocapitalize="characters"> </div> </div> @@ -38,12 +38,12 @@ {{ l $.Lang "UserProfile" "VerifyButton" }} </button> </form> - <div id="verification-message" class="bg-paper bg-background-dark text-main-soft z-26 w-full inset-shadow-[0_0_0.4rem_black] py-2 px-4 text-center"></div> + <div id="verification-message" class="bg-paper bg-background-dark text-main-soft z-26 w-full inset-shadow-elevation-6 py-2 px-4 text-center"></div> </div> -<hr class="my-4 border-t-4 border-dotted border-main-hard"> +<hr class="my-4 border-t-[0.25rem] border-dotted border-main-hard"> -<div class="bg-paper bg-background-light flex flex-col inset-shadow-[0_0_0.4rem_black] px-8 py-4"> +<div class="bg-paper bg-background-light flex flex-col inset-shadow-elevation-6 px-8 py-4"> <form id="subs-form" class="w-full mb-2 flex flex-col relative crossable"> <div class="flex flex-col w-full mb-4 z-21"> <label class="block text-main-medium font-bold mb-1 z-22"> @@ -61,14 +61,14 @@ </div> </div> <div id="pick-tags-form" class="flex flex-col w-full relative crossable"> - <input type="text" class="bg-background-medium appearance-none border-background-medium rounded ml-6 mr-4 py-2 px-4 text-main-medium leading-tight inset-shadow-[0_0_0.4rem_black] focus:outline-none focus:bg-background-light focus:text-main-hard z-22" + <input type="text" class="bg-background-medium appearance-none border-background-medium rounded ml-6 mr-4 py-2 px-4 text-main-medium leading-tight inset-shadow-elevation-6 focus:outline-none focus:bg-background-light focus:text-main-hard z-22" list="existing-tags" onchange="addTag(this.value);" onkeydown="if (event.keyCode === 13) {addTag(this.value); this.value=''; return false;}"> <datalist id="existing-tags"> {{- range .ExistingTags }} <option value="{{ .Name }}"> {{- end }} </datalist> - <div id="tags-picked-list" class="bg-paper bg-background-dark flex flex-wrap inset-shadow-[0_0_0.4rem_black] p-2 m-4 w-full"> + <div id="tags-picked-list" class="bg-paper bg-background-dark flex flex-wrap inset-shadow-elevation-6 p-2 m-4 w-full"> {{- range .TagsPickedList }} <div id="tagPicked{{ . }}" class="bg-paper bg-main-hard text-background-light m-2 flex flex-row"> <p class="py-1 px-2">{{ . }}</p> @@ -84,7 +84,7 @@ </button> </div> </form> - <div id="subs-message" class="bg-paper bg-background-dark text-main-soft z-26 w-full inset-shadow-[0_0_0.4rem_black] py-2 px-4 text-center"></div> + <div id="subs-message" class="bg-paper bg-background-dark text-main-soft z-26 w-full inset-shadow-elevation-6 py-2 px-4 text-center"></div> </div> <script> diff --git a/views/partials/blog-page-like-button.html b/views/partials/blog-page-like-button.html index af668e6..50ceae9 100644 --- a/views/partials/blog-page-like-button.html +++ b/views/partials/blog-page-like-button.html @@ -1,5 +1,5 @@ <button hx-put="/api/v1/like" hx-vals='{"like": {{ not .Liked }}}' hx-target="this" hx-swap="outerHTML" hx-trigger="click" - class="accent-button rounded-xl flex flex-row shrink-0 w-fit h-full mx-2 my-auto{{ if .Liked }} active{{ end }}"> + class="accent-button rounded-xl relative shrink-0 w-fit aspect-square mr-4 p-2 my-auto{{ if .Liked }} active{{ end }}"> {{ if .Liked }} <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="Like-Hand-1--Streamline-Freehand" class="h-10"> <desc> @@ -12,7 +12,7 @@ <path d="M13.94 4.33a2.57 2.57 0 0 1 1.43 0.88 4.52 4.52 0 0 1 1 1.54 0.57 0.57 0 0 0 0.56 0.37 0.57 0.57 0 0 0 0.53 -0.41 2.81 2.81 0 0 1 1 -1.41 6.9 6.9 0 0 1 1.69 -1 0.49 0.49 0 0 0 0.31 -0.43 0.5 0.5 0 0 0 -0.24 -0.47 8.18 8.18 0 0 1 -2.26 -1.47 2.39 2.39 0 0 1 -0.71 -1.46 0.34 0.34 0 0 0 -0.34 -0.34 0.35 0.35 0 0 0 -0.34 0.35 0.29 0.29 0 0 0 -0.27 0.23 4.41 4.41 0 0 1 -1 1.63 3.53 3.53 0 0 1 -1.48 1 0.47 0.47 0 0 0 -0.32 0.5 0.48 0.48 0 0 0 0.44 0.49Zm2 -1.47a5.53 5.53 0 0 0 0.82 -1.44 3.34 3.34 0 0 0 0.59 1 6.91 6.91 0 0 0 1.53 1.3 5.93 5.93 0 0 0 -1.48 1 6.91 6.91 0 0 0 -0.51 0.57 4.8 4.8 0 0 0 -0.7 -0.85 4 4 0 0 0 -1 -0.74 5.76 5.76 0 0 0 0.71 -0.84Z" fill="var(--color-accent-deep)" fill-rule="evenodd" stroke-width="1"></path> </g> </svg> - <div class="ml-auto bg-accent-light rounded-full w-6 h-6 text-main! font-m-plus! font-normal! tracking-normal! not-italic! text-lg!">{{ .LikedCount }}</div> + <div class="absolute left-10 -top-2 bg-accent-light rounded-full w-6 h-6 text-main! font-m-plus! font-normal! tracking-normal! not-italic! text-lg!">{{ .LikedCount }}</div> {{ else }} <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="Like-Hand--Streamline-Freehand" class="h-10"> <desc> @@ -23,6 +23,6 @@ <path d="M3.53 9.34a2.22 2.22 0 0 0 -1.24 -0.88 5.37 5.37 0 0 0 -2 -0.26 0.3 0.3 0 0 0 -0.29 0.31 0.3 0.3 0 0 0 0.32 0.29 4.36 4.36 0 0 1 1.44 0.25 1.74 1.74 0 0 1 1 0.69A9.16 9.16 0 0 1 3 11.8c0 0.64 0.06 1.38 0.09 2.14 0 1.07 0.08 2.2 0.11 3.22s0.06 2.19 0 2.84c0 0.23 -0.12 0.45 -0.07 0.51A2.7 2.7 0 0 1 2 21a3.34 3.34 0 0 1 -1.49 0 0.34 0.34 0 0 0 -0.22 0.65 4.22 4.22 0 0 0 1.82 0.13 3.37 3.37 0 0 0 1.79 -0.69 1.82 1.82 0 0 0 0.26 -0.82 37.13 37.13 0 0 0 0.14 -5.13c0 -1 -0.1 -2 -0.19 -2.83a12.48 12.48 0 0 0 -0.58 -2.97Z" fill="var(--color-accent-deep)" fill-rule="evenodd" stroke-width="1"></path> </g> </svg> - <div class="ml-auto bg-accent-deep rounded-full w-6 h-6 text-background! font-m-plus! font-normal! tracking-normal! not-italic! text-lg!">{{ .LikedCount }}</div> + <div class="absolute left-10 -top-2 bg-accent-deep rounded-full w-6 h-6 text-background! font-m-plus! font-normal! tracking-normal! not-italic! text-lg!">{{ .LikedCount }}</div> {{ end }} </button> diff --git a/views/partials/catalogue-blog-cards.html b/views/partials/catalogue-blog-cards.html index 799955f..6466262 100644 --- a/views/partials/catalogue-blog-cards.html +++ b/views/partials/catalogue-blog-cards.html @@ -1,8 +1,8 @@ {{- range .BlogPages }} -<hr class="first-of-type:hidden border-t-2 border-dotted border-main-hard"> -<div {{ if .ToHighlight }}id="blog-page-highlighted"{{ end }} class="w-full flex flex-row items-stretch {{ if .ToHighlight }}bg-accent-light bg-paper{{ end }}"> - <div class="relative min-h-24 flex flex-col w-28 overflow-hidden shrink-0 mask-r-from-70% mask-r-to-100%"> - <img onclick="return changeUrl('{{ .ArticleLink }}');" class="h-full min-h-24 w-full cursor-pointer select-none object-cover block absolute" src="{{ printf $.ThumbnailBaseUrl .Thumbnail }}"> +<hr class="first-of-type:hidden border-t-[0.375rem] my-1 w-24 mx-auto border-dotted border-main-hard"> +<div {{ if .ToHighlight }}id="blog-page-highlighted"{{ end }} class="w-full flex flex-row items-stretch mask-t-from-[calc(100%-0.5rem)] mask-t-to-100% mask-b-from-[calc(100%-0.5rem)] mask-b-to-100% {{ if .ToHighlight }}bg-accent-light bg-paper{{ end }}"> + <div class="relative min-h-24 flex flex-col w-32 overflow-hidden shrink-0 mask-r-from-[calc(100%-1rem)] mask-r-to-100%"> + <img onclick="return changeUrl('{{ .ArticleLink }}');" class="h-full min-h-24 w-full cursor-pointer select-none object-cover block absolute" src="{{ printf $.PhotoStorage.Thumbnail320p.BaseUrl .Thumbnail }}"> <div class="w-full h-6 flex flex-row mt-auto relative"> <div class="flex-1/2 flex flex-row justify-center select-none bg-linear-180 {{ if .Liked }}from-accent-deep/40 to-accent-deep/80 text-background-dark{{ else }}from-accent-light/40 to-accent-light/80 text-main-hard{{ end }}"> <svg viewBox="0 0 24 24"><use href="#icon-like"/></svg> @@ -14,7 +14,7 @@ </div> </div> </div> - <div class="grow w-full h-fit flex flex-col justify-center my-auto py-1 px-1"> + <div class="grow w-full h-fit flex flex-col justify-center my-auto py-1 px-2"> <div class="flex flex-row gap-2 max-w-full"> <a href="{{ .ArticleLink }}" onclick="return changeUrl('{{ .ArticleLink }}');" class="grow-2 text-base cursor-pointer font-m-plus"> <span class="font-extrabold">{{ .Title }}</span> diff --git a/views/partials/general-page-header.html b/views/partials/general-page-header.html index 3b8a6c8..a75d32f 100644 --- a/views/partials/general-page-header.html +++ b/views/partials/general-page-header.html @@ -1,8 +1,8 @@ -<title>{{ .Title }} // SAYA.UZ</title> +<title>{{ or .PageTitle .Title }}</title> <div class="flex flex-row mb-2"> {{ block "header" . }}{{ end }} <h1 - class="text-4xl font-m-plus font-bold italic text-shadow-[0_2px_2px_var(--color-main-soft)] text-left my-auto" + class="text-4xl font-m-plus font-bold italic text-shadow-[0_0.125rem_0.125rem_var(--color-main-soft)] text-left my-auto" > {{ .Title }} </h1> diff --git a/views/partials/global-map-widget.html b/views/partials/global-map-widget.html new file mode 100644 index 0000000..f50020f --- /dev/null +++ b/views/partials/global-map-widget.html @@ -0,0 +1,109 @@ +<div + id="map-container" + class="bg-paper bg-background-dark flex z-10 w-full h-full" +></div> + +<script> +(function() { + var map; + var markers = L.markerClusterGroup(); + const THUMB_BASE = "{{ .PhotoStorage.Thumbnail320p.BaseUrl }}"; + + function createCustomIcon(color, borderColor = "var(--color-main-soft)") { + const svgIcon = ` + <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <path d="M12 1C7.5 1 4 4.5 4 9c0 6.5 8 14 8 14s8-7.5 8-14c0-4.5-3.5-8-8-8zm0 11c-1.5 0-3-1.5-3-3s1.5-3 3-3 3 1.5 3 3-1.5 3-3 3z" + fill="${color}" + stroke="${borderColor}" + stroke-width="1" + stroke-linejoin="round"/> + </svg> + `; + + return L.divIcon({ + html: svgIcon, + className: 'custom-svg-marker', + iconSize: [32, 32], + iconAnchor: [16, 32], + popupAnchor: [0, -32] + }); + } + + function initLocationMap() { + map = L.map('map-container').setView([{{ .MapLocationLat }}, {{ .MapLocationLong }}], {{ .MapLocationZoom }}); + + protomapsL.leafletLayer({ + url: '{{ .StaticStorage.Map.BaseUrl }}/{{ .StaticStorage.Map.PMTiles }}', + maxZoom: 15, + flavor: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', + lang: '{{ .Lang }}' + }).addTo(map); + + {{- with .ZoomPosition }} + L.control.zoom({ + position: '{{ . }}' + }).addTo(map); + {{- end }} + + map.addLayer(markers); + } + + function markerToHsl(str, newCoef) { + let hash = 0; + for (let i = 0; i < str.length / 2; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + hash = str.charCodeAt(str.length - i - 1) + ((hash << 5) - hash); + hash %= 360; + } + + return [hash, (20 + newCoef * 80).toFixed(0), (20 + newCoef * 75).toFixed(0)]; + } + + function mapAddMarker(x, y, relativeErrorMeters = 0, title = "", link = "", thumbnail = "", newCoef = 1, toHighlight = false) { + const [h, s, l] = markerToHsl(title, newCoef); + const color = `hsl(${h} ${s}% ${l}%)`; + + if (relativeErrorMeters != 0) { + var circle = L.circle([x, y], { + color: color, + fillColor: color, + fillOpacity: 0.15, + radius: relativeErrorMeters + }).addTo(map); + } + + var marker = L.marker([x, y], { + icon: createCustomIcon(color), + title: title, + }).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-12 h-12 mr-1" src="${THUMB_BASE.replace('%s', thumbnail)}"> + <span><b><a href="${link}">${title}</a></b><br><a href="https://www.openstreetmap.org/#map=13/${x}/${y}">${x}, ${y}</a></span> + </div>`); + + markers.addLayer(marker); + + if (toHighlight) { + markers.zoomToShowLayer(marker, () => marker.openPopup()); + } + } + + initLocationMap(); + {{- range .MapMarkers }} + mapAddMarker({{ .Lat }}, {{ .Long }}, {{ .AccuracyMeters }}, {{ .Title }}, {{ .PageLink }}, {{ .Thumbnail }}, {{ fdiv .Index (len $.MapMarkers) }}, {{ .ToHighlight }}); + {{- end }} + + function onResize() { + setTimeout(() => { + if (map) map.invalidateSize(); + }, 300); + } + window.addEventListener('resize', onResize); + new MutationObserver((muts, obs) => { + if (!document.getElementById('map-container')) { + window.removeEventListener('resize', onResize); + obs.disconnect(); + } + }).observe(document.body, { childList: true, subtree: true }); +})(); +</script> diff --git a/views/partials/personal-page-status.html b/views/partials/personal-page-status.html index 078fb24..41033a9 100644 --- a/views/partials/personal-page-status.html +++ b/views/partials/personal-page-status.html @@ -1,4 +1,4 @@ -<div id="{{ .StatusId }}" class='z-26 w-full inset-shadow-[0_0_0.4rem_black] +<div id="{{ .StatusId }}" class='z-26 w-full inset-shadow-elevation-6 {{if eq .Status "OK"}}bg-green-700 text-green-100 {{else if eq .Status "Failed"}}bg-red-700 text-red-100 {{else}}bg-paper bg-background-dark text-main-soft{{end}} |