diff --git a/.github/actions/setup-hugo/action.yml b/.github/actions/setup-hugo/action.yml index 81e8d8fe2..c06fa1969 100644 --- a/.github/actions/setup-hugo/action.yml +++ b/.github/actions/setup-hugo/action.yml @@ -4,14 +4,20 @@ description: 'Install Hugo + Bun, cache dependencies and build artifacts' inputs: hugo-version: description: 'Hugo version' - default: '0.149.1' + default: '0.160.1' environment: description: 'Hugo build environment' - default: 'development' + default: 'production' + output-dir: + description: 'Hugo build output directory' + default: '_dest/public-test' + base-url: + description: 'Hugo base URL (default: /)' + default: '/' runs: using: 'composite' steps: - - uses: peaceiris/actions-hugo@v3 + - uses: jetthoughts/actions-hugo@feat/node24 with: hugo-version: ${{ inputs.hugo-version }} extended: true @@ -48,3 +54,5 @@ runs: env: HUGO_CACHEDIR: /tmp/hugo_cache ENVIRONMENT: ${{ inputs.environment }} + OUTPUT_DIR: ${{ inputs.output-dir }} + BASE_URL: ${{ inputs.base-url }} diff --git a/.github/workflows/_hugo.yml b/.github/workflows/_hugo.yml index 5bcbc3b62..eed151f1a 100644 --- a/.github/workflows/_hugo.yml +++ b/.github/workflows/_hugo.yml @@ -16,9 +16,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup Hugo - uses: peaceiris/actions-hugo@v3 + uses: jetthoughts/actions-hugo@feat/node24 with: - hugo-version: 0.149.1 + hugo-version: 0.160.1 extended: true - name: Checkout @@ -28,7 +28,7 @@ jobs: - name: Setup Pages id: pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 # Tier 1: Dependencies cache (changes infrequently) - uses: actions/cache@v5 @@ -78,8 +78,14 @@ jobs: --baseURL "https://jetthoughts.com/" \ --environment production - - name: Upload artifact - uses: actions/upload-pages-artifact@v4 + - name: Remove blog media from artifact + run: | + find public/blog/ -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.gif' -o -iname '*.webp' \) -delete + echo "Cleaned blog media. Artifact size:" + du -sh public/ + + - name: Upload pages artifact + uses: actions/upload-pages-artifact@v5 with: path: ./public @@ -94,4 +100,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 875d7f71b..c30e6be6f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,3 +29,22 @@ defaults: jobs: build_and_deploy: uses: ./.github/workflows/_hugo.yml + + unit_tests: + name: Unit Tests + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '4.0' + bundler-cache: true + + - uses: ./.github/actions/setup-hugo + + - run: bundle exec rake test:unit + env: + PRECOMPILED_ASSETS: '1' + HUGO_DEFAULT_PATH: _dest/public-test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb9c4d1b6..a8a5a0120 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,15 +1,13 @@ --- -name: Tests +name: Screenshot Tests on: - pull_request: - types: [opened, synchronize, reopened] workflow_dispatch: inputs: screenshots: description: 'Run screenshot tests (slow, requires Hugo build)' type: boolean - default: false + default: true update-baselines: description: 'Re-record screenshot baselines and commit' type: boolean @@ -20,26 +18,10 @@ permissions: pull-requests: write concurrency: - group: tests-${{ github.event.pull_request.number || github.ref }} + group: screenshots-${{ github.ref }} cancel-in-progress: true jobs: - unit: - name: Unit Tests - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v6 - - - uses: ruby/setup-ruby@v1 - with: - ruby-version: '4.0' - bundler-cache: true - - - uses: ./.github/actions/setup-hugo - - - run: bundle exec rake test:unit - screenshots: name: Screenshot Tests if: ${{ inputs.screenshots || inputs.update-baselines }} @@ -63,12 +45,16 @@ jobs: run: bundle exec rake test env: SCREENSHOT_DRIVER: vips + PRECOMPILED_ASSETS: '1' + HUGO_DEFAULT_PATH: _dest/public-test - name: Record baselines if: ${{ inputs.update-baselines }} run: FORCE_SCREENSHOT_UPDATE=true bundle exec rake test env: SCREENSHOT_DRIVER: vips + PRECOMPILED_ASSETS: '1' + HUGO_DEFAULT_PATH: _dest/public-test - name: Commit updated baselines if: ${{ inputs.update-baselines }} diff --git a/.gitignore b/.gitignore index c6a36e918..4fc64e56f 100644 --- a/.gitignore +++ b/.gitignore @@ -97,3 +97,4 @@ docs/projects/2509-css-migration/50-59-execution/ *.local.* .cache _workspace +.claude/scheduled_tasks.lock diff --git a/bin/hugo-build b/bin/hugo-build index 7a920420e..8080fcf7e 100755 --- a/bin/hugo-build +++ b/bin/hugo-build @@ -4,12 +4,21 @@ set -euo pipefail # Default settings -OUTPUT_DIR="_dest/public-dev" +OUTPUT_DIR=${OUTPUT_DIR:-_dest/public-dev} ENVIRONMENT=${ENVIRONMENT:-development} +BASE_URL=${BASE_URL:-} + echo "Building Hugo site..." echo "Environment: $ENVIRONMENT" echo "Output: $OUTPUT_DIR" -hugo build --noBuildLock --environment "$ENVIRONMENT" --destination "$OUTPUT_DIR" + +BUILD_DRAFTS=${BUILD_DRAFTS:-} + +HUGO_ARGS=(hugo build --noBuildLock --environment "$ENVIRONMENT" --destination "$OUTPUT_DIR") +[ -n "$BASE_URL" ] && HUGO_ARGS+=(--baseURL "$BASE_URL") +[ -n "$BUILD_DRAFTS" ] && HUGO_ARGS+=(--buildDrafts) + +"${HUGO_ARGS[@]}" echo "✓ Build complete" diff --git a/config/_default/hugo.toml b/config/_default/hugo.toml index 253d9526c..95c9f3a57 100644 --- a/config/_default/hugo.toml +++ b/config/_default/hugo.toml @@ -53,6 +53,10 @@ disableKinds = [] changeFreq = 'weekly' priority = 0.5 +[params.cdn] + enabled = false # enabled per-environment in config/production/hugo.toml + rawBase = "raw.githubusercontent.com/jetthoughts/jetthoughts.github.io/master/content/" + [params] email="info@jetthoughts.com" phone="+1 754 216 9568" diff --git a/config/production/hugo.toml b/config/production/hugo.toml index fc7c1ae67..0f0e5cb8d 100644 --- a/config/production/hugo.toml +++ b/config/production/hugo.toml @@ -26,6 +26,9 @@ disableKinds = [] # Production environment environment = "production" +[params.cdn] + enabled = true + [outputs] home = ["HTML"] # Only build HTML, skip JSON/RSS in tests section = ["HTML"] diff --git a/docs/workflows/cdn-image-proxy.md b/docs/workflows/cdn-image-proxy.md new file mode 100644 index 000000000..bc4b1cacb --- /dev/null +++ b/docs/workflows/cdn-image-proxy.md @@ -0,0 +1,62 @@ +# CDN Image Proxy for Production + +Solves the GitHub Pages 1 GB artifact limit by serving blog images from a CDN proxy in production, while keeping local Hugo image processing for development. + +## How It Works + +1. **Development** (`hugo server`): Images processed locally by Hugo — no change. +2. **Production** (`--environment production`): Templates emit CDN URLs instead of local paths via `partials/cdn/url.html`. +3. **GitHub Actions**: After Hugo build, blog media files are deleted from `public/blog/` before uploading the artifact. + +## Configuration + +**Default** (`config/_default/hugo.toml`) — CDN disabled: +```toml +[params.cdn] + enabled = false + rawBase = "raw.githubusercontent.com/jetthoughts/jetthoughts.github.io/master/content/" +``` + +**Production** (`config/production/hugo.toml`) — CDN enabled: +```toml +[params.cdn] + enabled = true +``` + +Gate in templates: `{{ if site.Params.cdn.enabled }}` + +## Shared Partial + +`themes/beaver/layouts/partials/cdn/url.html` — single source for CDN URL construction: + +```text +{{ partial "cdn/url" (dict "page" $page "resource" $resource "params" "w=400&output=webp&q=80") }} +→ "https://wsrv.nl/?url=raw.githubusercontent.com/.../image.jpg&w=400&output=webp&q=80" +``` + +## Templates Modified + +| Template | CDN behavior | +|----------|-------------| +| `_markup/render-image.html` | `` srcset via wsrv.nl (webp+jpeg, 400/800/1200/1600w) | +| `partials/blog/img-cropped.html` | Retina thumbnail via wsrv.nl | +| `partials/seo/enhanced-meta-tags.html` | OG image (1200×630) via wsrv.nl | +| `_shortcodes/img.html` | Direct image via wsrv.nl | + +**Note:** Root `layouts/` overrides `themes/beaver/layouts/`. Both `enhanced-meta-tags.html` copies must stay in sync. + +## wsrv.nl Params + +- `&w=N` — width resize +- `&h=N` — height resize +- `&output=webp|jpeg` — format conversion +- `&q=N` — quality (80 content, 85 OG, 90 thumbnails) +- `&fit=cover` — crop to exact dimensions (OG images) + +## Not Yet Covered + +Non-blog image templates (assets from `assets/`/`static/`, unaffected by blog media cleanup): + +- `partials/img/generic.html` (hero, homepage, testimonials) +- `partials/img/resize.html` +- `partials/page/cover_image.html` diff --git a/layouts/partials/seo/enhanced-meta-tags.html b/layouts/partials/seo/enhanced-meta-tags.html index 8b135fce0..341e7523a 100644 --- a/layouts/partials/seo/enhanced-meta-tags.html +++ b/layouts/partials/seo/enhanced-meta-tags.html @@ -97,57 +97,32 @@ {{- end -}} {{- end -}} -{{/* Image Handling - metatags.image is primary, then cover_image/cover/featured_image */}} +{{/* Image Handling — try frontmatter fields in priority order */}} {{- $image := "" -}} {{- $imageWidth := "1200" -}} {{- $imageHeight := "630" -}} -{{/* Try metatags.image first (primary convention) */}} -{{- with .Params.metatags.image -}} - {{- $resource := $page.Resources.Get . -}} - {{- if $resource -}} - {{- $optimized := $resource.Resize "1200x630 jpg q90" -}} - {{- $image = $optimized.Permalink -}} - {{- $imageWidth = $optimized.Width -}} - {{- $imageHeight = $optimized.Height -}} - {{- end -}} -{{- end -}} - -{{/* Try cover_image */}} -{{- if not $image -}} - {{- with .Params.cover_image -}} - {{- $resource := $page.Resources.Get . -}} - {{- if $resource -}} - {{- $optimized := $resource.Resize "1200x630 jpg q90" -}} - {{- $image = $optimized.Permalink -}} - {{- $imageWidth = $optimized.Width -}} - {{- $imageHeight = $optimized.Height -}} - {{- end -}} - {{- end -}} -{{- end -}} - -{{/* Try cover (alias) */}} -{{- if not $image -}} - {{- with .Params.cover -}} - {{- $resource := $page.Resources.Get . -}} - {{- if $resource -}} - {{- $optimized := $resource.Resize "1200x630 jpg q90" -}} - {{- $image = $optimized.Permalink -}} - {{- $imageWidth = $optimized.Width -}} - {{- $imageHeight = $optimized.Height -}} +{{- $imageFields := slice "metatags.image" "cover_image" "cover" "featured_image" -}} +{{- range $field := $imageFields -}} + {{- if not $image -}} + {{- $value := "" -}} + {{- if eq $field "metatags.image" -}} + {{- $value = $page.Params.metatags.image -}} + {{- else -}} + {{- $value = index $page.Params $field -}} {{- end -}} - {{- end -}} -{{- end -}} - -{{/* Try featured_image (legacy) */}} -{{- if not $image -}} - {{- with .Params.featured_image -}} - {{- $resource := $page.Resources.Get . -}} - {{- if $resource -}} - {{- $optimized := $resource.Resize "1200x630 jpg q90" -}} - {{- $image = $optimized.Permalink -}} - {{- $imageWidth = $optimized.Width -}} - {{- $imageHeight = $optimized.Height -}} + {{- with $value -}} + {{- $resource := $page.Resources.Get . -}} + {{- if $resource -}} + {{- if site.Params.cdn.enabled -}} + {{- $image = partial "cdn/url" (dict "page" $page "resource" $resource "params" "w=1200&h=630&fit=cover&output=webp&q=85") -}} + {{- else -}} + {{- $optimized := $resource.Resize "1200x630 jpg q90" -}} + {{- $image = $optimized.Permalink -}} + {{- $imageWidth = $optimized.Width -}} + {{- $imageHeight = $optimized.Height -}} + {{- end -}} + {{- end -}} {{- end -}} {{- end -}} {{- end -}} @@ -162,7 +137,6 @@ - {{- end -}} diff --git a/test/unit/cdn_image_proxy_test.rb b/test/unit/cdn_image_proxy_test.rb new file mode 100644 index 000000000..1c93fe7ae --- /dev/null +++ b/test/unit/cdn_image_proxy_test.rb @@ -0,0 +1,116 @@ +require_relative "../base_page_test_case" + +class CdnImageProxyTest < BasePageTestCase + # Tests that production builds serve blog images via wsrv.nl CDN proxy. + # The test helper builds with --environment production, so CDN is enabled. + + def setup + @blog_posts = Dir.glob("#{root_path}/blog/*/index.html") + .reject { |f| f.end_with?("blog/index.html") } + + refute_empty @blog_posts, "Expected compiled blog posts in blog/*/index.html" + end + + def test_markdown_images_use_cdn_picture_element + post = require_post_with_picture_element + doc = Nokogiri::HTML(File.read(post)) + picture = doc.css("picture").first + + sources = picture.css("source") + assert sources.length >= 2, "Picture should have at least webp and jpeg sources" + + sources.each do |source| + srcset = source["srcset"] + assert_includes srcset, "wsrv.nl", "Source srcset should use wsrv.nl CDN" + assert_match(/w=\d+/, srcset, "CDN URLs should include width parameter") + assert_match(/output=(webp|jpeg)/, srcset, "CDN URLs should include output format") + assert_match(/q=\d+/, srcset, "CDN URLs should include quality parameter") + assert_match(/\d+w/, srcset, "Srcset should include width descriptors") + end + + img = picture.css("img").first + refute_nil img, "Picture element must have fallback img" + assert_includes img["src"], "wsrv.nl", "Fallback img should use wsrv.nl CDN" + assert_equal "lazy", img["loading"], "Images should have lazy loading" + end + + def test_cdn_urls_have_correct_sizes + post = require_post_with_picture_element + doc = Nokogiri::HTML(File.read(post)) + srcset = doc.css("picture source").first["srcset"] + + %w[w=400 w=800 w=1200 w=1600].each do |size| + assert_includes srcset, size, "Srcset should include #{size}" + end + end + + def test_cdn_urls_proxy_through_wsrv + post = require_post_with_picture_element + doc = Nokogiri::HTML(File.read(post)) + srcset = doc.css("picture source").first["srcset"] + + assert_match(/url=.*\/content\//, srcset, "CDN URLs should proxy source content path") + end + + def test_blog_thumbnails_use_cdn + doc = parse_html_file("blog/index.html") + + thumbnail_imgs = doc.css(".blog-post img, .post-image img, article img") + cdn_thumbnails = thumbnail_imgs.select { |img| img["src"]&.include?("wsrv.nl") } + + refute_empty cdn_thumbnails, "Blog listing thumbnails should use wsrv.nl CDN" + + cdn_thumbnails.each do |img| + src = img["src"] + assert_match(/output=jpg/, src, "Thumbnails should request JPG format") + assert_match(/q=90/, src, "Thumbnails should use q=90 quality") + end + end + + def test_og_images_use_cdn + post_with_og = @blog_posts.find do |path| + content = File.read(path) + content.include?("og:image") && content.include?("wsrv.nl") + end + refute_nil post_with_og, "At least one blog post should have CDN og:image" + + doc = Nokogiri::HTML(File.read(post_with_og)) + og_url = doc.css("meta[property='og:image']").first["content"] + + assert_includes og_url, "wsrv.nl", "OG image should use wsrv.nl CDN" + assert_match(/w=1200/, og_url, "OG image should be 1200px wide") + assert_match(/h=630/, og_url, "OG image should be 630px tall") + assert_match(/fit=cover/, og_url, "OG image should use cover fit") + end + + def test_picture_element_has_sizes_attribute + post = require_post_with_picture_element + doc = Nokogiri::HTML(File.read(post)) + source = doc.css("picture source").first + + assert source["sizes"], "Source should have sizes attribute" + assert_includes source["sizes"], "48rem", "Sizes should reference max content width" + end + + def test_images_preserve_dimensions + post = require_post_with_picture_element + doc = Nokogiri::HTML(File.read(post)) + img = doc.css("picture img").first + + assert img["width"], "Image should have width attribute for CLS prevention" + assert img["height"], "Image should have height attribute for CLS prevention" + end + + private + + def require_post_with_picture_element + @_post_with_picture ||= begin + post = @blog_posts.find do |path| + content = File.read(path) + content.include?("") && content.include?("wsrv.nl") + end + refute_nil post, "Expected at least one blog post with CDN element" + post + end + end +end diff --git a/themes/beaver/layouts/_markup/render-image.html b/themes/beaver/layouts/_markup/render-image.html index d4bfd1269..7a1fb2800 100644 --- a/themes/beaver/layouts/_markup/render-image.html +++ b/themes/beaver/layouts/_markup/render-image.html @@ -3,26 +3,54 @@ {{ $alt := .PlainText | safeHTML }} {{ if $src }} -{{/* Check if image is a GIF - if so, render it directly */}} -{{ if eq (path.Ext $src.RelPermalink | lower) ".gif" }} +{{ $useCDN := site.Params.cdn.enabled }} +{{ $isGif := eq (path.Ext $src.Name | lower) ".gif" }} + +{{ if $isGif }} + {{ if $useCDN }} + {{ $cdnURL := partial "cdn/url" (dict "page" .Page "resource" $src "params" "") }} + {{ $alt }} + {{ else }} {{ $alt }} + {{ end }} +{{ else if $useCDN }} + {{ $formats := slice "webp" "jpeg" }} + {{ $sizes := slice 400 800 1200 1600 }} + + + {{ range $format := $formats }} + {{ $srcset := slice }} + {{ range $size := $sizes }} + {{ $url := partial "cdn/url" (dict "page" $.Page "resource" $src "params" (printf "w=%d&output=%s&q=80" $size $format)) }} + {{ $srcset = $srcset | append (printf "%s %dw" $url $size) }} + {{ end }} + + {{ end }} + {{ $fallbackURL := partial "cdn/url" (dict "page" .Page "resource" $src "params" "w=400&output=jpeg&q=80") }} + {{ $alt }} + {{ else }} - {{/* Get formats and sizes from config */}} - {{/* From less common to most common. For example avif webp jpeg */}} + {{/* Local: Hugo image processing for development */}} {{ $formats := slice "webp" "jpeg" }} {{ $sizes := slice 400 800 1200 1600 }} - {{/* default format if browser doesn't support picture element */}} {{ $defaultFormat := "jpeg" }} - {{/* Create optimized versions of the original image in the specified formats */}} {{ $cacheKey := printf "img-%s" ($src.RelPermalink | md5) }} {{ $data := partialCached "image-processor" (dict "src" $src "formats" $formats "sizes" $sizes) $cacheKey }} - {{ range $format := $formats }} {{ $srcset := slice }} diff --git a/themes/beaver/layouts/_shortcodes/img.html b/themes/beaver/layouts/_shortcodes/img.html index 32a0c04d6..4aa8cfa64 100644 --- a/themes/beaver/layouts/_shortcodes/img.html +++ b/themes/beaver/layouts/_shortcodes/img.html @@ -1,6 +1,11 @@ {{ $image := .Page.Resources.GetMatch (printf "%s" (.Get 0)) }} {{ if $image }} +{{ if site.Params.cdn.enabled }} +{{ $cdnURL := partial "cdn/url" (dict "page" .Page "resource" $image "params" "") }} +{{ .Get 1 }} +{{ else }} {{ .Get 1 }} +{{ end }} {{ else }}

Image not found: {{ .Get 0 }}

-{{ end }} \ No newline at end of file +{{ end }} diff --git a/themes/beaver/layouts/partials/blog/img-cropped.html b/themes/beaver/layouts/partials/blog/img-cropped.html index 14fe76dc0..716af4fb7 100644 --- a/themes/beaver/layouts/partials/blog/img-cropped.html +++ b/themes/beaver/layouts/partials/blog/img-cropped.html @@ -5,27 +5,25 @@ {{- if $page.Params.metatags.image -}} {{ $image = $page.Resources.Get $page.Params.metatags.image }} {{- else if $page.Params.cover_image -}} - {{/* Try cover_image */}} {{ $image = $page.Resources.Get $page.Params.cover_image }} {{- else if $page.Params.cover -}} - {{/* Try cover (alias) */}} {{ $image = $page.Resources.Get $page.Params.cover }} {{- else if $page.Params.featured_image -}} - {{/* Try featured_image (legacy) */}} {{ $image = $page.Resources.Get $page.Params.featured_image }} {{- end -}} {{ if $image }} -{{/* Aspect-preserving resize at 2× retina. The cover is designed as a - landscape composition (2400×1260 = 1.905:1) following the canonical - 6-slot layout in .stitch/design.md — we must NOT center-crop it - because the left-aligned hero + right visual would be mangled. - CSS uses object-fit: contain with a dark background to letterbox - cleanly inside the 180×180 thumbnail container. */}} {{ $retinaW := mul $.width 2 }} -{{ $resized := $image.Resize (printf "%dx jpg q90" $retinaW) }} +{{ if site.Params.cdn.enabled }} +{{ $cdnURL := partial "cdn/url" (dict "page" $page "resource" $image "params" (printf "w=%d&output=jpg&q=90" $retinaW)) }} +{{ $page.Title }} +{{ else }} +{{/* Aspect-preserving resize at 2× retina. CSS uses object-fit: contain + with a dark background to letterbox inside the thumbnail container. */}} +{{ $resized := $image.Resize (printf "%dx jpg q90" $retinaW) }} {{ $page.Title }} +{{ end }} {{ else }} Placeholder Image diff --git a/themes/beaver/layouts/partials/blog/json-ld.html b/themes/beaver/layouts/partials/blog/json-ld.html index 0ad721697..0bdb911d1 100644 --- a/themes/beaver/layouts/partials/blog/json-ld.html +++ b/themes/beaver/layouts/partials/blog/json-ld.html @@ -14,7 +14,7 @@ "datePublished": "{{ with .Params.created_at }}{{ . }}{{ else }}{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}{{ end }}", "dateModified": "{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}", "mainEntityOfPage": "{{ .Permalink }}", - "publisher": {{ site.Data.company | jsonify }}, + "publisher": {{ hugo.Data.company | jsonify }}, {{ with (.Resources.Get .Params.metatags.image) }} "image": { "@type": "ImageObject", diff --git a/themes/beaver/layouts/partials/cdn/url.html b/themes/beaver/layouts/partials/cdn/url.html new file mode 100644 index 000000000..09580ea91 --- /dev/null +++ b/themes/beaver/layouts/partials/cdn/url.html @@ -0,0 +1,6 @@ +{{/* Constructs a CDN proxy URL for a page bundle image resource. + Input: dict "page" $page "resource" $resource "params" "w=400&output=webp&q=80" + Returns: "https://wsrv.nl/?url=...&w=400&output=webp&q=80" */}} +{{- $rawURL := printf "%s%s%s" site.Params.cdn.rawBase .page.File.Dir .resource.Name -}} +{{- $suffix := cond (ne .params "") (printf "&%s" .params) "" -}} +{{- return printf "https://wsrv.nl/?url=%s%s" $rawURL $suffix -}} diff --git a/themes/beaver/layouts/partials/components/testimonial.html b/themes/beaver/layouts/partials/components/testimonial.html index 1ed0fee7e..dc9b89752 100644 --- a/themes/beaver/layouts/partials/components/testimonial.html +++ b/themes/beaver/layouts/partials/components/testimonial.html @@ -32,7 +32,7 @@ {{- $rating := .rating | default "4.8" -}} {{- $rating_text := .rating_text | default "Based on client reviews" -}} {{- $node_id := .node_id | default "testimonials" -}} -{{- $testimonialsData := site.Data.testimonials -}} +{{- $testimonialsData := hugo.Data.testimonials -}} {{- $nonCriticalCSS := resources.Get "css/vendors/swiper.min.css" | fingerprint "md5" -}} diff --git a/themes/beaver/layouts/partials/data/authors-cached.html b/themes/beaver/layouts/partials/data/authors-cached.html index 516d72056..3c49cf0c2 100644 --- a/themes/beaver/layouts/partials/data/authors-cached.html +++ b/themes/beaver/layouts/partials/data/authors-cached.html @@ -1,3 +1,3 @@ {{/* Cached authors data access - invalidates when authors.yaml changes */}} -{{ $authorsHash := site.Data.authors | jsonify | md5 }} +{{ $authorsHash := hugo.Data.authors | jsonify | md5 }} {{ return partialCached "data/authors-content.html" . $authorsHash }} diff --git a/themes/beaver/layouts/partials/data/company-cached.html b/themes/beaver/layouts/partials/data/company-cached.html index 27c0b5016..de23944f4 100644 --- a/themes/beaver/layouts/partials/data/company-cached.html +++ b/themes/beaver/layouts/partials/data/company-cached.html @@ -1,3 +1,3 @@ {{/* Cached company data access - invalidates when company.yaml changes */}} -{{ $companyHash := site.Data.company | jsonify | md5 }} +{{ $companyHash := hugo.Data.company | jsonify | md5 }} {{ return partialCached "data/company-content.html" . $companyHash }} diff --git a/themes/beaver/layouts/partials/data/testimonials-cached.html b/themes/beaver/layouts/partials/data/testimonials-cached.html index ecfe747a7..1c39d33f2 100644 --- a/themes/beaver/layouts/partials/data/testimonials-cached.html +++ b/themes/beaver/layouts/partials/data/testimonials-cached.html @@ -1,3 +1,3 @@ {{/* Cached testimonials data access - invalidates when testimonials.yaml changes */}} -{{ $testimonialsHash := site.Data.testimonials | jsonify | md5 }} +{{ $testimonialsHash := hugo.Data.testimonials | jsonify | md5 }} {{ return partialCached "data/testimonials-content.html" . $testimonialsHash }} diff --git a/themes/beaver/layouts/partials/homepage/companies.html b/themes/beaver/layouts/partials/homepage/companies.html index edd854f4e..56c959bb8 100644 --- a/themes/beaver/layouts/partials/homepage/companies.html +++ b/themes/beaver/layouts/partials/homepage/companies.html @@ -1,5 +1,5 @@
    - {{ range $index, $tech := site.Data.companies }} + {{ range $index, $tech := hugo.Data.companies }}