diff --git a/generator.go b/generator.go index cb63186..42c09aa 100644 --- a/generator.go +++ b/generator.go @@ -12,6 +12,7 @@ import ( "os" "path/filepath" "sort" + "strconv" "strings" "sync" "time" @@ -23,6 +24,7 @@ import ( const ( thumbSize = 600 mediumMaxSize = 1600 + pageSize = 60 ) // PhotoMeta is the structure of a .toml sidecar file. @@ -103,7 +105,7 @@ func (g *Generator) Build() error { } } - if err := g.renderIndex(photos); err != nil { + if err := g.renderIndexPages(photos); err != nil { return err } for _, p := range photos { @@ -290,21 +292,69 @@ func generateMedium(src, dst string, maxDim int) error { return jpeg.Encode(out, resized, &jpeg.Options{Quality: 88}) } -// renderIndex writes public/index.html. -func (g *Generator) renderIndex(photos []Photo) error { - return g.render("index.html", filepath.Join(g.outputDir, "index.html"), map[string]any{ - "Photos": photos, - "BaseURL": g.baseURL, - "Year": time.Now().Year(), - "Author": g.author, - "Handle": g.handle, - "Bio": g.bio, - "BioAlt": g.bioAlt, - "Initial": g.initial(), - "FontsURL": g.fontsURL, - "SerifFamily": g.serifFamily, - "MlFamily": g.mlFamily, - }) +// renderIndexPages writes public/index.html and public/page/N/index.html +// files, chunking photos into pageSize slices for progressive pagination. +func (g *Generator) renderIndexPages(photos []Photo) error { + total := len(photos) + totalPages := (total + pageSize - 1) / pageSize + if totalPages == 0 { + totalPages = 1 + } + + for i := 0; i < totalPages; i++ { + start := i * pageSize + end := start + pageSize + if end > total { + end = total + } + slice := photos[start:end] + + dst := filepath.Join(g.outputDir, "index.html") + if i > 0 { + dir := filepath.Join(g.outputDir, "page", strconv.Itoa(i+1)) + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + dst = filepath.Join(dir, "index.html") + } + + prev := "" + next := "" + switch i { + case 0: + // no prev + case 1: + prev = "/" + default: + prev = "/page/" + strconv.Itoa(i) + "/" + } + if i+1 < totalPages { + next = "/page/" + strconv.Itoa(i+2) + "/" + } + + data := map[string]any{ + "Photos": slice, + "Total": total, + "Page": i + 1, + "TotalPages": totalPages, + "PrevURL": prev, + "NextURL": next, + "BaseURL": g.baseURL, + "Year": time.Now().Year(), + "Author": g.author, + "Handle": g.handle, + "Bio": g.bio, + "BioAlt": g.bioAlt, + "Initial": g.initial(), + "FontsURL": g.fontsURL, + "SerifFamily": g.serifFamily, + "MlFamily": g.mlFamily, + } + if err := g.render("index.html", dst, data); err != nil { + return err + } + } + return nil } // renderPhotoPage writes public/photo//index.html. diff --git a/static/style.css b/static/style.css index c599f54..e0a91a3 100644 --- a/static/style.css +++ b/static/style.css @@ -163,6 +163,30 @@ body { transform: scale(1.05); } +/* ── Pager ─────────────────────────────────────────────────── */ + +.pager { + display: flex; + justify-content: space-between; + align-items: center; + padding: 24px 20px 8px; + font-family: monospace; + font-size: 13px; + letter-spacing: 0.04em; +} + +.pager a { + color: var(--aqua); + text-decoration: none; +} + +.pager a:hover { + color: var(--fg2); +} + +.pager-newer { margin-right: auto; } +.pager-older { margin-left: auto; } + /* ── Single photo page ─────────────────────────────────────── */ .photo-page { diff --git a/templates/index.html b/templates/index.html index 9af3214..a83f242 100644 --- a/templates/index.html +++ b/templates/index.html @@ -18,7 +18,7 @@

{{.Author}}

{{.Handle}}

-

{{len .Photos}} posts

+

{{.Total}} posts

{{.Bio}}{{if .BioAlt}}
{{.BioAlt}}{{end}}

{{end}}
+ + {{if or .PrevURL .NextURL}} + + {{end}} + +