Skip to content

Commit 224eb0a

Browse files
authored
Merge branch 'master' into feature/homepage-rework-kris
2 parents c90232c + e6aea31 commit 224eb0a

File tree

11 files changed

+412
-3
lines changed

11 files changed

+412
-3
lines changed

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Ignore Hugo template files with Go template syntax that prettier cannot parse
2+
layouts/_default/_markup/render-heading.html

assets/css/custom.scss

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3313,3 +3313,175 @@ footer {
33133313
}
33143314
}
33153315
}
3316+
3317+
// Floating Table of Contents
3318+
.floating-toc {
3319+
position: fixed;
3320+
left: 2rem;
3321+
top: 120px;
3322+
width: 260px;
3323+
max-height: calc(100vh - 160px);
3324+
overflow-y: auto;
3325+
z-index: 40;
3326+
opacity: 0;
3327+
visibility: hidden;
3328+
transform: translateX(-20px);
3329+
transition: opacity 0.4s ease, visibility 0.4s ease, transform 0.4s ease;
3330+
3331+
// Show when scrolled past header
3332+
&.visible {
3333+
opacity: 1;
3334+
visibility: visible;
3335+
transform: translateX(0);
3336+
}
3337+
3338+
// Hide on mobile and tablets (adjust to show on larger screens)
3339+
@media (max-width: 1400px) {
3340+
display: none;
3341+
}
3342+
3343+
.toc-nav {
3344+
background: rgba(255, 255, 255, 0.98);
3345+
border: 2px solid #e8e8e8;
3346+
border-radius: 6px;
3347+
padding: 0.9rem 1rem;
3348+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
3349+
}
3350+
3351+
.toc-title {
3352+
font-size: 0.7rem;
3353+
font-weight: 800;
3354+
text-transform: uppercase;
3355+
letter-spacing: 0.05em;
3356+
color: $pine;
3357+
margin-bottom: 0.9rem;
3358+
padding-bottom: 0.6rem;
3359+
border-bottom: 2px solid $highlight;
3360+
}
3361+
3362+
.toc-content {
3363+
3364+
// Hugo generates nested ul/li structure
3365+
ul, ol {
3366+
list-style: none !important;
3367+
list-style-type: none !important;
3368+
padding-left: 0 !important;
3369+
margin-left: 0 !important;
3370+
list-style: none;
3371+
padding-left: 0;
3372+
margin: 0;
3373+
3374+
li {
3375+
list-style: none !important;
3376+
list-style-type: none !important;
3377+
3378+
&::before {
3379+
display: none !important;
3380+
content: none !important;
3381+
}
3382+
3383+
// Add subtle separator between items
3384+
&:not(:last-child) {
3385+
border-bottom: 1px solid #f0f0f0;
3386+
}
3387+
3388+
> a {
3389+
font-weight: 600;
3390+
color: #555;
3391+
text-decoration: none;
3392+
display: block;
3393+
padding: 0.3rem 0.4rem;
3394+
padding-left: calc(0.4rem - 3px);
3395+
border-radius: 4px;
3396+
border-left: 3px solid transparent;
3397+
transition: color 0.2s ease, background 0.2s ease, border-left-color 0.2s ease;
3398+
line-height: 1.25;
3399+
3400+
&:hover {
3401+
color: $highlight;
3402+
background: rgba(0, 224, 224, 0.08);
3403+
text-decoration: none;
3404+
}
3405+
3406+
&.active {
3407+
color: $highlight;
3408+
background: rgba(0, 224, 224, 0.12);
3409+
border-left-color: $highlight;
3410+
}
3411+
}
3412+
3413+
// Hide all nested lists (H3, H4, etc.)
3414+
> ul {
3415+
display: none !important;
3416+
}
3417+
}
3418+
}
3419+
}
3420+
3421+
// Custom scrollbar styling
3422+
&::-webkit-scrollbar {
3423+
width: 5px;
3424+
}
3425+
3426+
&::-webkit-scrollbar-track {
3427+
background: transparent;
3428+
}
3429+
3430+
&::-webkit-scrollbar-thumb {
3431+
background: rgba(0, 224, 224, 0.3);
3432+
border-radius: 3px;
3433+
3434+
&:hover {
3435+
background: rgba(0, 224, 224, 0.5);
3436+
}
3437+
}
3438+
}
3439+
3440+
// Header anchor links for blog posts (H1-H6)
3441+
// Entire heading is clickable with # symbol on the right and underline animation
3442+
.heading-with-anchor {
3443+
.heading-anchor-link {
3444+
color: inherit;
3445+
text-decoration: none;
3446+
display: inline-flex;
3447+
align-items: center;
3448+
position: relative;
3449+
3450+
&:hover {
3451+
text-decoration: none;
3452+
color: inherit;
3453+
}
3454+
3455+
.heading-text {
3456+
position: relative;
3457+
3458+
&::after {
3459+
content: '';
3460+
position: absolute;
3461+
left: 0;
3462+
bottom: -2px;
3463+
width: 0;
3464+
height: 2px;
3465+
background-color: $highlight;
3466+
transition: width 0.3s ease-in-out;
3467+
}
3468+
}
3469+
3470+
.heading-anchor-symbol {
3471+
margin-left: 0.5em;
3472+
color: $highlight;
3473+
opacity: 0;
3474+
transition: opacity 0.2s ease-in-out;
3475+
font-weight: 400;
3476+
font-size: 0.8em;
3477+
}
3478+
3479+
&:hover .heading-anchor-symbol {
3480+
opacity: 0.8;
3481+
}
3482+
3483+
&:hover .heading-text::after {
3484+
width: 100%;
3485+
}
3486+
}
3487+
}

assets/js/floating-toc.js

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* Floating Table of Contents with Scroll Spy
3+
* Highlights the current section as user scrolls through the page
4+
*/
5+
(function () {
6+
"use strict";
7+
8+
// Wait for DOM to be ready
9+
if (document.readyState === "loading") {
10+
document.addEventListener("DOMContentLoaded", initTOC);
11+
} else {
12+
initTOC();
13+
}
14+
15+
function initTOC() {
16+
const toc = document.querySelector(".floating-toc");
17+
if (!toc) return; // Exit if no TOC on page
18+
19+
const tocLinks = toc.querySelectorAll("a");
20+
if (tocLinks.length === 0) return;
21+
22+
// Handle TOC visibility based on scroll position
23+
function updateTOCVisibility() {
24+
const scrollPosition =
25+
window.pageYOffset || document.documentElement.scrollTop;
26+
27+
// Find the article content area to determine when to show TOC
28+
const articleContent = document.querySelector(".singleContent");
29+
30+
if (articleContent) {
31+
// Show TOC once user scrolls past the article start
32+
const articleTop =
33+
articleContent.getBoundingClientRect().top + window.pageYOffset;
34+
const triggerPoint = articleTop - 200; // Show 200px before content
35+
36+
if (scrollPosition > triggerPoint) {
37+
toc.classList.add("visible");
38+
} else {
39+
toc.classList.remove("visible");
40+
}
41+
} else {
42+
// Fallback: show after scrolling 600px (roughly past banner)
43+
if (scrollPosition > 600) {
44+
toc.classList.add("visible");
45+
} else {
46+
toc.classList.remove("visible");
47+
}
48+
}
49+
}
50+
51+
// Initialize visibility on page load
52+
updateTOCVisibility();
53+
54+
// Update visibility on scroll (throttled for performance)
55+
let visibilityTimeout;
56+
window.addEventListener(
57+
"scroll",
58+
function () {
59+
if (!visibilityTimeout) {
60+
visibilityTimeout = setTimeout(function () {
61+
updateTOCVisibility();
62+
visibilityTimeout = null;
63+
}, 50);
64+
}
65+
},
66+
{ passive: true },
67+
);
68+
69+
// Get all H2 heading elements that have IDs (these are TOC targets)
70+
const headings = Array.from(document.querySelectorAll("h2[id]")).filter(
71+
(heading) => {
72+
// Only include headings in the main content area
73+
const singleContent = document.querySelector(".singleContent");
74+
return singleContent && singleContent.contains(heading);
75+
},
76+
);
77+
78+
if (headings.length === 0) return;
79+
80+
// Smooth scroll when clicking TOC links
81+
tocLinks.forEach((link) => {
82+
link.addEventListener("click", function (e) {
83+
const href = this.getAttribute("href");
84+
if (href && href.startsWith("#")) {
85+
e.preventDefault();
86+
const targetId = href.substring(1);
87+
const targetElement = document.getElementById(targetId);
88+
89+
if (targetElement) {
90+
const headerHeight = 100; // Adjust based on your fixed header height
91+
const targetPosition =
92+
targetElement.getBoundingClientRect().top +
93+
window.pageYOffset -
94+
headerHeight;
95+
96+
window.scrollTo({
97+
top: targetPosition,
98+
behavior: "smooth",
99+
});
100+
}
101+
}
102+
});
103+
});
104+
105+
// Intersection Observer for scroll spy
106+
const observerOptions = {
107+
rootMargin: "-100px 0px -66%",
108+
threshold: 0,
109+
};
110+
111+
let activeHeading = null;
112+
113+
const observer = new IntersectionObserver((entries) => {
114+
entries.forEach((entry) => {
115+
if (entry.isIntersecting) {
116+
const id = entry.target.id;
117+
activeHeading = id;
118+
updateActiveTOCLink(id);
119+
}
120+
});
121+
}, observerOptions);
122+
123+
// Observe all headings
124+
headings.forEach((heading) => observer.observe(heading));
125+
126+
// Update active link styling
127+
function updateActiveTOCLink(activeId) {
128+
// Remove active class from all links
129+
tocLinks.forEach((link) => link.classList.remove("active"));
130+
131+
// Add active class to current link
132+
if (activeId) {
133+
const activeLink = toc.querySelector(`a[href="#${activeId}"]`);
134+
if (activeLink) {
135+
activeLink.classList.add("active");
136+
137+
// Scroll the TOC if needed to keep active link visible
138+
const tocNav = toc.querySelector(".toc-nav");
139+
if (tocNav) {
140+
const linkRect = activeLink.getBoundingClientRect();
141+
const navRect = tocNav.getBoundingClientRect();
142+
143+
if (
144+
linkRect.bottom > navRect.bottom ||
145+
linkRect.top < navRect.top
146+
) {
147+
activeLink.scrollIntoView({
148+
block: "nearest",
149+
behavior: "smooth",
150+
});
151+
}
152+
}
153+
}
154+
}
155+
}
156+
157+
// Handle initial scroll position on page load
158+
function setInitialActiveLink() {
159+
const scrollPosition = window.pageYOffset;
160+
let currentHeading = null;
161+
162+
for (let i = headings.length - 1; i >= 0; i--) {
163+
const heading = headings[i];
164+
if (heading.offsetTop <= scrollPosition + 150) {
165+
currentHeading = heading.id;
166+
break;
167+
}
168+
}
169+
170+
if (currentHeading) {
171+
updateActiveTOCLink(currentHeading);
172+
}
173+
}
174+
175+
// Set initial active state
176+
setInitialActiveLink();
177+
178+
// Fallback: throttled scroll event listener for browsers with poor Intersection Observer support
179+
let activeLinkTimeout;
180+
window.addEventListener(
181+
"scroll",
182+
function () {
183+
if (activeLinkTimeout) {
184+
window.cancelAnimationFrame(activeLinkTimeout);
185+
}
186+
187+
activeLinkTimeout = window.requestAnimationFrame(function () {
188+
setInitialActiveLink();
189+
});
190+
},
191+
{ passive: true },
192+
);
193+
}
194+
})();

config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ markup:
4646
hardWraps: false
4747
unsafe: true
4848
xhtml: false
49+
tableOfContents:
50+
startLevel: 2
51+
endLevel: 2
52+
ordered: false
4953
menu:
5054
main:
5155
- name: Home

content/testimonials/alec-baker.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ position: Principal Solutions Architect, Leadership Analytics Group
44
weight: 1
55
---
66

7-
“We could not have been happier with the AWS platform that Masterpoint spearheaded for one of our largest clients. Their expertise in cloud architecture and automation filled a critical knowledge gap on our team that was vital to the project’s success. <strong> Matt and his team are an indispensable resource for our business.</strong>
7+
"Masterpoint's expertise in cloud architecture and automation filled a critical knowledge gap on our team. <strong>Matt and his team are an indispensable resource for our business.</strong>"

content/testimonials/gunter-pfau.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ position: CEO, Stuzo
55
weight: 3
66
---
77

8-
"<strong>We rarely use outside vendors due to our high quality standards;</strong> however, our team decided to evaluate Masterpoint based on a recommendation from one of our executives. After a stringent vetting process, we engaged a lead technical architect from Masterpoint who came on board and provided considerable value to a critical part of our business within the first month of our engagement. <strong>We are delighted with Masterpoint’s performance over the past year</strong> and highly recommend their team to any company who can benefit from innovative / thoughtful technical solutions delivered in a disciplined manner with strong consultative leadership."
8+
"<strong>We rarely use outside vendors due to our high quality standards,</strong> but Masterpoint passed our stringent vetting process and provided considerable value within the first month. <strong>We are delighted with their performance</strong> and highly recommend their innovative technical solutions and strong consultative leadership."

0 commit comments

Comments
 (0)