From 1b6efd5cc8820da207cd981c3e53ddbafb4b7911 Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Sun, 2 Jun 2024 20:02:13 +0200 Subject: [PATCH 01/14] Don't update unused collection data for home page --- justfile | 13 +++++-------- main.go | 5 +---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/justfile b/justfile index 259d602..67b40e7 100644 --- a/justfile +++ b/justfile @@ -82,20 +82,17 @@ grafana-export: pprof := "http://localhost:8080/debug/pprof" prof-cpu seconds="10": - mkdir -p profiles/cpu/ - filepath=profiles/cpu/cpu-$(date +"%F-%H%M%S").pprof && \ - curl --progress-bar -o $filepath {{pprof}}/profile?seconds={{seconds}} && \ - go tool pprof -http=: $filepath + go tool pprof -http=: {{pprof}}/profile?seconds={{seconds}} prof-heap: - mkdir -p profiles/heap/ - filepath=profiles/heap/heap-$(date +"%F-%H%M%S").pprof && \ - curl --progress-bar -o $filepath {{pprof}}/heap && \ - go tool pprof -http=: $filepath + go tool pprof -http=: {{pprof}}/heap prof-reload: go test -benchmem -benchtime 10s '-run=^$' -bench '^BenchmarkReload$' photofield +monitor: + docker compose up prometheus grafana pyroscope + test-reload: mkdir -p profiles/ go test -v '-run=^TestReloadLeaks$' photofield diff --git a/main.go b/main.go index 6d1b925..c9fbe2d 100644 --- a/main.go +++ b/main.go @@ -470,10 +470,7 @@ func (*Api) GetScenesId(w http.ResponseWriter, r *http.Request, id openapi.Scene } func (*Api) GetCollections(w http.ResponseWriter, r *http.Request) { - for i := range collections { - collection := &collections[i] - collection.UpdateStatus(imageSource) - } + // Explicitly do not update collection status to avoid long delays items := collections if items == nil { items = make([]collection.Collection, 0) From bb10f8f9bf67ab51f9faf88202b0117381966ea5 Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Sun, 2 Jun 2024 20:06:12 +0200 Subject: [PATCH 02/14] Assume different photo times relate to local/utc times and extract the timezone --- internal/image/exiftool-mostlygeek.go | 30 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/internal/image/exiftool-mostlygeek.go b/internal/image/exiftool-mostlygeek.go index 7b9466d..5b475f7 100644 --- a/internal/image/exiftool-mostlygeek.go +++ b/internal/image/exiftool-mostlygeek.go @@ -90,7 +90,7 @@ func (decoder *ExifToolMostlyGeekLoader) DecodeInfo(path string, info *Info) ([] } name := strings.TrimSpace(nameValueSplit[0]) value := strings.TrimSpace(nameValueSplit[1]) - // println(name, value) + // println(path, name, value) switch name { case "Orientation": orientation = value @@ -104,22 +104,34 @@ func (decoder *ExifToolMostlyGeekLoader) DecodeInfo(path string, info *Info) ([] latitude = value case "GPSLongitude": longitude = value - - // case "GPSDateTime": - // gpsTime, _ = parseDateTime(value) default: if name, ok := tag.ExifTagToName[name]; ok { tags = append(tags, tag.NewExif(name, value)) } if strings.Contains(name, "Date") || strings.Contains(name, "Time") { if info.DateTime.IsZero() { - info.DateTime, _, _, _ = parseDateTime(value) - } else if name != "GPSDateTime" && name != "FileModifyDate" && name != "FileCreateDate" { - // Prefer time with timezone if available - t, hasTimezone, _, _ := parseDateTime(value) - if hasTimezone && info.DateTime.Location() == time.UTC { + t, _, _, err := parseDateTime(value) + if err == nil { info.DateTime = t } + } else if name != "GPSDateTime" && name != "FileModifyDate" && name != "FileCreateDate" { + t, hasTimezone, _, err := parseDateTime(value) + if err == nil && info.DateTime.Location() == time.UTC { + if hasTimezone { + // Prefer time with timezone if available + info.DateTime = t + } else { + // If there are two times that are more than 10 minutes apart and + // the first one doesn't have a timezone, it's likely that the + // first one is local time and the second one is UTC, so we add + // the timezone to the first one. + d := info.DateTime.Sub(t) + if d.Abs() > 10*time.Minute { + d = d.Truncate(time.Minute) + info.DateTime = info.DateTime.Add(-d).In(time.FixedZone("", int(d.Seconds()))) + } + } + } } } else if strings.HasSuffix(name, "Image") { match := previewValueMatcher.FindStringSubmatch(value) From 405c4af82ded409a38e2478df3e38995fd08209c Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Sun, 2 Jun 2024 20:21:58 +0200 Subject: [PATCH 03/14] Add cropping letter/pillarboxed thumbnails --- internal/render/bitmap.go | 142 +++++++++++++++++++++++++++++++------- internal/render/rect.go | 17 +++++ io/cached/cached.go | 6 +- 3 files changed, 138 insertions(+), 27 deletions(-) diff --git a/internal/render/bitmap.go b/internal/render/bitmap.go index 6a0b4f1..065c94c 100644 --- a/internal/render/bitmap.go +++ b/internal/render/bitmap.go @@ -45,15 +45,126 @@ func fitInside(cw float64, ch float64, w float64, h float64) (float64, float64) // return r // } +func cropsBlackbarsOnly(img goimage.Image, crop goimage.Rectangle) bool { + bounds := img.Bounds() + + sum := uint64(0) + maxBlack := uint64(0xC00) + + // Horizontal top left black bar line + for x := 0; x < crop.Min.X; x++ { + c := img.At(x, bounds.Min.Y) + r, g, b, _ := c.RGBA() + sum += uint64((r + g + b)) / 3 / maxBlack + } + // fmt.Printf("horizontal top left black bar line: %v\n", sum) + + // Horizontal top right black bar line + for x := crop.Max.X; x < bounds.Max.X; x++ { + c := img.At(x, bounds.Min.Y) + r, g, b, _ := c.RGBA() + sum += uint64((r + g + b)) / 3 / maxBlack + } + // fmt.Printf("horizontal top right black bar line: %v\n", sum) + + // Horizontal bottom left black bar line + for x := 0; x < crop.Min.X; x++ { + c := img.At(x, bounds.Max.Y-1) + r, g, b, _ := c.RGBA() + sum += uint64((r + g + b)) / 3 / maxBlack + } + // fmt.Printf("horizontal bottom left black bar line: %v\n", sum) + + // Horizontal bottom right black bar line + for x := crop.Max.X; x < bounds.Max.X; x++ { + c := img.At(x, bounds.Max.Y-1) + r, g, b, _ := c.RGBA() + sum += uint64((r + g + b)) / 3 / maxBlack + } + // fmt.Printf("horizontal bottom right black bar line: %v\n", sum) + + // Vertical top left black bar line + for y := 0; y < crop.Min.Y; y++ { + c := img.At(bounds.Min.X, y) + r, g, b, _ := c.RGBA() + sum += uint64((r + g + b)) / 3 / maxBlack + } + // fmt.Printf("vertical top left black bar line: %v\n", sum) + + // Vertical top right black bar line + for y := crop.Max.Y; y < bounds.Max.Y; y++ { + c := img.At(bounds.Min.X, y) + r, g, b, _ := c.RGBA() + sum += uint64((r + g + b)) / 3 / maxBlack + } + // fmt.Printf("vertical top right black bar line: %v\n", sum) + + // Vertical bottom left black bar line + for y := 0; y < crop.Min.Y; y++ { + c := img.At(bounds.Max.X-1, y) + r, g, b, _ := c.RGBA() + sum += uint64((r + g + b)) / 3 / maxBlack + } + // fmt.Printf("vertical bottom left black bar line: %v\n", sum) + + // Vertical bottom right black bar line + for y := crop.Max.Y; y < bounds.Max.Y; y++ { + c := img.At(bounds.Max.X-1, y) + r, g, b, _ := c.RGBA() + sum += uint64((r + g + b)) / 3 / maxBlack + } + // fmt.Printf("vertical bottom right black bar line: %v\n", sum) + + return sum < 2 +} + +func cropRect(bitmap *Bitmap, bounds goimage.Rectangle) goimage.Rectangle { + brect := Rect{W: float64(bounds.Dx()), H: float64(bounds.Dy())} + rect := bitmap.Sprite.Rect + if bitmap.Orientation.SwapsDimensions() { + rect.W, rect.H = rect.H, rect.W + } + cropr := rect.FitInside(brect) + croprect := goimage.Rectangle{ + Min: goimage.Point{X: int(math.Round(cropr.X)), Y: int(math.Round(cropr.Y))}, + Max: goimage.Point{X: int(math.Round(cropr.X + cropr.W)), Y: int(math.Round(cropr.Y + cropr.H))}, + } + return croprect +} + func (bitmap *Bitmap) DrawImage(rimg draw.Image, img goimage.Image, c *canvas.Context, scale float64) { bounds := img.Bounds() - model := bitmap.Sprite.Rect.GetMatrixFitBoundsRotate(bounds, bitmap.Orientation) + arb := float64(bounds.Dx()) / float64(bounds.Dy()) + aro := float64(bitmap.Sprite.Rect.W) / float64(bitmap.Sprite.Rect.H) + ard := math.Abs(arb - aro) + crop := ard > 0.05 + // cut = false + + var croprect goimage.Rectangle + if crop { + croprect = cropRect(bitmap, bounds) + if !cropsBlackbarsOnly(img, croprect) { + crop = false + } + } + + var model canvas.Matrix + if crop { + model = bitmap.Sprite.Rect.GetMatrixFillBoundsRotate(bounds, bitmap.Orientation) + } else { + model = bitmap.Sprite.Rect.GetMatrixFitBoundsRotate(bounds, bitmap.Orientation) + } + m := c.View().Mul(model.ScaleAbout(scale, scale, float64(bounds.Max.X)*0.5, float64(bounds.Max.Y)*0.5)) - renderImageFast(rimg, img, m) + if crop { + renderImageFastCropped(rimg, img, m, croprect) + } else { + renderImageFast(rimg, img, m) + } } -func renderImageFast(rimg draw.Image, img goimage.Image, m canvas.Matrix) { +func renderImageFastCropped(rimg draw.Image, img goimage.Image, m canvas.Matrix, crop goimage.Rectangle) { bounds := img.Bounds() origin := m.Dot(canvas.Point{X: 0, Y: float64(bounds.Size().Y)}) h := float64(rimg.Bounds().Size().Y) @@ -61,10 +172,11 @@ func renderImageFast(rimg draw.Image, img goimage.Image, m canvas.Matrix) { m[0][0], -m[0][1], origin.X, -m[1][0], m[1][1], h - origin.Y, } - draw.ApproxBiLinear.Transform(rimg, aff3, img, bounds, draw.Src, nil) + draw.ApproxBiLinear.Transform(rimg, aff3, img, crop, draw.Src, nil) } -func renderImageFastBounds(rimg draw.Image, img goimage.Image, m canvas.Matrix, bounds goimage.Rectangle) { +func renderImageFast(rimg draw.Image, img goimage.Image, m canvas.Matrix) { + bounds := img.Bounds() origin := m.Dot(canvas.Point{X: 0, Y: float64(bounds.Size().Y)}) h := float64(rimg.Bounds().Size().Y) aff3 := f64.Aff3{ @@ -74,31 +186,13 @@ func renderImageFastBounds(rimg draw.Image, img goimage.Image, m canvas.Matrix, draw.ApproxBiLinear.Transform(rimg, aff3, img, bounds, draw.Src, nil) } -// TODO finish implementation -func renderImageFastCropped(rimg draw.Image, img goimage.Image, m canvas.Matrix, crop Rect, modelTopLeft canvas.Point, modelBottomRight canvas.Point) { - bounds := img.Bounds() - // bounds := goimage.Rect(0, 0, int(modelBounds.X), int(modelBounds.Y)) +func renderImageFastBounds(rimg draw.Image, img goimage.Image, m canvas.Matrix, bounds goimage.Rectangle) { origin := m.Dot(canvas.Point{X: 0, Y: float64(bounds.Size().Y)}) h := float64(rimg.Bounds().Size().Y) aff3 := f64.Aff3{ m[0][0], -m[0][1], origin.X, -m[1][0], m[1][1], h - origin.Y, } - // croptl := m.Dot(canvas.Point{crop.X, crop.Y}) - // cropbr := m.Dot(canvas.Point{crop.X + crop.W, crop.Y + crop.H}) - // println(bounds.String(), crop.String(), croptl.String(), cropbr.String()) - // tx, ty := m.D - - model := Rect{ - X: modelTopLeft.X, - Y: modelTopLeft.Y, - W: modelBottomRight.X - modelTopLeft.X, - H: modelBottomRight.Y - modelTopLeft.Y, - } - - println(bounds.String(), "crop", crop.String(), "model", model.String()) - bounds = bounds.Inset(10) - // bounds = draw.ApproxBiLinear.Transform(rimg, aff3, img, bounds, draw.Src, nil) } diff --git a/internal/render/rect.go b/internal/render/rect.go index 22b7338..68bac13 100644 --- a/internal/render/rect.go +++ b/internal/render/rect.go @@ -76,6 +76,23 @@ func (rect Rect) FitInside(container Rect) (out Rect) { return out } +func (rect Rect) FillOutside(container Rect) (out Rect) { + imageRatio := rect.W / rect.H + + var scale float64 + if container.W/container.H > imageRatio { + scale = container.W / rect.W + } else { + scale = container.H / rect.H + } + + out.W = rect.W * scale + out.H = rect.H * scale + out.X = container.X + (container.W-out.W)*0.5 + out.Y = container.Y + (container.H-out.H)*0.5 + return out +} + func (rect Rect) GetMatrix() canvas.Matrix { return canvas.Identity. Translate(rect.X, -rect.Y-rect.H) diff --git a/io/cached/cached.go b/io/cached/cached.go index 4e5eb85..e21bbfd 100644 --- a/io/cached/cached.go +++ b/io/cached/cached.go @@ -56,16 +56,16 @@ func (c *Cached) Exists(ctx context.Context, id io.ImageId, path string) bool { func (c *Cached) Get(ctx context.Context, id io.ImageId, path string) io.Result { r := c.Cache.GetWithName(ctx, id, c.Source.Name()) - // fmt.Printf("%v %v\n", r.Image, r.Error) + // fmt.Printf("%v %v %v\n", id, c.Source.Name(), r.Error) if r.Image != nil || r.Error != nil { - // fmt.Printf("%v cache found\n", id) + // fmt.Printf("%v %v cache found\n", id, c.Source.Name()) // println("found in cache") r.FromCache = true return r } // r = c.Source.Get(ctx, id, path) r = c.load(ctx, id, path) - // fmt.Printf("%v cache load end\n", id) + // fmt.Printf("%v %v cache load end\n", id, c.Source.Name()) // c.Ristretto.SetWithName(ctx, id, c.Source.Name(), r) // fmt.Printf("%v cache set\n", id) // println("saved to cache", s) From 8114fc9e0104517d87feeb553effdf0649b5c6f3 Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Mon, 3 Jun 2024 21:02:52 +0200 Subject: [PATCH 04/14] Fix image cache size not being configurable --- internal/image/source.go | 4 ++-- internal/render/scene.go | 4 ++-- io/ristretto/ristretto.go | 19 +++++++++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/internal/image/source.go b/internal/image/source.go index b4a1502..2a06058 100644 --- a/internal/image/source.go +++ b/internal/image/source.go @@ -109,7 +109,7 @@ func (config *CacheConfig) MaxSizeBytes() int64 { } type Caches struct { - Image CacheConfig + Image CacheConfig `json:"image"` } type Config struct { @@ -198,7 +198,7 @@ func NewSource(config Config, migrations embed.FS, migrationsThumbs embed.FS, ge []string{"source"}, ) - source.imageCache = ristretto.New() + source.imageCache = ristretto.New(config.Caches.Image.MaxSizeBytes()) env := SourceEnvironment{ SourceTypes: config.SourceTypes, FFmpegPath: ffmpeg.FindPath(), diff --git a/internal/render/scene.go b/internal/render/scene.go index 91158a7..f52a31e 100644 --- a/internal/render/scene.go +++ b/internal/render/scene.go @@ -135,7 +135,7 @@ func (scene *Scene) Draw(config *Render, c *canvas.Context, scales Scales, sourc // photo.Draw(config, scene, c, scales, source) // } - concurrent := 10 + concurrent := runtime.NumCPU() photoCount := len(scene.Photos) if photoCount < concurrent { concurrent = photoCount @@ -214,7 +214,7 @@ func (scene *Scene) AddPhotosFromIdSlice(ids []image.ImageId) { } func (scene *Scene) GetVisiblePhotoRefs(view Rect, maxCount int) <-chan PhotoRef { - out := make(chan PhotoRef) + out := make(chan PhotoRef, 10) go func() { count := 0 if maxCount == 0 { diff --git a/io/ristretto/ristretto.go b/io/ristretto/ristretto.go index 574353b..4cfb2fd 100644 --- a/io/ristretto/ristretto.go +++ b/io/ristretto/ristretto.go @@ -36,13 +36,11 @@ func (ids IdWithSize) String() string { return fmt.Sprintf("%6d %4d %4d", ids.Id, ids.Size.X, ids.Size.Y) } -func New() *Ristretto { - maxSizeBytes := int64(256000000) - +func New(sizeBytes int64) *Ristretto { cache, err := drist.NewCache(&drist.Config[IdWithName, io.Result]{ - NumCounters: 1e6, // number of keys to track frequency of - MaxCost: maxSizeBytes, // maximum cost of cache - BufferItems: 64, // number of keys per Get buffer + NumCounters: 1e6, // number of keys to track frequency of + MaxCost: sizeBytes, // maximum cost of cache + BufferItems: 64, // number of keys per Get buffer Metrics: true, Cost: cost, KeyToHash: keyToHash, @@ -117,11 +115,20 @@ func (r *Ristretto) Set(ctx context.Context, id io.ImageId, path string, v io.Re return r.cache.SetWithTTL(idn, v, 0, 10*time.Minute) } +func printedCost(r io.Result) int64 { + c := cost(r) + if r.Image != nil { + fmt.Printf("cost %v %v %v\n", r.Image.Bounds().Dx(), r.Image.Bounds().Dy(), c/1000000) + } + return c +} + func cost(r io.Result) int64 { img := r.Image if img == nil { return 1 } + switch img := img.(type) { case *image.YCbCr: From 2fa715486996388f7d0718f5be304064e9891e15 Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Mon, 3 Jun 2024 22:33:12 +0200 Subject: [PATCH 05/14] Fix default width and height for photos --- internal/image/info.go | 7 +++++++ internal/image/source.go | 25 ++----------------------- internal/layout/flex.go | 4 ---- internal/layout/highlights.go | 5 ----- 4 files changed, 9 insertions(+), 32 deletions(-) diff --git a/internal/image/info.go b/internal/image/info.go index 252f7a3..d353920 100644 --- a/internal/image/info.go +++ b/internal/image/info.go @@ -39,6 +39,13 @@ func AngleToKm(a s1.Angle) float64 { return a.Radians() * earthRadiusKm } +func (info *Info) MakeValid() { + if info.Width == 0 || info.Height == 0 { + info.Width = 3 + info.Height = 2 + } +} + func (info *Info) Size() Size { return Size{X: info.Width, Y: info.Height} } diff --git a/internal/image/source.go b/internal/image/source.go index 2a06058..bbb318e 100644 --- a/internal/image/source.go +++ b/internal/image/source.go @@ -393,9 +393,7 @@ func (source *Source) ListInfos(dirs []string, options ListOptions) <-chan Sourc infos := source.database.List(dirs, options) for info := range infos { - // if info.NeedsMeta() || info.NeedsColor() { - // info.Info = source.GetInfo(info.Id) - // } + info.SourcedInfo.Info.MakeValid() out <- info.SourcedInfo } close(out) @@ -413,6 +411,7 @@ func (source *Source) ListInfosEmb(dirs []string, options ListOptions) <-chan In infos := source.database.ListWithEmbeddings(dirs, options) for info := range infos { + info.SourcedInfo.Info.MakeValid() out <- info } close(out) @@ -420,26 +419,6 @@ func (source *Source) ListInfosEmb(dirs []string, options ListOptions) <-chan In return out } -func (source *Source) ListInfosWithExistence(dirs []string, options ListOptions) <-chan SourcedInfo { - for i := range dirs { - dirs[i] = filepath.FromSlash(dirs[i]) - } - out := make(chan SourcedInfo, 1000) - go func() { - defer metrics.Elapsed("list infos")() - - infos := source.database.List(dirs, options) - for info := range infos { - if info.NeedsMeta() || info.NeedsColor() { - info.Info = source.GetInfo(info.Id) - } - out <- info.SourcedInfo - } - close(out) - }() - return out -} - // Prefer using ImageId over this unless you absolutely need the path func (source *Source) GetImagePath(id ImageId) (string, error) { path, ok := source.pathCache.Get(id) diff --git a/internal/layout/flex.go b/internal/layout/flex.go index 986fd05..2a5b453 100644 --- a/internal/layout/flex.go +++ b/internal/layout/flex.go @@ -96,10 +96,6 @@ func LayoutFlex(infos <-chan image.SourcedInfo, layout Layout, scene *render.Sce } } } - if info.Width == 0 || info.Height == 0 { - info.Width = 3 - info.Height = 2 - } photo := dag.Photo{ Id: info.Id, AspectRatio: float32(info.Width) / float32(info.Height), diff --git a/internal/layout/highlights.go b/internal/layout/highlights.go index fec03a5..cd12971 100644 --- a/internal/layout/highlights.go +++ b/internal/layout/highlights.go @@ -133,11 +133,6 @@ func LayoutHighlights(infos <-chan image.InfoEmb, layout Layout, scene *render.S } prevEmb = emb prevInvNorm = invnorm - - if info.Width == 0 || info.Height == 0 { - info.Width = 3 - info.Height = 2 - } photo := HighlightPhoto{ Photo: dag.Photo{ Id: info.Id, From 13d05e61412db478261ed47a98e4a20faf084865 Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Mon, 3 Jun 2024 22:40:02 +0200 Subject: [PATCH 06/14] Fix runtime import --- internal/render/scene.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/render/scene.go b/internal/render/scene.go index f52a31e..3a446c4 100644 --- a/internal/render/scene.go +++ b/internal/render/scene.go @@ -3,6 +3,7 @@ package render import ( "image/color" "math" + "runtime" "sync" "time" From f2a97800e9c39ed58f9885d6c8c1481f0ca90983 Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Mon, 3 Jun 2024 22:40:55 +0200 Subject: [PATCH 07/14] Bump default image cache to 1Gi --- defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defaults.yaml b/defaults.yaml index 3096c9f..48d5069 100644 --- a/defaults.yaml +++ b/defaults.yaml @@ -108,7 +108,7 @@ media: # Size of the image cache used while rendering images # A larger cache might make display/rendering faster, while a smaller # cache will conserve memory. - max_size: 256Mi + max_size: 1024Mi # File extensions to index on the file system extensions: [ From 247556f1f47449e69b76ecb518fea094d536bf17 Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Tue, 4 Jun 2024 00:06:46 +0200 Subject: [PATCH 08/14] Allow collection overrides --- config.go | 24 ++++++++++++++++++------ defaults.yaml | 4 ++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/config.go b/config.go index b5c7a6b..0645e6e 100644 --- a/config.go +++ b/config.go @@ -125,26 +125,25 @@ func loadConfig(dataDir string) (*AppConfig, error) { } // Expand collections - expanded := make([]collection.Collection, 0, len(appConfig.Collections)) + collections := make([]collection.Collection, 0, len(appConfig.Collections)) expandedDirs := make(map[string]bool) // Track deduplicated dirs for _, collection := range appConfig.Collections { if collection.ExpandSubdirs { for _, dir := range collection.Dirs { expandedDirs[dir] = true } - expanded = append(expanded, collection.Expand()...) + collections = append(collections, collection.Expand()...) } else { - expanded = append(expanded, collection) + collections = append(collections, collection) } } - appConfig.Collections = expanded appConfig.ExpandedPaths = make([]string, 0, len(expandedDirs)) for dir := range expandedDirs { appConfig.ExpandedPaths = append(appConfig.ExpandedPaths, dir) } - for i := range appConfig.Collections { - collection := &appConfig.Collections[i] + for i := range collections { + collection := &collections[i] collection.GenerateId() collection.Layout = strings.ToUpper(collection.Layout) if collection.Limit > 0 && collection.IndexLimit == 0 { @@ -152,6 +151,19 @@ func loadConfig(dataDir string) (*AppConfig, error) { } } + // Override earlier collection with the same ID + collectionsMap := make(map[string]int) + for i := 0; i < len(collections); i++ { + if idx, exists := collectionsMap[collections[i].Id]; exists { + collections[idx] = collections[i] + collections = append(collections[:i], collections[i+1:]...) + i-- + continue + } + collectionsMap[collections[i].Id] = i + } + appConfig.Collections = collections + appConfig.Media.AI = appConfig.AI appConfig.Media.DataDir = dataDir appConfig.Tags.Enable = appConfig.Tags.Enable || appConfig.Tags.Enabled diff --git a/defaults.yaml b/defaults.yaml index 48d5069..f3cfc80 100644 --- a/defaults.yaml +++ b/defaults.yaml @@ -15,6 +15,10 @@ collections: # - /second/dir # - C:/third/windows/dir # - ./relative/dir + # + # Later collections override earlier ones with the same name / id + # so that you can have expanded collections and override settings + # of specific collections. # Default layout of all collections layout: From a5e3f5fecc33848eb1a54d92e77d52ac4218e4cd Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Thu, 6 Jun 2024 00:59:22 +0200 Subject: [PATCH 09/14] Add created and similarity threshold search filters --- internal/clip/clip.go | 8 ++ internal/image/database.go | 64 +++++++++++++++- internal/scene/sceneSource.go | 28 +++++-- search/query.go | 136 ++++++++++++++++++++++++++++++---- search/query_test.go | 80 ++++++++++++++++++++ 5 files changed, 289 insertions(+), 27 deletions(-) diff --git a/internal/clip/clip.go b/internal/clip/clip.go index 9904e14..49abb28 100644 --- a/internal/clip/clip.go +++ b/internal/clip/clip.go @@ -42,6 +42,14 @@ func DotProductFloat32Float32(a []float32, b []float32) (float32, error) { return dot, nil } +func CosineSimilarityEmbeddingFloat32(e Embedding, f []float32, invnorm float32) (float32, error) { + dot, err := DotProductFloat32Float32(e.Float32(), f) + if err != nil { + return 0, err + } + return dot * invnorm * e.InvNormFloat32(), nil +} + // Most real world inverse vector norms of embeddings fall // within ~500 of 11843, so it's more efficient to store // the inverse vector norm as an offset of this number. diff --git a/internal/image/database.go b/internal/image/database.go index e6a6e9d..f4d64ce 100644 --- a/internal/image/database.go +++ b/internal/image/database.go @@ -38,9 +38,10 @@ const ( ) type ListOptions struct { - OrderBy ListOrder - Limit int - Query *search.Query + OrderBy ListOrder + Limit int + Query *search.Query + Embedding clip.Embedding } type DirsFunc func(dirs []string) @@ -1374,8 +1375,25 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList } } + var emb []float32 + var embInvNorm float32 + if options.Embedding != nil { + emb = options.Embedding.Float32() + embInvNorm = options.Embedding.InvNormFloat32() + } + embThreshold := float32(0) + if f, err := options.Query.QualifierFloat32("t"); err == nil { + embThreshold = f + } else { + emb = nil + } + + sql += ` + SELECT infos.id, width, height, orientation, color, created_at_unix, created_at_tz_offset, latitude, longitude` + if emb != nil { + sql += `, inv_norm, embedding` + } sql += ` - SELECT infos.id, width, height, orientation, color, created_at_unix, created_at_tz_offset, latitude, longitude FROM infos ` @@ -1387,6 +1405,12 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList } } + if emb != nil { + sql += ` + LEFT JOIN clip_emb ON clip_emb.file_id = id + ` + } + sql += ` WHERE path_prefix_id IN ( SELECT id @@ -1404,6 +1428,14 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList ) ` + createdFrom, createdTo, createdErr := options.Query.QualifierDateRange("created") + if createdErr == nil { + sql += ` + AND created_at_unix >= ? + AND created_at_unix <= ? + ` + } + switch options.OrderBy { case None: case DateAsc: @@ -1441,6 +1473,13 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList bindIndex++ } + if createdErr == nil { + stmt.BindInt64(bindIndex, createdFrom.Unix()) + bindIndex++ + stmt.BindInt64(bindIndex, createdTo.Unix()) + bindIndex++ + } + if options.Limit > 0 { stmt.BindInt64(bindIndex, (int64)(options.Limit)) } @@ -1478,6 +1517,23 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList info.LatLng = s2.LatLngFromDegrees(stmt.ColumnFloat(7), stmt.ColumnFloat(8)) } + if emb != nil { + e, err := readEmbedding(stmt, 9, 10) + if err != nil { + log.Printf("Error reading embedding for %d: %v\n", info.Id, err) + continue + } + sim, err := clip.CosineSimilarityEmbeddingFloat32(e, emb, embInvNorm) + if err != nil { + log.Printf("Error calculating similarity for %d: %v\n", info.Id, err) + continue + } + // fmt.Printf("id %d sim %f %f\n", info.Id, sim, embThreshold) + if sim < embThreshold { + continue + } + } + out <- info } diff --git a/internal/scene/sceneSource.go b/internal/scene/sceneSource.go index ddcfac6..4d7d1e7 100644 --- a/internal/scene/sceneSource.go +++ b/internal/scene/sceneSource.go @@ -82,11 +82,13 @@ func (source *SceneSource) loadScene(config SceneConfig, imageSource *image.Sour finished := metrics.Elapsed("scene load " + config.Collection.Id) var query *search.Query + embFilter := false if scene.Search != "" { - searchDone := metrics.Elapsed("search embed") + searchDone := metrics.Elapsed("search") q, err := search.Parse(scene.Search) if err == nil { + embFilter = len(q.QualifierValues("t")) > 0 if similar, err := q.QualifierInt("img"); err == nil { embedding, err := imageSource.GetImageEmbedding(image.ImageId(similar)) if err != nil { @@ -94,14 +96,23 @@ func (source *SceneSource) loadScene(config SceneConfig, imageSource *image.Sour scene.Error = fmt.Sprintf("Search failed: %s", err.Error()) } scene.SearchEmbedding = embedding - } else if len(q.QualifierValues("tag")) > 0 { + query = q + } else if len(q.QualifierValues("tag")) > 0 || len(q.QualifierValues("created")) > 0 || embFilter { query = q } + } else { + log.Printf("search parse failed: %s", err.Error()) } // Fallback - if scene.SearchEmbedding == nil && scene.Error == "" && query == nil { - embedding, err := imageSource.Clip.EmbedText(scene.Search) + if scene.SearchEmbedding == nil && scene.Error == "" && (query == nil || embFilter) { + text := scene.Search + if query != nil { + text = query.Words() + } + done := metrics.Elapsed("search embed") + embedding, err := imageSource.Clip.EmbedText(text) + done() if err != nil { log.Println("search embed failed") scene.Error = fmt.Sprintf("Search failed: %s", err.Error()) @@ -118,7 +129,7 @@ func (source *SceneSource) loadScene(config SceneConfig, imageSource *image.Sour }) layout.LayoutHighlights(infos, config.Layout, &scene, imageSource) - } else if scene.SearchEmbedding != nil { + } else if !embFilter && scene.SearchEmbedding != nil { // Similarity order infos := config.Collection.GetSimilar(imageSource, scene.SearchEmbedding, image.ListOptions{ Limit: config.Collection.Limit, @@ -134,9 +145,10 @@ func (source *SceneSource) loadScene(config SceneConfig, imageSource *image.Sour } else { // Normal order infos := config.Collection.GetInfos(imageSource, image.ListOptions{ - OrderBy: image.ListOrder(config.Layout.Order), - Limit: config.Collection.Limit, - Query: query, + OrderBy: image.ListOrder(config.Layout.Order), + Limit: config.Collection.Limit, + Query: query, + Embedding: scene.SearchEmbedding, }) switch config.Layout.Type { case layout.Timeline: diff --git a/search/query.go b/search/query.go index 1464574..d8b3c3f 100644 --- a/search/query.go +++ b/search/query.go @@ -2,7 +2,10 @@ package search import ( "fmt" + "os" "strconv" + "strings" + "time" "github.com/alecthomas/participle/v2" "github.com/alecthomas/participle/v2/lexer" @@ -13,9 +16,10 @@ type Query struct { } type Term struct { - String *string `parser:"@String" json:"string,omitempty"` + Not bool `parser:"@'NOT'?" json:"not,omitempty"` + String *string `parser:"(@String" json:"string,omitempty"` Qualifier *Qualifier `parser:"| @@" json:"qualifier,omitempty"` - Word *string `parser:"| @Word" json:"word,omitempty"` + Word *string `parser:"| @Word)" json:"word,omitempty"` Pos lexer.Position `parser:"" json:"start"` EndPos lexer.Position `parser:"" json:"end"` } @@ -28,47 +32,133 @@ type Qualifier struct { var lex *lexer.StatefulDefinition var par *participle.Parser[Query] +var ErrNilQuery = fmt.Errorf("nil query") +var ErrNotFound = fmt.Errorf("not found") + func init() { lex = lexer.MustSimple([]lexer.SimpleRule{ - {Name: "Whitespace", Pattern: `[ \t]+`}, - {Name: "Word", Pattern: `[^\s:]+`}, {Name: "String", Pattern: `"(\\"|[^"])*"`}, + {Name: "Word", Pattern: `[^\s:]+`}, {Name: "Colon", Pattern: `:`}, + {Name: "Whitespace", Pattern: `[ \t]+`}, }) - par = participle.MustBuild[Query]( participle.Lexer(lex), participle.Elide("Whitespace"), participle.Unquote("String"), + participle.UseLookahead(2), ) } +func PrintTokens(str string) { + l, err := lex.LexString("", str) + if err != nil { + fmt.Println(err) + return + } + for { + tok, err := l.Next() + if err != nil { + fmt.Println(err) + return + } + if tok.EOF() { + break + } + fmt.Println(tok.GoString()) + } +} + func Parse(str string) (*Query, error) { return par.ParseString("", str) } +func ParseDebug(str string) (*Query, error) { + PrintTokens(str) + return par.ParseString("", str, participle.Trace(os.Stdout)) +} + func (q *Query) QualifierInt(key string) (int, error) { if q == nil { - return 0, fmt.Errorf("nil query") + return 0, ErrNilQuery } - if len(q.Terms) == 0 { - return 0, fmt.Errorf("empty query") + values := q.QualifierValues(key) + if len(values) == 0 { + return 0, ErrNotFound } - if len(q.Terms) > 1 { - return 0, fmt.Errorf("too many terms") + if len(values) > 1 { + return 0, fmt.Errorf("multiple qualifiers %s", key) } - if q.Terms[0].Qualifier == nil { - return 0, fmt.Errorf("no qualifier") + return strconv.Atoi(q.Terms[0].Qualifier.Value) +} + +func (q *Query) QualifierFloat32(key string) (float32, error) { + if q == nil { + return 0, ErrNilQuery } - if q.Terms[0].Qualifier.Key != key { - return 0, fmt.Errorf(`qualifier not %s`, key) + values := q.QualifierValues(key) + if len(values) == 0 { + return 0, ErrNotFound } - return strconv.Atoi(q.Terms[0].Qualifier.Value) + if len(values) > 1 { + return 0, fmt.Errorf("multiple qualifiers %s", key) + } + + value := values[0] + + f, err := strconv.ParseFloat(value, 32) + if err != nil { + return 0, fmt.Errorf("failed to parse float32: %v", err) + } + + return float32(f), nil +} + +func (q *Query) QualifierDateRange(key string) (a time.Time, b time.Time, err error) { + if q == nil { + err = ErrNilQuery + return + } + + values := q.QualifierValues(key) + if len(values) == 0 { + err = ErrNotFound + return + } + + if len(values) > 1 { + err = fmt.Errorf("multiple qualifiers %s", key) + return + } + + value := values[0] + + dateRange := strings.SplitN(value, "..", 2) + if len(dateRange) != 2 { + err = fmt.Errorf("invalid date range format") + return + } + + a, err = time.Parse("2006-01-02", dateRange[0]) + if err != nil { + err = fmt.Errorf("failed to parse start date: %v", err) + return + } + + b, err = time.Parse("2006-01-02", dateRange[1]) + if err != nil { + err = fmt.Errorf("failed to parse end date: %v", err) + return + } + + b = b.AddDate(0, 0, 1) + + return } func (q *Query) QualifierValues(key string) []string { @@ -83,3 +173,19 @@ func (q *Query) QualifierValues(key string) []string { } return values } + +func (q *Query) Words() string { + if q == nil { + return "" + } + var words string + for _, term := range q.Terms { + if term.Word != nil { + words += *term.Word + " " + } + } + if len(words) == 0 { + return "" + } + return words[:len(words)-1] +} diff --git a/search/query_test.go b/search/query_test.go index d23520c..b62c001 100644 --- a/search/query_test.go +++ b/search/query_test.go @@ -2,6 +2,7 @@ package search import ( "testing" + "time" "github.com/alecthomas/assert/v2" ) @@ -52,3 +53,82 @@ func TestQualifierValues(t *testing.T) { query.QualifierValues("tag"), ) } + +func TestWords(t *testing.T) { + query, err := Parse("hello world created:2016-04-29..2016-07-04") + if err != nil { + t.Error(err) + } + assert.Equal( + t, + []string{"2016-04-29..2016-07-04"}, + query.QualifierValues("created"), + ) + assert.Equal(t, "hello world", query.Words()) +} + +func TestEmptyWords(t *testing.T) { + query, err := Parse("tag:hello") + if err != nil { + t.Error(err) + } + assert.Equal(t, "", query.Words()) +} +func TestQualifierDateRange(t *testing.T) { + query, err := Parse("created:2022-01-01..2022-12-31") + if err != nil { + t.Error(err) + } + + startDate, endDate, err := query.QualifierDateRange("created") + if err != nil { + t.Error(err) + } + + expectedStartDate, _ := time.Parse("2006-01-02", "2022-01-01") + expectedEndDate, _ := time.Parse("2006-01-02", "2023-01-01") + + if !startDate.Equal(expectedStartDate) { + t.Errorf("Expected start date '%s', got '%s'", expectedStartDate.Format("2006-01-02"), startDate.Format("2006-01-02")) + } + + if !endDate.Equal(expectedEndDate) { + t.Errorf("Expected end date '%s', got '%s'", expectedEndDate.Format("2006-01-02"), endDate.Format("2006-01-02")) + } +} + +func TestQualifierDateRangeNoQualifier(t *testing.T) { + query, err := Parse("hello world") + if err != nil { + t.Error(err) + } + + _, _, err = query.QualifierDateRange("created") + if err == nil { + t.Error("Expected error, got nil") + } +} + +func TestQualifierDateRangeMultipleQualifiers(t *testing.T) { + query, err := Parse("created:2022-01-01..2022-12-31 created:2023-01-01..2023-12-31") + if err != nil { + t.Error(err) + } + + _, _, err = query.QualifierDateRange("created") + if err == nil { + t.Error("Expected error, got nil") + } +} + +func TestQualifierDateRangeInvalidDateFormat(t *testing.T) { + query, err := Parse("created:2022-01-01..2022-12-31") + if err != nil { + t.Error(err) + } + + _, _, err = query.QualifierDateRange("invalid") + if err == nil { + t.Error("Expected error, got nil") + } +} From 3cb329b77ee839aaa03dd024db002d5adb6cf8f3 Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Fri, 7 Jun 2024 22:27:07 +0200 Subject: [PATCH 10/14] Add "dedup" deduplication search filter, tweaks, high quality mode --- api.yaml | 16 +++++++++ internal/clip/clip.go | 8 +++++ internal/codec/image.go | 4 +-- internal/image/database.go | 52 +++++++++++++++++++++------- internal/layout/common.go | 1 + internal/layout/flex.go | 10 ++++-- internal/openapi/api.gen.go | 28 +++++++++++++++ internal/render/bitmap.go | 24 ++++++++++--- internal/render/photo.go | 14 ++++++-- internal/render/scene.go | 8 +++++ internal/scene/sceneSource.go | 4 +++ io/io.go | 34 ++++++++++++------ main.go | 20 ++++++++++- ui/src/api.js | 2 ++ ui/src/components/CollectionView.vue | 5 +++ ui/src/components/ScrollViewer.vue | 9 +++++ ui/src/components/TileViewer.vue | 4 +++ 17 files changed, 209 insertions(+), 34 deletions(-) diff --git a/api.yaml b/api.yaml index ddb6cba..77f0aee 100644 --- a/api.yaml +++ b/api.yaml @@ -111,6 +111,11 @@ paths: schema: $ref: "#/components/schemas/Search" + - name: tweaks + in: query + schema: + $ref: "#/components/schemas/Tweaks" + responses: "200": description: List of scenes created for the specified collection @@ -227,6 +232,12 @@ paths: type: boolean example: false + - name: quality_preset + in: query + schema: + type: string + example: "HIGH" + responses: "200": description: OK @@ -734,6 +745,8 @@ components: $ref: "#/components/schemas/ViewportWidth" viewport_height: $ref: "#/components/schemas/ViewportHeight" + tweaks: + $ref: "#/components/schemas/Tweaks" image_height: $ref: "#/components/schemas/ImageHeight" layout: @@ -819,6 +832,9 @@ components: minimum: 0 example: 800 + Tweaks: + type: string + ImageWidth: type: number minimum: 0 diff --git a/internal/clip/clip.go b/internal/clip/clip.go index 49abb28..74e0059 100644 --- a/internal/clip/clip.go +++ b/internal/clip/clip.go @@ -50,6 +50,14 @@ func CosineSimilarityEmbeddingFloat32(e Embedding, f []float32, invnorm float32) return dot * invnorm * e.InvNormFloat32(), nil } +func CosineSimilarityFloat32Float32(a []float32, ainvnorm float32, b []float32, binvnorm float32) (float32, error) { + dot, err := DotProductFloat32Float32(a, b) + if err != nil { + return 0, err + } + return dot * ainvnorm * binvnorm, nil +} + // Most real world inverse vector norms of embeddings fall // within ~500 of 11843, so it's more efficient to store // the inverse vector norm as an offset of this number. diff --git a/internal/codec/image.go b/internal/codec/image.go index 840331b..eaf2c45 100644 --- a/internal/codec/image.go +++ b/internal/codec/image.go @@ -13,8 +13,8 @@ func DecodeJpeg(reader io.ReadSeeker) (image.Image, error) { return jpeg.Decode(reader) } -func EncodeJpeg(w io.Writer, image image.Image) error { +func EncodeJpeg(w io.Writer, image image.Image, quality int) error { return jpeg.Encode(w, image, &jpeg.Options{ - Quality: 80, + Quality: quality, }) } diff --git a/internal/image/database.go b/internal/image/database.go index f4d64ce..19d60ad 100644 --- a/internal/image/database.go +++ b/internal/image/database.go @@ -1375,22 +1375,29 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList } } + joinEmbeddings := false var emb []float32 var embInvNorm float32 if options.Embedding != nil { emb = options.Embedding.Float32() embInvNorm = options.Embedding.InvNormFloat32() } + embThreshold := float32(0) if f, err := options.Query.QualifierFloat32("t"); err == nil { embThreshold = f - } else { - emb = nil + joinEmbeddings = true + } + + embDedup := float32(0) + if f, err := options.Query.QualifierFloat32("dedup"); err == nil { + embDedup = f + joinEmbeddings = true } sql += ` SELECT infos.id, width, height, orientation, color, created_at_unix, created_at_tz_offset, latitude, longitude` - if emb != nil { + if joinEmbeddings { sql += `, inv_norm, embedding` } sql += ` @@ -1405,7 +1412,7 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList } } - if emb != nil { + if joinEmbeddings { sql += ` LEFT JOIN clip_emb ON clip_emb.file_id = id ` @@ -1484,6 +1491,9 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList stmt.BindInt64(bindIndex, (int64)(options.Limit)) } + var lastEmb []float32 + var lastEmbInvNorm float32 + for { if exists, err := stmt.Step(); err != nil { log.Printf("Error listing files: %s\n", err.Error()) @@ -1517,20 +1527,38 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList info.LatLng = s2.LatLngFromDegrees(stmt.ColumnFloat(7), stmt.ColumnFloat(8)) } - if emb != nil { + if joinEmbeddings { e, err := readEmbedding(stmt, 9, 10) if err != nil { log.Printf("Error reading embedding for %d: %v\n", info.Id, err) continue } - sim, err := clip.CosineSimilarityEmbeddingFloat32(e, emb, embInvNorm) - if err != nil { - log.Printf("Error calculating similarity for %d: %v\n", info.Id, err) - continue + ee := e.Float32() + einv := e.InvNormFloat32() + if emb != nil { + sim, err := clip.CosineSimilarityFloat32Float32(emb, embInvNorm, ee, einv) + if err != nil { + log.Printf("Error calculating similarity for %d: %v\n", info.Id, err) + continue + } + // fmt.Printf("id %d sim %f %f\n", info.Id, sim, embThreshold) + if sim < embThreshold { + continue + } } - // fmt.Printf("id %d sim %f %f\n", info.Id, sim, embThreshold) - if sim < embThreshold { - continue + if embDedup > 0 { + if lastEmb != nil { + sim, err := clip.CosineSimilarityFloat32Float32(lastEmb, lastEmbInvNorm, ee, einv) + if err != nil { + log.Printf("Error calculating similarity for %d: %v\n", info.Id, err) + continue + } + if sim >= embDedup { + continue + } + } + lastEmb = ee + lastEmbInvNorm = einv } } diff --git a/internal/layout/common.go b/internal/layout/common.go index 2d7846f..c54c0dc 100644 --- a/internal/layout/common.go +++ b/internal/layout/common.go @@ -55,6 +55,7 @@ type Layout struct { ImageHeight float64 ImageSpacing float64 LineSpacing float64 + Tweaks string } type Section struct { diff --git a/internal/layout/flex.go b/internal/layout/flex.go index 2a5b453..efe2757 100644 --- a/internal/layout/flex.go +++ b/internal/layout/flex.go @@ -9,6 +9,7 @@ import ( "photofield/internal/layout/dag" "photofield/internal/metrics" "photofield/internal/render" + "strings" "time" "github.com/gammazero/deque" @@ -22,12 +23,16 @@ func LayoutFlex(infos <-chan image.SourcedInfo, layout Layout, scene *render.Sce layout.LineSpacing = 0.02 * layout.ImageHeight sceneMargin := 10. + topMargin := 64. + if strings.Contains(layout.Tweaks, "notopmargin") { + topMargin = 0 + } scene.Bounds.W = layout.ViewportWidth rect := render.Rect{ X: sceneMargin, - Y: sceneMargin + 64, + Y: sceneMargin + topMargin, W: scene.Bounds.W - sceneMargin*2, H: 0, } @@ -58,8 +63,9 @@ func LayoutFlex(infos <-chan image.SourcedInfo, layout Layout, scene *render.Sce var prevLocTime time.Time var prevLocation string var prevAuxTime time.Time + nogeo := strings.Contains(layout.Tweaks, "nogeo") for info := range infos { - if source.Geo.Available() { + if !nogeo && source.Geo.Available() { photoTime := info.DateTime lastLocCheck := prevLocTime.Sub(photoTime) if lastLocCheck < 0 { diff --git a/internal/openapi/api.gen.go b/internal/openapi/api.gen.go index a1edaa5..2f2d2bb 100644 --- a/internal/openapi/api.gen.go +++ b/internal/openapi/api.gen.go @@ -152,6 +152,7 @@ type SceneParams struct { Layout LayoutType `json:"layout"` Search *Search `json:"search,omitempty"` Sort *Sort `json:"sort,omitempty"` + Tweaks *Tweaks `json:"tweaks,omitempty"` ViewportHeight ViewportHeight `json:"viewport_height"` ViewportWidth ViewportWidth `json:"viewport_width"` } @@ -208,6 +209,9 @@ type TaskType string // TileCoord defines model for TileCoord. type TileCoord int +// Tweaks defines model for Tweaks. +type Tweaks string + // ViewportHeight defines model for ViewportHeight. type ViewportHeight float32 @@ -239,6 +243,7 @@ type GetScenesParams struct { Layout *LayoutType `json:"layout,omitempty"` Sort *Sort `json:"sort,omitempty"` Search *Search `json:"search,omitempty"` + Tweaks *Tweaks `json:"tweaks,omitempty"` } // PostScenesJSONBody defines parameters for PostScenes. @@ -273,6 +278,7 @@ type GetScenesSceneIdTilesParams struct { SelectTag *string `json:"select_tag,omitempty"` DebugOverdraw *bool `json:"debug_overdraw,omitempty"` DebugThumbnails *bool `json:"debug_thumbnails,omitempty"` + QualityPreset *string `json:"quality_preset,omitempty"` } // GetTagsParams defines parameters for GetTags. @@ -630,6 +636,17 @@ func (siw *ServerInterfaceWrapper) GetScenes(w http.ResponseWriter, r *http.Requ return } + // ------------- Optional query parameter "tweaks" ------------- + if paramValue := r.URL.Query().Get("tweaks"); paramValue != "" { + + } + + err = runtime.BindQueryParameter("form", true, false, "tweaks", r.URL.Query(), ¶ms.Tweaks) + if err != nil { + http.Error(w, fmt.Sprintf("Invalid format for parameter tweaks: %s", err), http.StatusBadRequest) + return + } + var handler = func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetScenes(w, r, params) } @@ -995,6 +1012,17 @@ func (siw *ServerInterfaceWrapper) GetScenesSceneIdTiles(w http.ResponseWriter, return } + // ------------- Optional query parameter "quality_preset" ------------- + if paramValue := r.URL.Query().Get("quality_preset"); paramValue != "" { + + } + + err = runtime.BindQueryParameter("form", true, false, "quality_preset", r.URL.Query(), ¶ms.QualityPreset) + if err != nil { + http.Error(w, fmt.Sprintf("Invalid format for parameter quality_preset: %s", err), http.StatusBadRequest) + return + } + var handler = func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetScenesSceneIdTiles(w, r, sceneId, params) } diff --git a/internal/render/bitmap.go b/internal/render/bitmap.go index 065c94c..f51069e 100644 --- a/internal/render/bitmap.go +++ b/internal/render/bitmap.go @@ -132,7 +132,7 @@ func cropRect(bitmap *Bitmap, bounds goimage.Rectangle) goimage.Rectangle { return croprect } -func (bitmap *Bitmap) DrawImage(rimg draw.Image, img goimage.Image, c *canvas.Context, scale float64) { +func (bitmap *Bitmap) DrawImage(rimg draw.Image, img goimage.Image, c *canvas.Context, scale float64, hq bool) { bounds := img.Bounds() arb := float64(bounds.Dx()) / float64(bounds.Dy()) @@ -147,6 +147,8 @@ func (bitmap *Bitmap) DrawImage(rimg draw.Image, img goimage.Image, c *canvas.Co if !cropsBlackbarsOnly(img, croprect) { crop = false } + } else { + croprect = bounds } var model canvas.Matrix @@ -157,11 +159,25 @@ func (bitmap *Bitmap) DrawImage(rimg draw.Image, img goimage.Image, c *canvas.Co } m := c.View().Mul(model.ScaleAbout(scale, scale, float64(bounds.Max.X)*0.5, float64(bounds.Max.Y)*0.5)) - if crop { - renderImageFastCropped(rimg, img, m, croprect) + + var interp draw.Interpolator + if hq { + interp = draw.CatmullRom } else { - renderImageFast(rimg, img, m) + interp = draw.ApproxBiLinear + } + renderImage(rimg, img, m, croprect, interp) +} + +func renderImage(rimg draw.Image, img goimage.Image, m canvas.Matrix, crop goimage.Rectangle, interpolator draw.Interpolator) { + bounds := img.Bounds() + origin := m.Dot(canvas.Point{X: 0, Y: float64(bounds.Size().Y)}) + h := float64(rimg.Bounds().Size().Y) + aff3 := f64.Aff3{ + m[0][0], -m[0][1], origin.X, + -m[1][0], m[1][1], h - origin.Y, } + interpolator.Transform(rimg, aff3, img, crop, draw.Src, nil) } func renderImageFastCropped(rimg draw.Image, img goimage.Image, m canvas.Matrix, crop goimage.Rectangle) { diff --git a/internal/render/photo.go b/internal/render/photo.go index 6bf3913..4769af3 100644 --- a/internal/render/photo.go +++ b/internal/render/photo.go @@ -89,7 +89,17 @@ func (photo *Photo) Draw(config *Render, scene *Scene, c *canvas.Context, scales if config.Sources != nil { srcs = config.Sources } - sources := srcs.EstimateCost(io.Size(size), io.Size(rsize)) + + hq := false + if config.QualityPreset == QualityPresetHigh { + hq = true + } + costOpts := io.DefaultOptions + if hq { + costOpts.UnderdrawPenaltyMultiplier = 1000 + costOpts.DurationCostMultiplier = 0 + } + sources := srcs.EstimateCostWithOpts(io.Size(size), io.Size(rsize), costOpts) sources.Sort() var errs []error @@ -137,7 +147,7 @@ func (photo *Photo) Draw(config *Render, scene *Scene, c *canvas.Context, scales scale = 0.8 } - bitmap.DrawImage(config.CanvasImage, img, c, scale) + bitmap.DrawImage(config.CanvasImage, img, c, scale, hq) drawn = true if source.IsSupportedVideo(path) { diff --git a/internal/render/scene.go b/internal/render/scene.go index 3a446c4..f62db03 100644 --- a/internal/render/scene.go +++ b/internal/render/scene.go @@ -15,6 +15,13 @@ import ( "photofield/io" ) +type QualityPreset int + +const ( + QualityPresetFast QualityPreset = iota + QualityPresetHigh +) + type Render struct { TileSize int `json:"tile_size"` MaxSolidPixelArea float64 `json:"max_solid_pixel_area"` @@ -28,6 +35,7 @@ type Render struct { DebugOverdraw bool DebugThumbnails bool + QualityPreset QualityPreset Zoom int CanvasImage draw.Image diff --git a/internal/scene/sceneSource.go b/internal/scene/sceneSource.go index 4d7d1e7..f73829c 100644 --- a/internal/scene/sceneSource.go +++ b/internal/scene/sceneSource.go @@ -270,6 +270,10 @@ func sceneConfigEqual(a SceneConfig, b SceneConfig) bool { return false } + if a.Layout.Tweaks != b.Layout.Tweaks { + return false + } + if a.Scene.Search != b.Scene.Search { return false } diff --git a/io/io.go b/io/io.go index 2196248..2f9b999 100644 --- a/io/io.go +++ b/io/io.go @@ -141,11 +141,19 @@ type Sources []Source // var DurationCostMultiplier = 0.003 // Optimized for 0.9 max width ratio + square duration -var UnderdrawPenaltyMultiplier = 59.851585 -var SizeCostMultiplier = 0.000281 -var DurationCostMultiplier = 0.011857 +var DefaultOptions = Options{ + UnderdrawPenaltyMultiplier: 59.851585, + SizeCostMultiplier: 0.000281, + DurationCostMultiplier: 0.011857, +} + +type Options struct { + UnderdrawPenaltyMultiplier float64 + SizeCostMultiplier float64 + DurationCostMultiplier float64 +} -func SizeCost(source Size, original Size, target Size) (cost float64, area int64) { +func SizeCost(source Size, original Size, target Size, opts Options) (cost float64, area int64) { if source.X == 0 && source.Y == 0 { source = target } @@ -153,24 +161,24 @@ func SizeCost(source Size, original Size, target Size) (cost float64, area int64 targetArea := target.Area() diff := float64(targetArea) - float64(area) if targetArea > area { - diff *= UnderdrawPenaltyMultiplier + diff *= opts.UnderdrawPenaltyMultiplier } - cost = diff * diff * SizeCostMultiplier + cost = diff * diff * opts.SizeCostMultiplier return } -func DurationCost(dur time.Duration) float64 { +func DurationCost(dur time.Duration, opts Options) float64 { us := float64(dur.Microseconds()) - return us * us * DurationCostMultiplier + return us * us * opts.DurationCostMultiplier } -func (sources Sources) EstimateCost(original Size, target Size) SourceCosts { +func (sources Sources) EstimateCostWithOpts(original Size, target Size, opts Options) SourceCosts { costs := make([]SourceCost, len(sources)) for i := range sources { s := sources[i] - sizecost, sarea := SizeCost(s.Size(original), original, target) + sizecost, sarea := SizeCost(s.Size(original), original, target, opts) dur := s.GetDurationEstimate(original) - durcost := DurationCost(dur) + durcost := DurationCost(dur, opts) cost := sizecost + durcost costs[i] = SourceCost{ Source: s, @@ -184,6 +192,10 @@ func (sources Sources) EstimateCost(original Size, target Size) SourceCosts { return costs } +func (sources Sources) EstimateCost(original Size, target Size) SourceCosts { + return sources.EstimateCostWithOpts(original, target, DefaultOptions) +} + func (sources Sources) Close() { for _, s := range sources { err := s.Close() diff --git a/main.go b/main.go index c9fbe2d..858d37b 100644 --- a/main.go +++ b/main.go @@ -386,6 +386,9 @@ func (*Api) PostScenes(w http.ResponseWriter, r *http.Request) { sceneConfig.Layout.ViewportWidth = float64(data.ViewportWidth) sceneConfig.Layout.ViewportHeight = float64(data.ViewportHeight) + if data.Tweaks != nil { + sceneConfig.Layout.Tweaks = string(*data.Tweaks) + } sceneConfig.Layout.ImageHeight = 0 if data.ImageHeight != nil { sceneConfig.Layout.ImageHeight = float64(*data.ImageHeight) @@ -437,6 +440,9 @@ func (*Api) GetScenes(w http.ResponseWriter, r *http.Request, params openapi.Get if params.Search != nil { sceneConfig.Scene.Search = string(*params.Search) } + if params.Tweaks != nil { + sceneConfig.Layout.Tweaks = string(*params.Tweaks) + } collection := getCollectionById(string(params.CollectionId)) if collection == nil { problem(w, r, http.StatusBadRequest, "Collection not found") @@ -739,6 +745,14 @@ func GetScenesSceneIdTilesImpl(w http.ResponseWriter, r *http.Request, sceneId o if params.DebugThumbnails != nil { rn.DebugThumbnails = *params.DebugThumbnails } + if params.QualityPreset != nil { + switch *params.QualityPreset { + case "HIGH": + rn.QualityPreset = render.QualityPresetHigh + default: + rn.QualityPreset = render.QualityPresetFast + } + } zoom := params.Zoom x := int(params.X) @@ -783,7 +797,11 @@ func GetScenesSceneIdTilesImpl(w http.ResponseWriter, r *http.Request, sceneId o return } w.Header().Add("Content-Type", "image/jpeg") - codec.EncodeJpeg(w, img) + quality := 80 + if rn.QualityPreset == render.QualityPresetHigh { + quality = 100 + } + codec.EncodeJpeg(w, img, quality) } func (*Api) GetScenesSceneIdDates(w http.ResponseWriter, r *http.Request, sceneId openapi.SceneId, params openapi.GetScenesSceneIdDatesParams) { diff --git a/ui/src/api.js b/ui/src/api.js index a13bcf3..cdcc1dd 100644 --- a/ui/src/api.js +++ b/ui/src/api.js @@ -184,6 +184,7 @@ export function useScene({ imageHeight, viewport, search, + tweaks, }) { const sceneParams = computed(() => @@ -197,6 +198,7 @@ export function useScene({ viewport_width: viewport.width.value, viewport_height: viewport.height.value, search: search?.value || undefined, + tweaks: tweaks.value, } ); diff --git a/ui/src/components/CollectionView.vue b/ui/src/components/CollectionView.vue index e17f714..02550d0 100644 --- a/ui/src/components/CollectionView.vue +++ b/ui/src/components/CollectionView.vue @@ -38,6 +38,7 @@ :imageHeight="imageHeight" :search="search" :debug="debug" + :tweaks="tweaks" :fullpage="true" :scrollbar="scrollbar" :selectTagId="selectTagId" @@ -227,6 +228,10 @@ const debug = computed(() => { return v; }); +const tweaks = computed(() => { + return route.query.tweaks; +}); + const onRegion = async (region) => { if (!region) return; const r = { diff --git a/ui/src/components/ScrollViewer.vue b/ui/src/components/ScrollViewer.vue index f001611..b72fb35 100644 --- a/ui/src/components/ScrollViewer.vue +++ b/ui/src/components/ScrollViewer.vue @@ -17,6 +17,7 @@ :focus="!!region" :crossNav="!!region" :viewport="viewport" + :qualityPreset="qualityPreset" @click="onClick" @view="onView" @nav="onNav" @@ -92,6 +93,7 @@ const props = defineProps({ debug: Object, fullpage: Boolean, scrollbar: Object, + tweaks: String, }); const emit = defineEmits({ @@ -116,6 +118,7 @@ const { sort, imageHeight, search, + tweaks, selectTagId, debug, } = toRefs(props); @@ -133,6 +136,12 @@ const { scene, recreate: recreateScene, loadSpeed } = useScene({ imageHeight, viewport, search, + tweaks, +}); + +const qualityPreset = computed(() => { + if (tweaks.value?.indexOf("hq") != -1) return "HIGH"; + return null; }); watch(scene, async (newScene, oldScene) => { diff --git a/ui/src/components/TileViewer.vue b/ui/src/components/TileViewer.vue index 289af00..5e9ea92 100644 --- a/ui/src/components/TileViewer.vue +++ b/ui/src/components/TileViewer.vue @@ -79,6 +79,7 @@ export default { loading: Boolean, geo: Boolean, viewport: Object, + qualityPreset: String, }, emits: [ @@ -721,6 +722,9 @@ export default { if (this.selectTagId) { extra.select_tag = this.selectTagId; } + if (this.qualityPreset) { + extra.quality_preset = this.qualityPreset; + } return getTileUrl( this.scene.id, z, x, y, From 5827e2dd7a382041a7623f85566e0eef5103a0fa Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Sun, 9 Jun 2024 15:48:57 +0200 Subject: [PATCH 11/14] Add "imageonly" tweak to exclude video --- internal/image/database.go | 30 +++++++++++++++++++++++++----- internal/scene/sceneSource.go | 14 ++++++++++---- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/internal/image/database.go b/internal/image/database.go index 19d60ad..3693716 100644 --- a/internal/image/database.go +++ b/internal/image/database.go @@ -38,10 +38,11 @@ const ( ) type ListOptions struct { - OrderBy ListOrder - Limit int - Query *search.Query - Embedding clip.Embedding + OrderBy ListOrder + Limit int + Query *search.Query + Embedding clip.Embedding + Extensions []string } type DirsFunc func(dirs []string) @@ -1435,6 +1436,21 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList ) ` + if len(options.Extensions) > 0 { + sql += ` + AND ( + ` + for i := range options.Extensions { + sql += `filename LIKE ? ` + if i < len(options.Extensions)-1 { + sql += "OR " + } + } + sql += ` + ) + ` + } + createdFrom, createdTo, createdErr := options.Query.QualifierDateRange("created") if createdErr == nil { sql += ` @@ -1480,6 +1496,11 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList bindIndex++ } + for _, ext := range options.Extensions { + stmt.BindText(bindIndex, "%"+ext) + bindIndex++ + } + if createdErr == nil { stmt.BindInt64(bindIndex, createdFrom.Unix()) bindIndex++ @@ -1530,7 +1551,6 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList if joinEmbeddings { e, err := readEmbedding(stmt, 9, 10) if err != nil { - log.Printf("Error reading embedding for %d: %v\n", info.Id, err) continue } ee := e.Float32() diff --git a/internal/scene/sceneSource.go b/internal/scene/sceneSource.go index f73829c..f474e75 100644 --- a/internal/scene/sceneSource.go +++ b/internal/scene/sceneSource.go @@ -3,6 +3,7 @@ package scene import ( "fmt" "log" + "strings" "sync" "time" "unsafe" @@ -144,11 +145,16 @@ func (source *SceneSource) loadScene(config SceneConfig, imageSource *image.Sour } } else { // Normal order + var extensions []string + if strings.Contains(config.Layout.Tweaks, "imageonly") { + extensions = imageSource.Images.Extensions + } infos := config.Collection.GetInfos(imageSource, image.ListOptions{ - OrderBy: image.ListOrder(config.Layout.Order), - Limit: config.Collection.Limit, - Query: query, - Embedding: scene.SearchEmbedding, + OrderBy: image.ListOrder(config.Layout.Order), + Limit: config.Collection.Limit, + Query: query, + Embedding: scene.SearchEmbedding, + Extensions: extensions, }) switch config.Layout.Type { case layout.Timeline: From c9ad26378e1e8e60536a9d0d26bb9bdf9007bdba Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Sun, 18 Aug 2024 20:33:46 +0200 Subject: [PATCH 12/14] Clean up README and document layouts, search, tags, and geolocation --- CHANGELOG.md | 619 +++++++++++++++ README.md | 130 +--- docs/.vitepress/config.mts | 10 + docs/.vitepress/theme/custom.css | 9 + docs/assets/album.jpg | Bin 0 -> 14859 bytes docs/assets/flex.png | Bin 0 -> 22602 bytes docs/assets/highlights.png | Bin 0 -> 36586 bytes docs/assets/map.jpg | Bin 0 -> 53532 bytes docs/assets/timeline.jpg | Bin 0 -> 20965 bytes docs/assets/wall.jpg | Bin 0 -> 33648 bytes docs/features/geolocation.md | 14 + docs/features/layouts.md | 51 ++ docs/features/search.md | 81 ++ docs/features/tags.md | 50 ++ docs/index.md | 5 +- docs/package-lock.json | 1212 +++++++++++++++++++----------- docs/package.json | 2 +- internal/scene/sceneSource.go | 2 +- 18 files changed, 1641 insertions(+), 544 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 docs/assets/album.jpg create mode 100644 docs/assets/flex.png create mode 100644 docs/assets/highlights.png create mode 100644 docs/assets/map.jpg create mode 100644 docs/assets/timeline.jpg create mode 100644 docs/assets/wall.jpg create mode 100644 docs/features/geolocation.md create mode 100644 docs/features/layouts.md create mode 100644 docs/features/search.md create mode 100644 docs/features/tags.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dd8fe2a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,619 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project theoretically adheres to [Semantic +Versioning](https://semver.org/spec/v2.0.0.html), but it's still at major +version zero. + +## [Unreleased] + +### Added + +- Assume a timezone based on difference between stored EXIF times +- Auto-crop black bars from pillarboxed EXIF-embedded thumbnails +- Allow collections that are defined lower in the configuration with the same + name to override the earlier ones +- Add `created`, `t`, and `dedup` filters (check docs for more) +- Added "tweaks" for preview features + +### Changed + +- Fixed excessive CPU usage and glitches due to file watches watching the database +- Fixed slow home page loading due to unnecessary database queries +- Fixed image cache size configuration not being applied +- Increase the default image memory cache size to 1 GiB +- Fixed default photo width and height across all layouts + +### Removed + +- TBD + +## [v0.15.1] - 2024-05-28 - Timezone & build fixes, and experimental layouts + +### Added + +* New **Flex layout** using a variant of Knuth & Plass, which makes a smarter layout, especially for odd aspect ratios +* New experimental **Highlights layout** with a twist! It varies the layout row height based on the "sameness" of the photos. The idea is to make travel photo collections more skimmable by shrinking similar and repeating photos. Requires AI to be enabled. + +### Changed + +* Upgrade pyroscope-go for Go 1.22 +* Fix `GPSDateTime` handling by not letting it override already-detected timezone-aware dates + +[@Terrance](https://github.com/Terrance) made their first contribution in https://github.com/SmilyOrg/photofield/pull/101 🥳 + + + +## [v0.15.0] - 2024-02-18 - Polished interaction with more zoomy bits + +### Changed +- Left/right/down interaction now works a little more smoothly similar to mobile galleries +- Clicking on a photo now zooms into it directly +- Lots of other tweaks + +### Fixed +- Video is finally controllable +- Back button should now work a little more like expected, esp. on mobile +- Fix dates not showing while scrolling on mobile +- Selection works on map view now (but it's still useless) +- A bunch of map fixes + + + +## [v0.14.2] - 2024-01-07 + +### Added + +Added `PHOTOFIELD_DOCS_PATH` for rewriting paths in documentation so that the same deployment can host the docs at https://photofield.dev and the demo at https://demo.photofield.dev/ + +### Fixed + +Fixed scene constantly loading while contents are being indexed. + + + +## [v0.14.1] - 2024-01-06 - ARM Docker images + +Docker images are now built as multiarch - x64 and arm. + +Good for cheaper cloud servers, maybe also M1/M2/M3 Macs (let me know!) + + + +## [v0.14.0] - 2024-01-06 - Logs, autoreload, errors, fixes + +Various quality of life improvements and bug fixes. + +- **Logs**: timestamp is not shown anymore as I didn't find it useful (logging systems usually provide their own). Let me know if it was useful to you. All exposed urls are now shown on startup, so it's more obvious how to access it. + ``` + app running (api under /api) + local http://127.0.0.1:8080 + network http://172.22.0.27:8080 + ``` +- **Loading & errors**: there are more loading indicators, especially for loading collections, so that it doesn't just show a white screen. If the connection to the server drops, there is also a more obvious error message and status shown. Same if you run it without any collections configured. Fixes #84. + +- **Autoreload**: the configuration should be automatically reloaded on yaml file change, so restarting is not required anymore. The same applies to collections with `expand_subdirs: true`, if you change the dirs the server is automatically reloaded. + +- **Rescan applies faster**: previously the view was sometimes cached "too much" leading to it showing the same thing, even after a rescan and page reload. Switching back to the photos or refreshing the page should now always show the up to date view, resolving point 1 in #81 from the scanning point of view. + +- **Cleaner exit**: if you Ctrl+C or otherwise "soft close" (`SIGTERM`) the app, it should close the database cleanly, so `-shm` and `-wal` files are not left laying around anymore. They are still left if you forcibly close the app (`SIGKILL`) as that cannot be handled. + +- **Layout fixes**: timeline view shouldn't overlap with the app bar on top anymore. Small collections should now be top-left aligned and not centered. The "Wall" layout was bugged and should now work as expected again. + +- **Add Playwright e2e tests**: this should make it easier to make sure that some of the features above keep working as expected in the future. Still a bit of a proof of concept though. + +- **File reorganization**: the UI, docs, and e2e tests are now there as three independent nodejs projects so that you don't have to install all of them to work on just one. + + + +## [v0.13.0] - 2023-11-06 - Embedded docs MVP + +It's basically the same as the README right now, but more room for growth. + +It's accessible by clicking on the question mark in the top right corner of the app in the collections / home page. + +Eventually this should be hosted on https://photofield.dev as the main documentation source with the README being trimmed down to the basics. + + + +## [v0.12.0] - 2023-10-26 - Map view 🗺 + +✨ It shows _ALL_ the photos in the collection/album on the map near to where they were taken ✨ + +### Considerations +- Works for photos with embedded exif GPS coordinates +- Since often there are many photos taken in close proximity, there is a balance struck between "not overlapping with other photos", "distance from taken location", and "displayed size". +- Uses OpenStreetMap for the background map for now, so it's not fully self-hosted. + - The photos themselves are rendered locally, but the background map layer is loaded from OSM + +It's still somewhat rough, you might need to refresh after it's done loading. If you haven't reindexed metadata since [v0.11.0](https://github.com/SmilyOrg/photofield/releases/tag/v0.11.0), you will need to do it for the GPS coordinates to be picked up + +### Changes + +* Fixed gradual browser slowdown bugs that have persisted for a while, especially noticeable on low-powered devices. + + + +## [v0.11.1] - 2023-10-09 - Reverse geolocation by default + +Reverse geolocation is now enabled by default using the custom-built [tinygpkg package](https://github.com/SmilyOrg/tinygpkg). + +### Changes + +- Instant startup compared to ~10s before +- Increased number of places from 6018 to 49689, powered by https://www.geoboundaries.org/ +- Adds only 16MB to build (uncompressed) +- Negligible impact on performance +- Still timeline-only + + +If you don't see any locations, you might need to reindex your metadata. + + + +## [v0.11.0] - 2023-08-12 - Reverse geolocation + +Location names shown in the timeline view via embedded local-only geolocation package rgeo! + +Has to be enabled explicitly via `configuration.yaml` due to the relatively long startup time (10s or more) and higher memory usage (~1.5GB), see [defaults.yaml](defaults.yaml#L58-L66) for details. + +Thanks to [@zach-capalbo](https://github.com/zach-capalbo) for the contribution and apologies for the delayed release notes! + + + +## [v0.10.4] - 2023-06-27 - Better loading and blurry photos fix + +Tuned image loading to be more efficient and less likely to show blurry photos. + + + +## [v0.10.3] - 2023-05-18 - Tag search + +You can filter photos in the collection by searching for `tag:TAG`. + +For example, you can search for `tag:fav` to only show favorited photos, or `tag:hello tag:world` to only show photos with both `hello` and `world` tags. This is an early version of filtering and should be more user-friendly in the future. + +Related image search #62, tag filtering, and semantic search #40 currently cannot be combined and only one will be applied (in that order). + + + +## [v0.10.2] - 2023-05-16 - EXIF tags + +### Added + +Automatically add tags from EXIF data. + +The only EXIF tags are the currently hardcoded `make` and `model`, and they are added to the file as `exif:make:` and `exif:model:` tags respectively. + +To enable the automatic addition of these tags, you need to enable it in the config. +```yaml +tags: + enable: true + exif: + enable: true +``` + +### Fixed + +Fix `tags.enable` to now actually work, as only `enabled` worked before. `enabled` will keep working for now to not break existing configurations. + + + +## [v0.10.1] - 2023-05-14 - Related image search + +* Adds **Find Similar Images** to the context menu +* Adds a basic search query parser currently supporting `img:ID` for searching related images +* This can be extended later filtering for tags, arithmetic/boolean logic, etc. + + + +## [v0.10.0] - 2023-05-07 - Tags + +In their current form the tags can be a bit volatile, so consider them alpha-level and don't get too attached. + +_**Note:** You need to enable tagging explicitly first in the `tags` section of the configuration._ + +They have some cool features in their current form. The intention here is to form a foundation on top of which many other features can be built. See [H_Q_'s comment thread from a while ago](https://old.reddit.com/r/selfhosted/comments/x601ql/photofield_v05_released_google_photos_alternative/incw9bp/) for details and ideas. + +1. **Selection.** You can select photos now via Ctrl + Click or Ctrl + (Shift) + Drag. Selections are handled as "system tags" (tags with `sys:` prefix) and persist across refreshes, restarts, and across browsers (as long as you keep the link and don't delete the database). You cannot do anything else with the selection right now, so functionally they're more of a tech demo (i.e. useless). This will make it easier to implement #4 however. + +2. **Tag picker.** You can click the # button to show a photo's tags (excluding "system tags"). You can add and remove tags as you please using the multiselect with auto-complete. The ΓÖÑ button toggles a `fav` tag as a simple "liking" functionality. + +3. **Range tree tagging.** This is an interesting implementation detail that makes it so that in some cases tags can be stored in a compressed "id range tree" manner, so that you can theoretically select/tag thousands of photos all at once. This should make it a lot more efficient to also add e.g. location tags to large subsets of photos for example as part of #59 + +I'm not 100% happy with the way tag IDs/revisions/etc. work right now, so that's something to look at in the future, but it's alright as a first draft. + +Clearly the biggest missing part is search and/or filtering, which will actually make them a bit more useful than what is there. But let's see :) + +Also bonus: fixes #21 in a hacky way (show the hand cursor for all canvas interaction regardless of photo or background) + + + +## [v0.9.4] - 2023-04-23 + +* Add FFmpeg hack to make RAW files brighter + + + +## [v0.9.3] - 2023-04-23 + +* Dependency updates +* Fix `panic: determinant of affine transformation matrix is zero` for some cases + + + +## [v0.9.2] - 2023-04-11 + +* Disable AI indexing if there is no visual host +* Remove search input on collections view +* Tuning to pick higher resolution thumbnails more eagerly + + + +## [v0.9.1] - 2023-04-10 + +These changes were intended to be part of v0.9.0, but were mistakenly left out. + +* Adds pyroscope support for performance profiling the app +* More flexible image variant source configuration +* Fix timeline view + upgrade build + + + +## [v0.9.0] - 2023-04-10 - Refactor image loading and rendering + +Replaces previous image and thumbnail loading/rendering system with a more flexible one. + +* New system supports all image formats that FFmpeg does, including AVIF and some RAW formats +* Added sqlite-based thumbnail generation system to resolve the limitation of pre-generated thumbnails +* Some bugs may still be present as testing was limited + + + +## [v0.8.0] - 2023-01-08 - Strip Viewer + +Clicking now opens the new strip viewer, but you can still zoom in the original layout with ctrl+zoom or with pinch-to-zoom. + +As this is a big refactor it likely has some remaining bugs, oddities, and visual quirks. + + + +## [v0.7.0] - 2022-10-12 - Remove sidebar + +The sidebar has been removed in favor of a dropdown menu for collections. + + + +## [v0.6.2] - 2022-10-11 + +* Disable loading AI if not available +* Split AI hosts support + + + +## [v0.6.1] - 2022-10-09 + +Upgrade to golang 1.19 in GitHub workflow. + + + +## [v0.6.0] - 2022-10-09 - Add semantic search using photofield-ai + +If you set up a [photofield-ai](https://github.com/SmilyOrg/photofield-ai) server and configure it in the `ai` section of the configuration, you should be able to search for photo contents using words like "beach sunset", "a couple kissing", or "cat eyes". + +## [v0.5.3] - 2022-09-18 - Context menu fixes + +Fixes a bunch of bugs with the context menu closing at bad times and the focus not working right. + + + +## [v0.5.2] - 2022-09-18 + +Better memory cache size estimation, potentially making images load slightly faster. + + + +## [v0.5.1] - 2022-09-06 + +Fix some off-by-one navigation issues. + + + +## [v0.5.0] - 2022-09-04 - OpenLayers + +- Replace OpenSeadragon with OpenLayers for tile rendering +- Fix date and small scene handling + +There might be some bugs from the reimplementation, but the rendering seems a lot faster (especially on low power devices) and it doesn't slow down over time as was the case with OpenSeadragon. + + + +## [v0.4.5] - 2022-08-15 + +Fix the CORS configuration being too loose by default. This should improve security somewhat. + + + +## [v0.4.4] - 2022-07-31 + +Logging error hotfix + + + +## [v0.4.3] - 2022-07-31 + +- Reduce memory usage by pruning scenes +- Add more logs for easier debugging + + + +## [v0.4.2] - 2022-07-26 + +Fix dates getting stuck sometimes + + + +## [v0.4.1] - 2022-07-26 - Preload all scrollbar dates and UI update + +The scrollbar dates don't need a server check to load anymore as they are all preloaded on scene load. The UI has been changed so that instead of the dates showing up next to the scrollbar, they are fixed to the top left corner and only show up while scrolling quickly (similar to Google Photos on mobile). + + + +## [v0.4.0] - 2022-07-16 - Large collections + +Loading large collections can be way faster now (loads at 100k-1000k or more photos / second). + +Collections of more than 100M photos should also work, but as I don't have that many photos I've only been able to try it by adding a hack to repeat a 40k collection thousands of times. + +There is a frontend-based loader now while a scene is loading. This makes it more obvious what's going on without checking the logs. +PhotofieldMillions2 + +Image/video thumbnails/variants have been refactored to fix some issues with the previous setup. The configuration has changed slightly to support this. Video thumbnails should also show up faster and throw fewer errors in the logs now. + + + +## [v0.3.3] - 2022-07-09 - Frontend optimization + +The app wasn't scoring that well on Lighthouse due to a variety of small low-hanging fruit optimizations that were missing (like gzipping js and css) and cache policies. This change addresses most of those and updates frontend dependencies while we're at it. + +The following examples are for an album with ~5000 photos on a DS418play (warm caches). + +![lighthouse score before](https://user-images.githubusercontent.com/1451391/178107242-c1b97a8c-deb0-4536-9777-0b754c14aa76.png) + +_Photofield Lighthouse performance score was 76 before the optimizations_ + +Adding these smaller optimizations makes it much better, even scoring 100 on the performance metric. + +![lighthouse score after](https://user-images.githubusercontent.com/1451391/178106178-d9d930c3-3065-496a-9a35-263e2f57362d.png) + +_Photofield Lighthouse performance score is 100 after optimizations_ + +For reference, here is the same album with Google Photos (yes, it's slower!). + +![google photos lighthouse score](https://user-images.githubusercontent.com/1451391/178106326-8e5f005d-56c9-45dc-b7cc-380fd3c46d01.png) + +_Google Photos Lighthouse performance score_ + + + +## [v0.3.2] - 2022-07-08 - Smoother scrolling on slow devices + +Previously the whole canvas was redrawn every time while scrolling. Fast devices like PCs had no problem with this, but slower devices, like phones, tablets and TVs were not able to keep up the redraws while scrolling leading to a laggy experience sometimes nearing slideshow levels. With this release the canvas is scrolled natively by the browser, which can be a lot smoother. + +### Technical Details + +Since the canvas is virtual, the native scrolling needs to be combined with redrawing so that the photos never "scroll out of view". If the device is slow and is unable to keep up with the redraws, this now presents itself as "white space" where the redraw has not happened yet. This seems like a better tradeoff as it feels a lot better to have a smooth scroll with some edge artifacts than an always-stable, but laggy scrolling. + +These artifacts could be overcome by overdrawing at the edges, so that there is some wiggle room before a redraw needs to happen. This is not implemented yet as a larger canvas can also lead to slower redraws in the first place, so a balance would need to be found. + + + +## [v0.3.1] - 2022-06-16 - Make it Actually Work + +### Added + +* Environment variable to specify a custom server address by @luusl in https://github.com/SmilyOrg/photofield/pull/7 + +### Fixed + +* App refusing to start if you don't already have a database +* App showing a blank white screen in browser (in some cases, especially Windows) + + + +## [v0.3.0] - 2022-06-06 - More Efficient Database + +This release should be functionally equivalent to v0.2.1, however it reorganizes database storage and processing so that it both takes less space and runs faster (especially scene creation on slow disks). + +As an example, in v0.2.1, it takes up 193MiB for my database of ~540k photos. In v0.3.0 the same database with the same data takes up 74MiB. + +### More Detail + +Both sizes are after vacuuming for a more accurate comparison. You can run a vacuum as a [maintenance task](https://github.com/SmilyOrg/photofield/#maintenance) through the app itself now too. Ideally in the future this would be automatic, but for now you can run it now and then if you'd like to reclaim some space after adding/removing a lot of files. + +### Even More Detail + +Previously all the file paths were stored as absolute paths, which worked pretty well, but if you have a lot of files, it can lead to a lot of duplication as the same long path is stored for each file. This release de-duplicates all the directories into a separate table, so each file only references this table. It almost sounds like I'm reimplementing file systems here, but I swear it makes sense + +This leads to only the file names and a reference to a directory having to be stored. With the above real-world collection of 540k photos, they are only part of ~4600 directories, leading to the big storage savings. This complicates the queries a bit, but SQLite does a good job at executing them. + +Due to internal reworking, file paths are also no longer needed when listing / opening a collection (album), so the query can just filter to a few specific directory ids, returning the file metadata in order. With the right indexes this can be pretty fast and efficient. At least that's the idea + + + +## [v0.2.1] - 2022-05-21 + +* Add embedded jpeg docs (forgot) +* Build on new tags only +* Sort by UTC time + + + +## [v0.2.0] - 2021-12-01 - Embedded JPEG thumbnails + +Embedded JPEG thumbnails are now supported. Extracting them is slower than loading pregenerated thumbnails already on disk, but way faster than loading the original, so it's a nice middle ground if you don't already have thumbnails. +![Photofield8](https://user-images.githubusercontent.com/1451391/144293168-173845cf-e0bd-4d95-ab80-ceca55e8b6ef.gif) + +### Debug Modes + +Additionally, the two debug modes supported by the API are now accessible again. They exist to more easily debug how and when the thumbnails are being used. + +#### Debug Overdraw + +Shows how close to "perfect" is the thumbnail / image being loaded. +More red ≡ƒæë too high resolution / wasted loaded pixels / slow loading +More blue ≡ƒæë not enough resolution / blurry display + +Using only embedded JPEG thumbnails shows that when the original images has to be used, it is way too high of a resolution for the currently displayed size, so it's slow. + +https://user-images.githubusercontent.com/1451391/144292927-0e5e3d3b-84a0-4d36-bb83-7a8a8f7cd8d6.mp4 + +#### Debug Thumbnails + +Shows the resolution of the thumbnail / image being used, the "distance" from the ideal resolution for the currently displayed size and the name of the thumbnail (or `original` for the original photo). + +https://user-images.githubusercontent.com/1451391/144292940-f298474f-2924-42b2-83a0-b5646b00d186.mp4 + +### Changelog + +* e95e1c3 Merge pull request #5 from SmilyOrg/embedded +* 9aa66f8 Support embedded jpeg thumbs + fix debug modes + + +### Docker images + +- `docker pull ghcr.io/smilyorg/photofield:v0.2.0` +- `docker pull ghcr.io/smilyorg/photofield:v0` +- `docker pull ghcr.io/smilyorg/photofield:v0.2` + + + +## [v0.1.10] - 2021-11-08 + +Fixed default Docker data dir `PHOTOFIELD_DATA_DIR` in actual container releases and not just `Dockerfile`. + + + +## [v0.1.9] - 2021-10-03 + +GitHub workflow debugging and improved README. + + + +## [v0.1.8] - 2021-10-02 + +### Added + +- README.md +- `defaults.yaml` documentation + + + +## [v0.1.7] - 2021-09-27 + +GitHub workflow debugging. + + + +## [v0.1.6] - 2021-09-27 + +GitHub workflow debugging. + + + +## [v0.1.5] - 2021-09-27 + +Fixed `PHOTOFIELD_DATA_DIR` in Docker image to point to the right place. + + + +## [v0.1.4] - 2021-09-26 + +GitHub workflow debugging. + +### Added + +- `jpeg` and `png` to default extensions + + + +## [v0.1.3] - 2021-09-25 + +Identical to v0.1.2. + + + +## [v0.1.2] - 2021-09-25 + +GitHub workflow debugging. + + + +## [v0.1.1] - 2021-09-25 + +GitHub templates and workflow debugging. + + + +## [v0.1.0] - 2021-09-25 + +First release + + + + + +[unreleased]: https://github.com/SmilyOrg/photofield/compare/v0.15.1...HEAD +[v0.15.1]: https://github.com/SmilyOrg/photofield/compare/v0.15.0...v0.15.1 +[v0.15.0]: https://github.com/SmilyOrg/photofield/compare/v0.14.2...v0.15.0 +[v0.14.2]: https://github.com/SmilyOrg/photofield/compare/v0.14.1...v0.14.2 +[v0.14.1]: https://github.com/SmilyOrg/photofield/compare/v0.14.0...v0.14.1 +[v0.14.0]: https://github.com/SmilyOrg/photofield/compare/v0.13.0...v0.14.0 +[v0.13.0]: https://github.com/SmilyOrg/photofield/compare/v0.12.0...v0.13.0 +[v0.12.0]: https://github.com/SmilyOrg/photofield/compare/v0.11.1...v0.12.0 +[v0.11.1]: https://github.com/SmilyOrg/photofield/compare/v0.11.0...v0.11.1 +[v0.11.0]: https://github.com/SmilyOrg/photofield/compare/v0.10.4...v0.11.0 +[v0.10.4]: https://github.com/SmilyOrg/photofield/compare/v0.10.3...v0.10.4 +[v0.10.3]: https://github.com/SmilyOrg/photofield/compare/v0.10.2...v0.10.3 +[v0.10.2]: https://github.com/SmilyOrg/photofield/compare/v0.10.1...v0.10.2 +[v0.10.1]: https://github.com/SmilyOrg/photofield/compare/v0.10.0...v0.10.1 +[v0.10.0]: https://github.com/SmilyOrg/photofield/compare/v0.9.4...v0.10.0 +[v0.9.4]: https://github.com/SmilyOrg/photofield/compare/v0.9.3...v0.9.4 +[v0.9.3]: https://github.com/SmilyOrg/photofield/compare/v0.9.2...v0.9.3 +[v0.9.2]: https://github.com/SmilyOrg/photofield/compare/v0.9.1...v0.9.2 +[v0.9.1]: https://github.com/SmilyOrg/photofield/compare/v0.9.0...v0.9.1 +[v0.9.0]: https://github.com/SmilyOrg/photofield/compare/v0.8.0...v0.9.0 +[v0.8.0]: https://github.com/SmilyOrg/photofield/compare/v0.7.0...v0.8.0 +[v0.7.0]: https://github.com/SmilyOrg/photofield/compare/v0.6.2...v0.7.0 +[v0.6.2]: https://github.com/SmilyOrg/photofield/compare/v0.6.1...v0.6.2 +[v0.6.1]: https://github.com/SmilyOrg/photofield/compare/v0.6.0...v0.6.1 +[v0.6.0]: https://github.com/SmilyOrg/photofield/compare/v0.5.3...v0.6.0 +[v0.5.3]: https://github.com/SmilyOrg/photofield/compare/v0.5.2...v0.5.3 +[v0.5.2]: https://github.com/SmilyOrg/photofield/compare/v0.5.1...v0.5.2 +[v0.5.1]: https://github.com/SmilyOrg/photofield/compare/v0.5.0...v0.5.1 +[v0.5.0]: https://github.com/SmilyOrg/photofield/compare/v0.4.5...v0.5.0 +[v0.4.5]: https://github.com/SmilyOrg/photofield/compare/v0.4.4...v0.4.5 +[v0.4.4]: https://github.com/SmilyOrg/photofield/compare/v0.4.3...v0.4.4 +[v0.4.3]: https://github.com/SmilyOrg/photofield/compare/v0.4.2...v0.4.3 +[v0.4.2]: https://github.com/SmilyOrg/photofield/compare/v0.4.1...v0.4.2 +[v0.4.1]: https://github.com/SmilyOrg/photofield/compare/v0.4.0...v0.4.1 +[v0.4.0]: https://github.com/SmilyOrg/photofield/compare/v0.3.3...v0.4.0 +[v0.3.3]: https://github.com/SmilyOrg/photofield/compare/v0.3.2...v0.3.3 +[v0.3.2]: https://github.com/SmilyOrg/photofield/compare/v0.3.1...v0.3.2 +[v0.3.1]: https://github.com/SmilyOrg/photofield/compare/v0.3.0...v0.3.1 +[v0.3.0]: https://github.com/SmilyOrg/photofield/compare/v0.2.1...v0.3.0 +[v0.2.1]: https://github.com/SmilyOrg/photofield/compare/v0.2.0...v0.2.1 +[v0.2.0]: https://github.com/SmilyOrg/photofield/compare/v0.1.10...v0.2.0 +[v0.1.10]: https://github.com/SmilyOrg/photofield/compare/v0.1.9...v0.1.10 +[v0.1.9]: https://github.com/SmilyOrg/photofield/compare/v0.1.8...v0.1.9 +[v0.1.8]: https://github.com/SmilyOrg/photofield/compare/v0.1.7...v0.1.8 +[v0.1.7]: https://github.com/SmilyOrg/photofield/compare/v0.1.6...v0.1.7 +[v0.1.6]: https://github.com/SmilyOrg/photofield/compare/v0.1.5...v0.1.6 +[v0.1.5]: https://github.com/SmilyOrg/photofield/compare/v0.1.4...v0.1.5 +[v0.1.4]: https://github.com/SmilyOrg/photofield/compare/v0.1.3...v0.1.4 +[v0.1.3]: https://github.com/SmilyOrg/photofield/compare/v0.1.2...v0.1.3 +[v0.1.2]: https://github.com/SmilyOrg/photofield/compare/v0.1.1...v0.1.2 +[v0.1.1]: https://github.com/SmilyOrg/photofield/compare/v0.1.0...v0.1.1 +[v0.1.0]: https://github.com/SmilyOrg/photofield/releases/tag/v0.1.0 diff --git a/README.md b/README.md index 8bb666a..0f775ab 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,13 @@ Experimental fast photo viewer.

- Demo - · - Report Bug - · - Request Feature + live demo + docs + License + Version + Image Size + GitHub Actions Workflow Status + Discord

@@ -79,61 +81,19 @@ form of feedback to not lose track. * **Different layouts**. Collections of photos can be displayed with different layouts. ![layout examples](docs/assets/layouts.png) - * **Album:** chronological photos grouped by event - * **Timeline:** reverse-chronological timeline similar to Google Photos - * **Wall:** a square collage of all the photos, because zooming is fun! - * **Map:** all the photos on a map? Sure! - * [More future ideas?](https://github.com/SmilyOrg/photofield/issues/1) -* **Semantic search using [photofield-ai] (alpha)**. If you set up an AI server - and configure it in the `ai` section of the [configuration], you should be - able to search for photo contents using words like "beach sunset", "a couple - kissing", or "cat eyes". - ![semantic search for "cat eyes"](docs/assets/semantic-search.jpg) -* **Tagging (alpha)**. You can tag photos with arbitrary tags. Currently tags - are only stored in the database and not in the photos themselves. You need to - enable them in the `tags` section of the [configuration] and restart the - server. This forms a foundation for many other features, see below (checked - - implemented). - * [x] **Persistent photo selection**. You can Ctrl+Click or Ctrl+Drag to - select photos. This creates a new randomly generated "selection" tag that is - persistent and shareable. It also means you can select tens of thousands of - photos without losing your progress. These tags are currently never cleaned - up and you can't do anything with it yet, so it's not useful yet, but it's - a start. - * [x] **Custom tags**. You canadd your own tags to photos, e.g. `#family` or - `#vacation`. Batch tagging not supported yet, but should be relatively easy - to add considering the selections (above) are already tags. - * [x] **EXIF tags**. Tags are automatically added from the EXIF data, e.g. - `exif:make:sony` or `exif:model:sm-g950f`. You need to enable this in the - `exif` section of the [configuration]. Only `make` and `model` are currently - supported (hardcoded). - * [x] **Filter by tags**. You can filter by a tag by searching for `tag:TAG`. - For example, you can search for `tag:fav` to only show favorited photos, or - `tag:hello tag:world` to only show photos with both `hello` and `world` - tags. This is an early version of filtering and should be more user-friendly - in the future. - * [ ] **Location tags**. Photos could be automatically tagged with the - location, e.g. `city:berlin` or `country:germany`. See #59. - * [ ] **Face recognition**. Photos could be automatically tagged with the - person's name. This would be a great way to search for photos of a specific - person. - * **Reverse geolocation**. Local, embedded reverse geolocation via [tinygpkg]. - Does not need any API calls, has negligible performance impact, and supports - ~50 thousand places. Currently only supported for photos with GPS coordinates - in the EXIF data and the Timeline view. - -* **Flexible media/thumbnail system**. Do you have hundreds of gigabytes of existing - thumbnails from an existing system? Me too! Let's reuse those. Don't have any? - No worries, they will be generated automatically to speed up display. Here are - the currently supported thumbnail sources: - * Bespoke SQLite thumbnail database - `photofield.thumbs.db`. - * Synology Moments / Photo Station auto-generated thumbnails in `@eaDir`. - * Embedded JPEG thumbnails - `ThumbnailImage` Exif tag. - * Native Go [image](https://pkg.go.dev/image) package. - * FFmpeg on-the-fly conversion - thumbnails and full sized variants. - * Configurable via the `sources` section of the [Configuration]. - * Please [open an issue] for other systems, bonus points for an idea on how to - integrate! +* **Semantic search using [photofield-ai] (alpha)**. If enabled, you can search + for photo contents using words like "beach sunset", "a couple kissing", or + "cat eyes". ![semantic search for "cat eyes"](docs/assets/semantic-search.jpg) +* **Tagging (alpha)**. You can tag and search photos with arbitrary tags. If + enabled, tags are stored in the cache database and can be used to filter + photos. +* **Reverse geolocation**. Local, embedded reverse geolocation of ~50 thousand + places via [tinygpkg] with neglibile overhead supported in the Timeline and + Flex layouts. +* **Flexible media/thumbnail system**. There are many different ways for images + and thumbnails to be generated and stored. Uses FFmpeg for on-the-fly + conversion, SQLite for caching, existing embedded JPEG thumbnails, Synology + Moments / Photo Station thumbnails, and more. * **Single file binary**. Thanks to [Go] and [GoReleaser], all the dependencies are packed into a [single binary file](#binaries) for most major OSes. * **Read-only file system based collections**. Photofield never changes your @@ -165,6 +125,8 @@ some time with a slow CPU and cold HDD cache. * **No permalinks**. Deep linking to images works, but it's currently not stable over time as IDs can change. +See the [documentation] for more information. + ### Built With * [Go] - API and server-side tile rendering @@ -269,53 +231,6 @@ collections: - /photo ``` - - -## Usage - -This section will cover some obvious uses, but also some possibly unintuitive UI -quirks that exist in the current version. - -### App Bar -![App bar explanation](docs/assets/app-bar.png) - -### Photo Viewer - -* Click to zoom to a photo - * `Escape` or pinch out to get back to the list of photos -* Zoom in/out directly with `Ctrl/Cmd`+`Wheel` -* Pinch-to-zoom on touch devices -* Press/hold `Arrow Left` or `Arrow Right` to quickly switch between photos -* Right-click or long-tap as usual to open a custom context menu allowing you to - copy or download original photos or thumbnails. - - ![context menu](docs/assets/context-menu.png) - - _You can open/copy/copy link the original or access any existing thumbnails - that already exist for it with the bottom list of thumbnails by pixel width._ - - - -## Maintenance - -Over time the cache database can grow in size due to version upgrades and so on. -To shrink the database to its minimum size, you can _vacuum_ it. Multiple vacuums in a row have no effect as the vacuum itself rewrites the database from -the ground up. - -While the vacuum is in progress, it will take twice the database size and may -take several minutes if you have lots of photos and a low-power system. - -As an example it took around 5 minutes to vacuum a 260 MiB database containing around 500k photos on a DS418play. The size after vacuuming was 61 MiB as all the -leftover data from database upgrades was cleaned up. - -```sh -# CLI -./photofield -vacuum - -# Docker -docker exec -it photofield ./photofield -vacuum -``` - ## Development Setup ### Prerequisites @@ -390,6 +305,7 @@ Distributed under the MIT License. See `LICENSE` for more information. * [readme.so](https://readme.so/) [Configuration]: #configuration +[documentation]: https://photofield.dev [open an issue]: https://github.com/SmilyOrg/photofield/issues [Getting Started]: #getting-started diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index c7cb8d0..f0bc6df 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -30,6 +30,16 @@ export default defineConfig({ { text: 'Quick Start', link: '/quick-start' }, ] }, + { + text: 'Features', + link: '/features', + items: [ + { text: 'Layouts', link: '/features/layouts' }, + { text: 'Search', link: '/features/search' }, + { text: 'Tags', link: '/features/tags' }, + { text: 'Reverse Geolocation', link: '/features/geolocation' }, + ] + }, { text: 'Usage', link: '/usage', diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index 69eb8fe..f698808 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -2,6 +2,15 @@ --vp-c-brand-1: #dd8888; --vp-c-brand-2: #dd8888; --vp-c-brand-3: #be6868; + --vp-code-color: #c46363; + --vp-code-bg: hsl(0, 0%, 97%); + --vp-custom-block-tip-code-bg: hsl(237, 100%, 99%); +} + +:root.dark { + --vp-code-color: var(--vp-c-brand-1); + --vp-code-bg: var(--vp-c-default-soft); + --vp-custom-block-tip-code-bg: var(--vp-custom-block-tip-code-bg); } .VPFeature article > img { diff --git a/docs/assets/album.jpg b/docs/assets/album.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9fd8fae5a00f0bac710fd4cfbfa3663bf7d0c925 GIT binary patch literal 14859 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

X)BU;DkIgsw{p`aZ-IFM8H@PM~B!pqD056A?F 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 A?*IS* literal 0 HcmV?d00001 diff --git a/docs/assets/flex.png b/docs/assets/flex.png new file mode 100644 index 0000000000000000000000000000000000000000..7048a79bea2c94ccf5807ab19d83defcd3b29b2e GIT binary patch literal 22602 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 literal 0 HcmV?d00001 diff --git a/docs/assets/map.jpg b/docs/assets/map.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fb91ee048654efbc181985f488fda6da5f47b8e0 GIT binary patch literal 53532 zcmb5VbyOV9w>CPs%i!)790myPk^sTo-Q8V-3_*hpF2SAP8e9i=O)?}91`8e_z{mUk z&UerG7^8x^_Za!XGO0skYhDLN~2LLPp0)PlW3c$3q_V#$Et*8U| zU*%@?rS6L?asAWQ|61+;U4dg`>un7HAke+2rL8@@d|t4{3l{SC@%RTrUNDihgQd+2 zu6x0pUN0Sd!R!B&Tm1(g|AX!S#fUEuK;)&PCHFEmq8ChO|39$R|G?G`Ual{71YYVe z*|@sBw2x@;A8hjv4*3VWy7<2I?O*w4gxI!jy4o*irk5`jKnd^`@D88>paWO|d;yLC z7l03d>*ehBQsNEJdeO`NFZLAw+H1U6S-n^}0<2#w{dl&Ai8 z8B-AefcG8%Xdn6SGOKI=pd$_dAlvY;^s@Z-K1eUWh<0`Wz(pkhfMW;%5Y7Vtn8yF{ z+l%fWJ>b(C0HE{YE7chQASV|9V6lH`+vI;4H~Ndg|Kqp+&pQ9*zvnH08~_Os@t@;G zkY5fIbQBb1WE4y^G*onKOl)i{Oe`!MTmpO?Tp%tM7Cs3+kdTO&m>3(6l#GOkjDU!k z=$}RqkX~$%Q7}+YFoYKNJT;b0uX@+NI-<=k(a>$UV8S=Jo>+a zjPi1giUvS`(NZ7)UUdIci-?4b06;-~-UMJHApj8Zk?>#MB7Q^u0!{dHxI`dJmDau@N?1TNCGasxG`T&`nhm|-)vPg> z-{?5`vT)Q`bbDv6!|YX+2uaG0$BB0dKmNgdtA*jNV~kUyTkoh#2s{#DJvCb%-Ea1yA2l)v8M#b};BWy%Z{0xc;JMuFG248{+gn&(&5^LmG5 zG?%Mix#ceKWa*A+|3(bHMCdv&SDrfV@%xEg2* z@h=8lqB(bot-UduhXfZ)4PDHNSkful^rP${3ulE9u{<}ecYS`%AsMf^B?UGu_?f3% zVEo-U$b#txKRN4r=SPx+?JzS6g^jSr`>c9u>XMO<3C zf{)dme`H2;&p!iX%LDJG&XU)6MVvf)c&LYo9O|z0NsHm`d0jmSBv#|d*RDqUY{wzJ z7w5;qAWg-@IbYb+kk`9N3lpW|xxjHz@8zHDZ!S1}KbRX0US`V(Ca$Xc1@i~d5|q*R z;nfCN=U(ml4TVJ}3g0zmB7M*M7=*HyWz1ew`eFH-o| zole|-=-V;NkMVO$M6#%}M7%Hg2IoZ8%>bcM>0@$G5j)Z+<^Ill)q>xs>8XFhv3*1T z$!0`BH3Fr2sqz(qCIgavi_CqnM}T0*tVEk?)Zpfw87S8wP85(>LT`{BPzM^gQHeSo zVwK7v{alVhfa2foU}|Pacy!$4?zy!20SeX`x>!x_GZt-ESB%wZB?6)?NnE%=-;Arv zt+LU3SmeS}7Bgwe@4kVs#pe3IT54Ch7HFD%hZp&H)Ih97bbr;ztu@u5vTIoGO{_FN zh^k{?+kg-?*4x%_6SnEMCr_tusS6m)f=z zza^q84}MA->~P=_FeY_jM!hE3vBlIlvR`L_{ew_nRKuz18 ziKd6KY8qJHUA5ap?v&*Ov=zmH3ufwHK%6CjQnU0&3 zl%i~hoKQ3@qik(j*-TIGN3BngPW()*P1FaM%Lhvp?Q!zCPZXm=9>o$hm%NVS7`Bz{ z_}8bdq12MLc%y>v>DE(B zI$dQk2S(qZNA`-$M2}3KK>S+BsP0;;Upr~szMz7mhYU%XUH_z z3@80%UcIh9!6e2xCB}6bC0G%+Wa-|~+d!<=6s#U(RlgoAnC0-XFX^*$)zZLs*=tUJ zxg}=#?^d?y;{#Vb6)F^;On{~T%j?6k;tM;2Z6S#hZ zLXAoNZ~v(chx=rN;rhE(scu>Ow>tfh`)+<+M96{FWmP~lhsjs}SCor^YvhIkxhkBR z(8BbuxVQjQw1RIo>Bes^z+0=eeiQTGG4-x?ww37X_P1tJR||dV94;%=*XvPE)uJwP zRZ7+BS7xGBCNX+E^H6-#g;J^I*yzJ3^VoPD8aTUu7oWRK-SSp=2nG+JlyS_trO=gf z6f`xqyeAlnKDw<#=tZ|v*ig z@+6oGcOX}C??2kUQ5%^W4^#10a4x{V=<4~jOpc10wSv^=rPa%+ch zMsAworus$i_~Q~R8XLSi`Z%2$&_ej!x8Q}Nk?H{95d&ua_#ZXc@DoX`Hm609X=hmG z`6rmrLjA07C~l}ehs#XaXXuI7o8JsWaaF2yIQQ$Tce}aQ{qmQF`#B_B8`jMVEhqK; zEnEJtE}-rj%7c{rOJZVWBJvJo1TCCW1k9>5aZXm80}IGEshi`iP@}d>jLa)jV#hX? zHtMjTo1br4qQ)0DH+TKkAecIXNy{N$#ijU&C86y2U#z^>O_m1bXVJ3PPCKb<;r2fyC900#2hnA7U_iMiRL z*$MhEr8|T{()pTtSDmv?+9=?s4E719?;9*iTj5~$(xC1XD|k|f7t)+?b3VyU$YXY? zu;RVio86T{moieCh}_6g(Xqh53;)2Ca~1X-!ssIdt^@fseYG4okM9lo-ABd<*j?qE zYrPaFN(?3-3L0P`eAzdnl-V6`azAU4vQ;ukr~KE{iVC*bdYrkE+S=rs_cx_`YQGI;xM1fhiZ8A6<9qr7}X#f#W=m^Ui?pYFKjqdVPCtU zSjei~$oGs>lZWqyun^O6^dgCk@`IHj9n+xD^t;t18OVyN?_U39k6@tE{BTWRzE0pe z2Z48atYgY1RH8N7PTy+TptH)%sdzh_+6kUNh(v!|HCa zuUeapXSA6_2L)%)q#{Eob(5%O;6QI{M|n{GGP7>oy#m(GUm6=Ekg<6e^f3Gv71zez zyl~n|)Z~L8#L}6=t`^A;bC+2=hwK6+TfQww;-iaU%O^XzPhE8;X-d##W9nNIlOAj%CUX<3VOg%Tk$WLF54?hJQ6FZ)eLgUceP+{}q|l|*b_uSBHMOQrVF|7YMu$~= z`+caC-F&Yfa`rZqo)tf+Ucey?-C%pW&(zxK3}8sCLN+V)D`2al$3a(0>Rad=mxDrI zmMKd8rt*){$liP172V2I7OuOs*F4-_d>{EplM)=INWQK#`Bj{5PDEeW$g&Z}*;3kx zv;Vp#AZcgwIpn%|6HP`6PFyea{Oa`#C>CGgwJjO`m3DY{EF;b{F_LaCbDh&g%(B7W zK)-Au5ds&wo+vzB2g>M$N^zm(@yQ3CenK14fNu2J!FRpf5c3df9eJ=@m;75Gr!=}I z_4EQn3EeY(5HROnd}T5>Q?ONY7iOP*wEYYaKuPS%;Y#s|Q$(=TH=^p3UYb{lx-MVY z;di`JM2HOdJvN5051lKJF zUJt7Eusv^3?Qh>nh~H`7CCMSCEamh26E#KZh4;n_BjLt> z;VX!U2*`*pgy@BVywDdU1Y`gTkbn@Ch@OF%j~|~AM8YH}gC-zL`odDsU&spr3L?+3 zNIR1!*JbPU)*z?>r?E?^ldbXkv^cgYu$G#|1=%L9(`p4P4|qG?Lh zN@L{oOU&N+WZ&`+#!cEU&_g{viPac7l|Onf)0LE**iWuSo~#C8j7Ood{z33%n-#+z zpQExrlTyec=1=G=sf-z#SpudAr)d%)tNJqj;r}fpxv>{HokvUJ;fMK|MA%CbWiBUd zmx$RSrpe^TW0QKj52NHl+H%V^km|=c(%84u8vhz@s=)i9noT4n%~muTSVrB;m=k(Wwr4MqAX;ec*wVaL=ZT6n_DVlTo{B~ z6!U#fxCe2<6>Jmy=lp{eAHsILXjX_vne6yC?>_?2rhmM@an(kHo_=Ja z-XJS&7Dk%AL;OCA2rJRB%6@ntBaD2n3OXqi#5>&^&~43{gJ_@J=rwUTLX`Ygb?O-~ zi2{nWvsqYG9*N8xw&VI(N^r``&mvAEBtrMK*J=P#3p!j0h3o6%TYVrj%tOV6IT&x%%9@>qO6o_!GW__-r>mH`z#Fe;Lh z=?3%+;A@J#?X(ed{C!U-iJh1ZBx^6^lyLU%Qd3x-kRx_b7M)_9;5(-Z31^!~iS6a6 zYcJv5;_eZUCxRAj|27oI^&TS(P9~M8Y_&%erq8!gGRQef)h2&oZM3YHpnKlJze)~_ zqpC7&6EW|@)I?Gduac&i~Add=j>FZYZIkRhyhTZkvs z9JL+aT5XN4EBDYtezvuEivuz#6-u6|o%KMu5VPVS z7*F!=v}{j*X1%Nvg=Ar7D(WuYV&5j9(=oyl69}OPn-g4MqBkwmkEpy~!jayK+l2gp z9;D}_(0{M{ZAzVao_(_d?{8mi%egiuY`e0!F%q%x9+^YNkBq$pn;TPquc){p5}Ty` z`(Du~(uLrD=ov5`m_DV`At(s(!(?G?oJb*GL%$y<6k%c`T_?a3_s!Wi_?k=c4HQJA zVpQU}%gC5MiTmEgq}-|BMy0(gwVR>MEZX@*57M47<+r>Ua$#2aL$E*d2_E{nEL0hv zG?eQixJa#L>uNczQX+Y`q5A%D)U-r!oRRsQI>G*%xU&cmmj<$UAG}Vj2U(Do{9(!^ z*-g8MAW`^DxRV)%0hEp+h=$lIr<5Dtc z7$r(RR=nqcP*?#8|1;zD_|sF~lL16<&4UzY!+ zhT{GKvAnUdYHYXB2+x&@cv=Bp0}6>xNB?foITKYJP0GuZN1!dW;Y5ziuhp(6n8n*O zEjCL?r%YeTzfx+F!n9V44mMY9b{_Fz_p%1!k6nMH{qhVj<9w8tPYx&e{a61L!Q*7= z;IKkEypMS&rry0$NU&Z1xM8ei$?L5KoHO`-DX@CzfcUT&~rjRQg2d=rn`egbn(~bZI@- zYdR+yy3%ww?v=*e%-Pn$^mZNV-X5J9;W-y(CaNu%{1x38v$D+Felb`-DCl!DnB z;j^Qrc5U6$uQ?1hUAYfU;NA6&8IkZk8YG-(Ql9y3Oj2t({`?r__1=mxWRty|G@$HSmnr^wTeG(3qE6WWJ zxo9qcK2mx97>9BdcLG0d#mZ2U8VZA8O#oL0P;ti0&SjOiHtc(y(|}=%0YyHK`4Lr# z2m6oTmE-AP@Hadb^i@k3u&Jy$k7RxHh}tHtQI@ys6)|@HUu({&l;lOnSoIGNjvD&0 zL^scXlGDFpY3WX4=Ud$OO^$}k?yR0!g0F3Fc|eBRhH#`lOT7j|uJrx)L69}UYolp} zj9bYiAUw9;>ApaWRYnEt#izZiDbCatA)X91!?+1w_{#=vVh$4bDVW!dbdzzhg^bxW zF#N!2QD?tzI(9QZxJ^{!RP_as$N+^IJc~4GH~5#qYRilgFzTW-^Y>>Bd?c1#gw)9* ziKx;9t3V+lh#>Z5?=v9#N?qc0Uba&~B_8j> zYnp(@e@xJ{K;H|cJVdBwhL5KJB=H{x=T13UcjN~+QRNtTWwDqf@iG(d+oNq>gS+Z@ zG>qBiJ9c#QtjTa(V~0XMKUCLPeGp^Q71YIRP(n6q_+)n&Msec=SgI|wFb}lrM%E>g zmsr8c5>TMu%5UmoN~~`iuM+hqJc0Wi5%NoSiPuR0Tim;P%#fv9I(Qkk#HXpaLGAu? zt@b1Pn}3}JH+gik;MY#vDO&TJx*Olmd=~{9bvv(>-gF#!)H>>o?SFZE73Qs7Ve6!M zC_q%Qa;03`WP00|c|9<`v7vxt z(`j@@9yLDwsu-_q#%4EU+O;c3a}TSK990~P!DQi}=0Oq9 z+1_$~m|I000fYolY&f;_9sDsnx14Ue?PPt} zj-7-rP18AS%h5F$Bv5+3;&a4pa*n$AbE?0TqIHba=*vfoZ*IM{M{T>sTjuNRe|0v@ zmq(}H)QE0cHWbwASJbOZZ5laMF*{{4eyvD2QXFFP-shbj`=v2Wgqj)nV4n?R$`P!g zYTuiHC_My3o_H?V0Y0*Zt@ah}GvTN$2;CGcZk1;cf zGh*#$oP*oTM>5~Ep{{#vmJx{x>y`#*R?w3=MbVkcgRT(Ew6=oE)U+0joo`QL%m2EzHz>X@p`VQI4CR1v$8|TR1$lyWfc7KGn!NM=-Uuj}Sv^jS|^wSVq3U3D?^c5YoxUhKFRGsUQ_ zERo&cGocitaAl}LIU#S$z5l6~A`$0geo{(DPk0cjP~|LwEJyWQ%3&`bZlL-5a}$)1 zjWN0FyTRz@)9YY6d1}w2`5izf_RXVgy_$zLI3!_!7MYVtXgeyoPy2GS%}{OTX%&82 zD%_xXyPjW>{lf)zBgCAi4DFy9+&;Qakt0DWtXVK_3F5lcKX+-bpFv-N$A!9Biuwi| zyBnL`#Mzy6n@CFOgB+yippA|Dv0gvFcrwz!0&|bs`N$2M-t3cqSat^DcM zw)!PI8Z2B(lYl+H0SK-n2X2PvS^r7zwvlge4`~bc2cuU` ziO}Q25D0S!DBJ0+(P=5H`_+%~AGk6(!}M1pB*+e=p?-dz0EVig?q@*e)RsX>RZaOY zL{fpK_5O*C6Zh*Iy_)QD&RDh{HsLo$Jh;@QK?>%ckMfsMTG>SD>wn;^jKp;1i0{Z^ zqK|WbCBzbwL0;i~y|Pr#uF@aw2F37?>&Pq0DtL|3^K&SHIHFS#l2Y_|EU!!r;@_4k z1YQ<`D+s+zXI+m`MET&3@um+q`g*^{`o})jKln>XCO9m?D~y3KDK@o0VV=SvB#@x~ zk4M`)G>fP&J!&;J>D&9(B7_t7_BWWm zuB6km)1-bmlt4B{VlovrHyHjiU3)Rw5lxozK6DoAByCo<`#ak@M`^t6BT3hXs}A zLshj7Wi~tQaW6RSat0M7)z86%W^J#ue4EPH<5M%vv7^$7<1sSEIO#0nIBKT5%Nl8}_KypB2{V^y!C<{k=VPtEfHQK|Ul}WkT_ks(J>{WdvZR z32N}aBxGOW z8UMcmN_1(DNbV%MdbX((zlO5HW&eJ+&g6e>0^m@ctXvc*)F4Auztp)+Vtw zR_$uQtlzH@Q;^AcfT{}l5OU!1Njovr5 z)sbs7RC%ZN<#!oj_7b(t=HRo0k!ik+NIR11kGVQ3W5PMg9bNL1sW#t~cf+i53pfAH zj4vdf2$=J_UfHm|txh;=-U=9ZPF!VwwY9?*GDfir{WjRkR_s*3f;#sF>lpx&GXGHi zB#sog>>GI5CK4a1SOS%ERz$|aTKI&}zu91Vt68;dHJSbvhCmeTi(vyUy>%EsC*VW0 z`72$9^&$C|?+MK*Tl=xN;Z1^|C$Z11R6LrSU!+g+h(T;kqNg@-PHOIZ#i|d+vw0Pd z!|o%Ga&=eZC%>NoUq`;Zvr!E)Cgc+vo7$!G^FEFGi~}oW53`({fVed{{bmshl;!CF z`}g7~@@=n$_vDOBh>upT&zR%e0CSU6EeNYSa|)c5lh+{*Z1ZfiZ&;((L(nsWXBb*% z=@R=@EraDv8A`f034`Sl;hPpCK~co4y30Ir9~MV%!*>qEZ;0hgzy_ikkN_A%kX;nW4XKKs=NRRu!<0Th$FO z(iTyEiYIG?VAY=72z{GI5c^?h<-2<~C3npkjO;2hQYH0gKqq{IteMX<1lr>w4AIdY zZndmZSDt~8bY{x-kYM^06$i_AKU9J%cf$26aA8CochRS38Gj({lryqUf8MViwD9z3 zoGJ`ALv%Vj|BRzznx@JfpH(vAcgBn!g0dF%icP2~;L}}%)khb;O46cwY|Lm-UoD=` z)ox-aTx?TYN3|)^G8-jRrB=)x=iEqA;~wl(aU<8*oJK+eoQo@jV8|aZ)@&_VuR1&im$!y;t=2WCz13T zs26^-mt7ARDP3r;kOW!_^`2maSi^WA96sI3$z9UIes@wsh;S^;kofNQOfDU1#5|C< z?@ulqDfDaTel!$#8}z|x*#b@X2031*x|1m9=6K0NlAcb{LvjAgxLe%S5luswq|}3G z$};DDfFG^Y-)Q>Zw(3wl%U;#Os>Snj(Sc>>Ut$V?hELdNEuO?2>DY2gUkG2OGNPn- zV*di4&v$*jz@$daT%s8|X&D$P7SyGkP1XI|jXF3jyi`9MtSEU8l)^{3hN8}!AP|hcy=Kj2%X0&nl3(QB zPk|Ij-I@q52j+V2m`Dge19YhQ8+YyM0&oy$YZ@5s*7kXtPi^vg<6e}s0$`_IIL5o%Dr zKP0i7^TyRuOW!8wiT`F}t{L1=#MiZHcFo~XPI^{YnsP3o^9)d(%w7=-C_LJCOz42| z0v-b5C1hE(&q5QgZZjB@WCId86*e4(cp$&f2;WAnwKqaqk=cp1^)NYyylgFsKsSkb z%U50@iif5hRlFk6dQ<3S5&N7J*aBq;_xlv4zLBp$%2VhjAQaUaz4%wS`de8RIou z7y-M9`6n#rR)=2$%O)LHCKlq?W4&WCsY9yVK0(%u$uE5!p-22vJr;f;@_ufTt zitMGUFBUEQ*M?tb)A&)XB?jL8y)m;y!Ipdqb^^AO0@qjx2DbW!oLm$aq!G!B*|6si zXxCXHFRvu`9c*{mByQiotd8DQg1})#Ws3JW?MsSF3OGG1{0KD6u9_ZV$EEwUOWv`| zejgV0WCB&)T4?axm~%usrYECWmi&b5S+G?H(qJaly@;U0O(*f0&7kyUf;JgUA-Qt9 zj|{F;vwk1m7mWxew2n>8-M$;>(pFsKZ;x4_zSK>V<(^_?K*Feh`cVIqwsQ>icwjeY znT0c|c4W1N(_Nrrgd_Dgoob~6WNCry?C;!P7cm68*P4F|8w7Jidrz93Vul0rrnY`z zZ-;V=-;7}KX#&)ND}f2(H{FSw_UeY}-Bgg~B8wdU_IHUa70s_58morW-&DN=r&n>c zK4g94rGB6|@$J2qbF*)723s?{V+E(sEUR8gC3Z$!qrHdRhm|Mw=~%=&LmxN9bX;be zgVwCyiN

  • nJX|0niqCpr{W!vbWrptiWY#9@h>&z2wI$Xg|AE=Eegq-S|7t%@_z? z^~hDnCrYgo-XgRw__lJbt|M3{C)r4QfFfRM$0~W4d9xnv%4c{n<5Nk_p9HVv2W(WTZs&n;?hqZBfLM0ORzZrYdoDmv= zJx7SP$fbwSAf4l{$>1;@4o@BQp&76bE6b-7J_MuOATZzFHMZR~|u$8-?`LC&AvevBd=jlX}vdxvAZBUxQdXE z5*ztYPDq-anwmuRq=!svogCV7GuU)eISvjAMzdf?X16P;I14nlVA!L^gOWkcwAp-& z3v0=e`Y%ld(|Eg6O_D}tQd`u4MuiatS`VC~K;xj7RhmUsDW3oHF?~p^@g+{WeVb7X zU&hNT*=$F*nP;L%_9v6)99Fp=S0f(JXRxo)QoQ6O-9mdT#M$ z?s3PY7}?jL5yVnq&rtZcYxQ_On?Jc;ZXFVk8J+9Tv>--XOZjaIfo;=2DP~rlt&<-@ zdyurN1(SPTAOf&B?s`+Z{?v__h{ABgM7F ztan*SpIZ^BuVHKRk`&DMC4Ut_xZK!l5CK0Y)0*1FqNLd*sVRkp$IsR{45n5b;Thbw zj^U-klQJ9^5D3AZnQlEc#=9QuLf*j`51B<>w1h(Hx)GX^rvXKdU64%IayTjayoF=c zU)h;+_B+Tt-6jF4H*wns>h35?OMV`3BF=$CHT0Hy?X|X*VEYMg0Zs!y@_0RTawQ5g z5`o395B4nyLw83UFot5Q%F^j^oP<%HLRsP6y-)rF09`9XT_qWZjj+ywD z%i^;vx}Q-04vELt*U-A1_uFY9M&l=JhS>utMxi=ZET6VcU14GLuKY>RPYICQ(2CeU zxp;zt;WSAgIYMt0q!Gp)?W?~u?XF5OMb+077Y;OHIwTb%=1|jI>oG5i!tHQ(5LaUX zR2P#?Ze8*tt48Z)e=r_64O5^N#-p}RGP?BPlCtL7KiSvkTcP}Q5t{(rL4d8a@)VO{ zoG*w0E<>a@W-9GV&ET+b0$I+w8R!l?HQp&xUgEcvIeE8Vn;lJ03#TBpZS*Q* zvg;7JeC`Rx{RuDQf(n^xY)$QoCkvSmwS*atUm&k@RXH$+nrLmwIyd6@B5S}wBMVid z?4|GBM&@(DuG$P)q@>hSh+&o>>N84>uQ}P~x9Y%<*$@1$mGyWlKMC)8&RMPqKYX;A z&ZgUNqTe*(#RyVcqLHwhBjlETS{Tu*E$W<}GXS5Kg%80mQ)6HhXKaMhH|k7v!M%8(Bp^m6!ky?a<#ZpxSdjw9PfU54 z-4UeVDe#Vkcj@W(rS{ugd&QF^U(V^j&w%BWNW_AkXTS?b<@ygtMFISid;d4_{=!j_ zfd6t-LIxRrO-mw1Sx-8UoK|GXzw{LGAA0J$=wWs2jX{bpIb2#ao|CdoiO%mA&!5O* zC%={~hGz;LmI9`h3}))NfCkV3g$%`JVn*mDU^VX!`y1_)^iN~y4><|O$GVbsz-bP5 zq-ODHvcDj$ruAVRTMhv`_LS6O@$uE@SG}a=(P$2>ueJqVh#Y9zRLoA|gKKp~z^a98 z(LL0FLF5_m%3ixa;TEKD<2A37S>Vdu%D3tCo}UGc#gR3@u1d9j${wi9H<`ta>$&bq zW}}u5SCvz1$>vi`OTNnzYSer+5!z$H{S4?@YL6NcizNdZW1S$3Yd`H#D~7QkK#28o zs#=|`zIS+I{S37>KnV<3QndGWSD` z`7w3$(&7G1>Y`1P%*KZgOF!)SS9Rzh_ey&={DoE()_CXmiE_kKGs7FE z;?T;lbb@dpCEDJ|q4D3aF)?y!VW_juF+;ziYWH~+a-mAyhCcpE7+;O944P^BesHES zT2qAMx>~gtLVg4GfrAmYbpR<{j@6;?an;N~CJQN!a9qIagU`*g>DaU-*mKTbm8sMq zDDS=xCn_&Vk6Z+}3h$Dv{ei+MR9TS_#b8u7fI|9mXdU9Hr$9)iJ$Mgs#u`$4H5fT! zi#)A+d~N!^p}fvQ_8S#>>Xss%H|JO4_)l*}_PQn~Mzq0@*ReSA7V__cTQxebp8*NR zU8s_tt;!Q3$-}}qBeU}3rvN0i;v-KJ-Ba1}adXtypH4_Eao=nTDy0#mJeChcMhHcG zW|3F46C2dSwM&GYwqZm1Rg$$W$%A5$(~*2B!}*-0(y1RZs7ANEdCB%2Z&vWmQTN*X z>91#h4{HguXFUUGnBT}@-@-8_GH6W?GNrsHFouSF5+S&jjbAlRrFlnOJK{Vkjtc~p z9hZ)`Z<`a&X-N`+Kml(0U33``Z@!|&Fe*EH;Z3;Laai%Guf5wz)9LuN;7M=Yk9>8^ z*)qLilzkn^MmMV}GsbIVA>^l$JwESr&TEb4n#FEt$uRfg-;X4u=kYGAw$U0Pvi5uf zNSY}@(FwPaoSn}Aj}u~DzqeJ2o)`H0+>I49LK!?2v=9Qvd~YOF7(e`2KV*>8Qor?3 z=gjhfycxA&FmkVNpfmFnHCXDDM5q7@Fffp1`pF@pYt>d7CHdX}_ZC}nz7ejG+m{F@ zicUvmW|V%pNrYv+_lOK05W>AL@$=C+w!*SzEFwS~T>E2HiEzRy%W>rE4_{v`nW&>M zwZa9jgEQwyTRP>`;;&sfBALssk}8Yy|THXv6cDUc4IU#~bq7;n)s6)QU2SCIY>`^VMbWkDdLsIdp$)aWZ z)ie}}z7t;7NqBUiZNhZxoQ-+~$sgvhOR@r82nE`PuBAS~h+9E#<|usOPB`YhcG`B1 zvlwe#wNEiVx%(bFE1_vg6g&qiHp7dpK<5&WNR(`!t#-amJ4IseDljh6Yv-T{Lsqy$ zK#Mov;qBGC+ibZoQLENw-3@kOz74nmD>gbaaPuS2O{ir;xf;VZt%~oI6->KbHS34VZU)ySy4Mn9&q&iObiENeZ@j*l2m; zX#Ac>R1JWgr#FHjE+P*M0kU3-ZP^@zzZU-%=>6#Pz4o;a}^9q;jXQafdQs z8W7YK<*PnweQd5-p?46CFgYg1rza^C(I!$|`kPOPV49@4S0&@|{V$5j4ABu4wUxQU z`pKK9TJlzST!mmFMghi%T)v&y-WtfzUE}kxuanx4&vdm&Aq&nI{1{Dvdzk)XZ9>@3 z5A~kJZ}z>peCuF%mTBbPg57gM^$xgn!5Ud>j{ zkQ1rndwx8JJ}SGgPDB=XbX=INd8bZ1fu?+41f zljX|Um#F!;dO?;|gRRa#jx6;~~4sWzwWRz{!4wO+5CjH|6vY&8*BwafejfN%*F#4gTjVu1B_ zV@aFB7@S@*w~6Cg{?Kr^z@a6!B1kjs!w8=s+R;7BkenV~Wbwd%`; zw;}8mIR$DsWKjL5IP&?3f*Et1dd-L(UnHXg+WPnpBfT$neAF)Art9cp>dp|!EA+gW zXG)k^wQhhp;7wvTbtf=;U`e=!rlA?bm2Ug* zl?p?%wk7siWUR{e=F9d}{}g2@2A(&$9oall1AFm%W1HFWI>}b{0I;o!(+NjtLe3pi z{Sz5k^+1+=I?%{zglW#rmQC3{$6mP;dU&XciWD~HD6-sC6+Y!6T&VCk$B>rBOYf#n zI5vOhbVK#d8(VEiQp?f+dqg8&&e2PgQE&S03`L>CanD3X3xbNuY>ybYkW6nY?tc^W zR*Wn>o*%7MC=vl@#Pf$%toAX^J0#cmGMwhE>RP4F0*Qoa#DjA_=SApMxz>ODGR#uE%)WS_*KW;WN%#KfMgDu%h@k1g5Ux%X&G|fW4k^R^)42{ zU%Y{dD(Pvh4QU2X_Aq{H^JIEBQUM`D*`{D}V9_#{6SCwu13R#H?}-Fa-x{1!LG_rk z=#BR@Bud)DA%cJKV|p63uT|)%#+Y zO>>y+&PiAd!d!WAFE-ms2rH)RoG|lO{U4RgG_2<;0f59yID|ehtay6E*eE649Lk2irB*HdoJH;>Vj{)XPI{r)3PJ>j|G2CxKGF*wd%G?#nRYDz9v zET=<}_V^c0p7!JjSERNz@PI_c136N*U$*JdSbW|5N*LDbJj9x~M(h!FO8m#AezQ`( zds!B3?Tt`R>?vhI)MSMW^C~}vk4E621}w71q_AQ0cm%e;#bl=kh&XcRvA5#rJ2H~B zf~b@OsC>dOY9w#eXw>)wlVp(tL{O&jl?M8HsYiEt_0N>x3CRW0n<&W*JGF$kV2KFuz20I9*>-5L$He1jx(OJ*$MEz$x zgyd&~y|7i|rD^Ro4r|u}6^uf2MX|7rR$KDt7z%nQJv?ca5(hn8tlgmfcM;h8q!vDP zi^l1Q=tLbrNBXA4Y0&3$sqNW-9JHKPFGeAYt$-Tx3)`787&b_)%;FUY_m|Gpno`}X zEF69^S0uvREl7V2NVFM_eg4tsZx(yL9p*{^uu4%hQNrlFg27QF=YU`{Iq5CV^h3sa z78Q~Jx)NLr?ndPrxp8)g?P7%IjkWl z9XQf3+}!31os5zRvcioK7as9(to(F*Y#WtYXSK!Va-YJrFtP{~LcR8pshUIl#Kd?I zKpYC>UBXXNd8$;YLRe=*BCImOAF|j3VI}qs@?v}!-qqD#+T3MU`Z2ICGL$Z=T5A`y z&Zt+*Nmd6VzLkO-&6pZ{%DsK}W4#$3UCCdyxL_NJdPoZu)7V_eNHi1gPOgLr8x_j%^g#{ z5X?eIW{e%LYg5{I`i@WiDd|!e^u_}u@IAh0WR^7*Y{?oh!&W)g^?-WvRPNr$0z%n0 zyn{g1`MN+dxyIlMA=w|LOSwiz>n}#!=g2pQD$6uTTBR0$hEwK%VeN&`SgLa zS~d|u5Z`Niw(@CdtFmHi$vT2n`FpxL{1uudnQP9wsL@S6fIDS%eK_RIb{q}-rXIQF zL4RMOJ612ef1?NtWoq6PS};au!3{0p)wd4Wq==G@Y|p508;6q#Z%UM?WkRGf{I+5C z9jOC`6=pV%G3DW1LnAQzE|_6}c4;59vQp&27^yUM>`cLt`=f$e>G=!y4bB?zbScyQ6Rk8%QFv zwnnn+B{y|Z9OHlbsmH-)-n_n zKLggYnj{x8UV|vDfMsb!k*V9{DU?>&#EW*A?`+<^BQ?g3-?{`&k=nu)9i7f?W6-+#+R321*TT_pZ|PRC^J z4g0Sh1*lSp$(C3e-(Qb2509PhP8Hz)AT=0Ja}-|jrKhGKK8Oj(9L70qJ_O`&=vI!@ z=3OF?8Q#}qM8F*STDEj7ejyGmyzU3G-r4_(1DZZGQViZT@RvlP>2s4`3S%Bs6fX*3 zzI!{aAa6W)_MUHYClTu7_u;o(4?CZ3dLE6s6^jdU*&vL3C@=gRFMzGyhUQB=YBVC z&3v-=FTTuNDP8Y%in(feAH$)cB;}cUe~v7ZFoXb8cSD6{&0SkLyU?%l)R>>%jSEd` zl8F-e?4$$=F|v5XDTFTw6vpE?iK$KPRqoiwDBh@h#0bTSBvYyV%2lsz!!!0y(Pl%7 z<{I$O_&)%?KtaDu^eN)efL9khD{Y8()fp$cFa~sSD=xdULYqrS=w)bipEMu|&>u#5 zazX(Ip;;l_hKf!~GmBIVHAl9joTpTAhP45R^k+`!iI$#DTp|>GN}mYobVLkKwalk6 z?a0umKOnG1HS}XuEf){64PloPl8GQ5h_$XBPUMS2t}`U^3xIff^FWMtN68LvJ{GH; zo~30R_CT~BG#o|Q=2I-gTr^Wr#-&emC)9(xoQ|L76RxKPxFCD@1btJ6?l`T^hr>Vn zlRaCZ@C0)>$JI$;WjK=K8s>lYsj>Qo*&O7L(ReC3!M7N1hh2V1n6?Z-L%P#NPMdLR zJc6EF%8zN^W#6iUqMZaN6P2A|b4869I;R*XtBHURGrXRd3lQHWBsBSi%r{<&;RBz9 zCtq`qCB&FVEPN3j9>GXbG(4B(3qQL1q`Xb~t&h#4Ow*N18@#*6u^((QR7;OqiG z?n1SsI$BKs0G?=qr4X~iKBarHpi;Xl-e4=6GBo6$0vYbNLFT<+4*fkhNswa*@e@3w zMA9x?MEqL6ne-tZHva(BvV2bZA|zw7ISU_kc7!Zpj`S7gfY;hXsZt6c6(!~X&WYKW z9BS!^PR=}(Ii0w0j0b#5i}BVMiylhnnJSGr;%^!FtDHa`R1X*3GK+N7p6U18XGYEt zY!kx+K$}dkmRqRDDE(E>bw_4$Mo-CJ*^}SP_C@IXEj>|&>Fv2opB?(Cxj=Z6Dqm4< zZ#+3t^9|)0l~R>AG92$`;J9us=Y$)N_c?zsPdEwTyg1bAu%r{7=yUX7We$zU#bd-! z-gu8??3{N{S#O#Rc8)44SEo(h>zoD`s;J7bk{IAnJ{iKec>t~)mkYR%#C48nFbjz3 z)dqWH-1RAfBo>K2j4d#7g{yk;rj6=V&UD?9R87&mCeOOK zbLMrGd2b(5h%ShK%^;qPfj1*5M`YJg4B65g!pLLur80@B2Rnc+|YycCx8Amt@nRkG@I8S?` z4v?c)ES&TS(ehkI-rUrVAG+y3GNN-cKMw)U*uVo|) zS|p(N;QOI)k0kKxm`MKsGMd_UvXE)CTQYO5e3L_FFSzd@;|e#I&bkg?ysALH+GAUQ zbGdi(R4KZlbIU<+5#Ccj8MLyQJVCE?D_gZvbxMWu&S9*W05$riL6^D4)D6ck4wZB44oxN3#M)7&o(a|L(mpd1XzTs$M{Q`C5g zVx z;CEMQLInF+J%XXj)&8qv{{Z&4?wt3Y28nSg%-O3lTKkF0s2=vZ?BO^|BTovYS8S;j zoWbqFFWK?D8kf!!>C~*x;#8h%?N*w>wl*qTDx;e1*?5D+ zDqiSu0OS{HxGA{5qxey~fFE!>{{WRbG~mxA`GyK`IY!uTnx|8z3G_)(D4tLItn>c> z3U#?`qS7Fz3x+i%G*cWM&})pl-3kOXs?70gQBCqX1;8+)sv)2JCe(kx6^sQ7RXl?F z`X`#={{Xp36O=EJ`W40Gq0WKOom4g3!}(PzlxSSQ$R_X>%~GWc7{`Y=-Y_L?Vw>Am z7(}$rRcV$>gkcS%dBAGj6Y)ZjWIftTRUOcEK-R$^DlKpG+Q1;w=Bb)nJ>D{&0IuJE(sngj7To*BJL+M939F|> zB64lPO^K5b*I^DJO`|wEDi9R{h@T0wYli3#+omce1nCv1BiTdP)e!OnoF*!yZu zOHX7QPOF~iFxR}o*!DLtH)*TJYP0ONp|n-FU4cd3da4vnI%STk8fCN1e+v97YBy(V z&ii+P7}adzfN{!dPpwLwwKJQFA;W``h`92*3T%&x;Q)O~7?asrT^AxXgzo9X zn((g!yuuzlEHVP)r<5%9Zs?u7411#-r{aW&U~D&{CaDA*I&@WOw=RPggkqCNnM9WG zRHy?mr&&gJ6D-hPY0)G0TbVM!X7MhKF%b?BoXPUSW zLc3F;Mcn@AT-i2{vf*&Iy4_uTc7(vr0r^%`1Myt1>J!-CSmVjOlda(gRxn))*HjIB zVsedA;$TNbds+m0MOza-_J`o3+U7kOa1qsE!2DLK-w$qUXbo-$KaZh~GD*ZOqQDyr zI(d34{{Tv+9>+fU@TW1f+}IMfkLz1Nw&FmJ$c`fZV<1Y|YwYjcZ%?zVLZ8JxR?l9x z(&Kw^-8s7+%-yZD{8a8OaQJgXegHtnh=oMA_T5VxVXv!_JIv`22r6`)Iyxw)NX zT;~8iJwEHI!)dFYYBj}^*x3jyQ zTso$gzS@3Sa*J;6{{Y!#thgg{Ts^N6fXCyJRcO*=yz-vw{vF>%N|jb_-r1S+RcO0q zxw2Y#)jFSrrW!yzNsrlRvT8^$s8re=_;MUtEmgLfFZ@IiP0hGrQrap|wY30?S_ulM zq{Bt5jc}mtZZ2`~wUR=iLZJ=W>3C{;$}X7-Pm|fANP96|tu=aqB?_iB`uD<^O zCaY;P=_OrR-&rc-sOyrr1P|*jGV*Qeyh5; zjP*?uZXsE!9aUYQ@Wu8Ry8i&|s@X?#Z&NC(hRaloj%-^ut7&l&$K-_?w5f(c$QfHu ztj5x7A2eR}+7&mktS+rDe2Hl|(NUt)b$OD}s0yUtvoi#j2$jdMRWJVlnXlVK*X}9O zYj6YHLyy5$*~GVMr>jW97t|mgf)!Rh&2t+601bjsG^vXQB!Cvm2;r+)zjW>ve6>%g z*2~VgMmBcmS!l|Q%{?Ht1O+yafLp@XJEK&+uvYQ9a|k#d61P-ySmzv+NE6LBBH%SW z7l;m8t?PY*k0dXtV^6^fbt?y(<)@+!$(<)CwY5Ma@{ie0@Sc!Yf*sWy9Env9@71)5 zM9yUPS!4Vl`#j7>y_MUik#J?jph5VdQllKm7e+Me^h3n*?HbYN`yyJ`2Wbu(b5pNU zr_omEv@4({y^evBWA*f21iQTsg;RGuPQB~)ciii6wiT)>(} z$3PZn>jw(q$L8j)L802{oCh(4;Tdbb>!Nk8Bd9{45f~}sOZb0u%6i+y@jf&hP5Kuch9wQuXV0v!QJ`IPc$ST4aQ#I1?DC1Pvs z9^*e!m}>f}XIc&G{&hu-wWMnSNhQOO0S*EF?5&q|MfGCowVq4<7N`9yW@Eu?+Sr51 zTvMs>I-9Y7C6--FR=RXVx4`WsH}_O(+&~!qrHnAA0Bv$>>OB7dNhr98W2V#tN39CH z{$HAhQ$9O!)2eG5NP_PX9FtpZxY1IfONx_xXS#E=Y^lU`3OyVxK~o=-(ZXZNX&nm{ zYU0|iXR$FT?B`}}9uoVmC2hM&rhg9lFdx@NQ*l!5-W?(oekGqZyQN*PI+*C4QOCuS zs?q!@b&f5YlPdUkwC;|Q78=j`l}(<#w?1?@1FGS#QaZxi0_udDu7oMF=Q~`5zOPgW z)2UHOyN#9FWgXa;9~D}flz4&d3mjh+h*Cs!M%q$#Ousc6Kkh09Z68&_f4V$Y56qjH_go6Vlg&B6Y1i(K8&I^0bEM^;`IO+=H3?|R zC#@`e0EWuPit*e@@L^a1%wKdRcC&6cy8YPtwv&Qm|rb9Q;|Fstus zI;2Dbs%NI2N%Jv&eu^Rs1*9iFjknr-dIh}NpZJRO#Mgs`3|ZZMiAYLJ>pTh!v`|Dg}@-7(`#A!^--)T7KaT2mkS!-6ZtG~uwkJ_ zi3~^SsW_dmYr0zgWgqFS~MK~Yj?V9SP&tZ(qTHSp0U^@@>@e0wABm`APbhn z`qdmNRBr8!-Ub+IGldSF|8q^kv*YO zr)GWQ@c#hBE(y>NlBW6R3#zEkdnmp&SKSom15xI%Bgifmp2)JH4QS2Dt)?QFJ`^Wa zfiShirpvU{NBP|il{NXl)D(W2AOp@jv!8 zQKz(R0rbqwvAqg3T}9Pe1~{N6RjEKzG@xF2N*k(@*0v3&nx(YXH)9jYN6j?*r3ukk zNw{cFFsfCh>>$8JJi#$JKsu$(8R(;hHO2&VT6jtf6$2d7d|cOnp5YQ);o27q4{qwA zx?KMNqH`{vB|3UIBmwOx7rQHu4O}4yIk8L?!-XS6;{N~%ARn4#Dr6d=qa757ME?M) z@6l7I{{St##20lc3BoPX+~qrnXuLyC>8@+r4FnknOn%AR%I$H02fz>61H8X^%m`o1 zcg_$R$DpelWQl-U6z-#^}Q8Yt#R*mB-)SnXCDA%bJ zm>q0mniShy5FI#nRp)5hJf**~r&Mhf1aff(dh%Q9MZ~~tb?+&``#`z=z$u%O%WZ=^ z%BM=Et>~jjR{O>mMH7MbP~Li?=JlVF$Wn;=dJcgKRGl{asg2ac6>lN+R@=aNDsO1V zgn*^kSM3kSxH+gEu9|`JQhr5WP-l}=azMDkaBGm+-Y<9;9(MzDb#zsqne{mj@M4E?@%ge6YJBc?d3RtOpQ?z%<4@rNmSh>rOlI~ zt?FpK&3PA81fROB>b8e4fCECerqBX*>XrtscB!{8`7G0L^C_0Gz%=Sqo?gnQQMt6I z76_4WK)Jvda$SM>tYb2o;gyC=aO$|-@CxRSF6CT(pQ%%xxl%r9xsn`%95PCAaFahJ z!Z5Lp>xthGgBwx7r3X_ap5lA_kI6e=+1qPStr)vNe@5dG$Q zN3?@f>77&9!Hjv8{HOl_(wpj^gV6_6tk!e1yDdS@sYp(|LRSe711%$(G^ugcj5nHf zU9H%7%{ju_A*MHnlG;mzHqnx+_2JL^hoX@u*%F@p<#~CWLiJb6-|eHcKV@O8P1nX{ zVZaxeS!_5i>i|xOJK1)=Xii%?Do!KWv?`6|DiNH8b)F+DMV~hwfdL*(=ojQ>o%{MD zQh6hA@f5(^kW=GC+n-_AqAk36$`2KrVG(aAK(+1psV@YPBPrDng`!vD3^~;>3~Ik9 z&&Sc(3XJhBk?kSPYeq-Jsc0m-l50k7A65GP%Ftp}tY{IbD`@_&M$Xm?>zR&&DfPe9 zetJd~q~QC+kc`$Y9w~6w5Q}aHh0+B+s<;(e$GX#zX!xwwq6b|5%9}th$4n`XB7>Yn zwbDTDow^wXIJ_JBpHA_ux^)ZIXfz87j$$RSjm>oHq7GKImbCM6KprAFtOe1|KR}_r zAHSirnnro8c#pJKs~7iI`(2gl8hrXDQ@n#vSR~JH)oVTa)}9y`cs?;pI_i zGX=$LzvQg!ir!sx_Nu-anN&K)4$xew6=PVXT5USTI#G74Itw#?UJ7V0L2U4 z#cK#Cyw-U-HNNperEMFA1k?2^4lU(sAE-vo5IZR@jC_^FoLya%aR4J}fHY2Y!=lwy zhxv{63UPnYHa<(m2!eCv<@>1b$!j3!q2t152wN>4>`I7&hbJ>t;}ML?7Sw5zZoK*? zL&fKWWkul6bVl--IG!%LE#ctPp`(Obz=xw2;vt!S~Zp`s8} zjQCwDo_&CaP@E;k776rtek}nOwAkZ~;pCd_qt`q(!slpJ&1U9$6|_?o9a5Q+*=$A= z9ceDBOg7(#_bYu>@Z8%>c$`dr$W?pop-+=^39v23iI*Krrat2bRUQ_PnzS>6?uPJO z*~&6*QOY4cnboC9@H<#SxYS|s&$HUBM$pg6DXw;qrLEUYrkV5y2V!(v%13s3 zBHw6pO{GwRHIS@Qa7RSLl1Te{8`dZ0h+Dl!BwNuEHH1gJArD#c{{TB68uA)66Q&ix z-(T~(w(N8A{{SU;jJVscKEG7PgW`8o-YkxD{&ZTCk*VkvYR?Wan3T$8uF#PSe85Ma zr~kwNCJ+Gt0s;X81Oov90RR91000315g{=_QDJd`k)iOh!O`LI5dYc$2mt{A0Y4#a z@wPdSCETnT3}}&7zYF?hKnd>`caC6fKH_ZVj?es+rE!$puo)V-%+#tamv?S)nO-ea z^-q%)B}!dW{>sp%Vu8(8SD~w<-3PhpMf{x3o*zhl7Cz z!kB^_E}H)Uv!?EG^@5MFG|hpYq!Ep6+!qIgbhDTzAXd_7OP%P!*SEdTiAz*A$lp^n z((@}83Jy`hViGFxWeTNbNXbok?JAm$nJ>h$g|&_#;-P3SBNh2E}j>tSO&qo zOF`*C14oO0Sl9}&!=j@IoX01>m{`U%69ng0OT^arou>=Z@ed$dsVPDWT_&>f{{Rro zc*_^3f;>$wJTVoTwCR)!S5$Q@H*Z(a%%=(#FQl&RO3c0r;`wE0JqrCoD}9Cul(zEl zdawo@+Gl<$p!9uF1iYYIeBXu~oH{yG!}8+bZ!5|6;|p3vQr6pGdrGrQy|h$h%%ff6 z1@h9TWX7O?nl6sA?%P>>y2oj?f3O#Wg*46Zq61}wX+2^su$}~^W)sQ~j&|B=$dm`H zYG08~!0W8Ng=n|x;F~R$)?U?35e9_zk5)F#7;?Skjn87C8PHH-%F-f^ep#9-hDKdRt@rF@BubpuW5hrZ z+Uc&h0$^3B4|T!4EG1Mr>kZ)C#*V6sC|@JlDVd`NCMm3Pb*QU$&-oIAbPWV4Rj-un z!bTQbXlF~IfPMARmMv1*X<9yCl&c(5@iaU+Zfy$C+`54Nl9+u~u9N01ewX`mFGG3H z@l!`;mDk&s9VmhNgzC=uNIM2dsa5UX0b_<9haO?~@^H$l&JNEq%8eQC9ZR2bP}q-A zj0(1>CPr-LxsFOP)ihv;cg!HZD{`3WTOt0&igefe91|K%8q@m^TRN$capZtSTc9@v zS$$c7E40$47Ztg`$(l=WF8syZ;wXLJPl8(*7^P9K;p|7P6hxoIJ>sgEXFf84qqO`? za|~LQD}%82idLvq64YYY84bGe_q5H+6i)r3EO)?gf06eMH-MQHspuB(W=J8`#zjR? zUD!XKvx43v(qUZ=nVN+N?OEmt@`it)hhe4#(dm|6@k>)z)}kqym0gc$)p&76?dapy zKKy-czgcHTTHxyOI-aKoIhu@4Bi#64MenBNdrRX0J*yuGyU^4&&aiw+SUQ*n$)Nb4 z8bP4%{h7~4#IWQv-F@OV4yPAgqkMLNT4H2v`IH)}>2(v@3pWgf;@8H+Ml_5mCmCJlqcMB08%()^pLmDn1d5J%ja*UR;IB?hIugKKvaIy|{F4z>xjuxpQm|{G`GSg< zBFBidrDDVW!G?j04~qGeK~78T?F|YUxWm*R%&cz%bM9bPBG!q417qstD@B(d*@oaP zk4Ozgs*D#9NM%=>>C9OYh%zhiG%@4-T%1=`x&U;nVn*QS5k@V$bRz&<9VwW)W|7pTy%u zZ23D8EULn&o>*0#zyZO@{{UDhvTM!yOxgul%nIgbw0k`mLA|_0MkgGvNPiA>l8!q^ zU@nhB4S7LrfW(?WdjCRNp> zSEu3=>#JriBPK}0f9a`XYPO#_&$ql6Yg10*V+cxnJ9vkJwV3AxM{zB>I?PKJqwb3U zr~5s>F}&gX{rpM*ITF31A31BkAD@^y%L4S`^>mpTm1V|L4Dwpf5FE0tWgbtxAYco> zfB6Gjc7_%`BS3!Q_hR!AjW@kY{Ddvo7cn+*ydpbWV4Z`+d@D(eLZMW6Atbh4vfpW8 z-gC+NOaj{t7UjP6QJ7!jVO^@LyA05_5afnMA(?HAr`wU&p$uDM^!I>3+hz&Y_U`qE z(1<=@6)t2i959C31y}LnG73yI>y0iVxEOaHzBkMq3TT@}JRIsJYKj}I-c46RG|Cw> zmEnwavkHk1b(bC~mL4>7M`g?}Y$G?)x#QXwTCcy~G3U*SX@$&*#h(ylmuS={$Vc6P z5)Q3*lr4Z~?zp6fQMY-!T9akF_54SPM``Wz8Vc@}%l=GSA#K9rq*zse_jV&5S0~mN z;@hr$CUMtazfH_CmQUg%$XyON_+~C;Eeu<)cWD06(3V)XH_@3Z6|{bdeq&XzfkjhO z1mlv&yuQ|D>O0IaZl~mhX0T`iQw7rM&f&ph!2aUPZPp^X7R{P;5e38i(t^X!U+Q{< zWY0(kRV!h%w%ogvo~H-i(v&pTL67!uonH9N;cJvA5V;PFUsVGh3S-CdnUoL(i_Ln> zW-qOd#}vt^=$=s78|3hX;GkFDG$57(P9>>cmlQyRZ3~;IC|AaJ|Fxu!KezFXuh!w9!@w#T4bZw>iuAaJ+v5B<`4;o2D*~~ z>%h(RWz<8n>cmw3nme#rEOK+&xnx8B{6>dtV5Gkq<$L_2U-e=$05uP2lC zPfF`@kSN`aPzvdu~;jts5te3NAlSp#`-yfb!!@oCpSq^43&2K9YIhH)pT zt4luiVD$>du1kE?9CaoC02o+vcICtrvJOIJj&oPJp+s8J=H*q+49ko-1J{|2{A>gp-vj<0S-s})5Oh}SyM;>Ra)tpsfu9gAM6#YVr268 z^pu?)R%AS>h{FX=fjmWo{mcNM*;2@K7Bd_5Fg%d6%{!ZODk7V-uMT=t%*lGCvoGQt zFUqueUwd~FqpHz5yXEr`8c;vXhoJy00&j#%<-%Z_Vr%0$xQ>VeY=ji4wPS_j zaY30Ys)K zL3N9WBC8jS#u-#7!O<`cnp`5$N4}xXu=MZDvaGU;F9gIz+E-3>4c-T!8ROy=T;MPd z;*4zqFu}@S_rwXs)vBEsKrjs&?Ju5qmxDH3YW#hncS?0F&Cy3cNJ>RlOkF?n^O4Do zvbBJqtN#GQR&?Ksmyc9YfUJDX-Ys>mg-T-8qBB;J%Q?7p*6TRYWpYo`k{ zFZME4$}}<1JmHDN-cx)d3NYcf)9d9z!*)#qQBOLlxGZ+_*_q`1dvu5wxz+xRpj1_z zCzf3)@KYI{+MyMX)#f+$PFnnP>k15M89WnouXCE{KnNTr614F{>K_zFiUW_piqJK) zl(w;(;Bx-@&ai5~Xk#7Xlx^j|h9WUo+hcz6xg&+_t}KsLGQh^8;Xz%|>`WK)e^Fg2 zDsrW8Ru0^BmSge2$)l{Up3>1@*;f(n?Q5@y&lP1Z^@DQKI1QA2VI+-b0M60IsC&j){vz?F@3ECRu-2% z2=pc7R8db}Qo8i$v?}_QxMtQu76u&eFwGrcX^RHNj9?uC^_AGpkWYfnqpTEX)Vs=B z6^w^Rqb#h>Dgp1$X-QtQXRq#4wOo{6N8 z{{Rs!KzP6K5oNER;vd3iD%_`MJ|*Fu2am?4M%!KW>`X#1ERWBtwx33V&+gXKeObcnY+EmW4CFdl0GIVz`k%F)TTKvMy<11xZn20t9`q$R7EIL9q+;I z6f3F*14qG$U33Ph`=VW_Ci#5+TwePxryk*w+fd4X`a>ndU? zOCzxh{`gO~;EA#qR)!`^2l8oze%5o+#{DLqw7RtW( ziD|)$d%H{u0k*qep{Yn5g1yDsfZ5@1>LuVh0UsnceaxOcVz3Hdq|A~;Ze1{_>hMFg zk#oAiZn`7BEGwuCwYve#e-Pwoef`T84*|m~4(`w=pixGTCu7)vZKg{s3S+yel~~ts z4nMNRFqGX}TI!*1GMfWWO~ovR+c9?Cp7NH-@|l#ft$B-L5au$3-z;GvYXal^vMO+L z`!V@uJ<;bN9ep6)qUxRg)E|dN-L3h3t8VRN)7q#MvdNvCO6zP5VQ7WY!1iLkG3-o>_l9i0t zMVYthWwvx;Sgla){{WOYFe0_O!LVI9oOq#1I1bB|o8jCA-*N$u$`WN63cjjX2{uQx z-msVjYPZ6+{zijpvvqoxMphM~0_l*aSgJ}2_8;WuDc7Xo;kz+#`0Svs!M7Xu49=Sp z9Jut!=`#lkVNvYzBlJTdcnH!XoYUQeqi<%bFL56!7dr1?3k6=C>mKUsYspm( ztCwldcbL`LIB(3WDQ_@0-5r-gvvqPp|=iJqoUSj58-+}ZIk zBG?$U{`*RA1#O(BeWKZLYr@*2?^p^3Rp9!tSY#OJ{{SZ5uI~EAPB3oZ7ygpW3f=qv z0NHxr79YK(wc6{hGX{dd@H2Y2I7`;HJ)@IY+T4S@@H2+iDu`W)X)s)T~(8+`}NGE;6P^bXAG0?+Cqv~glv+E~tl>&NN=PtzVc zAJ_m2wRt*!$T?g^Eag-1dy(K7S&8?85UrCIh3sw*u$O0)FkA+J{>p-`_U1LrUKXGm z#1mxN5x!O0XOtC(JxhwJ_V+{ZzFvokQR9MxLp7_qiX`|>6e!`Ak|7)gE0#E zj`2VVNk^bXU*3N_uMR zOn+r!%i%COLK1_b&vW@Os@T{Jp^gN9C9V)x0I;`W?jxQlGe=xw*oBpKhCr)&nV9e? zMa3t)SrpYN@-vi_kRq-wt34pFA{o=61l8t1sYbbeQF8N)uH09c#v!H-*x)-cQEXW3 z7SmaRj0csDbnw7D9?NrhC0z;@;55W5ojfk?g?g~g>rKPI!ax%RYJ-*W5GxE zolHI%lEkvQVqF4OU^xDn=FYJ*ffjZ@>{`X(>4WV#U3^1s*BM!jR;o&~DO4I-QTiq+ z#Ukqu!1;=*0~@X@KP0m61{Rviv?WSCp$c0WLf9_2SUZsMDfz(YfNi0u z=q8z&^Pmp3Z|-Lz3OcA+@Kz3T*XWILV9hNHPk7}|XS%Zc*UVDI0^fYUsJL=jIow%V zPcb8Gk>9D_B(dPpuM7;X?ySGkW5?_<*HWV_cIRnkl-V`@Ac;933+XFBQe~HnlF)yjC~}yH%rxXD8zjam_XoBfj?2(A#a1yS6D}ZRy54H;X!O#o56x2 z(+y$k5kdoyp<7(dAEesFRc7&jnR%Z_eyng~7Z+cJU=5sN<>n`LuW9C+#D9Z~tNwDc zVMft3c|YMAV(a)6L=B0-E{^YLR2QaY!=1y20f-Jx=cE%~ z#rb={GCDIpRkbf@xlrkTCR&PM!PnQM5Lp79;2Ak9tZn*WaY*TdN8exeATs$J1HZo0 z2_Y9~d}<|PLB6gfDQtnbU-IJsW?g39@o;n@iQ_9cb^DnW#6jx=;Zjyfl7PjQ`F^71 zMbB?pd++W1~JM~Eny zON&x~7oD7(Kv5{1pTc;&7aMgdp{gx!HBnIzJ%Kg%ybPi>6j^B4jPBp|2~-`>qs!I} zB~sVKPJr7H{X@oou`p%FBaP-X6>Ney2TRKRAkwJN)CTiKgh*BUijOP@f_xjAB@SEHRJtI}^7uyInY%dtSzOg$_mDys zC2-DITWb2;4ggglf-ZdhW;%tOl~)})LX`z%tv=BbqM(}_Ma0}jaPnl#tx>gCRjcVX z9ypRaR`b+_T5VG^P1F8DD|#*@J|WR_$@0fD2{dbU^p?(AnhHz--`Z0YCCxmCJk7xE zS0&flk3Ha5yIsV^>I1Qc0%OBW*^ApHZFLq`Z665$nYiP16aW&K92L*@$s%_l}` z8s^lmNWIO)ZWZPf30Amcyk8$<{Dxi1hvoAM05Uz1vY4pnh$;cA%%;p~eG;0Efwllz z8X-$b$kwp8V`oW?9hLD2iZI^~NChdQed5KS<_C^<%uJPcWw@ivzN)a{9bp7U8In@u zw4pgVGgZwOyBKpkJMl4O3p~KRjS=J#vWP%-H(-f6=+rg^TLj5~ip%n6;v-u$`)~}a zD3KRdWgpo>vK#MUm?bToDb`XO=9sU3A_l_&?<>9x^$_)>PB!~W1ZYt0KT?6V zs8Pj#*^|~~pnm+zaczKJUC;VRxpfU*SbpM8wI@;oH8jLs%0-}!gP-j9q~o+JNG`C> z@2^61$~9bEUA0(1w=jdSh9Y7!vwfjmlh!M36{wiMJVKRc1nl|H>$u&+hc`0#gK-TM zbig2QXPCn)7hu$BxVBf!ux^0#Sc+9$P8nR{02~=02FqwQn}4iL-a$aOjejOEu;^&% zG$RxklHWpOzb;~eqnh;lQmuOoP7BG}dT<+-=2%uznjE^TT_;hmC}}QoL>%t(8beJy zc%2&{IJ`z4^jzXK0wOeZr|zYiyMI&xSE;S)4I($*3Q4A%Fk91Cs$_qNIpo1oxV!E8 z=4NI1cbS4_eNTDKyXt!%`1jVsrvCskuqama{{YOpB{+XFmf@KvEAG0^w~fNX)L9jD z!r;#xHyD5LcTr8@i1$+R8nJmK?eupV=Nk$*M42Q zK(@CSOpA*79Fc8-qd14}I!(x9<^@qm-NKtaXGwq~6sc)O3RJ3#4A7oYs<9Hvmtz>~a)a(8HUJO5#GqaC%oT@fGY8?ABf?PgHEHipn+})9!|A6O$Ut3PT}T>kM#vW1&)<%mtis}&AWkBdjPs&}^1Q=9wf1&R z{?IVLnNZVkaO>sm0FV{Na=LHzMd^jrcwPKr4Jw)idwsauS+#|>uODh(dkJ;dk}e?< zVb)L(t~woHOE0p+-d0ep7$RWAE|i+A1r%dMR=BRS7dGvCveO5U>%`anKZLHjM?vOv zki~NmKm&FE0F@REjCyH;tYuJG^GMo~7`uZ2m%gOEBB~CzPueP?7>;P$5D}w+Oj$A1 z%jfcK^=5~XV>-FD1)h5SCOvifV{B?z#7ysNsqX+b=9}{kki!=VabtR=tCv@55*dM} zKkyG3P)pS05nq_k~plAhiMb`Ia>mjI5Ml`r-%L z#T|Bd{H7p>$!+?61bLvTm4%C*a3`z+_U3Bt%#b0;N_rH2a~G2|I0fK|;%Ax+vB-$9 zUFsWu=l|LO3#^rj)3?0(`gKz=@F#;;AlD@EjOs1p~RDVJK zHQ>97F!mRW25oHv@V{*30~XP+H-hH%@w1}Z&RGSD%{AhrDn8r@1Ozk1M$!R|?Di6q zK4?V9h^RlwTI}~Jux{Ezcgic{+4`zTRJ$|WuqB2noT|oks(1iv2P4NCAZWcf`;NvCwz^FridfgbZPciG)FT4N*><6x@5G-n^5S0kebIZF=#KOO*~A zsR$pP${@itJY7H|hofl(mwgkD_m%$ulV{^X!%92KQ_K5+^&sbb`jB!SD++7Qc^7}q z&%vCk2F_`dr$M!( zgH#fj%Bm19q8nm9N&X6WQ_|9hhJQ>f0Dcu5ud$mc&Lb?0;M}lbnMH3`6koN-rOKJl zd0HwMEh11xb6G(?xYvc^w9t1ZoDL&uxG7yi;wU)&#N7;Zt0u5u&}!ud(u_DoO-7?2x;cPH z5SyM}eIK|=SC?V)-Tt|DHQ2%Mf8O!B>G9q#dXL!~zG@B^D>lGpK$|+&!qdsbMU*VH zv5jKF`)%zno6NKuh>I|;EVQLayP}R7n^Yc=O|ypph^S4gnuB?kP7Fb{zMPR9Xva%} zR31C7BHf}Z6L3IIVz%=s3MF_QzmHZ9rm&|6XXoR*qQ@+Ag1sd?n9<5}^Drxup#TBY zL6}veS1d`x5?JaCVVKcOR~4w=ZOWv?R2Ckn3?KrDU_%WR5&*FfCFWc#PAa7hNw|O- zdrQDE*?QeM$*!NfK?_~wNM-&V zyT3_q1#@so2m%P!*-?n%E@N#m2vC5M&d{#LE#s!U*#BCaF#T7!)(0tZN%{k`omT7T(L*GeFO6V zT03p^e;Sq^ht^OnJ*Bm26{yI-nl2@>G_o+k*KNH^2}f?A9QTY#K?_xiHK?=(^Qblk z9TLi6nj%*mO=d1RnCb|ad&=S3IZdF4MX=?cMY8&9cIbr#;PG-C{5%<>AaiAhLT!V|Dt> z>io;%g;XjzfCpO(@8RR-4c#7(@PA?ywx|Nrm{FGzO0^2hJTNDd#9lic^a~as+XyCJ zJ3(<99w9iPysH2)E+d&-F-%CH%uKkaEg>9FXhnO)5aUb-2-PS^5UTEr4`jdvr`j)f zONl^IvL%9CSRr{ST~8)OPoNyL`bZE>K|Unn2>2sjIiF z5FyUU@YTA zDrJReApj6j_&NUo9)2S;V@hk$tFOqNsf#TdoiuSVn^7m?^d*6=Cxz!*>Bn6a>E7foPA1dxfEQ9v z>)K|bX-zj;tY;SXh4WYH{{WsoW!2PInC1o72o&<>YYjh0XeIQvsv-7w6`mZOqEs)5 znQ($U!X4kDpcuHCQwZg54nGi;(eD%xqqNTB;sS$m)T%Z;uZS1=9U=-@(>fl32API; zFI_=0BnrnKo$g(sQCS?fvLO7^Q#ToW-TOE0jhFM5~lC4vd(0cLMq$M;MCHsPzz_)HjfxGZn#! zf>g&WkT%WunC~fKShqGK8wf64Ejw(2mD}4-`w3P7k&dP@W^zEefd%mqtg^Lvcia0vY1*HU+R8ZD>c(wcRcQEuwrUWw;$`M!>RDBQ8A`SU z6`dHlVpAPCM={psYWq#Yh{^DJ!DlDh5tN0}RJ2PQs(@vgmLy}+6Lg#784tG7R64D>ck8TOt?O`#@~UGo+$0v-V2@61=<+wI9zH zGQspeZ_l(p^$*(^bav74&%MO8vB_mL6DX$83lioO(#kg65lGpVV`8uz5x#;5j1*KN zmCD5ti9zz;<~C?u_Lo=k#Nw*&m~r$Gv7sz(17ubO#s(yI-o&;RKo-|iQ6|#Di+Cm} ziNbRcX3)5--5lI7it-!Ud%QbNL=wOzaAuKd{${h7=F)k@MfCauK*L-76JUI~eiTmT3vMeiT)?jiu`gHo?TzPpMl(Vo|x z=g)X??gF}(sL>vRaM@i8GW6OgZd4)8W1xn!s}v7YUQ`1+g43wnRWgm9!5sy8Z*Oaf zwwQH@Jpog3fn6*DU1f%)SjCiC3A#oMUg6nw^@DjMF|<;9OM+h z62h;>U<>t3cQ~o3a?uykLeUcCBXS>9v7u0FyYz+wL|C$;%;%&yx#Il%ej_ezjeBK1 z@h^uAaI85YDt!H>N|Uu;gvqMRtQ_uCLDT@m>f!;8@Lt#vvC76!y+IUBC$rI4Q+8fX1cUgImmX4OlVQeF`U9mqe(($5yWAE7*h#; zv?W5!#kJ$aLfljdh&Et!tuf}#|N6`p)qeE&uN9Ez|29Yf;pKE$e0ye z!rD${R^Zm4$>KHH?duF8+Xgi%`5=RLEnG@2=NBz>%o@U9utH_9cQF8qQmTwV-O2)Q zmz^uf_)WkhnzMip>(Z{cE1a8AD7+{vSF$M_Xms(;mQx%j0iF7j_2!!{5d1G#I zm~IqZSN0%TAT%a?M;T&0m>0m{{LAHk7(eVNjuHh2yr#OOXOu96LVdLY%IS|-9RZn* zQoSX{l9n9$z__HD0J)YJ3K%zD<__QYXwai^Yb>io0J@9fPFP1~KIxDkOK}2`hH#Ev zeIl5Ga{2z_MB#@n@S8Y}uBru>4AGSljs!CT4@qwl>%YPbm1xhOcxy;=&EuZL&e!Ah zFylMp?Gv^3iDfYcD}*et$^y!+ScMhDe8`>h%WEvF4DV9>OYU*F14GderG>W&Fyx8t z2tEOscu7ncf`W$-`isco2SpG8LL+LW_@;^`!l*f;4Qt?o)*$*+AW!j`hP=V~gtGvE zM=17!kld+N7)npnB~kJ-NRhg!{|z+FRuUS;6HS=Zh+v)WM8Ut8i2sHUd%LS6U73b}>f4U*)6LEco{Wi8B@sZqw_ z-pGDnA}IwB#d9)R+@+!wk%%Ki5d_CB^ycZe-vmAp=ZcIbCAn6|Y6SdP(ZR-Rz6+5>Jq zXE=S4EwynQdPcj~`7Tr>yN2H|1Bjv^)|rqq+Bysz`l#T+8e1Ti#vlP;3T81uO~$J* z)W+V#XUoe0rq18gx2cuLsck_N4quF%7Q0Z zlr}@tSyo_E)CHax^tA$pc8sqTsF`KC7+$Vhu0asHGc3`LVc~>UCLEkb4&c+A%P%Ab z`GIJH%`p}nj7x>G>Mqekal)cF3W$`gig7C7YWWkSDs)yhcQ-5z%%> zF>*x|Kr4Y1o&?LbqDDEHiBcCs8`u(6l9lz`` zXFv1xKTo0j%gIZRXrAZyh}L8ec<4HR|HJ?%5di=K0s#UB0|5a6000000096IAu&Nw zVGwbFk+JZh!O`LIAphC`2mt~C0Y4B_k*kpvrDdKfCPjY%V3ps)h_&})RaU&^F7Rg}QfG8eL7^><%(u*+_t`%$>{6k9*9f?cP;?on= zbiE+NW1kDi$1l13O2;Ap06&t^Rr3%Pm`sMBuktdicw;a*nL(*?%o3)Iv6HF91zu_v zEcEL!&YdnEt=~48>4|E$TQ?HvZ@+aAtgXMvK&|Wd9x9hCSi~zE!50H7m|b5ptU@TO zOZzLj--&RRbcqyTTp&X`bqaxrg9j56vys$4-DamfR>G%Gh!=8S4Z^q>gKOZqqY%#k z+9Sm`f0p8^tU8*mM7uHv>c6t4gDWPoyhmq(c64BZ?YI~^k4RGxOaxV2<0DGtFA%IU z!8@9VXwfe!*6Z$NJO_XG`3HcVK9M%#P^v(Qj{yt}a6}i0L2M#Y#1kCAvP+|hRGY-5 zhh~e`IDiTWt#Km=0jR~WluBiQO-t31*KkKrT(bnDE@5=+*DJ0re)fS&cc@zlU9wf7 z%v>c$z(T5{N{L&R7M7mg@`B8H1-ZyT6d^@|2?~>*z6%vm?*y(`g?K~p1P>8GY_qR} zxx%+DTRNix%PhutWvVgPQ1u0Wk*DD&Q1>UCqADIKJFmxEqqChU7Q)uopUL+%3C23f>+55wMKY51$4ack0vGB?! zV5XUJdZjLL5~|+6ep!^3xrG?;6hu8H-$_`rT+3l~9T|vIn4r3VEtD?En7d-#Q!h0w zg<-n;-e_W?UKjy@mJDm33brD~UM>X@fHq)D8jVqj+6*SmGNbO{Hk?E~e0}Zn04FhOF!)r}FwY@8l?68U1uE=1!8J1*!V>yG7Wjtj zK<%Pn6?(&#yM6vmO&`AU&i&cgUY)pj+4!+a|k&kD>3|r^@lph?Qc=&}G9pGHD zfjmGJXo(V+UVnV5QmsF@%Gs!rsSTu?&VZUrZXCf<^jRQl!(%CXeZQl`1SzF#pj z?Rfj~E@&Mk2AO5oD6Y-GT|v8>$j1z)GuGe*Xo~vHe!Za+!{_f3z-nY#J+T-C`p2xu z7=k5{c7h!leE$H=Lr4HlUW<^FNa`k;%mlHtB^US*uhQycl|*#;;AlvDeB0mYHlve0BS{5UxHUAX?G; zmhZLrj$3a?7y$s?+{6~yE78L&+ZWG%k$IW~CkiDV2!zCH+u9^XkGCA|ii(x_@N^sN z5Yk+C{{X)-tz1DG+WtQ2{7X`(lWNzP?$7%K1gE2q`wCjR*>fbABvnno!$N2&?U(8oRAx`0wRZs~WPMFOC z`_^Kum1`?f!a;Fsvr(kXr(_V~Hvp37ZYE|OeE3q|nSS@gu$YFm@h%B)#nUp>2;i-* zAV6&Z1x@8U$CHE($cZC>mb}tb>OY(7f*;vwj!zra#|ynSKFu}Q!P(v*{%(6<>!$qj`6wm z>hvcO0C3mfe{mQKzYb9f_lrZ45@_)o%{p87#5QXkzkJL1%G0#0=if1rS~%iermhs} z$LcyOQEMzthI--;SfE=cQ=l{1(f~SGe))Vr4D0QF$%7Ca?H^A`b-^QvXXXuPY5+;* z_!&lGOEC~DamFH5>9^Z4<+6WO&rYA2Y+9>++$BME^WoR7Cb2Y@-QOHT2P%a$Dp1o% zk&v+W^Oj;6r35fbXx}nb#X_P4C^tBZw^$0Rm`+Y*YOc5~-`*jH1|SCzYamd9Z%J^- z1wa8uf-Y)a6qZMnRu*E_P}VxImEO<4iBM(414%@}>i+=9pju#@FRUvTmT*FgQxvT| zz4hxImvhrW>&kP-c*&)@HP_a=!d@%%=`9Wt@lxoB{Qm%bAg4k7LNsnW+{-E;09nKx zp$1RdmGUA@Jtag?y+(?$_=Om1>#1-THC%VZwgEWV{{Utqw=gXXlc2wdA(?qNj$vum zWK|G-%S2|f9406&@ufw5C29dC<{Ma+tLrF)E=^-87Q*R_Rq}HN=2KgVK^f}y`@}Gq zm7bq<7l5bV*UZlVef#*0ARGJE4YhVW6mIdLi)SI;ZPGZJ2#i?`F8Hl&~_&1 zY|L|zgA;1h(lO(hfc2OF7c!K6;AgKmJHO&u0j`*UL$o;J2|~~!5YR!Ia}^r6!wT>L zY%)w5mKH{G{{XXriAu*v4{q<-e^9=bA}Z9O+%38ezj^H#wmIe@1@h}s+|1CMYLnZT z(F)wFmUOw@@Al^REm@Wo$>MFGiCj?S;{{S(z?W2!v4rrpjF&QQ>!&5 z;8`Ho69NPVC%wLCisv*kQ0}4_Ey8 zhE?}<^A1CP-cb>2myh@So3FomxrNAV^OpkZCy=+mK}B3za)2m@2D$w)P%K5Wm=U&! zfSDE%AuFr~WVzS`cC13EP(a0{#*go$P$+%-b0TxspB+RU%8Eg_mPzfPh?$gJqTWu- zbT61eP=z}D%eabd5In(gjm6Zli3u~?z?$N&852;|CTi+0PCpO^r4It{yiCg=6a*9| zhxW=Y=AWLVdAZZ>J|Vi1k_V(p+El~=$r+ z-EU$&QYsfLlTpS10Yl7k#?I4%)`PTBq30-TzMJxbqV)2(s!Lw6^$A$K zM{5D9b$2lW0^dHfASi8_+y1%|CX zq_s?? z24%heBc-p(7HukEhAk4zZ5=d9RAnHbl~1^(dGpahqgDV&~sK@*!M4=`2`l&OJP#NtLf=20XV9%vzpzrWq*I{b!n`B zw+qxn^q{J>&fr5Ura2}^miCm(%;-oAjecdHG*79IwG}c&7)zB=Ek#qq8H66KMiZv} zy8Fbv$^gW#nAh@Floh6U58Tl(N^n3g>|lj?nBoDt#YLh(8X1gk!D4MP?o&&@S%28V~!zh-_ z0y+`Ypf}bJT3Dr8W1*sFTyYQ><>*0dvdac^KJZEeYg8YYvubmLmHW~N2C)Yc%c)o# zj8dIUrs98+@I>VlQKzpk^iX}*)^M|=H@iXk?Fb7m66B)$!S5@vR}#adbhxUl81DY=7!0c~PURP;UNXZe^Gmjsse#6cNiJ6O#pB8h>I{YXi<|X?%DiC9Y-da~O6+gy0LCCy5egqi(p3cge}59B75P|HfkZI*3E0=M*g)&e&^m zVFDG}S%8{NLx98wFrlbvdI+W638i_@yiTH7Qrpy5OSxKY`|lBA{=W1k&oFU@VsjU| zT(!622&sF2Qy_XW+U3};Y8_p%xI0TD6Dy}leje}%6mhtqY{U2N<=S!5IWUfzCCViv zBV^qr&ke`^sT9*Pz+uF4NVpLSHuwQljH(`xi^Xguyg;o-p%BzL6+s0u^_b}voueA$ zekFnEjVhdw>S`CvQ*3(9VSM_)YgBx8h^>VLFceCrU>(myu#|23e9IfLey|~nO~qwz zqweJ+NCiN95f$QXmdR^bAGaBjt551Ve0$3VW5&zvBnwUFT|r4nz>cR8ccZ%IIMgw> zQP!9tRMfQj6Ktm@C0h|Vl;H$1ENVSyt+fi~Fep3@&nQ*WWiH{m@T$`=G@P|ux))KL zA_g--(QukeOn5XWcq1sp@)UOb5&B3NV!|2VXDN@`t*D={(1tO`0!xau9kyS;*Wgt@*{{Uzau6b|>i zOa!k(3tBe7iSJId=7P!;zmYHQVB}N5-^sS&0;>|!#=FF}x?f|!AMfiEccc*bIZvnO zH?Fod2{+B*x|f_(?YgB=oIz6Zy#b2tPTr3O&35fDrznI5=Ba}b16+y~(!2A5d@uq{ z*w2m|>m4OmgJ#bKy3Q)~jXf}b&T$ix8YZ-A4L9!~_TVA+y5a#ZElnxb6G-kNd%yQs zTm+-I{{U~CDgdHWu7}rL-iGuW9_To1r-y z;|U;R(d5HK+Kx|8-X)!EqC0+iF<`AWJ3ZkY^c{`6d>?*wIM0Pd_w0MYqA-9I=d1$) z9f{UG077XJVw@xqmt>y#{9(0rQhVb7JkGrR`1t4J2xiAw(*FR}xhxvtJYxl-5upG_ z$Nu0l1BmQ>i?izylR)?kn=BzbRDTGcSPSYRv>l<{-;5pW0CYiqU*UoXX#~D!g$Yc9 zaoH}D_B{8Fdw3NXrPmgi(?M&7P#H_;{{ZeWzzFbuva%0Q&~wLkUpYe(iC%^G>+SYk zucOHv1pff$2Co%1e6RI^CG8O;MzyXV@e*xeH9L8l=ab$SK_$xgtzV8G8CZEvO>0lT zF~uj+;+qmV*#3H?@2f?6YBrA>3}JgH2^YZeoYSz-g;t_S2;(A!tjO$5A?(}6vz1;! zJlP+)onYX2d%om9IZ&h2F>osq&_@BTRriF*=qDDBi245jlLM6a<;agdT!*$|g>@N! z@2nAfgO4;{AMfiMb)g;DSK6=fir7LFR?nr+MO<$5gPZ*2Y$6lR_qcsw zF-D2T*um|Rvo}eA^Z{Z1+ToS}(Ljd;4Rf#F0LyLG=)SrAaV;9D5$_9F8iKeNHJliS zO6x?1SohuKy{8tcdBqqWQ*+S=x9@;tE>P*~NEi7($-1U-qVe}MxH^vqMdH{5tA*9J> z?T*3c0lrE$m^d{ae^?+RE~i0nEkWSmG6ExPN$s!ZO>u~b=%%};J$k?eZYUpdxAUv5 zYa=!aD&m3;xHZl_a_Q6&SUNTSF+2PZ4s!D#Yf9pwua?`%d((kuWLsV2@y+jy9@O7j zE%6Jp?-soQ4IDkkqqCC=IRu8;@!(B-VJI2N71$p$^OqMw2Kc^uryg8+By7{=9x_uZ z3bw*ted3WtLoR^$-XdURZoWRhHz0=|JYsAsk|X)b8HR)TKcC)21X_*!o<6@gD^z6# zUwhpD0Ju-o_aDez!mcnFov)@iTtm+A#Mq)IM0v1&ePM;l`#d~@r__iNOIN0) z*{vryxiTCMmYoQsQu)L^6p8?xQ}qMPykWLHMe4|->_>k@&%{CK=W zqo+7B2wT0sl4C%M72WIi>K71^w0e9UukG|=pon8Z0Yjca$bxeT&}Pt-KftHp&v+~@ zgeOw+0|S_D;*iA9{y*e?GKFdu-}$^wsG`2^$>HamMTF89W!>Yv6z#!i(}>d3JWD}` ztlj)mx$WcjePRT&H5wbPciYA$H`9@nhSqQVFigoq$}~-Xx)@sQsgMvo0Qd}Xu1gcK zO{hH;IB{YL7n4__x;x2{)I?6KgICRNRTCB>&Fl_fR`r!VS|A{JhaGi2;?bLi!@$3B z`(tXrm9w_qectlJ6O#kW^8C1^>seLOy5Z=VCVVvC2&Mp7Zk;&78B2-6yhn)D$T&ee z7T+=8dGVW~$`Sbx) zsAAU+R#J~ehB{GuV-P*)OVAV>fgo+*Fe?Ko!zdX)+bG43wzT| zJ>c@?p{-4;y(b*z@Hm1juFJ9}-k%ujMoL6pZT=ijE(kJ6G*-8#aCrH_qB#ymVK711 z2fsHE2vQJ)EBOcYiATT+K92`g{b|5u($J@!y!(6nf+Z0EHPiHBG(lS^d35u87m0vO z$u`~z7wp>GmX)>{4H24CyzG?b4EbwW??y#22HWWV^3wLxJb$-~RU#XS(mbAZjyD5T z79By4R++e6msdm+^gmedN83yf^6@iENHU2#8DRea_W&Wmsb|*=BrdwYsE6~M9N|QV zor-P!JvbqZ=H;LrwXV8yfDtV_q2S}7$~RM&FL#V%bQ}_uL$MkCn22!_RWC1MeVGU} z=#ydGc69G4F(e&wu+P8i)@VkpsGX0fed4}R8e9j9#CB^{tjI+od-=v0&#RJ&___Z-s(7^>X_ z=P#GbiK7c&0g(B}h)EMZV*ZXL^>X;vfWF@tyx*Z?1>%6o6jA2g{AQC(rwzC9e~);I0;=#D{@Ev(sQ@-n<&D(u#zr6| zQBayc7|ugF6uv{9Z_YHpOq-7lRrt996xMtNdT$RNUJe7H_MGG(2?9bZH^6tsY?(Nb z0(>HS#^S7AGy*;u^OuQXz)%mCq&?tL^nwuETV1C2hVN&{ zn}NbMR}inBGVgMseEC8r;}DV~v7=cR2Z}t)NzZ;haDswG1T-Kg^VTNuE{K}r*p3l_ zBRq|D^&R+_=~Jo^R(U<-+IR@6_zvbl` z=%fDq;+IBl6|dO77CzO01hiQwO$}(=1X2Q!0d0*Q8X#a`#AVWQF2vwQ-NZ+zqh zjG-B#I&WXbc5R2>M1|iTbAH3Y=d%vJcsGYs2~+@ftHGR65Lansn*juOPqQyDaY)l_ zl$4yjvkkm8jf0>tIs>b$Gs~U(fgC2s&L$)1Ys??KJena1ZiMHDIK%~8#$h)r)`Jf* zbBzb z+tyb-q<Z|fh)G=N$QK?rIbcX8aevWb6(O~xZFOTOP z69n7|LZ1)Gg<{t*v^jkEz+KXjRw_;MuhwcRY31xm%>DO{L}1xGpia}N=)eLUkgJ)G z1x?KC!?WmFZ&9wA7ai+is9+uoA?uEC(%|NU+?{|5`3R1c-mnIs+XUn_#u?Q`wvYn^ zgGRT#xEyShf@&1@VJQNtoeOy)YZ0-qY6x+!YGB^Wic$sieDmuG0g)s&UHs;!DH5vN zc;)%HGNlI=WcmyV%8>!ytxq_WV38!VgPQ9lWZoztv@}xcec+=-O&$VMjH5lW>wmC3 z3Sz4pId_B0XeCqvYolA@V-JV@6Xo!0eXc(Tz^M6a!EuvmsZ`l>vSM0o>4PjSMht{K z8}BQaE~JjlD@tg4!EymCtghUBm!!=K?y|LZENt`@{!sn}F9Yqi#XtO)JViv|$FMkMAx|g9;y{ zlGWvRh@({AwXObmz3j*51c*BBJn!gXLLb5=kPb55w?U5FKo@xh5KE_zxA^(OT&M?6 zVE+J2G~w(&*VYvuI>EzVznWnq%S1c?>T(<_ePIj`=Klb@3HOIgEt;=YehkFXCfy!s z4yNk--DjL`pT&&lb^^MALcj^N+b!V z3Y}mkMK(GN*DtHiHT-&;59JS|$NvC`0&+oPjom?7+lL;AfO1MwJwyA*06h?1pO@)| zBq$@ncyZ8HlP{s_V#F*v$>_R?-jlqmkQRJj@&1QykkY&V0EQ)GRnT6?kGkRFf++34 zY<&6ij!}U9Ti=_Ny=h5P-a#835ic?IiPky=@B!;sO;+%C zzd@Bhj`}O*VH&FAS%7Oakb8v)Xhjglj{Q) zn<$&6lfrk44~zgx5iLvMGB;Iupe~#7lchvJ-W#_<{ADdpm!a3Px9G-i2U2al{0G@{ zev%1v921lM&N(-3-?rwDo_%F#38{Nm!2{y~XkR(`=Pv#PBm2c6Jv+xOu(Y5)sjsY+ zM71Pj+cx-|`fQF88HD0L0%2yNj&{xOU-;q(L`NIKy!U|u<;V*58<|z70;|~o&yOfRqoN>PS#|StUf~YE2UWbfi14)+zwyoB@Tmql4Dc=A$ zMV>>;hE=jP78I9gO4h8IsQoP1FplI~=Qd?8NCzglz@DNosO{wW zp&(ILapxm79giRY!f5xLG}BNj^SS(Sg+RMcxEnafq|HSSgyei@3y4`)z~95pQgx!> ztkl;o7x6Tw`006k&@)e;}!tVGiRv9;#DaZwvvt!Zoj06sA7&;}1YlKtFsBcuTh~5dLp{x2r?8}J8>ZK8rxU|ZWl@ktrT^pAfnFe0-f0a4uk&y z!#-^;d_SWS6~EB_6VbCedd3=VvP8;k70~>C~jhf`Yc6jVJelhPI|kvF61;3{2ZDpl_p| z40|01oA&bH~nOORHV`lr<*G#y21*Z2Ov`?3uXd~tS%3LJf-)G*#K6d z0;)W7h7p&LO=8vb_-h)h0lW=CqLS4aj)-NUMyc*!s(Hv07LkNeP|#kgvlw@!A-;IQ z4`z|83W3Upj<<};O3-I)8x9dV0~wcodY{%=j0Myc?R#@t>2K^^EE8~*@t z{{ZJ4GXu(${=V^{5;0E7>HSmIB<-uEGiy)322)FeIGBL(DxmyHN4_wP5Fj+5A09Uh z2I(3a0MmS*yzjWc{WX$=L6I@3Hj)dob3iWyiAbZ+whi@<7IkB66zE-DOfa0Fi8d`S z1jac`r8n2!EfC276dLerHK9)13bBvQz2Ja|(_-FRF?%p2^cts=DF&oBSG{{-Nf<7g z?D+n$tOXM7ejDa*2etw~xWc^ZdBNobRW%4EuD2NM);Q(437eSuAvT-ksebMrpFuhg zKe9jiWj%i!G}`9{t4I+lgZMu_@btkD{=W`fA#nC-_mXvl8duJ><1Gt+Qwz)Si3{46 zxjTNo^H%p;FWNrW&N>+l=0Og{gl{+%#Kh17=r|txse&yq8Lew%H{KDY0v-vlv9v0l z^s!4#zjLn&B!+xlZ_0PYeXTlF!HSW<1IfHzJt_$LJ$EZtq(cqU%(8)>4IQ6uM< zjC>~qwYtd-YiC&utR8|K>>ALHh0CSDP;>eIndTMJ)~7t3n!z)m9w!VR4|xqW*(i~O zX|rw;;!uELVu})=`Eh)-;XtX@pxtLCa(kKy>eI04vw5satnQ`hA{t|@s^Z9%(He2~ zR$@KvVW1d>#On)~0m;LiO9RFX7s|tY#&hT67FkM}O`-Ef{;_ikAi8nad}2+^5C*9I zKGP^ViAlk=5s`+a%Q$PqAxSD}za;AB!pgXU2rmoIzB@4NsbU~3d)7369)MCLgW6hL zW^xTOc-s$xaEz5$3gqEnx?w#$eTlC)(hso{MpfWk9TW?-3nOlwQ(DDy7QTfR^+ZK) zQwH@CrwyuyLjW&Zz@Y)uqiq58{BOoT#5Bn`-&K3`_kh7Ep?Jx|cdu)MO2Ihtk;qq* z=d2-=G$8g{D(f`#%Qv({@ZfGsCJ(v9&P1lVB5SNFHH`dXV5eB~z<9!e04ru3W6~HX zVcwJL1m&k@sFC%{`1Os1+U91>=RR>1z76m@@@u<;qMIo=+1?YbP_}|yHy2+UkTp^| z{9_r`mku4Y9d~etufUWm6Ue6%ysvZJ254yPoV#$zzNq~u9JxLF$<(b$qfS9Q2hJ0# z?g>G$#kc32(c(N{8#Gc9=UX{VZ;Vyj;Uht)cn@$f zeIC^CdFKw1d1Ux9>ad^YJSRANn02Gi{rklVIjE(7J$iaDohk&#IzFRcoTV?&qIqmx zpC#)Jq!5@>L^w9q^vLX$Uv5jCS+w)e{6t3)oj9v*VICO)FQ8o3s**6gUs)tp>y zECSfzl0s`0icbS^K?Aj%Sv=1p)*Xy%ltmO8;0{@Qa z{zG{bCYHvk?c4tVOy5+j;~1K30Vp3=1JKm@hMhFR$guth@$Zipj8LEg21l#P*57VU zi1w_}iKnLE<5#cFu$wMn5Nsgp#%%!Dc6i1({j3hK7+PW*pXU;{6 zGPaIkA>ttM>U9YEA%~+Naa^KU(}7lUg~43BsIJRGaM9yfDdl@e3)Im3zEd_N(&&Yh ziej%Dm{b5QDme6bGEP~Fs}eT|2URMBQz5uZKvnu-kzvmQgYo^alV$Kejo{BJlU1Q_ zV?&=<5rd$D){6H303CCUP-yU*IUbXPGzz6?N{smYxjLyrzdqbNiEihxynGzo)3CZn z{mq70yu)u><23%~;Ha+HX`Qh^a`NbPMx14ZH62jHC!-+wiDZaL;4qd>20#G{$FvL9 zZ_Qi=gn-!SPLM5QfboK*w%>&<=CziJ8AUIR5jxd)$58O5g$M$!GKPYcxOuo7_1bcF z`ewdxun=AVmz(ZkLRLjh?IrOtKW4P?lGdp1FqlsrgI0pP(QU}=#2jLJ01?|RZ(cHH z7PObJy*702ZzuWeZX2hmoL_dhD1*=ddw_j-F+#@GRr0fsZ#tg=>|2_iA%SpI5IpmQ zh(y>M+g)=#V2#v_GE(p4{xEgmR9+Nx4?wSgrVGTJ2)mQ93=0`|wcSNT6{Kk6>ZYgZ z8&nq4VTV}*0U#w>g{&(C;tZ2nd`% zUN86m0Or`-Pz!Fkt80|Jq9WCksLMLb2*j~jHia8`SG?GEM^=?Jv-5&u1@?k1`NN+3 z5S^-CT)tdJiH87ogw~(*&H$KTx}!DX9N_FR$cLwc=-<387S*sY)m`-AEx3y8qvM|Z zIG7o&ASnIW_ljhlJdocAqaZOzvsi{Jv4l8hSlVr|!fKvIVt$)sE7(0}G6{2yAzt>q z<5mtyF6V6~U5%_kt0H!HaDqW414PjwP-~tr_zqsYSnAFUO-GM0(|$@~;Al53MfLvV z#X(}JGrk{3d}-={snnyL(tKeU#U;9I^y|Jd2E3l$g4yvp!^mPG8i@A3e^^k9AuOoy zF#5r15+3Fc&G_$m83wkgH(oz^yA(Qgc)*mCzm>uCwtV0z1CVB%Q@n8kW0*7t*BC$} zp=<97{YiiaN}D^tOmLz14?g+8XPmW!Cg-17dKzoA9zBQ8&OR{PF;zoukD<;5-q?_j zhl%LP`DmnWoGanW#l!0S(2fD!Piw9)1)x-|FgCx9=CUqM`%oX;#(evR{cEPR;}G+x zSEEODJ~6Tpr7Bl)y}WxG#D zesR>vs_^K4otQ=@(1)ZS!ouo|2!IHW;rhWA5TNq}kBjk~Ws!rjv`$;EIWVHn7BLo< z(*+>DilW1k+j_!4B!s#Jzc-8nK&19?O1o_Fjwb2?r-0wyF}|f32tBku7Zoi^Yrqcx z0sjEZbt_hHs;sk&Asayo2@bHB1&BNSV*)D`R2`9nZfpeC9V~d?d%O--_e#DLR05<-MyRI+#e8G)rn7DFxsa;f+KU~ ze$3LLm-X%Zu4{A(aEC){EmACr}l=?G85Y)-hwRpaCf82JG8+ zCU75P0<>O3@O(GE;Q;fL1&D0AQ>-RpdL-Yo6Gx*{D7!2ta})vM{q!CW84FpJ(s%K} zok<~wt!PqY(MIrvVLgSxCxy*e;~;nhw;g{!oJu?)*w?KLlgpTsfJc_W#e~HQN|3TX z3Iw#4?*z}Fn94L2Zyh(r4|@&)Ks^ilF58n7IQS|LpeySD)X{L4XnFqtINc&hTjP`R zuuczv5bPe2^Ao>RC2?TO>FMYGU^OVTSE}qi4pY9uphz&mZFW90mqdgH#B@goj;DSsks2wNeY5 zOP;lZ#}P`w54-g5d-2L`{6Fz-kWqw$H? z41{|P{{U+{4wks zeXiebbs|xsy*|7B;Hy^KNOR+h)^qESI`{LOyN_q{zVU5eUYIR^-DDwd&B?Fp@ZxO< zRs#M)Cz*|E;dE4LIy)0{9i)dS1ze37l8y15d@Pu?NywUYzA;96HK8){S@HD71ty3% zA9_y&!1qIzdj9~KwY~7qhlCcrbmKYit(eFm>82;F4x3@}NdDh8i7)&(mRlBpVY zg_X0`IX_GkIMC6q9~sbh1`*+5K>QxFi#mrG0|VAPNH$s9caUD)WTV-m06Gh&5gWXO zL_lqlK`BKM#z07WP_d^0CLe7N4aaGJF0uJVlR0QrFQys9S|CTURQEb=E*g7p8_c1p zylAO2RDl8A$NXBMnYaR;&K&M)Ab^hnycSktR!wRqieQl#O9l_eQm)j%@kBs&V@!x+ zrPqU7_km&`#sW~3A2qTStAh$RST$Z`>7H|A`T!7soh>3NCC8@wE$x1n9KjpPJulPF zEanwvr>EX&QxxF5zng($D7Y!YADkhz+*B27LG$A+3HGM{00}GTTqx@8D*OJJP0O(v z+vl9R&mlFa9(4R=M`5^i&)S>39A)vd75roeSj(03z2%Oei=u(y5S^j9tQQiUhr6h0 ze+ct{LZ|`(Qw^;0?5u~FdJlNWHpy4OCtUaA1XPe;1AG4f?tEDQfkV#p;|B-IOQ0dm zP96;_c+jaNUMVFYUoG>Bw%b;pE2E5zOO^-Qu3ZL z)~2T2r5`(g^8+gD40QhhPyS@EnSMc3hvV_@1R%iys3!TQH!oQ@uY=L8-& zZ=P9favEv<#K`D6E7wJe&YxYP4k<1U1D6}}=*1}j#iF}>^Z3Nt(Hb*5nM()F-?OvX zjj7iV08stpv^t2vo2K^V-AO2((s5tbM**<5&ed1%6Brb5_{sHYrNHCyy?Vp#QBs?t z{{X)xN!HefoWwZSEHbVLjjeI?{`<Ro6PL zzS;a`>w|qfGQvSv-O_egk8)!|HbaKWJfyHJ$Jh`OP)k==onvAHQOo#To;@_9H^*nr zK}5vvydA+GtPl{V0L|B|wl3~T0PUv)#sKc6uBPM~`sXj}pC|Jttv$CmBt+*RmuZ%j z^XSIUOtZJnK{v?cbfCM3>noDe=#&2djHO@z3ZbkpKa3M^5R2fQaQ+;bkUS8jt?Dk_oAXjUac(q=jV+7D1E0g&? zu@}+g3ZLE%_|1fei+P6~;V%VvH%ZF>0Jnz!0N943uURw608irLKOL!lhULy`-~*j` zoBD9Uu#|Z1f4sfmVG#7}{z&^VS(yM>ihu7W1QXRI(7H~Zv6RVJ`3Q%HyfuUZ5_oX0 z@MKL%cDo->lZ-j*`Uyvulb<+j=9q4ry}Qj0nGkXTe#am00hqv>BdN!ns{)Gd9!@aU z)5sJ&c)>O{fwFS?#P;+fo+q~w1u0gL?(4l}ot2I6+DLx#RGcw2E?Re4$CT4-915dY z^`ltp2PMp2^d#>~GM@x?J~bUJJYFy^QVIdP&4J0|2o?ko4Us^3pgzoKk_}25hg}9G zScFhrD~Z&_$*4~Oe^@88`2mb^rOBXz!UX4cz2kIxAo$o{X90y+0oeF6c_JeGkGwZd zj17Z?J?onyHO88KV)v%=VIUlT+-w)RB8(Gd-8N7VO#MpY@+`0@Pj zoHMz+B^|7s;6y%fb`R?T4J)8BA3^txMMtd|YA(7OG<@8eg5X{6JRhvEF?NY}5103w zbU1%d>C!>l^hTwvq9T z!;uu=*IKu)lZQLqF3*l!2FqNgm5woM9D`Kn) z4()?%gwZ<1id}k!na4Pc)j-N<_=z6#k(!MHu>eK-I`M=)u%V5Wi^}-MViYqdXz!DI z?-@ZbFh}O8_{d30&Mqg6i4tHxj6es7g*Eh{qG4XAsI{~0wTdcrQ@UAnEtplpg(92tkrbv+hThw zFZ|8eWo#7Z$CvzMu|~YM)93h^Aq9t_xAMP(g!R_~tiqgn?~aMy0U$z$LkdtDj3`z5 z{+u{D1t=II@zTf_V6;KjgT0kAEh8_0OT zkXTlR#QC`3gs~E*U_B25Tgc(p8jzoWKQ0h*8^8sW4ZQExSR=$L2g5L<{9GvhQvBcs zrP0dd_{R^B{{Sb@U0<1u3n&v+)pPRmu5x1pA7OI6?rr$UA{cc?OSkow#5qbQvpCDN zI7EW$lu$k2Oro>Mcw%?mce&P4luQIfU=}5!BO8Pdih}GmQVpu)t;({&3cwNztX7W# zb;@#dqKG_&dBD~tgS@2LmC-csC~#Ron-sii z#K!}e&2M~mW66oQJ9buM>KbqlGr!&k%i-l2OcefRQn;oM6dP|(!oFbH97@xp z9srX@6XTaT^LX5g-(%NV7e^6Po8TIE?;iW0XtKk}Q1ty_Q(OvFq@Jv91oI@7smB`Q z#yeOftkv7b&OWep7L??1{{XJK#uX!Do^fB1#8!{5C)50U#1B*#Gy-3P9XS$fWaoWM zd=Ld>H1AqAKF>L}AOaCUG@pUFr&zO^f)q#&Ifd7UfWrGwgedp+z8p`!Nd;wijUSIV zXnH`#*bd*`1I)PZ>L?IWOR5N}1ItGMPb)@Ms?A%pS1#d3L?;lO8&rcWVTvhEt zQE2?n-WqHOq+84s)g^K?e+C6p%g+!-9{&K0PmyjOoZE`{egGyq(jy+A&xfk31Rv^;P5Ox@~u2)G*2zt%N~1?1jTXldr(-fMOW zA&ge}kM`p=9A4SDasL3!k}8K_Fg~b{%d84QVC4sI-%haJpD{Xr6AC-2^bdA^Ge)S} zPJ_pU`8vgE7>f@u-#Z_iW}+G^yV0BQ=FnsSBHdHyef(!x))5eZct{7SgE0IHYK?hU z<;ZB717Jj-0pkmL54mvv0G=)-ti!xMgX!ZF`GayiQS@`adAa&7Wi&Jq>dx?}8H_<9 zMU~$oaNzetLU-SQy-%|Hp+^AjJEo-gHIlaJIW`0@V)C6ZKU{(0E6E*r^rAACe#Lk( z69RI_E&!db_IcZsIbgW#Uby_3S5Dzw(sO=tiH-IW*v@?6B85o_=rzVQ zur~_nskx)@U=&bJMtZ*|>n{uJhzOVg3th^28OOF6H%HjkC42WkjxI>4$FF+i=}c0X)z3S6$T&BbpW zW0^TEX+?XKOO7mb6gSI7--Ffzj{qPv=XU%bIIg5)$k;xE0xGH%?X&w_1X3gs2r~N3 zklH2gtG`AmwpcE>^h{BP;%tu2d%)14*GM>=nM9dWB`M9hzs#{%46q3Ie1;O6xNaX=L}wHb1ack!)ei(qNS%U>9?Y(w>1^?z90 zyLmz3{N(t1aK3Sv9RhY+KNy8+0jST|e&#_eQy2lKYQ5`*@WaR=c@Vp7r!Ov3U_jKs zO7f|Bs_PMSy8%h1+eE$(Sj=mz1x>9hP}jkT!{?E+rAE{N^MqRkcp9KXU?as{_5n%u uB!W(0*>3p9g(NU26auRJxv5swBptlHe^}Q?l}<_;z*a{^^Zvj7fB)GT(%NnS literal 0 HcmV?d00001 diff --git a/docs/assets/timeline.jpg b/docs/assets/timeline.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27d2047778e0fe4e61bbf6e377b1caaa9b04f915 GIT binary patch literal 20965 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 literal 0 HcmV?d00001 diff --git a/docs/assets/wall.jpg b/docs/assets/wall.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5deddf905c481f484f7f1585e943031d27dd5e21 GIT binary patch literal 33648 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 + +| Query | Description | +|-------|-------------| +| `dedup:0.9` | Filter out photos that are 90% similar to each other. | +| `dedup:0.5` | Filter out photos that are even kind-of similar. | +| `dedup:0.3` | Only show very different photos. | diff --git a/docs/features/tags.md b/docs/features/tags.md new file mode 100644 index 0000000..314f453 --- /dev/null +++ b/docs/features/tags.md @@ -0,0 +1,50 @@ +# Tags + +You can tag photos with arbitrary tags. There is only basic support for tags +right now, but they form a foundation for many other features. + +::: warning +Tags are currently in an alpha state and can be volatile. They are +not yet stored in the photos themselves, only in the "cache" database. +::: + +Tags needs to be enabled in the `tags` section of the [configuration] the server +needs to be restarted. + +```yaml +tags: + enable: true +``` + +[configuration]: ../configuration + +## Tagging Photos + +If tags are enabled, the fullscreen photo view adds a # (hash) button for toggling the tag selection dropdown. It also adds a 🤍 (heart) button that toggles the `fav` tag to serve as simple "liking" functionality. + +## Search + +You can filter photos in the collection by searching for `tag:TAG`. + +For example, you can search for `tag:fav` to only show favorited photos, or +`tag:hello tag:world` to only show photos with both `hello` and `world` tags. +This is an early version of filtering and should be more user-friendly in the +future. + +See the [search documentation](search.md) for more on search. + +## EXIF + +Automatically add tags from EXIF data. + +The only EXIF tags are the currently hardcoded `make` and `model`, and they are +added to the file as `exif:make:` and `exif:model:` tags +respectively. + +To enable the automatic addition of these tags, you need to enable it in the config. +```yaml +tags: + enable: true + exif: + enable: true +``` diff --git a/docs/index.md b/docs/index.md index fa43b7d..714b1e5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -75,6 +75,7 @@ features: src: /assets/features/map.jpg title: | Different views + link: /features/layouts details: | Collections of photos can be displayed with different layouts, like an album, a timeline, or a map. @@ -82,13 +83,15 @@ features: src: /assets/features/slovenia.jpg title: | Reverse geolocation + link: /features/geolocation details: | Local, embedded reverse geolocation, negligible performance impact, no API calls needed. Supports ~50 thousand places powered by geoBoundaries. - icon: src: /assets/features/cat-eyes.jpg title: | - Semantic search (alpha) + Semantic search + link: /features/search details: | You can search for photo contents using words like "beach sunset", "a couple kissing", or "cat eyes". Needs to be configured as it requires running a separate AI server. diff --git a/docs/package-lock.json b/docs/package-lock.json index 00a79f9..d27cf74 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "dependencies": { - "vitepress": "^1.0.0-rc.35" + "vitepress": "^1.3.3" } }, "node_modules/@algolia/autocomplete-core": { @@ -50,124 +50,255 @@ } }, "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.0.tgz", - "integrity": "sha512-uZ1uZMLDZb4qODLfTSNHxSi4fH9RdrQf7DXEzW01dS8XK7QFtFh29N5NGKa9S+Yudf1vUMIF+/RiL4i/J0pWlQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", + "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", "dependencies": { - "@algolia/cache-common": "4.22.0" + "@algolia/cache-common": "4.24.0" } }, "node_modules/@algolia/cache-common": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.0.tgz", - "integrity": "sha512-TPwUMlIGPN16eW67qamNQUmxNiGHg/WBqWcrOoCddhqNTqGDPVqmgfaM85LPbt24t3r1z0zEz/tdsmuq3Q6oaA==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", + "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==" }, "node_modules/@algolia/cache-in-memory": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.0.tgz", - "integrity": "sha512-kf4Cio9NpPjzp1+uXQgL4jsMDeck7MP89BYThSvXSjf2A6qV/0KeqQf90TL2ECS02ovLOBXkk98P7qVarM+zGA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", + "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", "dependencies": { - "@algolia/cache-common": "4.22.0" + "@algolia/cache-common": "4.24.0" } }, "node_modules/@algolia/client-account": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.0.tgz", - "integrity": "sha512-Bjb5UXpWmJT+yGWiqAJL0prkENyEZTBzdC+N1vBuHjwIJcjLMjPB6j1hNBRbT12Lmwi55uzqeMIKS69w+0aPzA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", + "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", "dependencies": { - "@algolia/client-common": "4.22.0", - "@algolia/client-search": "4.22.0", - "@algolia/transporter": "4.22.0" + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/@algolia/client-analytics": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.0.tgz", - "integrity": "sha512-os2K+kHUcwwRa4ArFl5p/3YbF9lN3TLOPkbXXXxOvDpqFh62n9IRZuzfxpHxMPKAQS3Et1s0BkKavnNP02E9Hg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", + "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", "dependencies": { - "@algolia/client-common": "4.22.0", - "@algolia/client-search": "4.22.0", - "@algolia/requester-common": "4.22.0", - "@algolia/transporter": "4.22.0" + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, - "node_modules/@algolia/client-common": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.0.tgz", - "integrity": "sha512-BlbkF4qXVWuwTmYxVWvqtatCR3lzXwxx628p1wj1Q7QP2+LsTmGt1DiUYRuy9jG7iMsnlExby6kRMOOlbhv2Ag==", + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", "dependencies": { - "@algolia/requester-common": "4.22.0", - "@algolia/transporter": "4.22.0" + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.0.0.tgz", + "integrity": "sha512-6N5Qygv/Z/B+rPufnPDLNWgsMf1uubMU7iS52xLcQSLiGlTS4f9eLUrmNXSzHccP33uoFi6xN9craN1sZi5MPQ==", + "peer": true, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.0.tgz", - "integrity": "sha512-pEOftCxeBdG5pL97WngOBi9w5Vxr5KCV2j2D+xMVZH8MuU/JX7CglDSDDb0ffQWYqcUN+40Ry+xtXEYaGXTGow==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", + "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", "dependencies": { - "@algolia/client-common": "4.22.0", - "@algolia/requester-common": "4.22.0", - "@algolia/transporter": "4.22.0" + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/@algolia/client-search": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.0.tgz", - "integrity": "sha512-bn4qQiIdRPBGCwsNuuqB8rdHhGKKWIij9OqidM1UkQxnSG8yzxHdb7CujM30pvp5EnV7jTqDZRbxacbjYVW20Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.0.0.tgz", + "integrity": "sha512-QdDYMzoxYZ3axzBy6CHe+M+NlOGvHEFTa2actchGnp25Uu0N6lyVNivT7nph+P1XoxgAD08cWbeJD3wWQXnpng==", + "peer": true, "dependencies": { - "@algolia/client-common": "4.22.0", - "@algolia/requester-common": "4.22.0", - "@algolia/transporter": "4.22.0" + "@algolia/client-common": "5.0.0", + "@algolia/requester-browser-xhr": "5.0.0", + "@algolia/requester-node-http": "5.0.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/logger-common": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.0.tgz", - "integrity": "sha512-HMUQTID0ucxNCXs5d1eBJ5q/HuKg8rFVE/vOiLaM4Abfeq1YnTtGV3+rFEhOPWhRQxNDd+YHa4q864IMc0zHpQ==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", + "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==" }, "node_modules/@algolia/logger-console": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.0.tgz", - "integrity": "sha512-7JKb6hgcY64H7CRm3u6DRAiiEVXMvCJV5gRE672QFOUgDxo4aiDpfU61g6Uzy8NKjlEzHMmgG4e2fklELmPXhQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", + "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", + "dependencies": { + "@algolia/logger-common": "4.24.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", + "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", "dependencies": { - "@algolia/logger-common": "4.22.0" + "@algolia/requester-common": "4.24.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.0.tgz", - "integrity": "sha512-BHfv1h7P9/SyvcDJDaRuIwDu2yrDLlXlYmjvaLZTtPw6Ok/ZVhBR55JqW832XN/Fsl6k3LjdkYHHR7xnsa5Wvg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.0.0.tgz", + "integrity": "sha512-oOoQhSpg/RGiGHjn/cqtYpHBkkd+5M/DCi1jmfW+ZOvLVx21QVt6PbWIJoKJF85moNFo4UG9pMBU35R1MaxUKQ==", + "peer": true, "dependencies": { - "@algolia/requester-common": "4.22.0" + "@algolia/client-common": "5.0.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-common": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.0.tgz", - "integrity": "sha512-Y9cEH/cKjIIZgzvI1aI0ARdtR/xRrOR13g5psCxkdhpgRN0Vcorx+zePhmAa4jdQNqexpxtkUdcKYugBzMZJgQ==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", + "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==" }, "node_modules/@algolia/requester-node-http": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.0.tgz", - "integrity": "sha512-8xHoGpxVhz3u2MYIieHIB6MsnX+vfd5PS4REgglejJ6lPigftRhTdBCToe6zbwq4p0anZXjjPDvNWMlgK2+xYA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.0.0.tgz", + "integrity": "sha512-FwCdugzpnW0wxbgWPauAz5vhmWGQnjZa5DCl9PBbIoDNEy/NIV8DmiL9CEA+LljQdDidG0l0ijojcTNaRRtPvQ==", + "peer": true, "dependencies": { - "@algolia/requester-common": "4.22.0" + "@algolia/client-common": "5.0.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/transporter": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.0.tgz", - "integrity": "sha512-ieO1k8x2o77GNvOoC+vAkFKppydQSVfbjM3YrSjLmgywiBejPTvU1R1nEvG59JIIUvtSLrZsLGPkd6vL14zopA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", + "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", "dependencies": { - "@algolia/cache-common": "4.22.0", - "@algolia/logger-common": "4.22.0", - "@algolia/requester-common": "4.22.0" + "@algolia/cache-common": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -175,28 +306,41 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/types": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@docsearch/css": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", - "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==" + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz", + "integrity": "sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==" }, "node_modules/@docsearch/js": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", - "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.1.tgz", + "integrity": "sha512-erI3RRZurDr1xES5hvYJ3Imp7jtrXj6f1xYIzDzxiS7nNBufYWPbJwrmMqWC5g9y165PmxEmN9pklGCdLi0Iqg==", "dependencies": { - "@docsearch/react": "3.5.2", + "@docsearch/react": "3.6.1", "preact": "^10.0.0" } }, "node_modules/@docsearch/react": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", - "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.1.tgz", + "integrity": "sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==", "dependencies": { "@algolia/autocomplete-core": "1.9.3", "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.5.2", + "@docsearch/css": "3.6.1", "algoliasearch": "^4.19.1" }, "peerDependencies": { @@ -221,9 +365,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", - "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -236,9 +380,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", - "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -251,9 +395,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", - "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -266,9 +410,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", - "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -281,9 +425,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", - "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -296,9 +440,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", - "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -311,9 +455,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", - "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -326,9 +470,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", - "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -341,9 +485,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", - "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -356,9 +500,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", - "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -371,9 +515,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", - "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -386,9 +530,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", - "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -401,9 +545,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", - "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -416,9 +560,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", - "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -431,9 +575,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", - "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -446,9 +590,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", - "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -461,9 +605,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", - "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -476,9 +620,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", - "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -491,9 +635,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", - "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -506,9 +650,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", - "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -521,9 +665,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", - "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -536,9 +680,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", - "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -551,9 +695,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", - "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -566,14 +710,14 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.3.tgz", - "integrity": "sha512-nvh9bB41vXEoKKvlWCGptpGt8EhrEwPQFDCY0VAto+R+qpSbaErPS3OjMZuXR8i/2UVw952Dtlnl2JFxH31Qvg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", + "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", "cpu": [ "arm" ], @@ -583,9 +727,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.3.tgz", - "integrity": "sha512-kffYCJ2RhDL1DlshLzYPyJtVeusHlA8Q1j6k6s4AEVKLq/3HfGa2ADDycLsmPo3OW83r4XtOPqRMbcFzFsEIzQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", + "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", "cpu": [ "arm64" ], @@ -595,9 +739,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.3.tgz", - "integrity": "sha512-Fo7DR6Q9/+ztTyMBZ79+WJtb8RWZonyCgkBCjV51rW5K/dizBzImTW6HLC0pzmHaAevwM0jW1GtB5LCFE81mSw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", + "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", "cpu": [ "arm64" ], @@ -607,9 +751,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.3.tgz", - "integrity": "sha512-5HcxDF9fqHucIlTiw/gmMb3Qv23L8bLCg904I74Q2lpl4j/20z9ogaD3tWkeguRuz+/17cuS321PT3PAuyjQdg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", + "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", "cpu": [ "x64" ], @@ -619,9 +763,21 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.3.tgz", - "integrity": "sha512-cO6hKV+99D1V7uNJQn1chWaF9EGp7qV2N8sGH99q9Y62bsbN6Il55EwJppEWT+JiqDRg396vWCgwdHwje8itBQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", + "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", + "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", "cpu": [ "arm" ], @@ -631,9 +787,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.3.tgz", - "integrity": "sha512-xANyq6lVg6KMO8UUs0LjA4q7di3tPpDbzLPgVEU2/F1ngIZ54eli8Zdt3uUUTMXVbgTCafIO+JPeGMhu097i3w==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", + "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", "cpu": [ "arm64" ], @@ -643,9 +799,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.3.tgz", - "integrity": "sha512-TZJUfRTugVFATQToCMD8DNV6jv/KpSwhE1lLq5kXiQbBX3Pqw6dRKtzNkh5wcp0n09reBBq/7CGDERRw9KmE+g==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", + "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", "cpu": [ "arm64" ], @@ -654,10 +810,22 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", + "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.3.tgz", - "integrity": "sha512-4/QVaRyaB5tkEAGfjVvWrmWdPF6F2NoaoO5uEP7N0AyeBw7l8SeCWWKAGrbx/00PUdHrJVURJiYikazslSKttQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", + "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", "cpu": [ "riscv64" ], @@ -666,10 +834,22 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", + "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.3.tgz", - "integrity": "sha512-koLC6D3pj1YLZSkTy/jsk3HOadp7q2h6VQl/lPX854twOmmLNekHB6yuS+MkWcKdGGdW1JPuPBv/ZYhr5Yhtdg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", + "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", "cpu": [ "x64" ], @@ -679,9 +859,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.3.tgz", - "integrity": "sha512-0OAkQ4HBp+JO2ip2Lgt/ShlrveOMzyhwt2D0KvqH28jFPqfZco28KSq76zymZwmU+F6GRojdxtQMJiNSXKNzeA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", + "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", "cpu": [ "x64" ], @@ -691,9 +871,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.3.tgz", - "integrity": "sha512-z5uvoMvdRWggigOnsb9OOCLERHV0ykRZoRB5O+URPZC9zM3pkoMg5fN4NKu2oHqgkzZtfx9u4njqqlYEzM1v9A==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", + "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", "cpu": [ "arm64" ], @@ -703,9 +883,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.3.tgz", - "integrity": "sha512-wxomCHjBVKws+O4N1WLnniKCXu7vkLtdq9Fl9CN/EbwEldojvUrkoHE/fBLZzC7IT/x12Ut6d6cRs4dFvqJkMg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", + "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", "cpu": [ "ia32" ], @@ -715,9 +895,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.3.tgz", - "integrity": "sha512-1Qf/qk/iEtx0aOi+AQQt5PBoW0mFngsm7bPuxHClC/hWh2hHBktR6ktSfUg5b5rC9v8hTwNmHE7lBWXkgqluUQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", + "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", "cpu": [ "x64" ], @@ -726,29 +906,58 @@ "win32" ] }, + "node_modules/@shikijs/core": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.13.0.tgz", + "integrity": "sha512-Mj5NVfbAXcD1GnwOTSPl8hBn/T8UDpfFQTptp+p41n/CbUcJtOq98WaRD7Lz3hCglYotUTHUWtzu3JhK6XlkAA==", + "dependencies": { + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/transformers": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.13.0.tgz", + "integrity": "sha512-51aLIT6a93rVGoTxl2+p6hb7ILbTA4p/unoibEAjnPMzHto4cqxhuHyDVgtQur5ANpGsL3ihSGKaZDrpcWH8vQ==", + "dependencies": { + "shiki": "1.13.0" + } + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" }, "node_modules/@types/markdown-it": { - "version": "13.0.7", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", - "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, "node_modules/@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", @@ -756,9 +965,9 @@ "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" }, "node_modules/@vitejs/plugin-vue": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.2.tgz", - "integrity": "sha512-kEjJHrLb5ePBvjD0SPZwJlw1QTRcjjCA9sB5VyfonoXVBxTS7TMnqL6EkLt1Eu61RDeiuZ/WN9Hf6PxXhPI2uA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.2.tgz", + "integrity": "sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==", "engines": { "node": "^18.0.0 || >=20.0.0" }, @@ -768,118 +977,144 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.5.tgz", - "integrity": "sha512-Daka7P1z2AgKjzuueWXhwzIsKu0NkLB6vGbNVEV2iJ8GJTrzraZo/Sk4GWCMRtd/qVi3zwnk+Owbd/xSZbwHtQ==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.38.tgz", + "integrity": "sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==", "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/shared": "3.4.5", + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.38", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.5.tgz", - "integrity": "sha512-J8YlxknJVd90SXFJ4HwGANSAXsx5I0lK30sO/zvYV7s5gXf7gZR7r/1BmZ2ju7RGH1lnc6bpBc6nL61yW+PsAQ==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.38.tgz", + "integrity": "sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==", "dependencies": { - "@vue/compiler-core": "3.4.5", - "@vue/shared": "3.4.5" + "@vue/compiler-core": "3.4.38", + "@vue/shared": "3.4.38" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.5.tgz", - "integrity": "sha512-jauvkDuSSUbP0ebhfNqljhShA90YEfX/0wZ+w40oZF43IjGyWYjqYaJbvMJwGOd+9+vODW6eSvnk28f0SGV7OQ==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.38.tgz", + "integrity": "sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==", "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/compiler-core": "3.4.5", - "@vue/compiler-dom": "3.4.5", - "@vue/compiler-ssr": "3.4.5", - "@vue/shared": "3.4.5", + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.38", + "@vue/compiler-dom": "3.4.38", + "@vue/compiler-ssr": "3.4.38", + "@vue/shared": "3.4.38", "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", - "postcss": "^8.4.32", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.10", + "postcss": "^8.4.40", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.5.tgz", - "integrity": "sha512-DDdEcDzj2lWTMfUMMtEpLDhURai9LhM0zSZ219jCt7b2Vyl0/jy3keFgCPMitG0V1S1YG4Cmws3lWHWdxHQOpg==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.38.tgz", + "integrity": "sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==", "dependencies": { - "@vue/compiler-dom": "3.4.5", - "@vue/shared": "3.4.5" + "@vue/compiler-dom": "3.4.38", + "@vue/shared": "3.4.38" } }, "node_modules/@vue/devtools-api": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz", - "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==" + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.3.8.tgz", + "integrity": "sha512-NURFwmxz4WukFU54IHgyGI2KSejdgHG5JC4xTcWmTWEBIc8aelj9fBy4qsboObGHFp3JIdRxxANO9s2wZA/pVQ==", + "dependencies": { + "@vue/devtools-kit": "^7.3.8" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.3.8.tgz", + "integrity": "sha512-HYy3MQP1nZ6GbE4vrgJ/UB+MvZnhYmEwCa/UafrEpdpwa+jNCkz1ZdUrC5I7LpkH1ShREEV2/pZlAQdBj+ncLQ==", + "dependencies": { + "@vue/devtools-shared": "^7.3.8", + "birpc": "^0.2.17", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.1" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.3.8.tgz", + "integrity": "sha512-1NiJbn7Yp47nPDWhFZyEKpB2+5/+7JYv8IQnU0ccMrgslPR2dL7u1DIyI7mLqy4HN1ll36gQy0k8GqBYSFgZJw==", + "dependencies": { + "rfdc": "^1.4.1" + } }, "node_modules/@vue/reactivity": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.5.tgz", - "integrity": "sha512-BcWkKvjdvqJwb7BhhFkXPLDCecX4d4a6GATvCduJQDLv21PkPowAE5GKuIE5p6RC07/Lp9FMkkq4AYCTVF5KlQ==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.38.tgz", + "integrity": "sha512-4vl4wMMVniLsSYYeldAKzbk72+D3hUnkw9z8lDeJacTxAkXeDAP1uE9xr2+aKIN0ipOL8EG2GPouVTH6yF7Gnw==", "dependencies": { - "@vue/shared": "3.4.5" + "@vue/shared": "3.4.38" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.5.tgz", - "integrity": "sha512-wh9ELIOQKeWT9SaUPdLrsxRkZv14jp+SJm9aiQGWio+/MWNM3Lib0wE6CoKEqQ9+SCYyGjDBhTOTtO47kCgbkg==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.38.tgz", + "integrity": "sha512-21z3wA99EABtuf+O3IhdxP0iHgkBs1vuoCAsCKLVJPEjpVqvblwBnTj42vzHRlWDCyxu9ptDm7sI2ZMcWrQqlA==", "dependencies": { - "@vue/reactivity": "3.4.5", - "@vue/shared": "3.4.5" + "@vue/reactivity": "3.4.38", + "@vue/shared": "3.4.38" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.5.tgz", - "integrity": "sha512-n5ewvOjyG3IEpqGBahdPXODFSpVlSz3H4LF76Sx0XAqpIOqyJ5bIb2PrdYuH2ogBMAQPh+o5tnoH4nJpBr8U0Q==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.38.tgz", + "integrity": "sha512-afZzmUreU7vKwKsV17H1NDThEEmdYI+GCAK/KY1U957Ig2NATPVjCROv61R19fjZNzMmiU03n79OMnXyJVN0UA==", "dependencies": { - "@vue/runtime-core": "3.4.5", - "@vue/shared": "3.4.5", + "@vue/reactivity": "3.4.38", + "@vue/runtime-core": "3.4.38", + "@vue/shared": "3.4.38", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.5.tgz", - "integrity": "sha512-jOFc/VE87yvifQpNju12VcqimH8pBLxdcT+t3xMeiED1K6DfH9SORyhFEoZlW5TG2Vwfn3Ul5KE+1aC99xnSBg==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.38.tgz", + "integrity": "sha512-NggOTr82FbPEkkUvBm4fTGcwUY8UuTsnWC/L2YZBmvaQ4C4Jl/Ao4HHTB+l7WnFCt5M/dN3l0XLuyjzswGYVCA==", "dependencies": { - "@vue/compiler-ssr": "3.4.5", - "@vue/shared": "3.4.5" + "@vue/compiler-ssr": "3.4.38", + "@vue/shared": "3.4.38" }, "peerDependencies": { - "vue": "3.4.5" + "vue": "3.4.38" } }, "node_modules/@vue/shared": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.5.tgz", - "integrity": "sha512-6XptuzlMvN4l4cDnDw36pdGEV+9njYkQ1ZE0Q6iZLwrKefKaOJyiFmcP3/KBDHbt72cJZGtllAc1GaHe6XGAyg==" + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.38.tgz", + "integrity": "sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==" }, "node_modules/@vueuse/core": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.1.tgz", - "integrity": "sha512-74mWHlaesJSWGp1ihg76vAnfVq9NTv1YT0SYhAQ6zwFNdBkkP+CKKJmVOEHcdSnLXCXYiL5e7MaewblfiYLP7g==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.0.0.tgz", + "integrity": "sha512-shibzNGjmRjZucEm97B8V0NO5J3vPHMCE/mltxQ3vHezbDoFQBMtK11XsfwfPionxSbo+buqPmsCljtYuXIBpw==", "dependencies": { "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.7.1", - "@vueuse/shared": "10.7.1", - "vue-demi": ">=0.14.6" + "@vueuse/metadata": "11.0.0", + "@vueuse/shared": "11.0.0", + "vue-demi": ">=0.14.10" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -902,30 +1137,30 @@ } }, "node_modules/@vueuse/integrations": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.7.1.tgz", - "integrity": "sha512-cKo5LEeKVHdBRBtMTOrDPdR0YNtrmN9IBfdcnY2P3m5LHVrsD0xiHUtAH1WKjHQRIErZG6rJUa6GA4tWZt89Og==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-11.0.0.tgz", + "integrity": "sha512-B95nBX4B2q2ZETBDldrKARM/fYXBHfwdo44UbHBq4bUTi25lrlc8MwAZGqEoRvdV4ND9T6O1Rb9e4kaCJFXnqw==", "dependencies": { - "@vueuse/core": "10.7.1", - "@vueuse/shared": "10.7.1", - "vue-demi": ">=0.14.6" + "@vueuse/core": "11.0.0", + "@vueuse/shared": "11.0.0", + "vue-demi": ">=0.14.10" }, "funding": { "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "async-validator": "*", - "axios": "*", - "change-case": "*", - "drauu": "*", - "focus-trap": "*", - "fuse.js": "*", - "idb-keyval": "*", - "jwt-decode": "*", - "nprogress": "*", - "qrcode": "*", - "sortablejs": "*", - "universal-cookie": "*" + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" }, "peerDependenciesMeta": { "async-validator": { @@ -967,9 +1202,9 @@ } }, "node_modules/@vueuse/integrations/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -992,28 +1227,28 @@ } }, "node_modules/@vueuse/metadata": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.1.tgz", - "integrity": "sha512-jX8MbX5UX067DYVsbtrmKn6eG6KMcXxLRLlurGkZku5ZYT3vxgBjui2zajvUZ18QLIjrgBkFRsu7CqTAg18QFw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.0.0.tgz", + "integrity": "sha512-0TKsAVT0iUOAPWyc9N79xWYfovJVPATiOPVKByG6jmAYdDiwvMVm9xXJ5hp4I8nZDxpCcYlLq/Rg9w1Z/jrGcg==", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.1.tgz", - "integrity": "sha512-v0jbRR31LSgRY/C5i5X279A/WQjD6/JsMzGa+eqt658oJ75IvQXAeONmwvEMrvJQKnRElq/frzBR7fhmWY5uLw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.0.0.tgz", + "integrity": "sha512-i4ZmOrIEjSsL94uAEt3hz88UCz93fMyP/fba9S+vypX90fKg3uYX9cThqvWc9aXxuTzR0UGhOKOTQd//Goh1nQ==", "dependencies": { - "vue-demi": ">=0.14.6" + "vue-demi": ">=0.14.10" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -1036,24 +1271,82 @@ } }, "node_modules/algoliasearch": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.0.tgz", - "integrity": "sha512-gfceltjkwh7PxXwtkS8KVvdfK+TSNQAWUeNSxf4dA29qW5tf2EGwa8jkJujlT9jLm17cixMVoGNc+GJFO1Mxhg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", + "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-account": "4.24.0", + "@algolia/client-analytics": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-personalization": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/recommend": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", "dependencies": { - "@algolia/cache-browser-local-storage": "4.22.0", - "@algolia/cache-common": "4.22.0", - "@algolia/cache-in-memory": "4.22.0", - "@algolia/client-account": "4.22.0", - "@algolia/client-analytics": "4.22.0", - "@algolia/client-common": "4.22.0", - "@algolia/client-personalization": "4.22.0", - "@algolia/client-search": "4.22.0", - "@algolia/logger-common": "4.22.0", - "@algolia/logger-console": "4.22.0", - "@algolia/requester-browser-xhr": "4.22.0", - "@algolia/requester-common": "4.22.0", - "@algolia/requester-node-http": "4.22.0", - "@algolia/transporter": "4.22.0" + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/birpc": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.17.tgz", + "integrity": "sha512-+hkTxhot+dWsLpp3gia5AkVHIsKlZybNT5gIYiDlNzJrmYPcTM9k5/w2uaj3IPpd7LlEYpmCj4Jj1nC41VhDFg==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" } }, "node_modules/csstype": { @@ -1073,9 +1366,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", - "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -1084,29 +1377,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.11", - "@esbuild/android-arm": "0.19.11", - "@esbuild/android-arm64": "0.19.11", - "@esbuild/android-x64": "0.19.11", - "@esbuild/darwin-arm64": "0.19.11", - "@esbuild/darwin-x64": "0.19.11", - "@esbuild/freebsd-arm64": "0.19.11", - "@esbuild/freebsd-x64": "0.19.11", - "@esbuild/linux-arm": "0.19.11", - "@esbuild/linux-arm64": "0.19.11", - "@esbuild/linux-ia32": "0.19.11", - "@esbuild/linux-loong64": "0.19.11", - "@esbuild/linux-mips64el": "0.19.11", - "@esbuild/linux-ppc64": "0.19.11", - "@esbuild/linux-riscv64": "0.19.11", - "@esbuild/linux-s390x": "0.19.11", - "@esbuild/linux-x64": "0.19.11", - "@esbuild/netbsd-x64": "0.19.11", - "@esbuild/openbsd-x64": "0.19.11", - "@esbuild/sunos-x64": "0.19.11", - "@esbuild/win32-arm64": "0.19.11", - "@esbuild/win32-ia32": "0.19.11", - "@esbuild/win32-x64": "0.19.11" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/estree-walker": { @@ -1135,15 +1428,28 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==" + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/mark.js": { @@ -1152,9 +1458,14 @@ "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==" }, "node_modules/minisearch": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", - "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==" + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.0.tgz", + "integrity": "sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" }, "node_modules/nanoid": { "version": "3.3.7", @@ -1173,15 +1484,20 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/picocolors": { + "node_modules/perfect-debounce": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==" + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "funding": [ { "type": "opencollective", @@ -1198,26 +1514,31 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/preact": { - "version": "10.19.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", - "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==", + "version": "10.23.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.2.tgz", + "integrity": "sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, "node_modules/rollup": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.3.tgz", - "integrity": "sha512-JnchF0ZGFiqGpAPjg3e89j656Ne4tTtCY1VZc1AxtoQcRIxjTu9jyYHBAtkDXE+X681n4un/nX9SU52AroSRzg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", + "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", "dependencies": { "@types/estree": "1.0.5" }, @@ -1229,70 +1550,88 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.3", - "@rollup/rollup-android-arm64": "4.9.3", - "@rollup/rollup-darwin-arm64": "4.9.3", - "@rollup/rollup-darwin-x64": "4.9.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.3", - "@rollup/rollup-linux-arm64-gnu": "4.9.3", - "@rollup/rollup-linux-arm64-musl": "4.9.3", - "@rollup/rollup-linux-riscv64-gnu": "4.9.3", - "@rollup/rollup-linux-x64-gnu": "4.9.3", - "@rollup/rollup-linux-x64-musl": "4.9.3", - "@rollup/rollup-win32-arm64-msvc": "4.9.3", - "@rollup/rollup-win32-ia32-msvc": "4.9.3", - "@rollup/rollup-win32-x64-msvc": "4.9.3", + "@rollup/rollup-android-arm-eabi": "4.20.0", + "@rollup/rollup-android-arm64": "4.20.0", + "@rollup/rollup-darwin-arm64": "4.20.0", + "@rollup/rollup-darwin-x64": "4.20.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", + "@rollup/rollup-linux-arm-musleabihf": "4.20.0", + "@rollup/rollup-linux-arm64-gnu": "4.20.0", + "@rollup/rollup-linux-arm64-musl": "4.20.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", + "@rollup/rollup-linux-riscv64-gnu": "4.20.0", + "@rollup/rollup-linux-s390x-gnu": "4.20.0", + "@rollup/rollup-linux-x64-gnu": "4.20.0", + "@rollup/rollup-linux-x64-musl": "4.20.0", + "@rollup/rollup-win32-arm64-msvc": "4.20.0", + "@rollup/rollup-win32-ia32-msvc": "4.20.0", + "@rollup/rollup-win32-x64-msvc": "4.20.0", "fsevents": "~2.3.2" } }, "node_modules/search-insights": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", - "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.16.3.tgz", + "integrity": "sha512-hSHy/s4Zk2xibhj9XTCACB+1PqS+CaJxepGNBhKc/OsHRpqvHAUAm5+uZ6kJJbGXn0pb3XqekHjg6JAqPExzqg==", "peer": true }, - "node_modules/shikiji": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/shikiji/-/shikiji-0.9.17.tgz", - "integrity": "sha512-0z/1NfkhBkm3ijrfFeHg3G9yDNuHhXdAGbQm7tRxj4WQ5z2y0XDbnagFyKyuV2ebCTS1Mwy1I3n0Fzcc/4xdmw==", + "node_modules/shiki": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.13.0.tgz", + "integrity": "sha512-e0dWfnONbEv6xl7FJy3XIhsVHQ/65XHDZl92+6H9+4xWjfdo7pmkqG7Kg47KWtDiEtzM5Z+oEfb4vtRvoZ/X9w==", "dependencies": { - "shikiji-core": "0.9.17" + "@shikijs/core": "1.13.0", + "@types/hast": "^3.0.4" } }, - "node_modules/shikiji-core": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/shikiji-core/-/shikiji-core-0.9.17.tgz", - "integrity": "sha512-r1FWTXk6SO2aYqfWgcsJ11MuVQ1ymPSdXzJjK7q8EXuyqu8yc2N5qrQy5+BL6gTVOaF4yLjbxFjF+KTRM1Sp8Q==" - }, - "node_modules/shikiji-transformers": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/shikiji-transformers/-/shikiji-transformers-0.9.17.tgz", - "integrity": "sha512-2CCG9qSLS6Bn/jbeUTEuvC6YSuP8gm8VyX5VjmCvDKyCPGhlLJbH1k/kg9wfRt7cJqpYjhdMDgT5rkdYrOZnsA==", - "dependencies": { - "shikiji": "0.9.17" + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", "engines": { "node": ">=0.10.0" } }, + "node_modules/superjson": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz", + "integrity": "sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, "node_modules/vite": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz", - "integrity": "sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.1.tgz", + "integrity": "sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==", "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.32", - "rollup": "^4.2.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.41", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -1311,6 +1650,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -1328,6 +1668,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -1340,32 +1683,33 @@ } }, "node_modules/vitepress": { - "version": "1.0.0-rc.35", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.35.tgz", - "integrity": "sha512-+2VnFwtYIiKWWAnMjWg7ik0PfsUdrNoZIZKeu5dbJtrkzKO/mTvlA3owiT5VBKJsZAgI17B5UV37aYfUvGrN6g==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.3.3.tgz", + "integrity": "sha512-6UzEw/wZ41S/CATby7ea7UlffvRER/uekxgN6hbEvSys9ukmLOKsz87Ehq9yOx1Rwiw+Sj97yjpivP8w1sUmng==", "dependencies": { - "@docsearch/css": "^3.5.2", - "@docsearch/js": "^3.5.2", - "@types/markdown-it": "^13.0.7", - "@vitejs/plugin-vue": "^5.0.2", - "@vue/devtools-api": "^6.5.1", - "@vueuse/core": "^10.7.1", - "@vueuse/integrations": "^10.7.1", + "@docsearch/css": "^3.6.1", + "@docsearch/js": "^3.6.1", + "@shikijs/core": "^1.13.0", + "@shikijs/transformers": "^1.13.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.1.2", + "@vue/devtools-api": "^7.3.8", + "@vue/shared": "^3.4.38", + "@vueuse/core": "^11.0.0", + "@vueuse/integrations": "^11.0.0", "focus-trap": "^7.5.4", "mark.js": "8.11.1", - "minisearch": "^6.3.0", - "shikiji": "^0.9.17", - "shikiji-core": "^0.9.17", - "shikiji-transformers": "^0.9.17", - "vite": "^5.0.10", - "vue": "^3.4.4" + "minisearch": "^7.1.0", + "shiki": "^1.13.0", + "vite": "^5.4.1", + "vue": "^3.4.38" }, "bin": { "vitepress": "bin/vitepress.js" }, "peerDependencies": { - "markdown-it-mathjax3": "^4.3.2", - "postcss": "^8.4.32" + "markdown-it-mathjax3": "^4", + "postcss": "^8" }, "peerDependenciesMeta": { "markdown-it-mathjax3": { @@ -1377,15 +1721,15 @@ } }, "node_modules/vue": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.5.tgz", - "integrity": "sha512-VH6nHFhLPjgu2oh5vEBXoNZxsGHuZNr3qf4PHClwJWw6IDqw6B3x+4J+ABdoZ0aJuT8Zi0zf3GpGlLQCrGWHrw==", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz", + "integrity": "sha512-f0ZgN+mZ5KFgVv9wz0f4OgVKukoXtS3nwET4c2vLBGQR50aI8G0cqbFtLlX9Yiyg3LFGBitruPHt2PxwTduJEw==", "dependencies": { - "@vue/compiler-dom": "3.4.5", - "@vue/compiler-sfc": "3.4.5", - "@vue/runtime-dom": "3.4.5", - "@vue/server-renderer": "3.4.5", - "@vue/shared": "3.4.5" + "@vue/compiler-dom": "3.4.38", + "@vue/compiler-sfc": "3.4.38", + "@vue/runtime-dom": "3.4.38", + "@vue/server-renderer": "3.4.38", + "@vue/shared": "3.4.38" }, "peerDependencies": { "typescript": "*" diff --git a/docs/package.json b/docs/package.json index f66494b..1e1eb7a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,6 +5,6 @@ "docs:preview": "vitepress preview" }, "dependencies": { - "vitepress": "^1.0.0-rc.35" + "vitepress": "^1.3.3" } } diff --git a/internal/scene/sceneSource.go b/internal/scene/sceneSource.go index f474e75..2394fe7 100644 --- a/internal/scene/sceneSource.go +++ b/internal/scene/sceneSource.go @@ -89,7 +89,7 @@ func (source *SceneSource) loadScene(config SceneConfig, imageSource *image.Sour searchDone := metrics.Elapsed("search") q, err := search.Parse(scene.Search) if err == nil { - embFilter = len(q.QualifierValues("t")) > 0 + embFilter = len(q.QualifierValues("t")) > 0 || len(q.QualifierValues("dedup")) > 0 if similar, err := q.QualifierInt("img"); err == nil { embedding, err := imageSource.GetImageEmbedding(image.ImageId(similar)) if err != nil { From 05cf31d70c63d624a872052a853d316203b0ce7e Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Sun, 18 Aug 2024 22:46:01 +0200 Subject: [PATCH 13/14] Fix images always loading with the high resolution tweak --- ui/src/components/ScrollViewer.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/components/ScrollViewer.vue b/ui/src/components/ScrollViewer.vue index b72fb35..39af077 100644 --- a/ui/src/components/ScrollViewer.vue +++ b/ui/src/components/ScrollViewer.vue @@ -140,7 +140,7 @@ const { scene, recreate: recreateScene, loadSpeed } = useScene({ }); const qualityPreset = computed(() => { - if (tweaks.value?.indexOf("hq") != -1) return "HIGH"; + if (tweaks.value?.indexOf("hq") > -1) return "HIGH"; return null; }); From 10e26ba1cbf1856b0b8a29cac611c060a4f4caac Mon Sep 17 00:00:00 2001 From: Miha Lunar Date: Sun, 18 Aug 2024 23:23:05 +0200 Subject: [PATCH 14/14] Fix blank Map view page --- ui/src/components/MapViewer.vue | 3 +++ ui/src/components/ScrollViewer.vue | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/src/components/MapViewer.vue b/ui/src/components/MapViewer.vue index 1336380..083e937 100644 --- a/ui/src/components/MapViewer.vue +++ b/ui/src/components/MapViewer.vue @@ -79,6 +79,7 @@ const props = defineProps({ debug: Object, fullpage: Boolean, scrollbar: Object, + tweaks: String, }); const emit = defineEmits({ @@ -103,6 +104,7 @@ const { search, selectTagId, debug, + tweaks, } = toRefs(props); const viewer = ref(null); @@ -124,6 +126,7 @@ const { scene, recreate: recreateScene, loadSpeed } = useScene({ imageHeight, viewport: staticViewport, search, + tweaks, }); const { diff --git a/ui/src/components/ScrollViewer.vue b/ui/src/components/ScrollViewer.vue index 39af077..12e33eb 100644 --- a/ui/src/components/ScrollViewer.vue +++ b/ui/src/components/ScrollViewer.vue @@ -118,9 +118,9 @@ const { sort, imageHeight, search, - tweaks, selectTagId, debug, + tweaks, } = toRefs(props); const viewer = ref(null);