Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
628 changes: 628 additions & 0 deletions assets/css/case-studies-home.scss

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions assets/css/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4234,3 +4234,4 @@ font-size: 1.55rem;
}

@import "case-studies.scss";
@import "case-studies-home.scss";
6 changes: 6 additions & 0 deletions content/case-studies/marketspark.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ stat_bar:
preview_image: /img/case-studies/marketspark/marketspark-aws.png
og_img: /img/case-studies/marketspark/marketspark-aws.png

# Homepage showcase (read by the case-studies-showcase shortcode only)
home_blurb: "Rebuilt a single, manually operated AWS account into an 11-account, 100% codified and automated multi-account platform."
home_metric: "0 → 100%"
home_metric_label: "Infrastructure as Code"
home_logo: /img/case-studies/marketspark/marketspark-logo-dark.png

sitemap:
priority: 0
---
Expand Down
14 changes: 14 additions & 0 deletions content/case-studies/power-digital-case-study.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ preview_image: /img/case-studies/power-digital-case-study-preview.jpg
sitemap:
priority: 0
download_button: /download/power-digital-case-study.pdf

# Homepage showcase (read by the case-studies-showcase shortcode only)
client: "Power Digital"
home_blurb: "Migrated 43,000+ resources from Terraform Cloud to Spacelift for a 10x cut in infrastructure automation cost and far faster plan & apply cycles."
home_metric: "10x"
home_metric_label: "Lower automation cost"
home_logo: /img/case-studies/power-logo.png
stat_bar:
- value: "10x"
label: "reduction in infrastructure automation cost"
- value: "43,000+"
label: "resources migrated to Spacelift"
- value: "Faster"
label: "plan & apply cycles across the platform"
---

![Power Digital x MasterPoint Logos](/img/case-studies/power-digital-x-masterpoint-case-study-logos.png)
Expand Down
9 changes: 9 additions & 0 deletions content/sections/home-cs-01-cards.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: "Case Studies Showcase — Option 1"
weight: 11
section_categories:
- Home
id: cs-showcase-01
---

{{< case-studies-showcase style="1" label="Option 1 — Logo tabs (Microsoft pattern)" >}}
9 changes: 9 additions & 0 deletions content/sections/home-cs-02-splits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: "Case Studies Showcase — Option 2"
weight: 12
section_categories:
- Home
id: cs-showcase-02
---

{{< case-studies-showcase style="2" label="Option 2 — Peek carousel" >}}
9 changes: 9 additions & 0 deletions content/sections/home-cs-03-statband.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: "Case Studies Showcase — Option 3"
weight: 13
section_categories:
- Home
id: cs-showcase-03
---

{{< case-studies-showcase style="3" label="Option 3 — Combo (panel + carousel selector)" >}}
66 changes: 66 additions & 0 deletions docs/case-studies.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,72 @@ card grid to stay scannable as the list grows. Card image = each study's

---

## Homepage showcase (`case-studies-showcase` shortcode)

To give case studies visibility beyond the nav dropdown, the homepage can feature
them via the **`case-studies-showcase`** shortcode
(`layouts/shortcodes/case-studies-showcase.html`), styled under `.csh` in
`assets/css/case-studies-home.scss` (imported at the end of `custom.scss`, right
after `case-studies.scss`).

It renders the case studies as an **interactive featured-story slider** on a
dark, on-brand **pine band** (the Microsoft "customer stories" pattern): a large
featured panel beside its image, with a selector that hints "there's more" as
studies are added.

- **The showcase brings its own dark surface.** The homepage page background is
white (`body { background: #fff }`), so the whole showcase sits inside
`.csh-band` — a rounded pine gradient card (mint/pink radial glows + faint dot
grid). Section chrome is light-on-dark: eyebrow `$csh-mint`, title white, body
white-alpha. The **featured panel and cards are light** (`#fff`) so they pop on
the band and the client logos (which read on light) stay visible. Don't put the
showcase straight on the white page (the early drafts did, and it looked flat /
the title went white-on-white).
- **Data is pulled dynamically** — `where site.RegularPages "Section"
"case-studies"` sorted by `weight`, so adding a new case study automatically
flows in (no per-page wiring). `_index.md` is excluded.
- **Three selectable styles** (all the same slider), via `style="1".."3"`:
- **`1` Logo tabs** — featured `cshx-panel` + a logo-tab selector strip below
(active tab gets a gradient underline).
- **`2` Peek carousel** — a horizontally scrollable row of `cshx-card`s with
the next card peeking; prev/next arrows.
- **`3` Combo** — featured panel (style 1) **+** a peek-carousel of compact
cards beneath that doubles as the selector (style 2). Clicking a card swaps
the featured panel; the active card gets a mint border and scrolls into view.
- **Two engines, one script** (`.cshx-*`):
- **swap** (`data-cshx-mode="swap"`: styles 1 & 3) — one `cshx-panel` visible
at a time; nav buttons carry `data-cshx-go="{i}"`, optional
`data-cshx-prev`/`data-cshx-next`.
- **track** (`data-cshx-mode="track"`: style 2) — a scrollable
`[data-cshx-track]` with CSS peek (cards `< 100%` wide); arrows `scrollBy`
one card and loop at the ends.
- Both **auto-advance** (`data-cshx-autoplay` ms), **pause on hover/focus**,
and `init` is idempotent (marks `data-cshx-ready`). The `<script>` is emitted
**once per build** via `site.Store` (page-scoped `.Page.Scratch` would emit
once per *section page*, i.e. once per demo on the aggregated homepage).
- **Featured panel / card markup are partials** — `partials/cshx-panel.html`
(featured: logo pill, title, blurb, `Highlights` stat chips, "Read the story" +
image) and `partials/cshx-card.html` (compact card for the peek carousel). Both
take a `dict` (`page`, `index`, `active`). Style 3's selector card markup is
inline in the shortcode (it's a `<button>`, not the `<a>` card).
- **Homepage sections live in** `content/sections/home-cs-0N-*.md`
(`section_categories: [Home]`, weights 11–13) — one file per style so the team
can compare them live, then **keep one and delete the others**. The kept
section's `weight` controls where it lands among the other homepage sections
(existing ones are weights 1–5).
- **Remove the compare badges before shipping** — each demo passes
`label="Option N — …"` (renders the `.csh-flag` pill). Drop `label=` once
chosen. The shortcode's `eyebrow`/`title`/`cta_text` default to good copy.
- **Homepage-only front matter** read by the shortcode (added to each case study,
ignored by the case-study layouts themselves): `home_blurb` (short copy, falls
back to `description`), `home_metric` + `home_metric_label` (compact metric),
`home_logo` (a logo that reads on a LIGHT surface — required for the logo
selectors; falls back to the client name text), plus the existing `stat_bar`
(top 3 become the Highlight chips) / `client` / `preview_image` /
`hero_aside_image`. All reads are guarded — a missing field degrades gracefully.

---

## Modern layout — front matter schema

```yaml
Expand Down
18 changes: 18 additions & 0 deletions layouts/partials/cshx-card.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{{- /*
cshx-card — compact case-study card used by the track-based slider styles
(peek carousel, coverflow, filmstrip). Call: {{ partial "cshx-card.html" (dict "page" .) }}
*/ -}}
{{- $p := .page -}}
{{- $img := or ($p.Param "preview_image") ($p.Param "hero_aside_image") -}}
<a class="cshx-card" href="{{ $p.RelPermalink }}">
<div class="cshx-card__media">{{ with $img }}<img src="{{ . }}" alt="{{ $p.Title }}" loading="lazy">{{ end }}</div>
<div class="cshx-card__body">
<div class="cshx-card__logo">
{{- with $p.Param "home_logo" }}<img src="{{ . }}" alt="{{ $p.Param "client" | default $p.Title }}">{{ else }}<b>{{ or ($p.Param "client") $p.Title }}</b>{{ end }}

Check failure on line 11 in layouts/partials/cshx-card.html

View workflow job for this annotation

GitHub Actions / Trunk Check

prettier(SyntaxError)

[new] Opening tag "img" not terminated.
</div>
<h3 class="cshx-card__title">{{ $p.Title }}</h3>
<p class="cshx-card__desc">{{ or ($p.Param "home_blurb") $p.Description }}</p>
{{- with $p.Param "home_metric" }}<span class="cshx-card__metric">{{ . }} · {{ $p.Param "home_metric_label" }}</span>{{ end }}
<span class="csh-card__cta">Read the story <span aria-hidden="true">→</span></span>
</div>
</a>
26 changes: 26 additions & 0 deletions layouts/partials/cshx-panel.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{{- /*
cshx-panel — one featured case-study panel for the homepage slider styles.
Call: {{ partial "cshx-panel.html" (dict "page" . "index" $i "active" (eq $i 0)) }}
*/ -}}
{{- $p := .page -}}
{{- $i := .index -}}
{{- $img := or ($p.Param "preview_image") ($p.Param "hero_aside_image") -}}
<article class="cshx-panel{{ if .active }} is-active{{ end }}" data-cshx-panel="{{ $i }}">
<div class="cshx-panel__text">
<div class="cshx-panel__logo">
{{- with $p.Param "home_logo" }}<img src="{{ . }}" alt="{{ $p.Param "client" | default $p.Title }}">{{ else }}<span>{{ or ($p.Param "client") $p.Title }}</span>{{ end }}
</div>
<h3 class="cshx-panel__title">{{ $p.Title }}</h3>
<p class="cshx-panel__desc">{{ or ($p.Param "home_blurb") $p.Description }}</p>
{{- with $p.Param "stat_bar" }}
<div class="cshx-panel__meta">
<span class="cshx-panel__metalabel">Highlights</span>
<div class="csh-chiprow">{{ range first 3 . }}<span class="csh-chip">{{ .value | safeHTML }}</span>{{ end }}</div>
</div>
{{- end }}
<a class="button btn-gradient cshx-panel__btn" href="{{ $p.RelPermalink }}">Read the story</a>
</div>
<a class="cshx-panel__media" href="{{ $p.RelPermalink }}" aria-label="{{ $p.Title }}">
{{- with $img }}<img src="{{ . }}" alt="{{ $p.Title }}" loading="lazy">{{ end }}
</a>
</article>
182 changes: 182 additions & 0 deletions layouts/shortcodes/case-studies-showcase.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
{{- /*
case-studies-showcase — homepage case-study highlight slider.

Pulls case studies dynamically from content/case-studies/ (sorted by weight)
and renders them as a featured-story SLIDER on a dark, on-brand pine band.
Three treatments to compare:
style="1" Logo tabs (Microsoft "customer stories" pattern): featured panel
+ logo-tab selector strip below.
style="2" Peek carousel: horizontally scrollable cards, the next peeking.
style="3" Combo: featured panel (style 1) + a peek-carousel of cards that
doubles as the selector (style 2).

Params:
style "1".."3" (default "1")
eyebrow small label above the heading (default "Customer Stories")
title section heading, HTML allowed
intro optional sub-heading paragraph (off by default)
cta_text "all case studies" button text (default "See all case studies")
cta_link href for that button (default "/case-studies/")
label optional preview badge (e.g. "Option 1 — …"); omit in production.

Per-case-study data (all optional, graceful fallbacks): see partials
cshx-panel.html / cshx-card.html. `home_logo` should read on a LIGHT surface.
*/ -}}
{{- $style := .Get "style" | default "1" -}}
{{- $eyebrow := .Get "eyebrow" | default "Customer Stories" -}}
{{- $title := .Get "title" | default "Hear what our customers are saying" -}}
{{- $intro := .Get "intro" -}}
{{- $ctaText := .Get "cta_text" | default "See all case studies" -}}
{{- $ctaLink := .Get "cta_link" | default "/case-studies/" -}}
{{- $label := .Get "label" -}}
{{- $items := sort (where site.RegularPages "Section" "case-studies") "Weight" -}}

<div class="csh csh--s{{ $style }}">
<div class="csh-band">
{{- if $label }}
<div class="csh-flag"><span class="csh-flag__dot"></span>{{ $label | safeHTML }}</div>
{{- end }}

<div class="cshx-head">
<div class="cshx-head__intro">
<span class="csh-eyebrow">{{ $eyebrow }}</span>
<h2 class="csh-title">{{ $title | safeHTML }}</h2>
{{- with $intro }}<p class="csh-intro">{{ . | safeHTML }}</p>{{ end }}
</div>
<a class="button btn-gradient cshx-head__cta" href="{{ $ctaLink }}">{{ $ctaText }}</a>
</div>

{{- /* ===================== STYLE 1 — Logo tabs ===================== */ -}}
{{- if eq $style "1" }}
<div class="cshx-slider" data-cshx-mode="swap" data-cshx-autoplay="6000">
<div class="cshx-stage">
{{- range $i, $e := $items }}{{ partial "cshx-panel.html" (dict "page" . "index" $i "active" (eq $i 0)) }}{{ end }}
</div>
<div class="cshx-tabs" role="tablist" aria-label="Customer stories">
{{- range $i, $e := $items }}
<button type="button" class="cshx-tab{{ if eq $i 0 }} is-active{{ end }}" data-cshx-go="{{ $i }}" role="tab" aria-selected="{{ if eq $i 0 }}true{{ else }}false{{ end }}" aria-label="{{ .Title }}">
{{- with .Param "home_logo" }}<img src="{{ . }}" alt="{{ $e.Param "client" | default $e.Title }}">{{ else }}<span>{{ or ($e.Param "client") $e.Title }}</span>{{ end }}
<span class="cshx-tab__bar" aria-hidden="true"></span>
</button>
{{- end }}
</div>
</div>

{{- /* ===================== STYLE 2 — Peek carousel ===================== */ -}}
{{- else if eq $style "2" }}
<div class="cshx-slider cshx-trackwrap" data-cshx-mode="track" data-cshx-autoplay="5000">
<div class="cshx-track cshx-track--peek" data-cshx-track>
{{- range $items }}{{ partial "cshx-card.html" (dict "page" .) }}{{ end }}
</div>
<div class="cshx-controls">
<button type="button" class="cshx-arrow" data-cshx-prev aria-label="Previous">&lsaquo;</button>
<button type="button" class="cshx-arrow" data-cshx-next aria-label="Next">&rsaquo;</button>
</div>
</div>

{{- /* ===================== STYLE 3 — Combo (panel + peek-carousel selector) ===================== */ -}}
{{- else if eq $style "3" }}
<div class="cshx-slider cshx-combo" data-cshx-mode="swap" data-cshx-autoplay="6000">
<div class="cshx-stage">
{{- range $i, $e := $items }}{{ partial "cshx-panel.html" (dict "page" . "index" $i "active" (eq $i 0)) }}{{ end }}
</div>
<div class="cshx-selrow">
<div class="cshx-seltrack" role="tablist" aria-label="Customer stories">
{{- range $i, $e := $items }}
{{- $t := or (.Param "preview_image") (.Param "hero_aside_image") }}
<button type="button" class="cshx-selcard{{ if eq $i 0 }} is-active{{ end }}" data-cshx-go="{{ $i }}" role="tab" aria-selected="{{ if eq $i 0 }}true{{ else }}false{{ end }}" aria-label="{{ .Title }}">
<span class="cshx-selcard__media">{{ with $t }}<img src="{{ . }}" alt="" loading="lazy">{{ end }}</span>
<span class="cshx-selcard__body">
<span class="cshx-selcard__logo">{{ with .Param "home_logo" }}<img src="{{ . }}" alt="{{ $e.Param "client" | default $e.Title }}">{{ else }}<b>{{ or ($e.Param "client") $e.Title }}</b>{{ end }}</span>
<span class="cshx-selcard__title">{{ .Title }}</span>
{{- with .Param "home_metric" }}<span class="cshx-selcard__metric">{{ . }} · {{ $e.Param "home_metric_label" }}</span>{{ end }}
</span>
</button>
{{- end }}
</div>
<div class="cshx-controls cshx-controls--combo">
<button type="button" class="cshx-arrow" data-cshx-prev aria-label="Previous">&lsaquo;</button>
<button type="button" class="cshx-arrow" data-cshx-next aria-label="Next">&rsaquo;</button>
</div>
</div>
</div>
{{- end }}
</div>
</div>

{{- if not (site.Store.Get "cshx-js") }}{{ site.Store.Set "cshx-js" true }}
<script>
(function () {
function init(root) {
if (root.dataset.cshxReady) return;
root.dataset.cshxReady = "1";
var mode = root.getAttribute("data-cshx-mode") || "swap";
var delay = parseInt(root.getAttribute("data-cshx-autoplay") || "0", 10);
var timer = null;

if (mode === "track") {
var track = root.querySelector("[data-cshx-track]");
if (!track) return;
var prevT = root.querySelector("[data-cshx-prev]");
var nextT = root.querySelector("[data-cshx-next]");
function step(dir) {
var card = track.children[0];
var gap = parseFloat(getComputedStyle(track).columnGap || "20") || 20;
var w = card ? card.getBoundingClientRect().width + gap : track.clientWidth * 0.8;
var max = track.scrollWidth - track.clientWidth - 4;
if (dir > 0 && track.scrollLeft >= max) {
track.scrollTo({ left: 0, behavior: "smooth" });
} else if (dir < 0 && track.scrollLeft <= 4) {
track.scrollTo({ left: track.scrollWidth, behavior: "smooth" });
} else {
track.scrollBy({ left: dir * w, behavior: "smooth" });
}
}
if (prevT) prevT.addEventListener("click", function () { stop(); step(-1); start(); });
if (nextT) nextT.addEventListener("click", function () { stop(); step(1); start(); });
function start() { if (delay > 1 && track.children.length > 1) timer = setInterval(function () { step(1); }, delay); }
function stop() { if (timer) { clearInterval(timer); timer = null; } }
root.addEventListener("mouseenter", stop);
root.addEventListener("mouseleave", start);
start();
return;
}

// swap mode
var panels = root.querySelectorAll("[data-cshx-panel]");
var navs = root.querySelectorAll("[data-cshx-go]");
var prevS = root.querySelector("[data-cshx-prev]");
var nextS = root.querySelector("[data-cshx-next]");
if (!panels.length) return;
var current = 0;
function show(n) {
n = (n + panels.length) % panels.length;
panels[current].classList.remove("is-active");
if (navs[current]) { navs[current].classList.remove("is-active"); navs[current].setAttribute("aria-selected", "false"); }
current = n;
panels[current].classList.add("is-active");
if (navs[current]) {
navs[current].classList.add("is-active");
navs[current].setAttribute("aria-selected", "true");
navs[current].scrollIntoView({ block: "nearest", inline: "nearest", behavior: "smooth" });
}
}
function start() { if (delay > 1 && panels.length > 1) timer = setInterval(function () { show(current + 1); }, delay); }
function stop() { if (timer) { clearInterval(timer); timer = null; } }
navs.forEach(function (b, i) { b.addEventListener("click", function () { stop(); show(i); start(); }); });
if (prevS) prevS.addEventListener("click", function () { stop(); show(current - 1); start(); });
if (nextS) nextS.addEventListener("click", function () { stop(); show(current + 1); start(); });
root.addEventListener("mouseenter", stop);
root.addEventListener("mouseleave", start);
root.addEventListener("focusin", stop);
start();
}
function all() { document.querySelectorAll(".cshx-slider").forEach(init); }
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", all);
} else {
all();
}
})();
</script>
{{- end }}
Loading