diff --git a/assets/css/careers.scss b/assets/css/careers.scss index afc2b2a1..838ed2e0 100644 --- a/assets/css/careers.scss +++ b/assets/css/careers.scss @@ -26,8 +26,9 @@ $hire-pine-deep: #07262a; // Shared glow banner (copy of #caseStudiesPage #banner so the two sections match). // padding-top is inherited from the global #banner (9.9rem) — it clears the -// absolutely-positioned header; don't override it. -@mixin hire-glow-banner { +// absolutely-positioned header; don't override it. Also used by the +// testimonials index (testimonials.scss), which passes its own $image. +@mixin hire-glow-banner($image: "/img/bg_our_team.jpg") { position: relative; overflow: hidden; padding-bottom: 3.5rem; @@ -48,7 +49,7 @@ $hire-pine-deep: #07262a; rgba(9, 42, 44, 0.62) 45%, $pine 100% ), - url("/img/bg_our_team.jpg") center / cover no-repeat, + url($image) center / cover no-repeat, $pine; &::after { diff --git a/assets/css/custom.scss b/assets/css/custom.scss index 341d7465..9a77d351 100644 --- a/assets/css/custom.scss +++ b/assets/css/custom.scss @@ -5092,3 +5092,5 @@ font-size: 1.55rem; @import "case-studies.scss"; @import "careers.scss"; + +@import "testimonials.scss"; diff --git a/assets/css/testimonials.scss b/assets/css/testimonials.scss new file mode 100644 index 00000000..e605c7e5 --- /dev/null +++ b/assets/css/testimonials.scss @@ -0,0 +1,266 @@ +// Testimonials index — /testimonials/ (#testimonialsPage). +// Imported at the end of custom.scss, after careers.scss: it reuses that file's +// glow banner and brand-gradient mixins, and its dark rounded card language. +// Cards read the same front matter as the homepage slider (.quoteBx in +// custom.scss), including the --quote-logo-h / --quote-logo-w logo overrides. + +#testimonialsPage { + #banner { + // the homepage "our word" testimonial art, behind the same pine scrim + @include hire-glow-banner("/img/bg_our_word.jpg"); + } + + main { + background: $pine; + padding-top: 0.5rem; + + .container { + max-width: 1080px; + } + } + + .testimonials-intro { + color: rgba(255, 255, 255, 0.8); + text-align: center; + max-width: 760px; + margin: 0 auto 2.4rem; + font-size: 1.05rem; + line-height: 1.6; + } + + .testimonial-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1.5rem; + + // an odd card out spans the full row instead of leaving a hole + > :last-child:nth-child(odd) { + grid-column: 1 / -1; + } + + @media (max-width: 767px) { + grid-template-columns: minmax(0, 1fr); + } + } + + // ── Testimonial card ────────────────────────────────────────────── + .testimonial-card { + display: flex; + flex-direction: column; + margin: 0; + padding: 2.2rem 2.4rem 2.1rem; + border-radius: 18px; + color: #fff; + background: + radial-gradient( + ellipse at 100% 0%, + rgba(42, 217, 194, 0.12), + transparent 45% + ), + linear-gradient(120deg, #0c3133 0%, $hire-pine-deep 100%); + border: 1px solid rgba(42, 217, 194, 0.16); + box-shadow: 0 14px 32px -22px rgba(0, 0, 0, 0.7); + // clears the fixed header when deep-linked by #slug + scroll-margin-top: 7rem; + transition: border-color 0.24s ease; + + &:hover, + &:target { + border-color: rgba(42, 217, 194, 0.45); + } + + @media (max-width: 575px) { + padding: 1.6rem 1.4rem 1.5rem; + } + } + + .testimonial-card__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + min-height: 54px; + margin-bottom: 1.4rem; + } + + // Monochrome white by default (the source files are dark marks), opt out + // with `logo_full_color: true` — same rules as the slider's .quoteLogo. + .testimonial-card__logo { + min-width: 0; + + // A fixed height (not max-height) so SVGs without intrinsic dimensions + // still size from their viewBox instead of collapsing in the flex row; + // object-fit keeps wide marks undistorted when max-width kicks in. + img { + display: block; + height: var(--quote-logo-h, 34px); + width: auto; + // unquote() keeps Sass from trying to evaluate CSS min() itself + max-width: unquote("min(var(--quote-logo-w, 220px), 100%)"); + object-fit: contain; + object-position: left center; + filter: brightness(0) invert(1); + opacity: 0.92; + } + + &--color img { + filter: none; + opacity: 1; + } + + @media (max-width: 575px) { + img { + height: calc(var(--quote-logo-h, 34px) * 0.8); + } + } + } + + // big gradient opening quote, tucked into the top-right corner + .testimonial-card__mark { + flex: 0 0 auto; + margin-left: auto; + height: 54px; + font-family: Georgia, "Times New Roman", serif; + font-size: 5.5rem; + line-height: 1; + @include hire-brand-gradient-text; + opacity: 0.85; + } + + // Grows to fill the card so every attribution sits on the bottom edge, + // lined up with its neighbour in the row whether or not either card has a + // case-study link. + .testimonial-card__body { + display: flex; + flex: 1 1 auto; + flex-direction: column; + align-items: flex-start; + margin-bottom: 1.8rem; + } + + .testimonial-card__quote { + margin: 0; + padding: 0; + border: 0; + font-style: normal; + font-size: 1.08rem; + line-height: 1.7; + letter-spacing: 0.01em; + color: rgba(255, 255, 255, 0.74); + + p { + font-size: inherit; + line-height: inherit; + margin: 0 0 0.8em; + + &:last-child { + margin-bottom: 0; + } + } + + // the bolded phrases carry the quote: lift them to full white + strong { + color: #fff; + font-weight: 700; + } + + @media (max-width: 575px) { + font-size: 1rem; + } + } + + .testimonial-card__by { + display: flex; + align-items: center; + gap: 1rem; + padding-top: 1.4rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); + } + + // headshot with the brand-gradient ring (as in the slider) + .testimonial-card__avatar { + flex: 0 0 auto; + padding: 1px; + border-radius: 50%; + background: linear-gradient(40deg, #2ad9c2 0%, #55c1b4 42%, #d191bf 100%); + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.35); + + img { + display: block; + width: 60px; + height: 60px; + border-radius: 50%; + object-fit: cover; + } + } + + .testimonial-card__byText { + min-width: 0; + } + + .testimonial-card__name { + font-weight: 800; + font-size: 1.05rem; + line-height: 1.2; + letter-spacing: 0.04em; + text-transform: uppercase; + } + + .testimonial-card__position { + margin-top: 0.3rem; + font-size: 0.85rem; + line-height: 1.45; + color: rgba(255, 255, 255, 0.6); + } + + .testimonial-card__cta { + display: inline-flex; + align-items: center; + gap: 0.45rem; + margin-top: 1.3rem; + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0.12em; + text-transform: uppercase; + text-decoration: none; + @include hire-brand-gradient-text; + + &:hover, + &:focus-visible { + text-decoration: none; + + .testimonial-card__arrow { + transform: translateX(5px); + } + } + } + + .testimonial-card__arrow { + font-size: 1.05rem; + -webkit-text-fill-color: $hire-mint; + color: $hire-mint; + transition: transform 0.28s ease; + } + + // ── "See the work" links ────────────────────────────────────────── + .testimonials-links { + margin: 3.2rem 0 4rem; + text-align: center; + + &__label { + display: block; + margin-bottom: 1rem; + font-size: 0.75rem; + font-weight: 800; + letter-spacing: 0.24em; + text-transform: uppercase; + color: rgba(255, 255, 255, 0.55); + } + &__row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.8rem; + } + } +} diff --git a/content/faqs/4-prices.md b/content/faqs/4-prices.md index 89b289e8..e8f4312a 100644 --- a/content/faqs/4-prices.md +++ b/content/faqs/4-prices.md @@ -3,4 +3,4 @@ title: What are your prices? weight: 4 --- -Every project is a little different, but in general you can expect our pricing to start at about $15k for an audit or workshop. For more complicated projects, our minimum project size is $80k. +Every project is a little different, but in general you can expect our pricing to start at about $15k for an audit or workshop. For more complicated projects, our minimum project size is $100k. diff --git a/content/testimonials/_index.md b/content/testimonials/_index.md new file mode 100644 index 00000000..e722c03b --- /dev/null +++ b/content/testimonials/_index.md @@ -0,0 +1,20 @@ +--- +title: Testimonials +banner_title: "Don't take our word for it" +banner_tagline: IN THEIR WORDS +banner_text: Engineering leaders, from fast-moving startups to Fortune 500 enterprises, on what it's like to work with Masterpoint. +description: What engineering leaders at companies like Cursor, Staples, and Stuzo say about working with Masterpoint on Terraform, OpenTofu, and Infrastructure as Code. +id: testimonialsPage +# No RSS feed: its items would have no links (see below) and no dates. +outputs: + - HTML +# Each testimonial is a single quote, so it only renders as a card on this page +# and in the testimonials slider. Individual pages would be thin, near-empty +# pages; their old URLs redirect here (see netlify.toml). `list` stays at its +# default (always), so the slider's .Site.RegularPages query still finds them. +cascade: + - target: + kind: page + build: + render: never +--- diff --git a/content/testimonials/adam-pallin.md b/content/testimonials/adam-pallin.md index abc987b2..aaae0f8c 100644 --- a/content/testimonials/adam-pallin.md +++ b/content/testimonials/adam-pallin.md @@ -1,5 +1,6 @@ --- title: Adam Pallin +weight: 3 position: Director of Software Engineering, MarketSpark photo: /img/testimonials/adam-pallin.jpeg company: MarketSpark diff --git a/content/testimonials/alec-baker.md b/content/testimonials/alec-baker.md index acab5cfe..dc1f3621 100644 --- a/content/testimonials/alec-baker.md +++ b/content/testimonials/alec-baker.md @@ -1,5 +1,6 @@ --- title: Alec Baker +weight: 5 position: Principal Solutions Architect, Leadership Analytics Group photo: /img/testimonials/alec-baker.jpeg company: Leadership Analytics Group diff --git a/content/testimonials/gunter-pfau.md b/content/testimonials/gunter-pfau.md index 3f4e85ac..eabfdaf3 100644 --- a/content/testimonials/gunter-pfau.md +++ b/content/testimonials/gunter-pfau.md @@ -1,5 +1,6 @@ --- title: Gunter Pfau +weight: 2 position: CEO, Stuzo photo: /img/testimonials/gunter-pfau.jpg company: Stuzo diff --git a/content/testimonials/liam-egan.md b/content/testimonials/liam-egan.md index cfa545a1..41a5e4d0 100644 --- a/content/testimonials/liam-egan.md +++ b/content/testimonials/liam-egan.md @@ -1,5 +1,6 @@ --- title: Liam Egan +weight: 6 position: Director of Technology, We The Collective photo: /img/testimonials/liam-egan.jpeg company: We The Collective diff --git a/content/testimonials/nathan-wade.md b/content/testimonials/nathan-wade.md index 00240a34..8511c210 100644 --- a/content/testimonials/nathan-wade.md +++ b/content/testimonials/nathan-wade.md @@ -1,5 +1,6 @@ --- title: Nathan Wade +weight: 4 position: Solutions Architect, Staples photo: /img/testimonials/nathan-wade.jpeg company: Staples diff --git a/content/testimonials/travis-mcpeak.md b/content/testimonials/travis-mcpeak.md index a37f1fb0..5ae19899 100644 --- a/content/testimonials/travis-mcpeak.md +++ b/content/testimonials/travis-mcpeak.md @@ -1,5 +1,6 @@ --- title: Travis McPeak +weight: 1 position: Security Lead, Cursor photo: /img/testimonials/travis-mcpeak.jpeg company: Cursor diff --git a/layouts/partials/testimonial-card.html b/layouts/partials/testimonial-card.html new file mode 100644 index 00000000..85160bfe --- /dev/null +++ b/layouts/partials/testimonial-card.html @@ -0,0 +1,52 @@ +{{- /* One testimonial on /testimonials/. Reads the same front matter as the +slider (layouts/partials/testimonials-slider.html), including the per-logo size +and color overrides. */ -}} {{- $company := .Params.company | default .Title -}} +{{- $logoStyle := "" -}} {{- with .Params.logo_height }}{{ $logoStyle = printf +"--quote-logo-h:%dpx;" (int .) }}{{ end -}} {{- with .Params.logo_max_width }}{{ +$logoStyle = printf "%s--quote-logo-w:%dpx;" $logoStyle (int .) }}{{ end -}} {{- +/* Some quotes are wrapped in quote marks in their source and some aren't. The +card draws its own opening mark, so drop the wrapping pair for a consistent +grid. (\x3c is "<", spelled out so Prettier doesn't parse the patterns as HTML +tags.) */ -}} {{- $quote := .Content | replaceRE `^(\s*\x3cp>)“` "$1" | +replaceRE `”(\x3c/p>\s*)$` "$1" -}} +
+
+ {{ with .Params.logo }} +
+ {{ $company }} +
+ {{ end }} + +
+
+
+ {{ $quote | safeHTML }} +
+ {{ with .Params.case_study }} + Read the {{ $company }} case study + + {{ end }} +
+
+ {{ with .Params.photo }} +
+ {{ $.Title }} +
+ {{ end }} +
+
{{ .Title }}
+
{{ .Params.position }}
+
+
+
diff --git a/layouts/testimonials/list.html b/layouts/testimonials/list.html new file mode 100644 index 00000000..ee258f60 --- /dev/null +++ b/layouts/testimonials/list.html @@ -0,0 +1,67 @@ +{{ partial "head.html" . }} + + {{ partial "header.html" . }} + + +
+
+ {{ with .Content }} +
+
{{ . }}
+
+ {{ end }} + +
+ {{ range .Pages.ByWeight }} {{ partial "testimonial-card.html" . }} {{ + end }} +
+ + +
+
+ {{ partial "schedule-assessment.html" . }} {{ partial "footer.html" . }} {{ + partial "scripts.html" . }} + +{{- /* The html end tag is intentionally omitted (it is optional in HTML). +Prettier lints this file on its own and rejects closing an element that +head.html opened. */ -}} diff --git a/netlify.toml b/netlify.toml index 20ad18d6..10fc7174 100644 --- a/netlify.toml +++ b/netlify.toml @@ -66,6 +66,46 @@ status = 301 force = true ######################################################## +# Individual testimonial pages are no longer rendered (content/testimonials/_index.md), +# so send their old URLs to the index. One explicit rule per old page: a +# non-forced /testimonials/* splat didn't fire on Netlify (404s instead), and a +# forced splat would also catch /testimonials/ itself and loop. +[[redirects]] +from = "/testimonials/adam-pallin/" +to = "/testimonials/" +status = 301 +force = true + +[[redirects]] +from = "/testimonials/alec-baker/" +to = "/testimonials/" +status = 301 +force = true + +[[redirects]] +from = "/testimonials/gunter-pfau/" +to = "/testimonials/" +status = 301 +force = true + +[[redirects]] +from = "/testimonials/liam-egan/" +to = "/testimonials/" +status = 301 +force = true + +[[redirects]] +from = "/testimonials/nathan-wade/" +to = "/testimonials/" +status = 301 +force = true + +[[redirects]] +from = "/testimonials/travis-mcpeak/" +to = "/testimonials/" +status = 301 +force = true + ######################################################## # Plausible Analytics proxy. Serves the tracker first-party so ad blockers # don't strip it. These MUST stay above the catch-all 404 below, and the paths