This is an automated email from the ASF dual-hosted git repository.

schofielaj pushed a commit to branch markdown
in repository https://gitbox.apache.org/repos/asf/kafka-site.git


The following commit(s) were added to refs/heads/markdown by this push:
     new 1dc448767 Sticky sidebar and toc navbar with scrollspy implementation 
(#754)
1dc448767 is described below

commit 1dc448767e7fb50fa3d43158f98fe5ca3c46bd30
Author: Harish Vishwanath <[email protected]>
AuthorDate: Wed Dec 3 00:19:24 2025 -0800

    Sticky sidebar and toc navbar with scrollspy implementation (#754)
    
    * Adjust powered by page to a closer look and feel of current site
    
    * Update .gitignore to include .history directory
    
    * feat: Implement sticky sidebars and scrollspy for Table of Contents.
    
    * feat: Introduce sticky side and toc navbar and scrollspy to blog section
    
    * content refresh 12/2
    
    * feat: Enhance scrollspy to activate parent TOC links for nested headings 
and update parent container class name.
    
    * feat: Implement custom sidebar toggle logic and adjust sticky sidebar 
styles for mobile responsiveness.
---
 .gitignore                                       |   3 +-
 assets/js/sidebar-toggle.js                      |  49 ++++++++++
 assets/js/sticky-smart.js                        |  25 +++++
 assets/js/toc-scrollspy.js                       |  69 +++++++++++++
 assets/scss/_styles_project.scss                 |   2 +
 assets/scss/_variables_project.scss              |  33 +++++++
 assets/scss/_variables_project_after_bs.scss     | 117 +++++++++++++++++++++++
 content/en/40/streams/developer-guide/dsl-api.md |   4 +-
 content/en/41/streams/developer-guide/dsl-api.md |   4 +-
 content/en/testimonials/_index.md                |  11 +--
 layouts/blog/baseof.html                         |  48 ++++++++++
 layouts/docs/baseof.html                         |  79 ++++++++-------
 layouts/partials/hooks/body-end.html             |  14 +++
 layouts/shortcodes/about/testimonials.html       |  52 +++++++---
 14 files changed, 456 insertions(+), 54 deletions(-)

diff --git a/.gitignore b/.gitignore
index 13eca65d3..b455d3ad0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,7 @@ Thumbs.db
 .vscode/
 *.swp
 *.swo
+.history/
 
 # Environment files
 .env
@@ -45,4 +46,4 @@ postcss.config.js
 # Hugo's temp directories
 .hugo_build.lock
 .cache/
-tmp/ 
\ No newline at end of file
+tmp/ 
diff --git a/assets/js/sidebar-toggle.js b/assets/js/sidebar-toggle.js
new file mode 100644
index 000000000..63f04f1ae
--- /dev/null
+++ b/assets/js/sidebar-toggle.js
@@ -0,0 +1,49 @@
+document.addEventListener('DOMContentLoaded', function () {
+    const sidebarToggles = document.querySelectorAll('.td-sidebar__toggle');
+
+    sidebarToggles.forEach(toggle => {
+        // Remove any existing event listeners by cloning and replacing the 
node
+        // This is a nuclear option to ensure we kill the conflicting listener
+        const newToggle = toggle.cloneNode(true);
+        toggle.parentNode.replaceChild(newToggle, toggle);
+
+        newToggle.addEventListener('click', function (e) {
+            e.preventDefault();
+            e.stopPropagation();
+            e.stopImmediatePropagation();
+
+            console.log('Sidebar toggle clicked (custom handler)');
+
+            // Try to find target ID from various attributes
+            let targetId = this.getAttribute('aria-controls') ||
+                this.getAttribute('data-target') ||
+                this.getAttribute('data-bs-target');
+
+            // Default to standard ID if not found
+            if (!targetId) targetId = 'td-section-nav';
+
+            // Clean up ID selector if present
+            if (targetId.startsWith('#')) {
+                targetId = targetId.substring(1);
+            }
+
+            const targetElement = document.getElementById(targetId);
+            if (targetElement) {
+                // Toggle visibility manually
+                if (targetElement.classList.contains('show')) {
+                    targetElement.classList.remove('show');
+                    targetElement.style.height = '0';
+                    targetElement.style.display = 'none'; // Force hide
+                    this.setAttribute('aria-expanded', 'false');
+                } else {
+                    targetElement.classList.add('show');
+                    targetElement.style.height = 'auto';
+                    targetElement.style.display = 'block'; // Force show
+                    this.setAttribute('aria-expanded', 'true');
+                }
+            } else {
+                console.warn('Sidebar toggle: Target element not found', 
targetId);
+            }
+        });
+    });
+});
diff --git a/assets/js/sticky-smart.js b/assets/js/sticky-smart.js
new file mode 100644
index 000000000..cd374687e
--- /dev/null
+++ b/assets/js/sticky-smart.js
@@ -0,0 +1,25 @@
+document.addEventListener('DOMContentLoaded', function () {
+    const sidebarContent = document.querySelector('.td-sidebar 
.td-sticky-content');
+
+    if (!sidebarContent) return;
+
+    function syncSidebarScroll() {
+        // Only sync if mouse is NOT hovering the sidebar (to avoid fighting 
user)
+        if (sidebarContent.matches(':hover')) return;
+
+        const winScroll = window.scrollY;
+        const winHeight = window.innerHeight;
+        const docHeight = document.documentElement.scrollHeight;
+
+        const scrollPercent = winScroll / (docHeight - winHeight);
+
+        const sidebarHeight = sidebarContent.scrollHeight;
+        const sidebarVisibleHeight = sidebarContent.offsetHeight;
+
+        if (sidebarHeight > sidebarVisibleHeight) {
+            sidebarContent.scrollTop = scrollPercent * (sidebarHeight - 
sidebarVisibleHeight);
+        }
+    }
+
+    window.addEventListener('scroll', syncSidebarScroll);
+});
diff --git a/assets/js/toc-scrollspy.js b/assets/js/toc-scrollspy.js
new file mode 100644
index 000000000..17a26cdc1
--- /dev/null
+++ b/assets/js/toc-scrollspy.js
@@ -0,0 +1,69 @@
+document.addEventListener('DOMContentLoaded', function () {
+    const tocLinks = document.querySelectorAll('.td-toc-content a');
+    const headings = Array.from(document.querySelectorAll('main h2, main h3, 
main h4'));
+
+    if (tocLinks.length === 0 || headings.length === 0) return;
+
+    // Create a map of heading IDs to TOC links for O(1) lookup
+    const linkMap = new Map();
+    tocLinks.forEach(link => {
+        const href = link.getAttribute('href');
+        if (href && href.startsWith('#')) {
+            linkMap.set(href.substring(1), link);
+        }
+    });
+
+    const observerOptions = {
+        root: null,
+        rootMargin: '0px 0px -80% 0px', // Trigger when heading is near the 
top (20% from top)
+        threshold: 0
+    };
+
+    const observer = new IntersectionObserver((entries) => {
+        entries.forEach(entry => {
+            if (entry.isIntersecting) {
+                // Remove active class from all links
+                tocLinks.forEach(link => link.classList.remove('active'));
+
+                // Add active class to the corresponding link
+                const id = entry.target.id;
+                let activeLink = linkMap.get(id);
+
+                // If the current heading isn't in the TOC (e.g. h4/h5), find 
the closest preceding heading that is
+                if (!activeLink) {
+                    const currentIndex = headings.indexOf(entry.target);
+                    if (currentIndex > 0) {
+                        for (let i = currentIndex - 1; i >= 0; i--) {
+                            const prevHeading = headings[i];
+                            if (linkMap.has(prevHeading.id)) {
+                                activeLink = linkMap.get(prevHeading.id);
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                if (activeLink) {
+                    activeLink.classList.add('active');
+
+                    // Scroll active link into view within the sticky container
+                    activeLink.scrollIntoView({ behavior: 'smooth', block: 
'nearest' });
+
+                    // Also highlight parent links if nested
+                    let parent = activeLink.parentElement;
+                    while (parent) {
+                        if (parent.tagName === 'LI') {
+                            // Check if this LI has a direct link child
+                            const parentLink = parent.querySelector(':scope > 
a');
+                            if (parentLink) parentLink.classList.add('active');
+                        }
+                        if (parent.classList.contains('td-toc-content')) break;
+                        parent = parent.parentElement;
+                    }
+                }
+            }
+        });
+    }, observerOptions);
+
+    headings.forEach(heading => observer.observe(heading));
+});
diff --git a/assets/scss/_styles_project.scss b/assets/scss/_styles_project.scss
new file mode 100644
index 000000000..5537e1431
--- /dev/null
+++ b/assets/scss/_styles_project.scss
@@ -0,0 +1,2 @@
+/* Import custom styles after Bootstrap */
+@import "variables_project_after_bs";
\ No newline at end of file
diff --git a/assets/scss/_variables_project.scss 
b/assets/scss/_variables_project.scss
index 6655aa8b2..5fa418c2a 100644
--- a/assets/scss/_variables_project.scss
+++ b/assets/scss/_variables_project.scss
@@ -38,6 +38,9 @@ $primary: #ffffff;
   padding-top: 2rem;
 }
 
+// Align testimonials section with cover block content padding on testimonials 
page
+// The testimonials shortcode now wraps content in a .container to match cover 
block structure
+
 
 .c-testimonials__testimonial {
   text-align: center;
@@ -832,6 +835,36 @@ html {
   }
 }
 
+// Left-align cover block content for testimonials page
+body.testimonials-page {
+  .td-cover-block {
+    .display-1,
+    .display-2,
+    .display-3,
+    .display-4,
+    .display-5,
+    .display-6,
+    h1 {
+      text-align: left !important;
+    }
+
+    .lead,
+    p {
+      text-align: left !important;
+    }
+
+    .td-overlay__inner {
+      text-align: left !important;
+    }
+
+    // Ensure content container is left-aligned
+    .container,
+    .container-fluid {
+      text-align: left;
+    }
+  }
+}
+
 // Homepage use case cards - reduce size and spacing
 .td-box.td-box--primary {
   .col-lg-4 {
diff --git a/assets/scss/_variables_project_after_bs.scss 
b/assets/scss/_variables_project_after_bs.scss
index 5dd26c307..329565177 100644
--- a/assets/scss/_variables_project_after_bs.scss
+++ b/assets/scss/_variables_project_after_bs.scss
@@ -296,4 +296,121 @@ table {
     border-color: #dee2e6; // Lighter border
     padding: 0.75rem; // Ensure decent padding
   }
+}
+
+/* Sticky Sidebar and TOC */
+.td-sidebar,
+.td-sidebar-toc {
+  align-self: stretch;
+  /* Ensure parent has no overflow hidden */
+  overflow: visible !important;
+  height: auto !important;
+}
+
+/* TOC Scrollspy Active State */
+.td-toc-content {
+  a {
+    transition: all 0.2s ease-in-out;
+    border-left: 2px solid transparent;
+    padding-left: 0.5rem;
+    text-decoration: none;
+    display: block;
+    color: inherit;
+    opacity: 0.8;
+    margin-bottom: 0.2rem;
+
+    &:hover {
+      opacity: 1;
+      color: var(--bs-primary);
+    }
+
+    &.active {
+      font-weight: 700;
+      color: var(--bs-primary);
+      border-left: 3px solid var(--bs-primary);
+      opacity: 1;
+      background: linear-gradient(90deg, rgba(var(--bs-primary-rgb), 0.1) 0%, 
rgba(255, 255, 255, 0) 100%);
+    }
+  }
+
+  /* Indent nested items */
+  ul {
+    padding-left: 1rem;
+    list-style: none;
+  }
+}
+
+/* Sticky Sidebar and TOC - Desktop Only */
+@media (min-width: 768px) {
+  .td-sticky-container {
+    position: -webkit-sticky;
+    position: sticky;
+    top: 4rem;
+    height: calc(100vh - 4rem);
+    display: flex;
+    flex-direction: column;
+    z-index: 10;
+  }
+
+  .td-sticky-header {
+    flex: 0 0 auto;
+    background-color: #ffffff;
+    padding-bottom: 1rem;
+    z-index: 11;
+  }
+
+  .td-sticky-content {
+    flex: 1 1 auto;
+    overflow-y: auto;
+    padding-bottom: 2rem;
+    /* Hide scrollbar for cleaner look but keep functionality */
+    scrollbar-width: thin;
+  }
+}
+
+.td-sticky-wrapper {
+  /* Legacy wrapper if used anywhere else, or keep for safety */
+  position: -webkit-sticky;
+  position: sticky;
+  top: var(--sticky-top, 4rem);
+  z-index: 10;
+  padding-bottom: 2rem;
+  width: 100%;
+  max-height: none;
+  overflow-y: visible;
+}
+
+/* Ensure mobile menu is visible when expanded */
+.td-sidebar-nav.collapse.show {
+  display: block !important;
+  height: auto !important;
+}
+
+/* Fix for sticky positioning: Ensure no ancestor has overflow: hidden */
+html,
+body,
+.td-outer,
+.td-main,
+.row {
+  overflow: visible !important;
+  overflow-x: visible !important;
+  overflow-y: visible !important;
+}
+
+/* Restore overflow-x hidden on html/body if needed for mobile, but be careful 
*/
+@media (max-width: 767.98px) {
+
+  html,
+  body {
+    overflow-x: hidden !important;
+  }
+}
+
+/* Fix for internal sidebar scrolling: Override theme styles that might trap 
scroll */
+#td-sidebar-menu,
+.td-sidebar__inner,
+.td-sidebar-nav {
+  max-height: none !important;
+  overflow-y: visible !important;
+  height: auto !important;
 }
\ No newline at end of file
diff --git a/content/en/40/streams/developer-guide/dsl-api.md 
b/content/en/40/streams/developer-guide/dsl-api.md
index f16983851..448ef93a6 100644
--- a/content/en/40/streams/developer-guide/dsl-api.md
+++ b/content/en/40/streams/developer-guide/dsl-api.md
@@ -1911,7 +1911,7 @@ The following deprecated methods are no longer available 
in Kafka Streams:
 
 The Processor API now serves as a unified replacement for all these methods. 
It simplifies the API surface while maintaining support for both stateless and 
stateful operations.
 
-**CAUTION:** If you are using `KStream.transformValues()` and you have the 
"merge repartition topics" optimization enabled, rewriting your program to 
`KStream.processValues()` might not be safe due to 
[KAFKA-19668](https://issues.apache.org/jira/browse/KAFKA-19668). For this 
case, you should not upgrade to Kafka Streams 4.0.0 or 4.1.0, but use Kafka 
Streams 4.0.1 instead, which contains a fix. Note, that the fix is not enabled 
by default for backward compatibility reasons, and you would  [...]
+**CAUTION:** If you are using `KStream.transformValues()` or 
`KStream.flatTransformValues()` and you have the "merge repartition topics" 
optimization enabled, rewriting your program to `KStream.processValues()` might 
not be safe due to 
[KAFKA-19668](https://issues.apache.org/jira/browse/KAFKA-19668). For this 
case, you should not upgrade to Kafka Streams 4.0.0 or 4.1.0, but use Kafka 
Streams 4.0.1 or 4.1.1 instead, which contain a fix. Note, that the fix is not 
enabled by default for bac [...]
     
     
     final Properties properties = new Properties();
@@ -2097,6 +2097,8 @@ Below, methods `replaceWithFlatTransformValues` and 
`replaceWithProcessValues` s
     }
 
 #### Cumulative Discounts for a Loyalty Program
+
+Below, methods `applyDiscountWithTransform` and `applyDiscountWithProcess` 
show how you can migrate from `transform` to `process`.
     
     
     public class CumulativeDiscountsForALoyaltyProgramExample {
diff --git a/content/en/41/streams/developer-guide/dsl-api.md 
b/content/en/41/streams/developer-guide/dsl-api.md
index c90d9b296..4d08c4fba 100644
--- a/content/en/41/streams/developer-guide/dsl-api.md
+++ b/content/en/41/streams/developer-guide/dsl-api.md
@@ -1911,7 +1911,7 @@ The following deprecated methods are no longer available 
in Kafka Streams:
 
 The Processor API now serves as a unified replacement for all these methods. 
It simplifies the API surface while maintaining support for both stateless and 
stateful operations.
 
-**CAUTION:** If you are using `KStream.transformValues()` and you have the 
"merge repartition topics" optimization enabled, rewriting your program to 
`KStream.processValues()` might not be safe due to 
[KAFKA-19668](https://issues.apache.org/jira/browse/KAFKA-19668). For this 
case, you should not upgrade to Kafka Streams 4.0.0 or 4.1.0, but use Kafka 
Streams 4.0.1 instead, which contains a fix. Note, that the fix is not enabled 
by default for backward compatibility reasons, and you would  [...]
+**CAUTION:** If you are using `KStream.transformValues()` or 
`KStream.flatTransformValues()` and you have the "merge repartition topics" 
optimization enabled, rewriting your program to `KStream.processValues()` might 
not be safe due to 
[KAFKA-19668](https://issues.apache.org/jira/browse/KAFKA-19668). For this 
case, you should not upgrade to Kafka Streams 4.0.0 or 4.1.0, but use Kafka 
Streams 4.0.1 or 4.1.1 instead, which contain a fix. Note, that the fix is not 
enabled by default for bac [...]
     
     
     final Properties properties = new Properties();
@@ -2097,6 +2097,8 @@ Below, methods `replaceWithFlatTransformValues` and 
`replaceWithProcessValues` s
     }
 
 #### Cumulative Discounts for a Loyalty Program
+
+Below, methods `applyDiscountWithTransform` and `applyDiscountWithProcess` 
show how you can migrate from `transform` to `process`.
     
     
     public class CumulativeDiscountsForALoyaltyProgramExample {
diff --git a/content/en/testimonials/_index.md 
b/content/en/testimonials/_index.md
index 036f41e0a..a29e32d05 100644
--- a/content/en/testimonials/_index.md
+++ b/content/en/testimonials/_index.md
@@ -1,17 +1,14 @@
 ---
 title: Powered By
+body_class: testimonials-page
 ---
 
+{{% blocks/cover title="Powered By" image_anchor="bottom" height="auto" 
align="left" %}}
 
-{{% blocks/cover title="Powered By" image_anchor="bottom" height="auto" %}}
-
-Apache Kafka is the most popular open-source stream-processing software for 
collecting, processing, storing, and analyzing data at scale. Most known for 
its excellent performance, low latency, fault tolerance, and high throughput, 
it's capable of handling thousands of messages per second. With over 1,000 
Kafka use cases and counting, some common benefits are building data pipelines, 
leveraging real-time data streams, enabling operational metrics, and data 
integration across countless sources. 
-
-Today, Kafka is used by thousands of companies including over 80% of the 
Fortune 100. Among these are Box, Goldman Sachs, Target, Cisco, Intuit, and 
more. As the trusted tool for empowering and innovating companies, Kafka allows 
organizations to modernize their data strategies with event streaming 
architecture. Learn how Kafka is used by organizations in every industry - from 
computer software, financial services, and health care, to government and 
transportation. 
+Apache Kafka is the most popular open-source stream-processing software for 
collecting, processing, storing, and analyzing data at scale. Most known for 
its excellent performance, low latency, fault tolerance, and high throughput, 
it's capable of handling thousands of messages per second. With over 1,000 
Kafka use cases and counting, some common benefits are building data pipelines, 
leveraging real-time data streams, enabling operational metrics, and data 
integration across countless sources.
 
+Today, Kafka is used by thousands of companies including over 80% of the 
Fortune 100. Among these are Box, Goldman Sachs, Target, Cisco, Intuit, and 
more. As the trusted tool for empowering and innovating companies, Kafka allows 
organizations to modernize their data strategies with event streaming 
architecture. Learn how Kafka is used by organizations in every industry - from 
computer software, financial services, and health care, to government and 
transportation.
 
 {{% /blocks/cover %}}
 
-
 {{< about/testimonials >}}
-
diff --git a/layouts/blog/baseof.html b/layouts/blog/baseof.html
new file mode 100644
index 000000000..fc9959b6e
--- /dev/null
+++ b/layouts/blog/baseof.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<html itemscope itemtype="http://schema.org/WebPage"; {{- with 
.Site.Language.LanguageDirection }} dir="{{ . }}" {{- end
+    -}} {{ with .Site.Language.Lang }} lang="{{ . }}" {{- end }} {{/**/ -}} 
class="no-js">
+
+<head>
+    {{ partial "head.html" . }}
+</head>
+
+<body class="td-{{ .Kind }}{{ with .Page.Params.body_class }} {{ . }}{{ end 
}}">
+    <header>
+        {{ partial "navbar.html" . }}
+    </header>
+    <div class="container-fluid td-outer">
+        <div class="td-main">
+            <div class="row flex-xl-nowrap">
+                <aside class="col-12 col-md-3 col-xl-2 td-sidebar 
d-print-none">
+                    <div class="td-sticky-container">
+                        <div class="td-sticky-content">
+                            {{ partial "sidebar.html" . }}
+                        </div>
+                    </div>
+                </aside>
+                <aside class="d-none d-xl-block col-xl-2 td-sidebar-toc 
d-print-none">
+                    <div class="td-sticky-container">
+                        <div class="td-sticky-header">
+                            {{ partial "page-meta-links.html" . }}
+                        </div>
+                        <div class="td-sticky-content">
+                            <div class="td-toc-content">
+                                {{ partial "toc.html" . }}
+                            </div>
+                            {{ partial "taxonomy_terms_clouds.html" . }}
+                        </div>
+                    </div>
+                </aside>
+                <main class="col-12 col-md-9 col-xl-8 ps-md-5" role="main">
+                    {{ if not .Site.Params.ui.breadcrumb_disable }}{{ partial 
"breadcrumb.html" . }}{{ end }}
+                    {{ block "main" . }}{{ end }}
+                </main>
+            </div>
+
+        </div>
+        {{ partial "footer.html" . }}
+    </div>
+    {{ partial "scripts.html" . }}
+</body>
+
+</html>
\ No newline at end of file
diff --git a/layouts/docs/baseof.html b/layouts/docs/baseof.html
index 04d007cee..9f6dcd84a 100644
--- a/layouts/docs/baseof.html
+++ b/layouts/docs/baseof.html
@@ -1,37 +1,50 @@
 <!doctype html>
-<html itemscope itemtype="http://schema.org/WebPage";
-    {{- with .Site.Language.LanguageDirection }} dir="{{ . }}" {{- end -}}
-    {{ with .Site.Language.Lang }} lang="{{ . }}" {{- end }} {{/**/ -}}
-    class="no-js">
-  <head>
-    {{ partial "head.html" . }}
-  </head>
-  <body class="td-{{ .Kind }}{{ with .Page.Params.body_class }} {{ . }}{{ end 
}}">
-    <header>
-      {{ partial "navbar.html" . }}
-    </header>
-    <div class="container-fluid td-outer">
-      <div class="td-main">
-        <div class="row flex-xl-nowrap">
-          <aside class="col-12 col-md-3 col-xl-2 td-sidebar d-print-none">
-            {{ partial "sidebar.html" . }}
-          </aside>
-          <aside class="d-none d-xl-block col-xl-2 td-sidebar-toc 
d-print-none">
-            {{ partial "page-meta-links.html" . }}
-            {{ partial "toc.html" . }}
-            {{ partial "taxonomy_terms_clouds.html" . }}
-          </aside>
-          <main class="col-12 col-md-9 col-xl-8 ps-md-5" role="main">
-            {{ partial "version-banner.html" . }}
-            {{ if not .Site.Params.ui.breadcrumb_disable }}{{ partial 
"breadcrumb.html" . }}{{ end }}
-            {{ block "main" . }}{{ end }}
-            {{ partial "pager.html" . }}
-          </main>
-        </div>
-        
+<html itemscope itemtype="http://schema.org/WebPage"; {{- with 
.Site.Language.LanguageDirection }} dir="{{ . }}" {{- end
+  -}} {{ with .Site.Language.Lang }} lang="{{ . }}" {{- end }} {{/**/ -}} 
class="no-js">
+
+<head>
+  {{ partial "head.html" . }}
+</head>
+
+<body class="td-{{ .Kind }}{{ with .Page.Params.body_class }} {{ . }}{{ end 
}}">
+  <header>
+    {{ partial "navbar.html" . }}
+  </header>
+  <div class="container-fluid td-outer">
+    <div class="td-main">
+      <div class="row flex-xl-nowrap">
+        <aside class="col-12 col-md-3 col-xl-2 td-sidebar d-print-none">
+          <div class="td-sticky-container">
+            <div class="td-sticky-content">
+              {{ partial "sidebar.html" . }}
+            </div>
+          </div>
+        </aside>
+        <aside class="d-none d-xl-block col-xl-2 td-sidebar-toc d-print-none">
+          <div class="td-sticky-container">
+            <div class="td-sticky-header">
+              {{ partial "page-meta-links.html" . }}
+            </div>
+            <div class="td-sticky-content">
+              <div class="td-toc-content">
+                {{ partial "toc.html" . }}
+              </div>
+              {{ partial "taxonomy_terms_clouds.html" . }}
+            </div>
+          </div>
+        </aside>
+        <main class="col-12 col-md-9 col-xl-8 ps-md-5" role="main">
+          {{ partial "version-banner.html" . }}
+          {{ if not .Site.Params.ui.breadcrumb_disable }}{{ partial 
"breadcrumb.html" . }}{{ end }}
+          {{ block "main" . }}{{ end }}
+          {{ partial "pager.html" . }}
+        </main>
       </div>
-      {{ partial "footer.html" . }}
+
     </div>
-    {{ partial "scripts.html" . }}
-  </body>
+    {{ partial "footer.html" . }}
+  </div>
+  {{ partial "scripts.html" . }}
+</body>
+
 </html>
\ No newline at end of file
diff --git a/layouts/partials/hooks/body-end.html 
b/layouts/partials/hooks/body-end.html
index eac50ad3e..ad3c4546f 100644
--- a/layouts/partials/hooks/body-end.html
+++ b/layouts/partials/hooks/body-end.html
@@ -6,3 +6,17 @@
 <!-- Carousel YouTube video control - pauses videos when carousel slides 
change -->
 <script src="/js/carousel-youtube.js"></script>
 
+{{ $stickySmart := resources.Get "js/sticky-smart.js" }}
+{{ if $stickySmart }}
+<script src="{{ $stickySmart.Permalink }}"></script>
+{{ end }}
+
+{{ $sidebarToggle := resources.Get "js/sidebar-toggle.js" }}
+{{ if $sidebarToggle }}
+<script src="{{ $sidebarToggle.Permalink }}"></script>
+{{ end }}
+
+{{ $tocScrollspy := resources.Get "js/toc-scrollspy.js" }}
+{{ if $tocScrollspy }}
+<script src="{{ $tocScrollspy.Permalink }}"></script>
+{{ end }}
\ No newline at end of file
diff --git a/layouts/shortcodes/about/testimonials.html 
b/layouts/shortcodes/about/testimonials.html
index bbe8f5771..636d7f18f 100644
--- a/layouts/shortcodes/about/testimonials.html
+++ b/layouts/shortcodes/about/testimonials.html
@@ -1,15 +1,45 @@
 {{ $testimonials := site.Data.testimonials -}}
-<div class="row c-testimonials">
+{{/* Extract and sort testimonials by company name from domain */}}
+{{ $sortedTestimonials := slice }}
 {{- range $testimonials -}}
-  {{ $img   := printf "/images/powered-by/%s" .logo | relURL -}}
-  {{ $bgColor := .logoBgColor | default "#FFFFFF" -}}
-  <div class="col-12 col-sm-6 col-md-4 col-lg-3 c-testimonials__testimonial">
-    <a href="{{ .link }}" target="_blank" rel="noopener">
-      <span class="grid__item__logo" style="background-image: url({{ $img }}); 
background-color: {{ $bgColor }};"></span>
-    </a>
-    <p class="c-testimonials__description">
-      {{- .description | safeHTML -}}
-    </p>
+  {{/* Extract domain from link URL */}}
+  {{ $link := .link }}
+  {{/* Remove protocol (http://, https://) */}}
+  {{ $domain := replaceRE "^https?://" "" $link }}
+  {{/* Remove www. prefix */}}
+  {{ $domain = replaceRE "^www\\." "" $domain }}
+  {{/* Remove path (everything after first /) */}}
+  {{ $domain = replaceRE "/.*$" "" $domain }}
+  {{/* Remove trailing slash if any */}}
+  {{ $domain = strings.TrimSuffix "/" $domain }}
+  {{/* Extract company name (first part of domain before first dot, or full 
domain if no dots) */}}
+  {{ $domainParts := split $domain "." }}
+  {{ $companyName := index $domainParts 0 }}
+  {{/* If domain has multiple parts, use the main domain (second-to-last part 
for .com, .org, etc.) */}}
+  {{ $domainPartsCount := len $domainParts }}
+  {{ if gt $domainPartsCount 2 }}
+    {{ $companyName = index $domainParts (sub $domainPartsCount 2) }}
+  {{ end }}
+  {{/* Create a map with company name (lowercase for case-insensitive sorting) 
and original data */}}
+  {{ $sortedTestimonials = $sortedTestimonials | append (dict "company" (lower 
$companyName) "data" .) }}
+{{- end -}}
+{{/* Sort by company name alphabetically */}}
+{{ $sortedTestimonials = sort $sortedTestimonials "company" "asc" }}
+
+<div class="container">
+  <div class="row c-testimonials">
+  {{- range $sortedTestimonials -}}
+    {{ $testimonial := .data }}
+    {{ $img   := printf "/images/powered-by/%s" $testimonial.logo | relURL -}}
+    {{ $bgColor := $testimonial.logoBgColor | default "#FFFFFF" -}}
+    <div class="col-12 col-sm-6 col-md-4 col-lg-3 c-testimonials__testimonial">
+      <a href="{{ $testimonial.link }}" target="_blank" rel="noopener">
+        <span class="grid__item__logo" style="background-image: url({{ $img 
}}); background-color: {{ $bgColor }};"></span>
+      </a>
+      <p class="c-testimonials__description">
+        {{- $testimonial.description | safeHTML -}}
+      </p>
+    </div>
+  {{ end -}}
   </div>
-{{ end -}}
 </div>
\ No newline at end of file

Reply via email to