From f2b0d2e7370f798d5191de121042fcb7f1d06be3 Mon Sep 17 00:00:00 2001 From: 514sid Date: Mon, 27 Jul 2026 19:10:26 +0400 Subject: [PATCH] feat: generate product comparison pages for curated products --- content/compare/_content.gotmpl | 51 +++++++++++++++++++++ content/compare/_index.md | 6 +++ content/updates/2026-07-27.md | 9 ++++ data/compare.yaml | 54 ++++++++++++++++++++++ layouts/compare/single.html | 80 +++++++++++++++++++++++++++++++++ layouts/products/single.html | 30 +++++++++++++ 6 files changed, 230 insertions(+) create mode 100644 content/compare/_content.gotmpl create mode 100644 content/compare/_index.md create mode 100644 content/updates/2026-07-27.md create mode 100644 data/compare.yaml create mode 100644 layouts/compare/single.html diff --git a/content/compare/_content.gotmpl b/content/compare/_content.gotmpl new file mode 100644 index 00000000..d8d37a9a --- /dev/null +++ b/content/compare/_content.gotmpl @@ -0,0 +1,51 @@ +{{/* Generate one comparison page per unordered pair of products that share a + category. Only products in the data/compare.yaml allowlist are eligible, + and pairs never cross categories (e.g. CMS vs Content provider). + Only slugs are stored in params; the layout looks up full data by slug. */}} + +{{/* Allowlist set */}} +{{ $allow := dict }} +{{ range .Site.Data.compare.products }} + {{ $allow = merge $allow (dict . true) }} +{{ end }} + +{{/* Group active, allowlisted products by category */}} +{{ $byCat := dict }} +{{ range $slug, $p := .Site.Data.products }} + {{ if and (not $p.discontinued) (index $allow $slug) }} + {{ range $p.categories }} + {{ $list := index $byCat . | default slice }} + {{ $byCat = merge $byCat (dict . ($list | append $slug)) }} + {{ end }} + {{ end }} +{{ end }} + +{{ $seen := newScratch }} +{{ range $cat, $slugs := $byCat }} + {{ $sorted := sort $slugs }} + {{ range $i, $a := $sorted }} + {{ range $j, $b := $sorted }} + {{ if lt $i $j }} + {{ $key := printf "%s|%s" $a $b }} + {{ if not ($seen.Get $key) }} + {{ $seen.Set $key true }} + {{ $pa := index $.Site.Data.products $a }} + {{ $pb := index $.Site.Data.products $b }} + {{ $.AddPage (dict + "kind" "page" + "path" (printf "%s-vs-%s" $a $b) + "title" (printf "%s vs %s" $pa.name $pb.name) + "params" (dict + "a" $a + "b" $b + "cat" $cat + "seo_title" (printf "%s vs %s: Compare %s Digital Signage" $pa.name $pb.name $cat) + "description" (printf "Compare %s and %s, two %s digital signage products, side by side on pricing, supported platforms, and capabilities." $pa.name $pb.name $cat) + ) + "content" (dict "mediaType" "text/html" "value" "") + ) }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} +{{ end }} diff --git a/content/compare/_index.md b/content/compare/_index.md new file mode 100644 index 00000000..426ab4e3 --- /dev/null +++ b/content/compare/_index.md @@ -0,0 +1,6 @@ +--- +title: "Compare Digital Signage Software" +build: + render: never + list: never +--- diff --git a/content/updates/2026-07-27.md b/content/updates/2026-07-27.md new file mode 100644 index 00000000..1bdb6531 --- /dev/null +++ b/content/updates/2026-07-27.md @@ -0,0 +1,9 @@ +--- +title: "Compare products side by side" +date: 2026-07-27 +description: "New side-by-side comparison pages for popular digital signage products." +--- + +You can now compare popular digital signage products head to head. Each comparison puts two products in the same category next to each other, with their pricing, supported platforms, headquarters, and capabilities laid out in one table. + +To open one, look for the "Compare with" section on a product's page and pick another product. diff --git a/data/compare.yaml b/data/compare.yaml new file mode 100644 index 00000000..d6717fa7 --- /dev/null +++ b/data/compare.yaml @@ -0,0 +1,54 @@ +products: + - 22miles + - anthias + - appspace + - axistv + - brightsign + - broadsign + - carousel + - cenareo + - dise + - displai + - displ + - embed-signage + - enplug + - fugo + - fusion-signage + - garlic-signage + - grassfish-ixm-platform + - info-beamer + - intuiface + - juuno + - kitcast + - korbyt + - lg-supersign + - livesignage + - magicinfo + - media4display + - mediacloud + - mvix + - navori + - novisign + - nowsignage + - omnivex + - onsign-tv + - optisigns + - pickcel + - pisignage + - playsignage + - poppulo + - posterbooking + - reach + - rise-vision + - scala + - screencloud + - screenly + - signagelive + - skykit + - spectrio + - spinetix-elementi + - telemetrytv + - uniguest + - wallboard + - xibo + - yodeck diff --git a/layouts/compare/single.html b/layouts/compare/single.html new file mode 100644 index 00000000..9149e4b8 --- /dev/null +++ b/layouts/compare/single.html @@ -0,0 +1,80 @@ +{{ define "main" }} +{{ $a := index site.Data.products .Params.a }} +{{ $b := index site.Data.products .Params.b }} +{{ $cat := .Params.cat }} + +{{/* Small helpers */}} +{{ $yesno := "text-neutral-400" }} + +
+ + + +

{{ $a.name }} vs {{ $b.name }}

+ + {{/* SEO intro paragraph, generated from data */}} +

+ {{ $a.name }} and {{ $b.name }} are both {{ $cat }} digital signage products. + {{ with $a.description }}{{ $a.name }} is {{ . | lower }}{{ end }} + {{ with $b.description }}{{ $b.name }} is {{ . | lower }}{{ end }} + This page compares them side by side on pricing, supported platforms, and capabilities so you can decide which fits your screens. +

+ + {{/* Two header cards */}} +
+ {{ range slice $a $b }} +
+ {{ partial "product-logo.html" (dict "Params" . "size" "lg") }} +

+ {{ .name }} +

+ {{ with .description }}

{{ . }}

{{ end }} +
+ {{ end }} +
+ + {{/* Spec comparison table */}} + {{ $rows := slice + (dict "label" "Category" "a" (delimit $a.categories ", ") "b" (delimit $b.categories ", ")) + (dict "label" "Headquarters" "a" (delimit $a.headquarters ", ") "b" (delimit $b.headquarters ", ")) + (dict "label" "Founded" "a" ($a.year_founded) "b" ($b.year_founded)) + (dict "label" "Platforms" "a" (delimit $a.platforms ", ") "b" (delimit $b.platforms ", ")) + (dict "label" "Open source" "a" (cond $a.open_source "Yes" "No") "b" (cond $b.open_source "Yes" "No")) + (dict "label" "Self-signup" "a" (cond $a.self_signup "Yes" "No") "b" (cond $b.self_signup "Yes" "No")) + (dict "label" "API" "a" (cond $a.has_api "Yes" "No") "b" (cond $b.has_api "Yes" "No")) + (dict "label" "Documentation" "a" (cond (ne (printf "%v" $a.docs_url) "") "Yes" "No") "b" (cond (ne (printf "%v" $b.docs_url) "") "Yes" "No")) + }} +
+ + + + + + + + + + {{ range $rows }} + + + + + + {{ end }} + +
{{ $a.name }}{{ $b.name }}
{{ .label }}{{ if .a }}{{ .a }}{{ else }}{{ end }}{{ if .b }}{{ .b }}{{ else }}{{ end }}
+
+ +
+ {{ range slice $a $b }} + {{ $url := partial "outbound-url.html" (dict "url" .website "campaign" "compare_cta") | safeURL }} + Visit {{ .name }} + {{ end }} +
+ +
+{{ end }} diff --git a/layouts/products/single.html b/layouts/products/single.html index 51dfeced..0caff6ea 100644 --- a/layouts/products/single.html +++ b/layouts/products/single.html @@ -143,6 +143,36 @@

Notes

{{/* RSS Feed */}} {{ partial "rss-feed.html" . }} + + {{/* Comparisons — only for products in the compare allowlist */}} + {{ $allow := site.Data.compare.products }} + {{ if in $allow $p.slug }} + {{ $cur := index site.Data.products $p.slug }} + {{ $peers := slice }} + {{ range $allow }} + {{ if ne . $p.slug }} + {{ $peer := index site.Data.products . }} + {{ if and $peer (not $peer.discontinued) }} + {{ $shared := false }} + {{ range $peer.categories }}{{ if in $cur.categories . }}{{ $shared = true }}{{ end }}{{ end }} + {{ if $shared }} + {{ $pair := sort (slice $p.slug .) }} + {{ $peers = $peers | append (dict "name" $peer.name "url" (printf "/compare/%s-vs-%s/" (index $pair 0) (index $pair 1))) }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + {{ if $peers }} +
+

Compare {{ $p.name }} with

+
+ {{ range sort $peers "name" }} + {{ .name }} + {{ end }} +
+
+ {{ end }} + {{ end }} {{/* ── Sidebar ── */}}