From dec3b8a71472053465b142a5b93a671741dfc4fb Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 27 Feb 2026 09:12:44 -0500 Subject: [PATCH 01/26] WIP --- static/css/v3/post-cards.css | 127 ++++++++++++++++++ static/css/v3/v3-examples-section.css | 21 +++ static/js/carousel.js | 88 ++++++++++++ templates/base.html | 3 + .../v3/examples/_v3_example_section.html | 25 ++++ templates/v3/includes/_content_card_v3.html | 19 +++ 6 files changed, 283 insertions(+) create mode 100644 static/js/carousel.js create mode 100644 templates/v3/includes/_content_card_v3.html diff --git a/static/css/v3/post-cards.css b/static/css/v3/post-cards.css index fab7120b..a8123942 100644 --- a/static/css/v3/post-cards.css +++ b/static/css/v3/post-cards.css @@ -251,3 +251,130 @@ border-color: var(--color-secondary-dark-blue); color: var(--color-secondary-dark-blue); } + +/* Content / detail cards (title + icon + description + optional CTA) */ +.post-cards--content .post-cards__list { + gap: 0; +} + +.post-cards--content-list .post-cards__item { + border-top: 1px solid var(--color-stroke-weak); + border-bottom: none; + border-left: none; + border-right: none; +} + +.post-cards--content-list .post-cards__item:last-child { + border-bottom: 1px solid var(--color-stroke-weak); +} + +.post-cards--content-list .content-card { + background: transparent; + border-radius: 0; + border: none; +} + +.post-cards--content-card .post-cards__item { + border: none; + padding: 0; +} + +.post-cards--content-card .post-cards__list { + gap: var(--space-card); +} + +.post-cards--content-card .content-card { + background: var(--color-surface-weak); + border: 1px solid var(--color-stroke-weak); + border-radius: var(--border-radius-l); +} + +.post-cards--content-card.post-cards--horizontal .post-cards__item { + border-right: none; +} + +.post-cards--content-card.post-cards--horizontal .post-cards__item:last-child { + border-right: none; +} + +.content-card { + display: flex; + flex-direction: column; + gap: var(--space-medium); + padding: var(--space-card); + min-width: 300px; + font-family: var(--font-sans); + color: var(--color-text-primary); +} + +.content-card__title { + margin: 0; + font-family: var(--font-sans); + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + line-height: 1.2; + letter-spacing: -0.16px; + color: var(--color-text-primary); +} + +.content-card__description { + margin: 0; + font-family: var(--font-sans); + font-weight: var(--font-weight-regular); + color: var(--color-text-secondary); +} + +.content-card__title-row { + display: flex; + align-items: center; + gap: var(--space-default); + width: 100%; +} + +.content-card--default-icon .content-card__title { + flex: 1 1 0; + min-width: 0; +} + +.content-card--default-icon .content-card__description { + font-size: var(--font-size-small); + line-height: 1.24; + letter-spacing: -0.14px; +} + +.content-card__body { + display: flex; + flex-direction: column; + gap: var(--space-medium); + width: 100%; +} + +.content-card__icon { + width: 24px; + height: 24px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--color-icon-primary); +} + +.content-card__icon svg { + width: 24px; + height: 24px; +} + +.content-card__cta { + font-family: var(--font-sans); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + line-height: 1; + letter-spacing: -0.12px; + color: var(--color-text-primary); + text-decoration: underline; + text-decoration-skip-ink: none; +} + +.content-card__cta:hover { + text-decoration: underline; +} diff --git a/static/css/v3/v3-examples-section.css b/static/css/v3/v3-examples-section.css index c220ea12..b9b99219 100644 --- a/static/css/v3/v3-examples-section.css +++ b/static/css/v3/v3-examples-section.css @@ -114,3 +114,24 @@ border-color: var(--color-surface-brand-accent-hovered); color: var(--color-text-reversed); } + +/* Post card carousel: hide scrollbar, layout wrapper */ +.post-cards-carousel { + display: flex; + flex-direction: column; + gap: var(--space-default, 8px); + min-width: 0; +} + +.post-cards-carousel .carousel-buttons { + flex-shrink: 0; +} + +.post-cards--carousel .post-cards__list { + scrollbar-width: none; + -webkit-overflow-scrolling: touch; +} + +.post-cards--carousel .post-cards__list::-webkit-scrollbar { + display: none; +} diff --git a/static/js/carousel.js b/static/js/carousel.js new file mode 100644 index 00000000..b909dd3b --- /dev/null +++ b/static/js/carousel.js @@ -0,0 +1,88 @@ +/** + * Carousel: scrolls a track by one step (e.g. one card) on prev/next button click. + * Optional autoplay via data-carousel-autoplay (delay in ms). + * + * DOM: [data-carousel] root with id; inside: [data-carousel-track] (scrollable), and + * controls with id "{root.id}-controls" containing [data-carousel-prev] / [data-carousel-next]. + */ +(function () { + 'use strict'; + + function getStepPx(track) { + var first = track.querySelector('.post-cards__item'); + if (first) { + var style = window.getComputedStyle(first); + var width = first.offsetWidth; + var marginRight = parseFloat(style.marginRight) || 0; + return width + marginRight; + } + return 320; + } + + function scrollCarousel(track, direction, smooth) { + if (!track) return; + var step = getStepPx(track); + var maxScroll = track.scrollWidth - track.clientWidth; + var current = track.scrollLeft; + var next = direction === 'next' + ? Math.min(current + step, maxScroll) + : Math.max(current - step, 0); + track.scrollTo({ left: next, behavior: smooth ? 'smooth' : 'auto' }); + } + + function initCarousel(root) { + if (!root || !root.id) return; + var track = root.querySelector('[data-carousel-track]'); + var controls = document.getElementById(root.id + '-controls'); + if (!track || !controls) return; + + var prevBtn = controls.querySelector('[data-carousel-prev]'); + var nextBtn = controls.querySelector('[data-carousel-next]'); + if (prevBtn) { + prevBtn.addEventListener('click', function () { scrollCarousel(track, 'prev', true); }); + } + if (nextBtn) { + nextBtn.addEventListener('click', function () { scrollCarousel(track, 'next', true); }); + } + + var autoplayDelay = root.getAttribute('data-carousel-autoplay'); + if (autoplayDelay) { + var delayMs = parseInt(autoplayDelay, 10) || 4000; + var timer = null; + + function startAutoplay() { + timer = setInterval(function () { + var maxScroll = track.scrollWidth - track.clientWidth; + if (track.scrollLeft >= maxScroll - 1) { + track.scrollTo({ left: 0, behavior: 'smooth' }); + } else { + scrollCarousel(track, 'next', true); + } + }, delayMs); + } + + function stopAutoplay() { + if (timer) clearInterval(timer); + timer = null; + } + + root.addEventListener('mouseenter', stopAutoplay); + root.addEventListener('mouseleave', startAutoplay); + root.addEventListener('focusin', stopAutoplay); + root.addEventListener('focusout', function (e) { + if (!root.contains(e.relatedTarget)) startAutoplay(); + }); + startAutoplay(); + } + } + + function init() { + document.querySelectorAll('[data-carousel]').forEach(initCarousel); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/templates/base.html b/templates/base.html index 7a39435a..ce18d6e2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -408,6 +408,9 @@ {% endif %} + {% flag "v3" %} + + {% endflag %} {% block footer_js %}{% endblock %} diff --git a/templates/v3/examples/_v3_example_section.html b/templates/v3/examples/_v3_example_section.html index 284de967..bf48a381 100644 --- a/templates/v3/examples/_v3_example_section.html +++ b/templates/v3/examples/_v3_example_section.html @@ -69,6 +69,31 @@

+
+

Detail card carousel

+
+
+
+
    +
  • + {% include "v3/includes/_content_card_v3.html" with card_title="Get help" card_description="Tap into quick answers, networking, and chat with 24,000+ members." card_icon_name="info-box" card_cta_label="Start here" card_cta_href="#" %} +
  • +
  • + {% include "v3/includes/_content_card_v3.html" with card_title="Documentation" card_description="Browse library docs, examples, and release notes in one place." card_icon_name="link" card_cta_label="View docs" card_cta_href="#" %} +
  • +
  • + {% include "v3/includes/_content_card_v3.html" with card_title="Community" card_description="Mailing lists, GitHub, and community guidelines for contributors." card_icon_name="human" card_cta_label="Join" card_cta_href="#" %} +
  • +
  • + {% include "v3/includes/_content_card_v3.html" with card_title="Releases" card_description="Latest releases, download links, and release notes." card_icon_name="info-box" card_cta_label="Download" card_cta_href="#" %} +
  • +
+
+ {% include "v3/includes/_carousel_buttons.html" with carousel_id="post-cards-carousel" %} +
+
+
+

Form inputs

diff --git a/templates/v3/includes/_content_card_v3.html b/templates/v3/includes/_content_card_v3.html new file mode 100644 index 00000000..868e6aec --- /dev/null +++ b/templates/v3/includes/_content_card_v3.html @@ -0,0 +1,19 @@ +{% comment %} + Detail card: title, optional icon, description, optional CTA. + Variables: card_title, card_description, card_icon_name (optional), card_cta_label (optional), card_cta_href (optional). + Usage: {% include "v3/includes/_content_card_v3.html" with card_title="Get help" card_description="..." card_icon_name="info-box" card_cta_label="Start here" card_cta_href="#" %} +{% endcomment %} +
+
+
+

{{ card_title }}

+ {% if card_icon_name %} + {% include "includes/icon.html" with icon_name=card_icon_name icon_class="content-card__icon" icon_size=24 %} + {% endif %} +
+

{{ card_description }}

+
+ {% if card_cta_label and card_cta_href %} + {{ card_cta_label }} + {% endif %} +
From aa5d85e9ebfc63fe0dd0e07da061040ec49bffbf Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 27 Feb 2026 10:27:49 -0500 Subject: [PATCH 02/26] fix with v3 --- static/css/v3/content.css | 15 +++ static/css/v3/post-cards.css | 105 +----------------- .../v3/examples/_v3_example_section.html | 49 ++------ templates/v3/includes/_content_card_v3.html | 19 ---- .../includes/_content_detail_card_item.html | 6 + 5 files changed, 30 insertions(+), 164 deletions(-) delete mode 100644 templates/v3/includes/_content_card_v3.html diff --git a/static/css/v3/content.css b/static/css/v3/content.css index 5cba3a96..388ef0be 100644 --- a/static/css/v3/content.css +++ b/static/css/v3/content.css @@ -94,6 +94,21 @@ padding: 0; } +.content-detail-icon__cta { + font-family: var(--font-sans); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + line-height: 1; + letter-spacing: -0.12px; + color: var(--color-text-primary); + text-decoration: underline; + text-decoration-skip-ink: none; +} + +.content-detail-icon__cta:hover { + text-decoration: underline; +} + .content-detail-icon--contained { background: transparent; border: none; diff --git a/static/css/v3/post-cards.css b/static/css/v3/post-cards.css index a8123942..994ba6e4 100644 --- a/static/css/v3/post-cards.css +++ b/static/css/v3/post-cards.css @@ -257,23 +257,6 @@ gap: 0; } -.post-cards--content-list .post-cards__item { - border-top: 1px solid var(--color-stroke-weak); - border-bottom: none; - border-left: none; - border-right: none; -} - -.post-cards--content-list .post-cards__item:last-child { - border-bottom: 1px solid var(--color-stroke-weak); -} - -.post-cards--content-list .content-card { - background: transparent; - border-radius: 0; - border: none; -} - .post-cards--content-card .post-cards__item { border: none; padding: 0; @@ -283,10 +266,8 @@ gap: var(--space-card); } -.post-cards--content-card .content-card { - background: var(--color-surface-weak); - border: 1px solid var(--color-stroke-weak); - border-radius: var(--border-radius-l); +.post-cards--content-card .content-detail-icon { + min-width: 300px; } .post-cards--content-card.post-cards--horizontal .post-cards__item { @@ -296,85 +277,3 @@ .post-cards--content-card.post-cards--horizontal .post-cards__item:last-child { border-right: none; } - -.content-card { - display: flex; - flex-direction: column; - gap: var(--space-medium); - padding: var(--space-card); - min-width: 300px; - font-family: var(--font-sans); - color: var(--color-text-primary); -} - -.content-card__title { - margin: 0; - font-family: var(--font-sans); - font-size: var(--font-size-base); - font-weight: var(--font-weight-medium); - line-height: 1.2; - letter-spacing: -0.16px; - color: var(--color-text-primary); -} - -.content-card__description { - margin: 0; - font-family: var(--font-sans); - font-weight: var(--font-weight-regular); - color: var(--color-text-secondary); -} - -.content-card__title-row { - display: flex; - align-items: center; - gap: var(--space-default); - width: 100%; -} - -.content-card--default-icon .content-card__title { - flex: 1 1 0; - min-width: 0; -} - -.content-card--default-icon .content-card__description { - font-size: var(--font-size-small); - line-height: 1.24; - letter-spacing: -0.14px; -} - -.content-card__body { - display: flex; - flex-direction: column; - gap: var(--space-medium); - width: 100%; -} - -.content-card__icon { - width: 24px; - height: 24px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--color-icon-primary); -} - -.content-card__icon svg { - width: 24px; - height: 24px; -} - -.content-card__cta { - font-family: var(--font-sans); - font-size: var(--font-size-xs); - font-weight: var(--font-weight-medium); - line-height: 1; - letter-spacing: -0.12px; - color: var(--color-text-primary); - text-decoration: underline; - text-decoration-skip-ink: none; -} - -.content-card__cta:hover { - text-decoration: underline; -} diff --git a/templates/v3/examples/_v3_example_section.html b/templates/v3/examples/_v3_example_section.html index 2161cc9a..532d2749 100644 --- a/templates/v3/examples/_v3_example_section.html +++ b/templates/v3/examples/_v3_example_section.html @@ -119,41 +119,6 @@

Carousel buttons

-
-

Post card (standalone)

-
- {% with post_title="A talk by Richard Thomson at the Utah C++ Programmers Group" post_url="#" post_date="03/03/2025" post_category="Issues" post_tag="beast" author_name="Richard Thomson" author_role="Contributor" author_show_badge=True %} - {% include "v3/includes/_post_card_v3.html" %} - {% endwith %} -
-
- -
-

Post cards

-
-
-

- Posts from the Boost community -

-
    -
  • - {% with post_title="A talk by Richard Thomson at the Utah C++ Programmers Group" post_url="#" post_date="03/03/2025" post_category="Issues" post_tag="beast" author_name="Richard Thomson" author_role="Contributor" author_show_badge=True %} - {% include "v3/includes/_post_card_v3.html" %} - {% endwith %} -
  • -
  • - {% with post_title="A talk by Richard Thomson at the Utah C++ Programmers Group" post_url="#" post_date="03/03/2025" post_category="Issues" post_tag="beast" author_name="Peter Dimov" author_role="Maintainer" author_show_badge=True %} - {% include "v3/includes/_post_card_v3.html" %} - {% endwith %} -
  • -
-
- View all posts -
-
-
-
-

Detail card carousel

@@ -161,16 +126,16 @@

Detail card carousel

  • - {% include "v3/includes/_content_card_v3.html" with card_title="Get help" card_description="Tap into quick answers, networking, and chat with 24,000+ members." card_icon_name="info-box" card_cta_label="Start here" card_cta_href="#" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="Tap into quick answers, networking, and chat with 24,000+ members." icon_name="info-box" cta_label="Start here" cta_href="#" %}
  • - {% include "v3/includes/_content_card_v3.html" with card_title="Documentation" card_description="Browse library docs, examples, and release notes in one place." card_icon_name="link" card_cta_label="View docs" card_cta_href="#" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Documentation" description="Browse library docs, examples, and release notes in one place." icon_name="link" cta_label="View docs" cta_href="#" %}
  • - {% include "v3/includes/_content_card_v3.html" with card_title="Community" card_description="Mailing lists, GitHub, and community guidelines for contributors." card_icon_name="human" card_cta_label="Join" card_cta_href="#" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Community" description="Mailing lists, GitHub, and community guidelines for contributors." icon_name="human" cta_label="Join" cta_href="#" %}
  • - {% include "v3/includes/_content_card_v3.html" with card_title="Releases" card_description="Latest releases, download links, and release notes." card_icon_name="info-box" card_cta_label="Download" card_cta_href="#" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Releases" description="Latest releases, download links, and release notes." icon_name="info-box" cta_label="Download" cta_href="#" %}
@@ -207,9 +172,9 @@

Why Boost

Why Boost?

{% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="Tap into quick answers, networking, and chat with 24,000+ members." icon_name="bullseye-arrow" %} - {% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="Tap into quick answers, networking, and chat with 24,000+ members." icon_name="bullseye-arrow" %} - {% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="Tap into quick answers, networking, and chat with 24,000+ members." icon_name="bullseye-arrow" %} - {% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="Tap into quick answers, networking, and chat with 24,000+ members." icon_name="bullseye-arrow" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Documentation" description="Browse library docs, examples, and release notes in one place." icon_name="link" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Community" description="Mailing lists, GitHub, and community guidelines for contributors." icon_name="human" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Releases" description="Latest releases, download links, and release notes." icon_name="info-box" %}
diff --git a/templates/v3/includes/_content_card_v3.html b/templates/v3/includes/_content_card_v3.html deleted file mode 100644 index 868e6aec..00000000 --- a/templates/v3/includes/_content_card_v3.html +++ /dev/null @@ -1,19 +0,0 @@ -{% comment %} - Detail card: title, optional icon, description, optional CTA. - Variables: card_title, card_description, card_icon_name (optional), card_cta_label (optional), card_cta_href (optional). - Usage: {% include "v3/includes/_content_card_v3.html" with card_title="Get help" card_description="..." card_icon_name="info-box" card_cta_label="Start here" card_cta_href="#" %} -{% endcomment %} -
-
-
-

{{ card_title }}

- {% if card_icon_name %} - {% include "includes/icon.html" with icon_name=card_icon_name icon_class="content-card__icon" icon_size=24 %} - {% endif %} -
-

{{ card_description }}

-
- {% if card_cta_label and card_cta_href %} - {{ card_cta_label }} - {% endif %} -
diff --git a/templates/v3/includes/_content_detail_card_item.html b/templates/v3/includes/_content_detail_card_item.html index eb702b32..ef65f5c6 100644 --- a/templates/v3/includes/_content_detail_card_item.html +++ b/templates/v3/includes/_content_detail_card_item.html @@ -6,8 +6,11 @@ icon_name (optional) — icon name for includes/icon.html (e.g. "bullseye-arrow"). If omitted, card is rendered without icon and modifier --has-icon is not applied. title_url (optional) — if set, title is wrapped in a link + cta_label (optional) — if set with cta_href, renders a CTA link below description + cta_href (optional) — used with cta_label for the CTA link Usage: {% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="Tap into quick answers..." icon_name="bullseye-arrow" %} + With CTA: {% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="..." icon_name="info-box" cta_label="Start here" cta_href="#" %} {% endcomment %} From 7db9bc8a73523567b517bb282c50f3e3eb5c410e Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 27 Feb 2026 11:15:39 -0500 Subject: [PATCH 03/26] feat: add cards carousel component with dynamic content and styling updates --- core/views.py | 72 +++++++++++++++ static/css/v3/carousel-buttons.css | 2 +- static/css/v3/post-cards.css | 88 +++++++++++++++++++ static/js/carousel.js | 51 +++++------ .../v3/examples/_v3_example_section.html | 20 +---- templates/v3/includes/_cards_carousel_v3.html | 49 +++++++++++ 6 files changed, 235 insertions(+), 47 deletions(-) create mode 100644 templates/v3/includes/_cards_carousel_v3.html diff --git a/core/views.py b/core/views.py index f6c09b5b..0e04513a 100644 --- a/core/views.py +++ b/core/views.py @@ -1070,4 +1070,76 @@ def get_context_data(self, **kwargs): {"value": "networking", "label": "Networking"}, ] ) + context["cards_carousel_cards"] = [ + { + "title": "Get help", + "description": "Tap into quick answers, networking, and chat with 24,000+ members.", + "icon_name": "info-box", + "cta_label": "Start here", + "cta_href": reverse("community"), + }, + { + "title": "Documentation", + "description": "Browse library docs, examples, and release notes in one place.", + "icon_name": "link", + "cta_label": "View docs", + "cta_href": reverse("docs"), + }, + { + "title": "Community", + "description": "Mailing lists, GitHub, and community guidelines for contributors.", + "icon_name": "human", + "cta_label": "Join", + "cta_href": reverse("community"), + }, + { + "title": "Releases", + "description": "Latest releases, download links, and release notes.", + "icon_name": "info-box", + "cta_label": "Download", + "cta_href": reverse("releases-most-recent"), + }, + { + "title": "Libraries", + "description": "Explore the full catalog of Boost C++ libraries with docs and metadata.", + "icon_name": "link", + "cta_label": "Browse libraries", + "cta_href": reverse("libraries"), + }, + { + "title": "News", + "description": "Blog posts, announcements, and community news from the Boost project.", + "icon_name": "device-tv", + "cta_label": "Read news", + "cta_href": reverse("news"), + }, + { + "title": "Getting started", + "description": "Step-by-step guides to build and use Boost in your projects.", + "icon_name": "bullseye-arrow", + "cta_label": "Get started", + "cta_href": reverse("getting-started"), + }, + { + "title": "Resources", + "description": "Learning resources, books, and other materials for Boost users.", + "icon_name": "get-help", + "cta_label": "View resources", + "cta_href": reverse("resources"), + }, + { + "title": "Calendar", + "description": "Community events, meetings, and review schedule.", + "icon_name": "info-box", + "cta_label": "View calendar", + "cta_href": reverse("calendar"), + }, + { + "title": "Donate", + "description": "Support the Boost Software Foundation and open-source C++.", + "icon_name": "human", + "cta_label": "Donate", + "cta_href": reverse("donate"), + }, + ] return context diff --git a/static/css/v3/carousel-buttons.css b/static/css/v3/carousel-buttons.css index a6fc016e..4ece3c98 100644 --- a/static/css/v3/carousel-buttons.css +++ b/static/css/v3/carousel-buttons.css @@ -12,7 +12,7 @@ width: 44px; height: 24px; border-radius: 8px; - background: var(--color-button-primary, #EFEFF1); + background: var(--color-surface-strong, #EFEFF1); } .carousel-buttons .btn-carousel { diff --git a/static/css/v3/post-cards.css b/static/css/v3/post-cards.css index 994ba6e4..d1246827 100644 --- a/static/css/v3/post-cards.css +++ b/static/css/v3/post-cards.css @@ -277,3 +277,91 @@ .post-cards--content-card.post-cards--horizontal .post-cards__item:last-child { border-right: none; } + + +.cards-carousel { + display: flex; + flex-direction: column; + gap: var(--space-large, 16px); + padding: var(--space-large, 16px) 0; + width: 100%; + max-width: 100%; + min-width: 0; + box-sizing: border-box; + background: var(--color-surface-mid, #f7f7f8); + border: 1px solid var(--color-stroke-weak, rgba(5, 8, 22, 0.1)); + border-radius: var(--border-radius-l, 8px); + overflow: hidden; +} + +.cards-carousel__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 var(--space-large, 16px); + width: 100%; + flex-shrink: 0; +} + +.cards-carousel__heading { + margin: 0; + font-family: var(--font-display); + font-size: var(--font-size-large, 24px); + font-weight: var(--font-weight-medium, 500); + line-height: 1; + letter-spacing: -0.24px; + color: var(--color-text-primary, #050816); + flex: 1 0 0; + min-width: 0; +} + +.cards-carousel__controls { + flex-shrink: 0; +} + +.cards-carousel__divider { + margin: 0; + border: none; + border-top: 1px solid var(--color-stroke-weak, rgba(5, 8, 22, 0.1)); + width: 100%; + height: 0; + flex-shrink: 0; +} + +.cards-carousel__track { + padding: 0 var(--space-large, 16px); + width: 100%; + min-width: 0; + flex-shrink: 0; +} + +.cards-carousel .post-cards { + background: transparent; + border: none; + padding: 0; + max-width: 100%; +} + +.cards-carousel .post-cards--content-card .post-cards__list { + gap: var(--space-large, 16px); + align-items: stretch; +} + +.cards-carousel .post-cards--content-card .post-cards__item { + flex: 0 0 300px; + width: 300px; + min-width: 300px; + display: flex; +} + +.cards-carousel .content-detail-icon { + background: var(--color-surface-weak, #fff); + border: 1px solid var(--color-stroke-weak, rgba(5, 8, 22, 0.1)); + border-radius: var(--border-radius-l, 8px); + padding: var(--space-large, 16px); + gap: var(--space-large, 16px); + width: 100%; + min-width: 0; + align-self: stretch; + min-height: 100%; +} diff --git a/static/js/carousel.js b/static/js/carousel.js index b909dd3b..f673605e 100644 --- a/static/js/carousel.js +++ b/static/js/carousel.js @@ -1,30 +1,27 @@ -/** - * Carousel: scrolls a track by one step (e.g. one card) on prev/next button click. - * Optional autoplay via data-carousel-autoplay (delay in ms). - * - * DOM: [data-carousel] root with id; inside: [data-carousel-track] (scrollable), and - * controls with id "{root.id}-controls" containing [data-carousel-prev] / [data-carousel-next]. - */ + (function () { - 'use strict'; + + const CAROUSEL_STEP_PX_FALLBACK = 320; function getStepPx(track) { - var first = track.querySelector('.post-cards__item'); + const first = track.querySelector('.post-cards__item'); if (first) { - var style = window.getComputedStyle(first); - var width = first.offsetWidth; - var marginRight = parseFloat(style.marginRight) || 0; - return width + marginRight; + const itemStyle = window.getComputedStyle(first); + const width = first.offsetWidth; + const marginRight = parseFloat(itemStyle.marginRight) || 0; + const trackStyle = window.getComputedStyle(track); + const gap = parseFloat(trackStyle.gap) || 0; + return width + marginRight + gap; } - return 320; + return CAROUSEL_STEP_PX_FALLBACK; } function scrollCarousel(track, direction, smooth) { if (!track) return; - var step = getStepPx(track); - var maxScroll = track.scrollWidth - track.clientWidth; - var current = track.scrollLeft; - var next = direction === 'next' + const step = getStepPx(track); + const maxScroll = track.scrollWidth - track.clientWidth; + const current = track.scrollLeft; + const next = direction === 'next' ? Math.min(current + step, maxScroll) : Math.max(current - step, 0); track.scrollTo({ left: next, behavior: smooth ? 'smooth' : 'auto' }); @@ -32,12 +29,12 @@ function initCarousel(root) { if (!root || !root.id) return; - var track = root.querySelector('[data-carousel-track]'); - var controls = document.getElementById(root.id + '-controls'); + const track = root.querySelector('[data-carousel-track]'); + const controls = document.getElementById(root.id + '-controls'); if (!track || !controls) return; - var prevBtn = controls.querySelector('[data-carousel-prev]'); - var nextBtn = controls.querySelector('[data-carousel-next]'); + const prevBtn = controls.querySelector('[data-carousel-prev]'); + const nextBtn = controls.querySelector('[data-carousel-next]'); if (prevBtn) { prevBtn.addEventListener('click', function () { scrollCarousel(track, 'prev', true); }); } @@ -45,12 +42,12 @@ nextBtn.addEventListener('click', function () { scrollCarousel(track, 'next', true); }); } - var autoplayDelay = root.getAttribute('data-carousel-autoplay'); + const autoplayDelay = root.getAttribute('data-carousel-autoplay'); if (autoplayDelay) { - var delayMs = parseInt(autoplayDelay, 10) || 4000; - var timer = null; + const delayMs = parseInt(autoplayDelay, 10) || 4000; + let timer = null; - function startAutoplay() { + const startAutoplay = () => { timer = setInterval(function () { var maxScroll = track.scrollWidth - track.clientWidth; if (track.scrollLeft >= maxScroll - 1) { @@ -61,7 +58,7 @@ }, delayMs); } - function stopAutoplay() { + const stopAutoplay = () => { if (timer) clearInterval(timer); timer = null; } diff --git a/templates/v3/examples/_v3_example_section.html b/templates/v3/examples/_v3_example_section.html index 532d2749..5743d8c1 100644 --- a/templates/v3/examples/_v3_example_section.html +++ b/templates/v3/examples/_v3_example_section.html @@ -122,25 +122,7 @@

Carousel buttons

Detail card carousel

-
-
-
    -
  • - {% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="Tap into quick answers, networking, and chat with 24,000+ members." icon_name="info-box" cta_label="Start here" cta_href="#" %} -
  • -
  • - {% include "v3/includes/_content_detail_card_item.html" with title="Documentation" description="Browse library docs, examples, and release notes in one place." icon_name="link" cta_label="View docs" cta_href="#" %} -
  • -
  • - {% include "v3/includes/_content_detail_card_item.html" with title="Community" description="Mailing lists, GitHub, and community guidelines for contributors." icon_name="human" cta_label="Join" cta_href="#" %} -
  • -
  • - {% include "v3/includes/_content_detail_card_item.html" with title="Releases" description="Latest releases, download links, and release notes." icon_name="info-box" cta_label="Download" cta_href="#" %} -
  • -
-
- {% include "v3/includes/_carousel_buttons.html" with carousel_id="post-cards-carousel" %} -
+ {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="post-cards-carousel" heading="Libraries categories" cards=cards_carousel_cards %}
diff --git a/templates/v3/includes/_cards_carousel_v3.html b/templates/v3/includes/_cards_carousel_v3.html new file mode 100644 index 00000000..80efba40 --- /dev/null +++ b/templates/v3/includes/_cards_carousel_v3.html @@ -0,0 +1,49 @@ +{% comment %} + Cards carousel: container with header (title + controls), divider, horizontal track of content detail cards. + Requires static/css/v3/post-cards.css and carousel-buttons.css; static/js/carousel.js for behaviour. + + Variables: + carousel_id (optional) — id for the carousel root and controls. Default: "cards-carousel" + heading (optional) — section heading. Default: "Libraries categories" + track_aria_label (optional) — aria-label for the track. Default: "Detail cards carousel" + cards (optional) — list of dicts with: title, description, icon_name, cta_label, cta_href. + When omitted, no cards are shown (pass from view for content). + autoplay (optional) — if True, carousel scrolls automatically. Default: False + autoplay_delay (optional) — autoplay interval in milliseconds. Only used when autoplay is True. Default: 4000 + + Usage (default demo): + {% include "v3/includes/_cards_carousel_v3.html" with cards=cards_carousel_cards %} + + Usage (custom heading and id): + {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="libs-carousel" heading="Library categories" %} + + Usage (with autoplay, 5 second interval): + {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="featured" autoplay=True autoplay_delay=5000 %} + + Usage (from view context with cards list): + {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="my-carousel" heading="Featured" cards=my_cards_list %} +{% endcomment %} +{% with carousel_id=carousel_id|default:"cards-carousel" heading=heading|default:"Libraries categories" track_aria_label=track_aria_label|default:"Detail cards carousel" %} + +{% endwith %} From bff0e3802e0f689df870c25cf73a26260a750ee4 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 27 Feb 2026 11:18:55 -0500 Subject: [PATCH 04/26] fix: nit --- static/css/v3/post-cards.css | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/static/css/v3/post-cards.css b/static/css/v3/post-cards.css index d1246827..f1cc8294 100644 --- a/static/css/v3/post-cards.css +++ b/static/css/v3/post-cards.css @@ -25,16 +25,6 @@ border-color: var(--color-stroke-weak); } -.post-cards--teal { - background: var(--color-surface-weak-accent-teal); - border-color: var(--color-accent-strong-teal); -} - -.post-cards--yellow { - background: var(--color-surface-weak-accent-yellow); - border-color: var(--color-accent-strong-yellow); -} - .post-cards__heading { margin: 0; padding: 0 var(--space-card); @@ -274,10 +264,6 @@ border-right: none; } -.post-cards--content-card.post-cards--horizontal .post-cards__item:last-child { - border-right: none; -} - .cards-carousel { display: flex; From 0a66f5039651bf7f8d39aec83af1ab16f7908707 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 27 Feb 2026 11:24:43 -0500 Subject: [PATCH 05/26] fix: more nits --- core/views.py | 2 +- templates/v3/examples/_v3_example_section.html | 2 +- templates/v3/includes/_cards_carousel_v3.html | 7 ++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/core/views.py b/core/views.py index 0e04513a..1cfb91ee 100644 --- a/core/views.py +++ b/core/views.py @@ -1070,7 +1070,7 @@ def get_context_data(self, **kwargs): {"value": "networking", "label": "Networking"}, ] ) - context["cards_carousel_cards"] = [ + context["demo_cards_carousel_cards"] = [ { "title": "Get help", "description": "Tap into quick answers, networking, and chat with 24,000+ members.", diff --git a/templates/v3/examples/_v3_example_section.html b/templates/v3/examples/_v3_example_section.html index 5743d8c1..fc0e6dec 100644 --- a/templates/v3/examples/_v3_example_section.html +++ b/templates/v3/examples/_v3_example_section.html @@ -122,7 +122,7 @@

Carousel buttons

Detail card carousel

- {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="post-cards-carousel" heading="Libraries categories" cards=cards_carousel_cards %} + {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="post-cards-carousel-demo" heading="Libraries categories" cards=demo_cards_carousel_cards %}
diff --git a/templates/v3/includes/_cards_carousel_v3.html b/templates/v3/includes/_cards_carousel_v3.html index 80efba40..3a67cbaa 100644 --- a/templates/v3/includes/_cards_carousel_v3.html +++ b/templates/v3/includes/_cards_carousel_v3.html @@ -3,17 +3,14 @@ Requires static/css/v3/post-cards.css and carousel-buttons.css; static/js/carousel.js for behaviour. Variables: - carousel_id (optional) — id for the carousel root and controls. Default: "cards-carousel" - heading (optional) — section heading. Default: "Libraries categories" + carousel_id (required) — id for the carousel root and controls. + heading (required) — section heading. track_aria_label (optional) — aria-label for the track. Default: "Detail cards carousel" cards (optional) — list of dicts with: title, description, icon_name, cta_label, cta_href. When omitted, no cards are shown (pass from view for content). autoplay (optional) — if True, carousel scrolls automatically. Default: False autoplay_delay (optional) — autoplay interval in milliseconds. Only used when autoplay is True. Default: 4000 - Usage (default demo): - {% include "v3/includes/_cards_carousel_v3.html" with cards=cards_carousel_cards %} - Usage (custom heading and id): {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="libs-carousel" heading="Library categories" %} From 80e98c5cf0920adf4ac2ee75ba2583bff70c5421 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 27 Feb 2026 11:35:44 -0500 Subject: [PATCH 06/26] feat: implement infinite looping and autoplay functionality for carousel component --- static/js/carousel.js | 135 ++++++++++++++---- .../v3/examples/_v3_example_section.html | 14 ++ templates/v3/includes/_cards_carousel_v3.html | 6 +- 3 files changed, 128 insertions(+), 27 deletions(-) diff --git a/static/js/carousel.js b/static/js/carousel.js index f673605e..67f353c6 100644 --- a/static/js/carousel.js +++ b/static/js/carousel.js @@ -2,6 +2,8 @@ (function () { const CAROUSEL_STEP_PX_FALLBACK = 320; + const SCROLL_RESET_EPSILON = 2; + const DEFAULT_AUTOPLAY_MS = 4000; function getStepPx(track) { const first = track.querySelector('.post-cards__item'); @@ -27,12 +29,111 @@ track.scrollTo({ left: next, behavior: smooth ? 'smooth' : 'auto' }); } + function setupAutoplay(root, onTick, delayMs) { + const ms = delayMs || DEFAULT_AUTOPLAY_MS; + let timer = null; + const start = function () { + timer = setInterval(onTick, ms); + }; + const stop = function () { + if (timer) clearInterval(timer); + timer = null; + }; + root.addEventListener('mouseenter', stop); + root.addEventListener('mouseleave', start); + root.addEventListener('focusin', stop); + root.addEventListener('focusout', function (e) { + if (!root.contains(e.relatedTarget)) start(); + }); + start(); + } + + function setupInfiniteCarousel(root, track) { + const items = track.querySelectorAll('.post-cards__item'); + if (items.length === 0) return; + + let setCount = 1; + const appendCloneSet = () => { + const list = track.querySelectorAll('.post-cards__item'); + const n = list.length / setCount; + for (let i = 0; i < n; i++) { + track.appendChild(list[i].cloneNode(true)); + } + setCount++; + }; + + appendCloneSet(); + while (track.scrollWidth / setCount < track.clientWidth) { + appendCloneSet(); + } + + const setWidth = track.scrollWidth / setCount; + const step = getStepPx(track); + let scrollResetInProgress = false; + + track.addEventListener('scroll', function () { + if (scrollResetInProgress) return; + const left = track.scrollLeft; + if (left >= setWidth - SCROLL_RESET_EPSILON) { + scrollResetInProgress = true; + track.scrollLeft = left - setWidth; + requestAnimationFrame(function () { + scrollResetInProgress = false; + }); + } + }); + + const controls = document.getElementById(root.id + '-controls'); + const prevBtn = controls && controls.querySelector('[data-carousel-prev]'); + const nextBtn = controls && controls.querySelector('[data-carousel-next]'); + + if (prevBtn) { + prevBtn.addEventListener('click', function () { + if (track.scrollLeft < step) { + scrollResetInProgress = true; + track.scrollLeft = setWidth; + requestAnimationFrame(function () { + scrollResetInProgress = false; + scrollCarousel(track, 'prev', true); + }); + } else { + scrollCarousel(track, 'prev', true); + } + }); + } + + if (nextBtn) { + nextBtn.addEventListener('click', function () { + scrollCarousel(track, 'next', true); + }); + } + + const autoplayDelay = root.getAttribute('data-carousel-autoplay'); + if (autoplayDelay) { + setupAutoplay(root, () => { + if (track.scrollLeft >= setWidth - SCROLL_RESET_EPSILON) { + scrollResetInProgress = true; + track.scrollLeft = track.scrollLeft - setWidth; + requestAnimationFrame(function () { + scrollResetInProgress = false; + }); + } + scrollCarousel(track, 'next', true); + }, parseInt(autoplayDelay, 10)); + } + } + function initCarousel(root) { if (!root || !root.id) return; const track = root.querySelector('[data-carousel-track]'); const controls = document.getElementById(root.id + '-controls'); if (!track || !controls) return; + if (root.hasAttribute('data-carousel-infinite')) { + setupInfiniteCarousel(root, track); + return; + } + const prevBtn = controls.querySelector('[data-carousel-prev]'); const nextBtn = controls.querySelector('[data-carousel-next]'); if (prevBtn) { @@ -44,32 +145,14 @@ const autoplayDelay = root.getAttribute('data-carousel-autoplay'); if (autoplayDelay) { - const delayMs = parseInt(autoplayDelay, 10) || 4000; - let timer = null; - - const startAutoplay = () => { - timer = setInterval(function () { - var maxScroll = track.scrollWidth - track.clientWidth; - if (track.scrollLeft >= maxScroll - 1) { - track.scrollTo({ left: 0, behavior: 'smooth' }); - } else { - scrollCarousel(track, 'next', true); - } - }, delayMs); - } - - const stopAutoplay = () => { - if (timer) clearInterval(timer); - timer = null; - } - - root.addEventListener('mouseenter', stopAutoplay); - root.addEventListener('mouseleave', startAutoplay); - root.addEventListener('focusin', stopAutoplay); - root.addEventListener('focusout', function (e) { - if (!root.contains(e.relatedTarget)) startAutoplay(); - }); - startAutoplay(); + setupAutoplay(root, () => { + const maxScroll = track.scrollWidth - track.clientWidth; + if (track.scrollLeft >= maxScroll - 1) { + track.scrollTo({ left: 0, behavior: 'smooth' }); + } else { + scrollCarousel(track, 'next', true); + } + }, parseInt(autoplayDelay, 10)); } } diff --git a/templates/v3/examples/_v3_example_section.html b/templates/v3/examples/_v3_example_section.html index fc0e6dec..1c0d6415 100644 --- a/templates/v3/examples/_v3_example_section.html +++ b/templates/v3/examples/_v3_example_section.html @@ -126,6 +126,20 @@

Detail card carousel

+
+

Detail card carousel with autoplay

+
+ {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="post-cards-carousel-demo-autoplay" heading="Libraries categories" cards=demo_cards_carousel_cards autoplay=True autoplay_delay=5000 %} +
+
+ +
+

Detail card carousel with infinite looping and autoplay

+
+ {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="post-cards-carousel-demo-infinite-autoplay" heading="Libraries categories" cards=demo_cards_carousel_cards infinite=True autoplay=True autoplay_delay=5000 %} +
+
+

Search Card

diff --git a/templates/v3/includes/_cards_carousel_v3.html b/templates/v3/includes/_cards_carousel_v3.html index 3a67cbaa..5c7464e4 100644 --- a/templates/v3/includes/_cards_carousel_v3.html +++ b/templates/v3/includes/_cards_carousel_v3.html @@ -8,6 +8,7 @@ track_aria_label (optional) — aria-label for the track. Default: "Detail cards carousel" cards (optional) — list of dicts with: title, description, icon_name, cta_label, cta_href. When omitted, no cards are shown (pass from view for content). + infinite (optional) — if True, carousel loops infinitely. Default: False autoplay (optional) — if True, carousel scrolls automatically. Default: False autoplay_delay (optional) — autoplay interval in milliseconds. Only used when autoplay is True. Default: 4000 @@ -19,9 +20,12 @@ Usage (from view context with cards list): {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="my-carousel" heading="Featured" cards=my_cards_list %} + + Usage (infinite looping carousel): + {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="my-carousel" heading="Featured" cards=my_cards_list infinite=True %} {% endcomment %} {% with carousel_id=carousel_id|default:"cards-carousel" heading=heading|default:"Libraries categories" track_aria_label=track_aria_label|default:"Detail cards carousel" %} - diff --git a/templates/v3/includes/_event_cards_section.html b/templates/v3/includes/_event_cards_section.html index 25b30549..fab83c0d 100644 --- a/templates/v3/includes/_event_cards_section.html +++ b/templates/v3/includes/_event_cards_section.html @@ -13,7 +13,7 @@

{{ secti {% endfor %}
- {% if primary_btn_text %}{{ primary_btn_text }}{% endif %} - {% if secondary_btn_text %}{{ secondary_btn_text }}{% endif %} + {% if primary_btn_text %}{{ primary_btn_text }}{% endif %} + {% if secondary_btn_text %}{{ secondary_btn_text }}{% endif %}
From c88eb15b930b0c3db89a5c98b66e0721d4c0512d Mon Sep 17 00:00:00 2001 From: DrJfrost Date: Sat, 28 Feb 2026 12:17:32 -0500 Subject: [PATCH 10/26] adding data and styles to code block --- core/templatetags/text_helpers.py | 54 +++++++ static/css/v3/cpp-highlight.css | 145 ++++++++++++++++++ static/css/v3/event-cards.css | 1 - static/css/v3/index.css | 1 + static/css/v3/themes.css | 8 + .../js/{code-block-copy.js => code-block.js} | 12 +- templates/base.html | 3 +- templates/v3/includes/_code_block.html | 23 +-- templates/v3/includes/_code_block_card.html | 3 +- templates/v3/includes/_code_blocks_story.html | 30 ++-- 10 files changed, 252 insertions(+), 28 deletions(-) create mode 100644 static/css/v3/cpp-highlight.css rename static/js/{code-block-copy.js => code-block.js} (87%) diff --git a/core/templatetags/text_helpers.py b/core/templatetags/text_helpers.py index b377a34a..daff1ba9 100644 --- a/core/templatetags/text_helpers.py +++ b/core/templatetags/text_helpers.py @@ -83,3 +83,57 @@ def strip_query_string(url): parsed = urlparse(url) clean = parsed._replace(query="", fragment="") return urlunparse(clean) + + +# ========== V3 Code block demo (plain text for highlight.js) ========== + +CODE_DEMO_BEAST = """int main() +{ + net::io_context ioc; + tcp::resolver resolver(ioc); + beast::tcp_stream stream(ioc); + + stream.connect(resolver.resolve("example.com", "80")); + + http::request req{http::verb::get, "/", 11}; + req.set(http::field::host, "example.com"); + + http::write(stream, req); + + beast::flat_buffer buffer; + http::response res; + http::read(stream, buffer, res); + + std::cout << res << std::endl; +}""" + +CODE_DEMO_HELLO = """#include +int main() +{ + std::cout << "Hello, Boost."; +}""" + +CODE_DEMO_INSTALL = """brew install openssl + +export OPENSSL_ROOT=$(brew --prefix openssl) + +# Install bjam tool user config: https://www.bfgroup.xyz/b2/manual/release/index.html +cp ./libs/beast/tools/user-config.jam $HOME""" + + +@register.simple_tag +def code_demo_beast(): + """Return Beast C++ demo code (plain text with newlines for highlight.js).""" + return CODE_DEMO_BEAST + + +@register.simple_tag +def code_demo_hello(): + """Return Hello Boost C++ demo code (plain text with newlines for highlight.js).""" + return CODE_DEMO_HELLO + + +@register.simple_tag +def code_demo_install(): + """Return Install bash demo code (plain text with newlines for highlight.js).""" + return CODE_DEMO_INSTALL diff --git a/static/css/v3/cpp-highlight.css b/static/css/v3/cpp-highlight.css new file mode 100644 index 00000000..29540b2b --- /dev/null +++ b/static/css/v3/cpp-highlight.css @@ -0,0 +1,145 @@ + +/* ========== C++ Syntax highlighting (cpp-highlight + highlight.js) ========== + * Supports both: (1) cpp-highlight custom classes (.cpp-keyword, .cpp-string, etc.) + * from Antora docs, and (2) standard highlight.js classes (.hljs-keyword, .hljs-string, etc.) + * Color scheme aligned with cpp-highlight.css for consistency. + */ + .code-block code.cpp-highlight, + .code-block .code-block__inner code.cpp-highlight { + color: inherit; + } + + /* Light theme – cpp-highlight classes */ + .code-block code.cpp-highlight .cpp-keyword, + .code-block .code-block__inner code.cpp-highlight .cpp-keyword { + color: #00f; + } + + .code-block code.cpp-highlight .cpp-string, + .code-block .code-block__inner code.cpp-highlight .cpp-string { + color: #a31515; + } + + .code-block code.cpp-highlight .cpp-preprocessor, + .code-block .code-block__inner code.cpp-highlight .cpp-preprocessor { + color: #6f008a; + } + + .code-block code.cpp-highlight .cpp-comment, + .code-block .code-block__inner code.cpp-highlight .cpp-comment { + color: #008000; + font-style: italic; + } + + .code-block code.cpp-highlight .cpp-attribute, + .code-block .code-block__inner code.cpp-highlight .cpp-attribute { + color: #9e9e9e; + } + + /* Light theme – highlight.js classes (same colors as cpp-highlight) */ + .code-block .hljs-keyword, + .code-block .hljs-selector-tag, + .code-block .hljs-addition { + color: #00f; + } + + .code-block .hljs-string { + color: #a31515; + } + + .code-block .hljs-meta, + .code-block .hljs-meta .hljs-keyword { + color: #6f008a; + } + + .code-block .hljs-comment, + .code-block .hljs-quote { + color: #008000; + font-style: italic; + } + + .code-block .hljs-attribute, + .code-block .hljs-variable, + .code-block .hljs-template-variable { + color: #9e9e9e; + } + + .code-block .hljs-number, + .code-block .hljs-literal { + color: #098658; + } + + .code-block .hljs-built_in, + .code-block .hljs-class .hljs-title { + color: #267f99; + } + + /* Dark theme – cpp-highlight classes */ + html.dark .code-block code.cpp-highlight .cpp-keyword, + html.dark .code-block .code-block__inner code.cpp-highlight .cpp-keyword { + color: var(--syntax-keyword, #c678dd); + } + + html.dark .code-block code.cpp-highlight .cpp-string, + html.dark .code-block .code-block__inner code.cpp-highlight .cpp-string { + color: var(--syntax-string, #98c379); + } + + html.dark .code-block code.cpp-highlight .cpp-preprocessor, + html.dark .code-block .code-block__inner code.cpp-highlight .cpp-preprocessor { + color: var(--syntax-preprocessor, #e06c75); + } + + html.dark .code-block code.cpp-highlight .cpp-comment, + html.dark .code-block .code-block__inner code.cpp-highlight .cpp-comment { + color: var(--syntax-comment, #5c6370); + font-style: italic; + } + + html.dark .code-block code.cpp-highlight .cpp-attribute, + html.dark .code-block .code-block__inner code.cpp-highlight .cpp-attribute { + color: var(--syntax-attribute, #d19a66); + } + + html.dark .code-block code.cpp-highlight, + html.dark .code-block .code-block__inner code.cpp-highlight { + color: var(--syntax-text, #abb2bf); + } + + /* Dark theme – highlight.js classes */ + html.dark .code-block .hljs-keyword, + html.dark .code-block .hljs-selector-tag, + html.dark .code-block .hljs-addition { + color: var(--syntax-keyword, #c678dd); + } + + html.dark .code-block .hljs-string { + color: var(--syntax-string, #98c379); + } + + html.dark .code-block .hljs-meta, + html.dark .code-block .hljs-meta .hljs-keyword { + color: var(--syntax-preprocessor, #e06c75); + } + + html.dark .code-block .hljs-comment, + html.dark .code-block .hljs-quote { + color: var(--syntax-comment, #5c6370); + font-style: italic; + } + + html.dark .code-block .hljs-attribute, + html.dark .code-block .hljs-variable, + html.dark .code-block .hljs-template-variable { + color: var(--syntax-attribute, #d19a66); + } + + html.dark .code-block .hljs-number, + html.dark .code-block .hljs-literal { + color: var(--syntax-string, #98c379); + } + + html.dark .code-block .hljs-built_in, + html.dark .code-block .hljs-class .hljs-title { + color: #61afef; + } diff --git a/static/css/v3/event-cards.css b/static/css/v3/event-cards.css index 1f919495..b3b3f20d 100644 --- a/static/css/v3/event-cards.css +++ b/static/css/v3/event-cards.css @@ -121,5 +121,4 @@ html.dark .event-cards--beige { html.dark .event-cards--grey { background: var(--color-primary-grey-900); - border: 1px solid var(--color-primary-grey-900); } diff --git a/static/css/v3/index.css b/static/css/v3/index.css index 57d02b4c..72f750c5 100644 --- a/static/css/v3/index.css +++ b/static/css/v3/index.css @@ -5,3 +5,4 @@ @import './semantics.css'; @import './cards.css'; @import './themes.css'; +@import './cpp-highlight.css'; diff --git a/static/css/v3/themes.css b/static/css/v3/themes.css index 5de68e47..3e92d105 100644 --- a/static/css/v3/themes.css +++ b/static/css/v3/themes.css @@ -99,6 +99,14 @@ html.dark { --code-block-bg-grey: var(--color-primary-grey-900); --code-block-card-btn-bg: var(--color-primary-grey-800); + /* Syntax highlighting (C++ / cpp-highlight, Atom One Dark palette) */ + --syntax-keyword: #c678dd; + --syntax-string: #98c379; + --syntax-preprocessor: #e06c75; + --syntax-comment: #5c6370; + --syntax-attribute: #d19a66; + --syntax-text: #abb2bf; + /* Text */ --color-text-error: var(--color-error-mid); --color-text-link-accent: var(--color-secondary-mid-blue); diff --git a/static/js/code-block-copy.js b/static/js/code-block.js similarity index 87% rename from static/js/code-block-copy.js rename to static/js/code-block.js index fd02e887..bd5d55f2 100644 --- a/static/js/code-block-copy.js +++ b/static/js/code-block.js @@ -50,9 +50,19 @@ btn.setAttribute("aria-label", copied ? "Copied" : "Copy code to clipboard"); } + function initHighlight() { + if (typeof hljs !== "undefined" && hljs.highlightAll) { + hljs.highlightAll(); + } + } + if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", init); + document.addEventListener("DOMContentLoaded", function () { + init(); + initHighlight(); + }); } else { init(); + initHighlight(); } })(); diff --git a/templates/base.html b/templates/base.html index 7a39435a..e694f36a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -45,7 +45,8 @@ {% flag "v3" %} - + + {% endflag %} {% block extra_head %} diff --git a/templates/v3/includes/_code_block.html b/templates/v3/includes/_code_block.html index b1086c84..a73c8739 100644 --- a/templates/v3/includes/_code_block.html +++ b/templates/v3/includes/_code_block.html @@ -1,22 +1,25 @@ {% comment %} - V3 Code block – code block with copy button. - Uses static/css/v3/code-block.css. - JS in static/js/code-block-copy.js copies text to clipboard and toggles icon to check. + V3 Code block – code block with copy button and C++ syntax highlighting. + Uses static/css/v3/code-block.css. Requires highlight.js for dynamic highlighting. + JS in static/js/code-block.js copies text to clipboard and toggles icon to check. Variables: - code (optional) — plain text string to show in the code box (auto-escaped). Use for dynamic content from view. - code_html (optional) — pre-rendered HTML inside (use |safe when you need token spans). Use code or code_html. + code (optional) — plain text string (auto-escaped). Use for highlight.js to process. + code_html (optional) — pre-rendered HTML with spans (.cpp-keyword, .cpp-string, etc. or .hljs-*). + Use code or code_html. variant (optional) — "standalone" | "white-bg" | "grey-bg". Default "standalone". + language (optional) — language for highlight.js (e.g. "cpp", "bash"). Default "cpp" when code is used. + cpp_highlight (optional) — if truthy and code_html used, adds class cpp-highlight for cpp-highlight.css styles. - Usage with plain string from view: - {% include "v3/includes/_code_block.html" with code=code_string variant="grey-bg" %} - Usage with pre-rendered HTML: - {% include "v3/includes/_code_block.html" with code_html=my_code_html variant="standalone" %} + Usage with plain code (highlight.js will add spans): + {% include "v3/includes/_code_block.html" with code=code_string variant="grey-bg" language="cpp" %} + Usage with pre-rendered HTML (cpp-highlight or hljs spans): + {% include "v3/includes/_code_block.html" with code_html=my_code_html variant="standalone" cpp_highlight=True %} {% endcomment %}
-
{% if code_html %}{{ code_html|safe }}{% else %}{{ code }}{% endif %}
+
{% if code_html %}{{ code_html|safe }}{% else %}{{ code }}{% endif %}
diff --git a/templates/v3/includes/_code_block_card.html b/templates/v3/includes/_code_block_card.html index e05109a8..602fc566 100644 --- a/templates/v3/includes/_code_block_card.html +++ b/templates/v3/includes/_code_block_card.html @@ -6,6 +6,7 @@ description (optional) — paragraph below heading code (optional) — plain text string for the code block (use code or code_html) code_html (optional) — pre-rendered HTML for + language (optional) — language for highlight.js when using code (e.g. "cpp", "bash") block_variant (optional) — "standalone" | "white-bg" | "grey-bg", default "grey-bg" button_text (optional) — if set, a button is shown button_url (optional) — button href, default "#" @@ -15,7 +16,7 @@

{{ heading }}

{% if description %}

{{ description }}

{% endif %} - {% include "v3/includes/_code_block.html" with code=code code_html=code_html variant=block_variant|default:"grey-bg" %} + {% include "v3/includes/_code_block.html" with code=code code_html=code_html language=language variant=block_variant|default:"grey-bg" %} {% if button_text %} {{ button_text }} {% endif %} diff --git a/templates/v3/includes/_code_blocks_story.html b/templates/v3/includes/_code_blocks_story.html index 5b08ba09..0be15888 100644 --- a/templates/v3/includes/_code_blocks_story.html +++ b/templates/v3/includes/_code_blocks_story.html @@ -1,22 +1,24 @@ {% comment %} V3 Code blocks story – two-column grid with code block variants and cards. - Uses static/css/v3/code-block.css. Requires static/js/code-block-copy.js for copy button. + Uses static/css/v3/code-block.css. Requires static/js/code-block.js and highlight.js for syntax highlighting. Variables (all optional; pass from view for dynamic code): code_standalone_1, code_standalone_2 — strings for the two standalone blocks (left column) code_card_1, code_card_2, code_card_3 — strings for the three cards (right column) - When omitted, the demo uses the inline code strings below. + When omitted, the demo uses code from code_demo template tags (plain text for highlight.js). Usage from view: {% include "v3/includes/_code_blocks_story.html" with code_standalone_1=my_code code_card_1=hello_code %} {% endcomment %} -{% with code_demo_beast='int main()
{
net::io_context ioc;
tcp::resolver resolver(ioc);
beast::tcp_stream stream(ioc);

stream.connect(resolver.resolve("example.com", "80"));

http::request<http::empty_body> req{http::verb::get, "/", 11};
req.set(http::field::host, "example.com");

http::write(stream, req);

beast::flat_buffer buffer;
http::response<http::string_body> res;
http::read(stream, buffer, res);

std::cout << res << std::endl;
}' code_demo_hello='#include <iostream>
int main()
{
std::cout << "Hello, Boost.";
}' code_demo_install='brew install openssl

export OPENSSL_ROOT=$(brew --prefix openssl)

# Install bjam tool user config: https://www.bfgroup.xyz/b2/manual/release/index.html
cp ./libs/beast/tools/user-config.jam $HOME' %} -
-
- {% include "v3/includes/_code_block.html" with code_html=code_standalone_1|default:code_demo_beast variant="standalone" %} - {% include "v3/includes/_code_block.html" with code_html=code_standalone_2|default:code_demo_beast variant="white-bg" %} -
-
- {% include "v3/includes/_code_block_card.html" with heading="Get started with our libraries" code_html=code_card_1|default:code_demo_hello block_variant="grey-bg" button_text="Explore examples" %} - {% include "v3/includes/_code_block_card.html" with card_variant="neutral" heading="About Beast" description="Beast lets you use HTTP and WebSocket to write clients and servers that connect to networks using Boost.Asio." code_html=code_card_2|default:code_demo_beast block_variant="grey-bg" %} - {% include "v3/includes/_code_block_card.html" with card_variant="teal" heading="Install" description="Get started with header-only libraries." code_html=code_card_3|default:code_demo_install block_variant="grey-bg" %} -
+{% load text_helpers %} +{% code_demo_beast as code_demo_beast %} +{% code_demo_hello as code_demo_hello %} +{% code_demo_install as code_demo_install %} +
+
+ {% include "v3/includes/_code_block.html" with code=code_standalone_1|default:code_demo_beast variant="standalone" language="cpp" %} + {% include "v3/includes/_code_block.html" with code=code_standalone_2|default:code_demo_beast variant="white-bg" language="cpp" %}
-{% endwith %} +
+ {% include "v3/includes/_code_block_card.html" with heading="Get started with our libraries" code=code_card_1|default:code_demo_hello block_variant="grey-bg" button_text="Explore examples" language="cpp" %} + {% include "v3/includes/_code_block_card.html" with card_variant="neutral" heading="About Beast" description="Beast lets you use HTTP and WebSocket to write clients and servers that connect to networks using Boost.Asio." code=code_card_2|default:code_demo_beast block_variant="grey-bg" language="cpp" %} + {% include "v3/includes/_code_block_card.html" with card_variant="teal" heading="Install" description="Get started with header-only libraries." code=code_card_3|default:code_demo_install block_variant="grey-bg" language="bash" %} +
+
From 41cfa7cec7f9ba4b12ff3d895e8bc70124c80e14 Mon Sep 17 00:00:00 2001 From: DrJfrost Date: Sun, 1 Mar 2026 11:57:34 -0500 Subject: [PATCH 11/26] Adding logic for highlighting --- static/css/v3/code-block.css | 6 + static/css/v3/cpp-highlight.css | 80 ++-------- static/css/v3/themes.css | 2 + static/js/code-block.js | 70 +++++++++ static/js/highlight-block.js | 262 ++++++++++++++++++++++++++++++++ templates/base.html | 3 +- 6 files changed, 352 insertions(+), 71 deletions(-) create mode 100644 static/js/highlight-block.js diff --git a/static/css/v3/code-block.css b/static/css/v3/code-block.css index d34c96cd..923648d9 100644 --- a/static/css/v3/code-block.css +++ b/static/css/v3/code-block.css @@ -15,6 +15,12 @@ --code-block-card-btn-bg: var(--color-primary-grey-200); } + +pre{ + color: var(--color-syntax-text) !important; + font-weight: 500; +} + .code-block--standalone { background: var(--code-block-bg-standalone); border: 1px dashed var(--code-block-border-standalone); diff --git a/static/css/v3/cpp-highlight.css b/static/css/v3/cpp-highlight.css index 29540b2b..ee5c282c 100644 --- a/static/css/v3/cpp-highlight.css +++ b/static/css/v3/cpp-highlight.css @@ -36,6 +36,16 @@ color: #9e9e9e; } + .code-block code.cpp-highlight .cpp-number, + .code-block .code-block__inner code.cpp-highlight .cpp-number { + color: var(--syntax-number, #098658); + } + + .code-block code.cpp-highlight .cpp-function, + .code-block .code-block__inner code.cpp-highlight .cpp-function { + color: var(--syntax-function, #267f99); + } + /* Light theme – highlight.js classes (same colors as cpp-highlight) */ .code-block .hljs-keyword, .code-block .hljs-selector-tag, @@ -73,73 +83,3 @@ .code-block .hljs-class .hljs-title { color: #267f99; } - - /* Dark theme – cpp-highlight classes */ - html.dark .code-block code.cpp-highlight .cpp-keyword, - html.dark .code-block .code-block__inner code.cpp-highlight .cpp-keyword { - color: var(--syntax-keyword, #c678dd); - } - - html.dark .code-block code.cpp-highlight .cpp-string, - html.dark .code-block .code-block__inner code.cpp-highlight .cpp-string { - color: var(--syntax-string, #98c379); - } - - html.dark .code-block code.cpp-highlight .cpp-preprocessor, - html.dark .code-block .code-block__inner code.cpp-highlight .cpp-preprocessor { - color: var(--syntax-preprocessor, #e06c75); - } - - html.dark .code-block code.cpp-highlight .cpp-comment, - html.dark .code-block .code-block__inner code.cpp-highlight .cpp-comment { - color: var(--syntax-comment, #5c6370); - font-style: italic; - } - - html.dark .code-block code.cpp-highlight .cpp-attribute, - html.dark .code-block .code-block__inner code.cpp-highlight .cpp-attribute { - color: var(--syntax-attribute, #d19a66); - } - - html.dark .code-block code.cpp-highlight, - html.dark .code-block .code-block__inner code.cpp-highlight { - color: var(--syntax-text, #abb2bf); - } - - /* Dark theme – highlight.js classes */ - html.dark .code-block .hljs-keyword, - html.dark .code-block .hljs-selector-tag, - html.dark .code-block .hljs-addition { - color: var(--syntax-keyword, #c678dd); - } - - html.dark .code-block .hljs-string { - color: var(--syntax-string, #98c379); - } - - html.dark .code-block .hljs-meta, - html.dark .code-block .hljs-meta .hljs-keyword { - color: var(--syntax-preprocessor, #e06c75); - } - - html.dark .code-block .hljs-comment, - html.dark .code-block .hljs-quote { - color: var(--syntax-comment, #5c6370); - font-style: italic; - } - - html.dark .code-block .hljs-attribute, - html.dark .code-block .hljs-variable, - html.dark .code-block .hljs-template-variable { - color: var(--syntax-attribute, #d19a66); - } - - html.dark .code-block .hljs-number, - html.dark .code-block .hljs-literal { - color: var(--syntax-string, #98c379); - } - - html.dark .code-block .hljs-built_in, - html.dark .code-block .hljs-class .hljs-title { - color: #61afef; - } diff --git a/static/css/v3/themes.css b/static/css/v3/themes.css index 3e92d105..7b0ce903 100644 --- a/static/css/v3/themes.css +++ b/static/css/v3/themes.css @@ -105,6 +105,8 @@ html.dark { --syntax-preprocessor: #e06c75; --syntax-comment: #5c6370; --syntax-attribute: #d19a66; + --syntax-number: #98c379; + --syntax-function: #61afef; --syntax-text: #abb2bf; /* Text */ diff --git a/static/js/code-block.js b/static/js/code-block.js index bd5d55f2..f8cef538 100644 --- a/static/js/code-block.js +++ b/static/js/code-block.js @@ -5,6 +5,7 @@ */ (function () { var COPY_FEEDBACK_MS = 2000; + var CppHighlight = typeof window !== 'undefined' && window.CppHighlight function init() { document.querySelectorAll(".code-block__copy").forEach(function (btn) { @@ -50,10 +51,56 @@ btn.setAttribute("aria-label", copied ? "Copied" : "Copy code to clipboard"); } + // Replace C++ highlighting AFTER highlight.js processes blocks + // Let hljs work initially, then replace C++ blocks with custom highlighter + function processCppBlocks () { + if (!CppHighlight) return + // Selectors for C++ code blocks that highlight.js has already processed + var cppSelectors = [ + 'code.language-cpp.hljs', + 'code.language-c++.hljs', + 'code[data-lang="cpp"].hljs', + 'code[data-lang="c++"].hljs', + '.doc pre.highlight code[data-lang="cpp"].hljs', + '.doc pre.highlight code[data-lang="c++"].hljs', + ] + + var processedCount = 0 + + cppSelectors.forEach(function (selector) { + try { + document.querySelectorAll(selector).forEach(function (el) { + // Skip if already processed + if (el.classList.contains('cpp-highlight')) return + + // Replace highlight.js's C++ highlighting with our custom highlighter + // This gives us full control over C++ syntax highlighting + CppHighlight.highlightElement(el) + + // Mark as processed with our custom highlighter + el.classList.add('cpp-highlight') + processedCount++ + }) + } catch (e) { + console.warn('cpp-highlight error:', selector, e) + } + }) + + if (processedCount > 0) { + console.log('cpp-highlight: Replaced ' + processedCount + ' C++ code blocks') + } + } + function initHighlight() { if (typeof hljs !== "undefined" && hljs.highlightAll) { hljs.highlightAll(); } + + // Then, replace C++ blocks with our custom highlighter + // Use setTimeout to ensure highlight.js is completely done + setTimeout(function () { + processCppBlocks() + }, 0) } if (document.readyState === "loading") { @@ -65,4 +112,27 @@ init(); initHighlight(); } + + // Also use MutationObserver to catch dynamically added content + // Process C++ blocks after highlight.js processes new content + if (typeof window.MutationObserver !== 'undefined') { + var observer = new window.MutationObserver(function (mutations) { + var shouldProcess = false + mutations.forEach(function (mutation) { + if (mutation.addedNodes.length > 0) { + shouldProcess = true + } + }) + if (shouldProcess) { + // Wait a bit for highlight.js to process new content + setTimeout(function () { + processCppBlocks() + }, 100) + } + }) + observer.observe(document.body || document.documentElement, { + childList: true, + subtree: true, + }) + } })(); diff --git a/static/js/highlight-block.js b/static/js/highlight-block.js new file mode 100644 index 00000000..3de01266 --- /dev/null +++ b/static/js/highlight-block.js @@ -0,0 +1,262 @@ +/** + * cpp-highlight.js - Lightweight C++ syntax highlighter + * + * Categories: + * - keyword : Language keywords (const, while, if, etc.) + * - string : String and character literals + * - preprocessor: Preprocessor directives (#include, #define, etc.) + * - comment : C and C++ style comments + * - attribute : C++ attributes ([[nodiscard]], [[noreturn]], etc.) + */ + +const CppHighlight = (function () { + 'use strict' + + const KEYWORDS = new Set([ + // Storage class + 'auto', 'register', 'static', 'extern', 'mutable', 'thread_local', + // Type qualifiers + 'const', 'volatile', 'constexpr', 'consteval', 'constinit', + // Type specifiers + 'void', 'bool', 'char', 'short', 'int', 'long', 'float', 'double', + 'signed', 'unsigned', 'wchar_t', 'char8_t', 'char16_t', 'char32_t', + // Complex types + 'class', 'struct', 'union', 'enum', 'typename', 'typedef', + // Control flow + 'if', 'else', 'switch', 'case', 'default', 'for', 'while', 'do', + 'break', 'continue', 'return', 'goto', + // Exception handling + 'try', 'catch', 'throw', 'noexcept', + // OOP + 'public', 'private', 'protected', 'virtual', 'override', 'final', + 'friend', 'this', 'operator', 'new', 'delete', + // Templates + 'template', 'concept', 'requires', + // Namespace + 'namespace', 'using', + // Other + 'sizeof', 'alignof', 'alignas', 'decltype', 'typeid', + 'static_cast', 'dynamic_cast', 'const_cast', 'reinterpret_cast', + 'static_assert', 'inline', 'explicit', 'export', 'module', 'import', + 'co_await', 'co_yield', 'co_return', + // Literals + 'true', 'false', 'nullptr', 'NULL', + ]) + + function escapeHtml (text) { + return text + .replace(/&/g, '&') + .replace(//g, '>') + } + + function span (cls, content) { + return `${escapeHtml(content)}` + } + + function highlight (code) { + const result = [] + let i = 0 + const n = code.length + + while (i < n) { + // C++ attributes [[...]] with nesting support + if (code[i] === '[' && code[i + 1] === '[') { + const start = i + i += 2 + let depth = 1 + while (i < n && depth > 0) { + if (code[i] === '[' && code[i + 1] === '[') { + depth++ + i += 2 + } else if (code[i] === ']' && code[i + 1] === ']') { + depth-- + i += 2 + } else { + i++ + } + } + result.push(span('attribute', code.slice(start, i))) + continue + } + + // Line comment + if (code[i] === '/' && code[i + 1] === '/') { + let end = i + 2 + while (end < n && code[end] !== '\n') end++ + result.push(span('comment', code.slice(i, end))) + i = end + continue + } + + // Block comment + if (code[i] === '/' && code[i + 1] === '*') { + let end = i + 2 + while (end < n - 1 && !(code[end] === '*' && code[end + 1] === '/')) end++ + end += 2 + result.push(span('comment', code.slice(i, end))) + i = end + continue + } + + // Preprocessor directive (at start of line or after whitespace) + if (code[i] === '#') { + // Check if # is at line start (allow leading whitespace) + let checkPos = i - 1 + while (checkPos >= 0 && (code[checkPos] === ' ' || code[checkPos] === '\t')) checkPos-- + if (checkPos < 0 || code[checkPos] === '\n') { + let end = i + 1 + // Handle line continuation with backslash + while (end < n) { + if (code[end] === '\n') { + if (code[end - 1] === '\\') { + end++ + continue + } + break + } + end++ + } + result.push(span('preprocessor', code.slice(i, end))) + i = end + continue + } + } + + // Raw string literal R"delimiter(...)delimiter" + if (code[i] === 'R' && code[i + 1] === '"') { + let delimEnd = i + 2 + while (delimEnd < n && code[delimEnd] !== '(') delimEnd++ + const delimiter = code.slice(i + 2, delimEnd) + const endMarker = ')' + delimiter + '"' + let end = delimEnd + 1 + while (end < n) { + if (code.slice(end, end + endMarker.length) === endMarker) { + end += endMarker.length + break + } + end++ + } + result.push(span('string', code.slice(i, end))) + i = end + continue + } + + // String literal (with optional prefix) + if (code[i] === '"' || + ((code[i] === 'L' || code[i] === 'u' || code[i] === 'U') && code[i + 1] === '"') || + (code[i] === 'u' && code[i + 1] === '8' && code[i + 2] === '"')) { + const start = i + if (code[i] === 'u' && code[i + 1] === '8') i += 2 + else if (code[i] !== '"') i++ + i++ // skip opening quote + while (i < n && code[i] !== '"') { + if (code[i] === '\\' && i + 1 < n) i += 2 + else i++ + } + i++ // skip closing quote + result.push(span('string', code.slice(start, i))) + continue + } + + // Character literal + if (code[i] === '\'' || + ((code[i] === 'L' || code[i] === 'u' || code[i] === 'U') && code[i + 1] === '\'') || + (code[i] === 'u' && code[i + 1] === '8' && code[i + 2] === '\'')) { + const start = i + if (code[i] === 'u' && code[i + 1] === '8') i += 2 + else if (code[i] !== '\'') i++ + i++ // skip opening quote + while (i < n && code[i] !== '\'') { + if (code[i] === '\\' && i + 1 < n) i += 2 + else i++ + } + i++ // skip closing quote + result.push(span('string', code.slice(start, i))) + continue + } + + // Numeric literal (integer or float, with optional suffix) + if (/[0-9]/.test(code[i])) { + const start = i + if (code[i] === '0' && i + 2 <= n) { + if (code[i + 1] === 'x' || code[i + 1] === 'X') { + i += 2 + while (i < n && /[0-9a-fA-F]/.test(code[i])) i++ + } else if (code[i + 1] === 'b' || code[i + 1] === 'B') { + i += 2 + while (i < n && /[01]/.test(code[i])) i++ + } else if (/[0-7]/.test(code[i + 1])) { + i++ + while (i < n && /[0-7]/.test(code[i])) i++ + } else { + i++ + } + } else { + while (i < n && /[0-9]/.test(code[i])) i++ + } + if (i < n && code[i] === '.' && /[0-9]/.test(code[i + 1])) { + i++ + while (i < n && /[0-9]/.test(code[i])) i++ + } + if (i < n && (code[i] === 'e' || code[i] === 'E')) { + i++ + if (i < n && (code[i] === '+' || code[i] === '-')) i++ + while (i < n && /[0-9]/.test(code[i])) i++ + } + while (i < n && /[fFlLuU]/.test(code[i])) i++ + result.push(span('number', code.slice(start, i))) + continue + } + + // Identifier or keyword + if (/[a-zA-Z_]/.test(code[i])) { + let end = i + 1 + while (end < n && /[a-zA-Z0-9_]/.test(code[end])) end++ + const word = code.slice(i, end) + let j = end + while (j < n && (code[j] === ' ' || code[j] === '\t')) j++ + const isFunctionCall = j < n && code[j] === '(' + if (KEYWORDS.has(word)) { + result.push(span('keyword', word)) + } else if (isFunctionCall) { + result.push(span('function', word)) + } else { + result.push(escapeHtml(word)) + } + i = end + continue + } + + // Default: single character + result.push(escapeHtml(code[i])) + i++ + } + + return result.join('') + } + + function highlightElement (el) { + el.innerHTML = highlight(el.textContent) + } + + function highlightAll (selector = 'code.cpp, code.c++, pre.cpp, pre.c++') { + document.querySelectorAll(selector).forEach(highlightElement) + } + + return { + highlight, + highlightElement, + highlightAll, + KEYWORDS, + } +})() + +// Browser global (for script tag usage) +if (typeof window !== 'undefined') { + window.CppHighlight = CppHighlight +} +// CommonJS / ES module support +if (typeof module !== 'undefined' && module.exports) { + module.exports = CppHighlight +} diff --git a/templates/base.html b/templates/base.html index e694f36a..da43645f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -45,8 +45,9 @@ {% flag "v3" %} - + + {% endflag %} {% block extra_head %} From e1370269adeb618c8de5619b16954ad82f932709 Mon Sep 17 00:00:00 2001 From: DrJfrost Date: Sun, 1 Mar 2026 13:06:58 -0500 Subject: [PATCH 12/26] Correction of category tags for tests and adaptation of code block styles --- static/css/v3/code-block.css | 37 +++++++++++++-- templates/v3/includes/_category_cards.html | 54 +++++++++++----------- templates/v3/includes/_category_tag.html | 3 +- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/static/css/v3/code-block.css b/static/css/v3/code-block.css index 923648d9..425cb5f9 100644 --- a/static/css/v3/code-block.css +++ b/static/css/v3/code-block.css @@ -85,13 +85,44 @@ pre{ border: 1px solid var(--color-border); } +.code-block-card--grey { + background: var(--color-surface-mid); + border: 1px solid var(--color-border); +} + .code-block-card--teal { - background: var(--color-accent-weak-teal); + background: var(--color-surface-weak-accent-teal); border: 1px solid var(--color-accent-strong-teal); } +.code-block-card--yellow { + background: var(--color-surface-weak-accent-yellow); + border: 1px solid var(--color-accent-strong-yellow); +} + +.code-block-card--green { + background: var(--color-surface-weak-accent-green); + border: 1px solid var(--color-accent-strong-green); +} + html.dark .code-block-card--teal { - background: var(--color-primary-grey-850); + background: var(--color-surface-weak-accent-teal); + border: 0; +} + +html.dark .code-block-card--yellow { + background: var(--color-surface-weak-accent-yellow); + border: 0; +} + +html.dark .code-block-card--green { + background: var(--color-surface-weak-accent-green); + border: 0; +} + +html.dark .code-block-card--grey { + background: var(--color-surface-mid); + border: 0; } .code-block-card__heading { @@ -106,7 +137,7 @@ html.dark .code-block-card--teal { .code-block-card__description { padding: var(--space-card) var(--space-medium) var(--space-default) var(--space-medium); margin: 0 0 var(--space-medium); - font-size: var(--font-size-small); + font-size: var(--font-size-medium); line-height: var(--line-height-relaxed); color: var(--color-text-secondary); } diff --git a/templates/v3/includes/_category_cards.html b/templates/v3/includes/_category_cards.html index db6f465f..9e4a8083 100644 --- a/templates/v3/includes/_category_cards.html +++ b/templates/v3/includes/_category_cards.html @@ -3,7 +3,7 @@ Uses static/css/v3/category-tags.css. Tags and variants only (no header/logo). Variables (all optional, for dynamic use from view): - category_tags (optional) — list of dicts with tag_label, url, variant, size. + category_tags (optional) — list of dicts with tag_label, url, variant, size, and optional aria_label. If omitted, shows the variant gallery (Default + Tight, neutral/green/yellow/teal). section_heading (optional) — section heading, default "Category tags" show_version_tags (optional) — if truthy, shows version tags block (Default + Hover). @@ -17,56 +17,58 @@

{{ section_heading|default:"Category tags" }}

{% if category_tags %} - {% comment %}Dynamic mode: single list of tags with classes per item.{% endcomment %} -
+ {% comment %}Dynamic mode: list of tags (ul/li for semantics and a11y).{% endcomment %} +
    {% for tag in category_tags %} - {% include "v3/includes/_category_tag.html" with tag_label=tag.tag_label url=tag.url variant=tag.variant size=tag.size %} +
  • + {% include "v3/includes/_category_tag.html" with tag_label=tag.tag_label url=tag.url variant=tag.variant size=tag.size aria_label=tag.aria_label %} +
  • {% endfor %} -
+ {% else %} - {% comment %}Variant gallery: Default and Tight, each with neutral / green / yellow / teal and normal + hover state.{% endcomment %} + {% comment %}Variant gallery: Default and Tight, each with neutral / green / yellow / teal and normal + hover state. ul/li for semantics.{% endcomment %}
Default -
-
+
    +
  • {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="neutral" size="default" %} {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="neutral" size="default" show_hover=True %} -
-
+ +
  • {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="green" size="default" %} {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="green" size="default" show_hover=True %} -
  • -
    + +
  • {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="yellow" size="default" %} {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="yellow" size="default" show_hover=True %} -
  • -
    + +
  • {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="teal" size="default" %} {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="teal" size="default" show_hover=True %} -
  • -
    + +
    Tight -
    -
    +
      +
    • {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="neutral" size="tight" %} {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="neutral" size="tight" show_hover=True %} -
    -
    + +
  • {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="green" size="tight" %} {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="green" size="tight" show_hover=True %} -
  • -
    + +
  • {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="yellow" size="tight" %} {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="yellow" size="tight" show_hover=True %} -
  • -
    + +
  • {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="teal" size="tight" %} {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="teal" size="tight" show_hover=True %} -
  • -
    + +
    {% if show_version_tags %} diff --git a/templates/v3/includes/_category_tag.html b/templates/v3/includes/_category_tag.html index 3b44682b..22768461 100644 --- a/templates/v3/includes/_category_tag.html +++ b/templates/v3/includes/_category_tag.html @@ -8,6 +8,7 @@ variant (required) — "neutral" | "green" | "yellow" | "teal" size (optional) — "default" | "tight", default "default" show_hover (optional) — if truthy, adds category-tag--hover-state for hover preview in docs + aria_label (optional) — for links, accessible name (e.g. "Filter by category: Math"). Defaults to "Filter by category: {{ tag_label }}". Usage (clickable category tag): {% include "v3/includes/_category_tag.html" with tag_label="Math" url="#" variant="neutral" size="default" %} @@ -15,7 +16,7 @@ {% include "v3/includes/_category_tag.html" with tag_label="C++ 03" variant="neutral" size="default" %} {% endcomment %} {% if url %} -{{ tag_label }} +{{ tag_label }} {% else %} {{ tag_label }} {% endif %} From 653ad43df03694fef169ca6fe2e1f56fb8cc6cce Mon Sep 17 00:00:00 2001 From: DrJfrost Date: Sun, 1 Mar 2026 16:42:53 -0500 Subject: [PATCH 13/26] assigning the main button to code blocks --- static/css/v3/code-block.css | 61 ++++++++++--------- .../v3/examples/_v3_example_section.html | 11 +++- templates/v3/includes/_code_block_card.html | 7 ++- templates/v3/includes/_code_blocks_story.html | 2 +- templates/v3/includes/_why_boost_cards.html | 6 ++ 5 files changed, 52 insertions(+), 35 deletions(-) diff --git a/static/css/v3/code-block.css b/static/css/v3/code-block.css index 425cb5f9..28ccffe1 100644 --- a/static/css/v3/code-block.css +++ b/static/css/v3/code-block.css @@ -12,13 +12,6 @@ --code-block-copy-icon-color-success: var(--color-syntax-green); --code-block-card-max-width: 458px; --code-block-card-radius: var(--card-radius-lg); - --code-block-card-btn-bg: var(--color-primary-grey-200); -} - - -pre{ - color: var(--color-syntax-text) !important; - font-weight: 500; } .code-block--standalone { @@ -66,7 +59,7 @@ pre{ } .code-block-card .code-block { - margin: 0 var(--space-medium); + margin: 0 var(--space-card); overflow: visible; } @@ -75,9 +68,34 @@ pre{ overflow-wrap: break-word; } -.code-block-card .code-block-card__btn { - margin: var(--space-medium) var(--space-medium) 0 var(--space-medium); - max-width: calc(100% - var(--space-medium) * 2); +.code-block__inner code{ + font-weight: 500; + color: var(--color-syntax-text) !important; + white-space: break-spaces !important; +} + +.code-block__inner code{ + font-weight: 500; + color: var(--color-syntax-text) !important; + white-space: break-spaces !important; +} + +/* CTA button in card – same look as .post-cards__cta .btn-secondary-grey */ +.code-block-card .btn { + flex: 1 1 0; + min-width: 128px; + text-decoration: none; + margin: var(--space-medium) 0 0 0; +} + +.code-block-card .btn:hover { + text-decoration: none; +} + +.code-block-card .btn-secondary-grey { + background-color: var(--color-button-primary); + border-color: var(--color-border); + color: var(--color-text-primary); } .code-block-card--neutral { @@ -127,7 +145,7 @@ html.dark .code-block-card--grey { .code-block-card__heading { margin: 0; - padding: 0 var(--space-medium) var(--space-card) var(--space-medium); + padding: 0 var(--space-card) var(--space-card) var(--space-card); font-size: var(--font-size-large); font-weight: 600; font-family: var(--font-display); @@ -135,7 +153,7 @@ html.dark .code-block-card--grey { } .code-block-card__description { - padding: var(--space-card) var(--space-medium) var(--space-default) var(--space-medium); + padding: var(--space-card) var(--space-card) var(--space-default) var(--space-card); margin: 0 0 var(--space-medium); font-size: var(--font-size-medium); line-height: var(--line-height-relaxed); @@ -146,23 +164,6 @@ html.dark .code-block-card--grey { border-top: 1px solid var(--color-border); } -.code-block-card__btn { - margin-top: var(--space-medium); - padding: var(--space-default) var(--space-default); - width: 100%; - background: var(--code-block-card-btn-bg); - border: 1px solid var(--color-border); - border-radius: var(--border-radius-m); - font-size: var(--font-size-small); - font-weight: 600; - font-family: var(--font-sans); - color: var(--color-text-primary); - cursor: pointer; - box-sizing: border-box; - text-align: center; - display: block; -} - .code-block__inner { display: block; margin: 0; diff --git a/templates/v3/examples/_v3_example_section.html b/templates/v3/examples/_v3_example_section.html index 284de967..ed234cd1 100644 --- a/templates/v3/examples/_v3_example_section.html +++ b/templates/v3/examples/_v3_example_section.html @@ -90,13 +90,18 @@

    Form inputs

    Why Boost

    + {% comment %}Use 8 cards to test ACs: "Why Boost Cards render correctly with 1–8 cards" and "Cards wrap correctly after four items on desktop/tablet" (4+4 rows).{% endcomment %}

    Why Boost?

    {% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="Tap into quick answers, networking, and chat with 24,000+ members." icon_name="bullseye-arrow" %} - {% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="Tap into quick answers, networking, and chat with 24,000+ members." icon_name="bullseye-arrow" %} - {% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="Tap into quick answers, networking, and chat with 24,000+ members." icon_name="bullseye-arrow" %} - {% include "v3/includes/_content_detail_card_item.html" with title="Get help" description="Tap into quick answers, networking, and chat with 24,000+ members." icon_name="bullseye-arrow" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Learn" description="Access documentation, books, and courses to level up your C++." icon_name="bullseye-arrow" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Contribute" description="Report issues, submit patches, and join the community." icon_name="bullseye-arrow" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Stay updated" description="Releases, news, and announcements from the Boost community." icon_name="bullseye-arrow" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Libraries" description="Portable, peer-reviewed libraries for a wide range of use cases." icon_name="bullseye-arrow" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Standards" description="Many Boost libraries have been adopted into the C++ standard." icon_name="bullseye-arrow" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Quality" description="Peer-reviewed code and documentation maintained by the community." icon_name="bullseye-arrow" %} + {% include "v3/includes/_content_detail_card_item.html" with title="Cross-platform" description="Libraries designed to work across compilers and platforms." icon_name="bullseye-arrow" %}
    diff --git a/templates/v3/includes/_code_block_card.html b/templates/v3/includes/_code_block_card.html index 602fc566..063f3fb6 100644 --- a/templates/v3/includes/_code_block_card.html +++ b/templates/v3/includes/_code_block_card.html @@ -10,6 +10,7 @@ block_variant (optional) — "standalone" | "white-bg" | "grey-bg", default "grey-bg" button_text (optional) — if set, a button is shown button_url (optional) — button href, default "#" + button_aria_label (optional) — accessible name for the button; defaults to button_text if omitted {% endcomment %}

    {{ heading }}

    @@ -18,6 +19,10 @@

    {{ heading }}

    {% endif %} {% include "v3/includes/_code_block.html" with code=code code_html=code_html language=language variant=block_variant|default:"grey-bg" %} {% if button_text %} - {{ button_text }} +
    + + {{ button_text }} + +
    {% endif %}
    diff --git a/templates/v3/includes/_code_blocks_story.html b/templates/v3/includes/_code_blocks_story.html index 0be15888..3ca016d0 100644 --- a/templates/v3/includes/_code_blocks_story.html +++ b/templates/v3/includes/_code_blocks_story.html @@ -17,7 +17,7 @@ {% include "v3/includes/_code_block.html" with code=code_standalone_2|default:code_demo_beast variant="white-bg" language="cpp" %}
    - {% include "v3/includes/_code_block_card.html" with heading="Get started with our libraries" code=code_card_1|default:code_demo_hello block_variant="grey-bg" button_text="Explore examples" language="cpp" %} + {% include "v3/includes/_code_block_card.html" with heading="Get started with our libraries" code=code_card_1|default:code_demo_hello block_variant="grey-bg" button_text="Explore examples" language="cpp" button_aria_label="Explore examples" %} {% include "v3/includes/_code_block_card.html" with card_variant="neutral" heading="About Beast" description="Beast lets you use HTTP and WebSocket to write clients and servers that connect to networks using Boost.Asio." code=code_card_2|default:code_demo_beast block_variant="grey-bg" language="cpp" %} {% include "v3/includes/_code_block_card.html" with card_variant="teal" heading="Install" description="Get started with header-only libraries." code=code_card_3|default:code_demo_install block_variant="grey-bg" language="bash" %}
    diff --git a/templates/v3/includes/_why_boost_cards.html b/templates/v3/includes/_why_boost_cards.html index 65ef3ae3..9dbaed95 100644 --- a/templates/v3/includes/_why_boost_cards.html +++ b/templates/v3/includes/_why_boost_cards.html @@ -12,6 +12,12 @@ Usage (from view context): {% include "v3/includes/_why_boost_cards.html" with section_heading="Why Boost?" why_boost_cards=cards %} + + Acceptance criteria (Why Boost Cards – not Category Tags): + - Render correctly with 1–8 cards (view passes why_boost_cards with 1 to 8 items). + - Layout: Cards wrap correctly after four items on desktop/tablet (grid 4 columns); single column on small viewports. + - Accessibility: Section has aria-labelledby pointing to the heading; each card is an
    with heading level and description. + - Analytics: Track as needed for Why Boost card clicks/views (component-specific, not Category Tags). {% endcomment %}

    {{ section_heading|default:"Why Boost?" }}

    From 510903524b425a663720132398e10f2b54193712 Mon Sep 17 00:00:00 2001 From: DrJfrost Date: Sun, 1 Mar 2026 21:11:46 -0500 Subject: [PATCH 14/26] improvement of event styles and content --- static/css/v3/components.css | 2 +- static/css/v3/content.css | 107 ++++++++-------- static/css/v3/event-cards.css | 11 +- static/css/v3/post-cards.css | 114 +++++++++++++++--- .../v3/examples/_v3_example_section.html | 38 +++--- .../v3/includes/_content_event_card_item.html | 43 ++++--- templates/v3/includes/_event_cards.html | 98 +++++++-------- .../v3/includes/_event_cards_section.html | 11 +- 8 files changed, 276 insertions(+), 148 deletions(-) diff --git a/static/css/v3/components.css b/static/css/v3/components.css index a5f9cc52..58e31ab1 100644 --- a/static/css/v3/components.css +++ b/static/css/v3/components.css @@ -1,8 +1,8 @@ @import './fonts.css'; @import './foundations.css'; -@import './buttons.css'; @import './button-tooltip.css'; @import './post-cards.css'; +@import './buttons.css'; @import './carousel-buttons.css'; @import './v3-examples-section.css'; @import './header.css'; diff --git a/static/css/v3/content.css b/static/css/v3/content.css index 61f59b3e..7ab0e3f0 100644 --- a/static/css/v3/content.css +++ b/static/css/v3/content.css @@ -1,51 +1,3 @@ - -.event-content { - font-family: var(--font-sans); - color: var(--color-text-primary); - margin: 0; - padding: var(--space-medium) var(--space-card); - border-radius: var(--card-radius); - box-sizing: border-box; - transition: background-color 0.2s ease, border-color 0.2s ease; -} - -a:hover .event-content:not(.event-content--card-contained) { - background: var(--color-primary-grey-100); -} - -.event-content--card-contained { - background: var(--color-surface-mid); - padding: var(--card-padding); - border: 1px solid var(--color-border); -} - -a:hover .event-content--card-contained { - background: var(--color-primary-grey-200); - border-color: var(--color-border); -} - -.event-content__title { - margin: 0 0 var(--space-xs); - font-size: var(--font-size-base); - font-weight: 700; - line-height: var(--line-height-default); - font-family: var(--font-sans); - color: var(--color-text-primary); -} - -.event-content__description { - margin: 0 0 var(--space-default); - font-size: var(--font-size-small); - line-height: var(--line-height-relaxed); - color: var(--color-text-secondary); - padding: 0; -} - -.event-content__date { - font-size: var(--font-size-small); - color: var(--color-text-tertiary); -} - .content-detail-icon { font-family: var(--font-sans); color: var(--color-text-primary); @@ -271,3 +223,62 @@ a:hover .content-detail-icon:not(.content-detail-icon--contained) { grid-template-columns: 1fr; } } + +/* ========== Content card (event and similar) ========== */ +.content-card-link { + text-decoration: none; + color: inherit; + display: block; +} + +.content-card { + font-family: var(--font-sans); + color: var(--color-text-primary); + display: flex; + flex-direction: column; + gap: var(--space-default); + padding: var(--space-card); + border-radius: var(--card-radius); + border: 1px solid var(--color-border); + background: var(--color-bg-secondary); + transition: background-color 0.2s ease, border-color 0.2s ease; +} + +.content-card--event { + gap: var(--space-default); +} + +.content-card__title { + margin: 0; + padding: 0; + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-default); + color: var(--color-text-primary); +} + +.content-card__description { + margin: 0; + padding: 0; + font-size: var(--font-size-small); + line-height: var(--line-height-relaxed); + color: var(--color-text-secondary); +} + +.content-card__date { + margin: 0; + font-size: var(--font-size-small); + line-height: var(--line-height-default); + color: var(--color-text-secondary); +} + +.content-card--contained { + background: transparent; + border: none; + padding: 0; +} + +a:hover .content-card:not(.content-card--contained) { + background: var(--color-primary-grey-200); + border-color: var(--color-border); +} diff --git a/static/css/v3/event-cards.css b/static/css/v3/event-cards.css index b3b3f20d..bdcce282 100644 --- a/static/css/v3/event-cards.css +++ b/static/css/v3/event-cards.css @@ -112,9 +112,18 @@ flex-shrink: 0; } +a:hover .content-card { + background: var(--color-primary-grey-100); +} + +a:hover .content-card { + background: var(--color-primary-grey-200); + border-color: var(--color-border); +} + html.dark .event-cards--teal, html.dark .event-cards--yellow, -html.dark .event-cards--beige { +html.dark .event-cards--beige{ background: var(--color-primary-grey-850); border: 1px solid var(--color-primary-grey-850); } diff --git a/static/css/v3/post-cards.css b/static/css/v3/post-cards.css index fab7120b..71639bf8 100644 --- a/static/css/v3/post-cards.css +++ b/static/css/v3/post-cards.css @@ -65,7 +65,7 @@ .post-cards--horizontal { max-width: 100%; - min-width: 960px; /* ~3 × post min-width (300px) + borders and padding */ + min-width: 960px; } .post-cards--horizontal .post-cards__list { @@ -103,15 +103,6 @@ flex-direction: column; gap: var(--space-medium); padding: var(--space-card); - border-radius: var(--border-radius-xl); - border: 1px solid var(--color-stroke-weak); - background: var(--color-surface-weak); -} - -.post-cards .post-card { - border: none; - border-radius: 0; - background: transparent; } .post-card__title-block { @@ -240,14 +231,103 @@ color: var(--color-text-primary); } -.post-cards__cta .btn-secondary-grey:hover { - background-color: var(--color-primary-grey-300); - border-color: var(--color-primary-grey-300); +.post-cards--content .post-cards__list { + gap: 0; +} + +.post-cards--content-list .post-cards__item { + border-top: 1px solid var(--color-stroke-weak); + border-bottom: none; + border-left: none; + border-right: none; +} + +.post-cards--content-list .post-cards__item:last-child { + border-bottom: 1px solid var(--color-stroke-weak); +} + +.post-cards--content-list .content-card { + background: transparent; + border-radius: 0; + border: none; +} + +.post-cards--content-card .post-cards__item { + border: none; + padding: 0; +} + +.post-cards--content-card .post-cards__list { + gap: var(--space-card); +} + +.post-cards--content-card .content-card { + background: var(--color-bg-secondary); + border: 1px solid var(--color-border); + border-radius: var(--card-radius); +} + +/* Content card base styles live in content.css; here only post-cards context overrides. */ +.post-cards--content-list .content-card--event .content-card__description { + font-size: var(--font-size-small); + line-height: var(--line-height-relaxed); +} +.post-cards--content-card .content-card--event .content-card__description { + font-size: var(--font-size-small); + line-height: var(--line-height-relaxed); +} + +.content-card__title-row { + display: flex; + align-items: center; + gap: var(--space-default); + width: 100%; +} + +.content-card--default-icon .content-card__title { + flex: 1 1 0; + min-width: 0; +} + +.content-card--default-icon .content-card__description { + font-size: var(--font-size-small); + line-height: 1.24; + letter-spacing: -0.14px; +} + +.content-card__body { + display: flex; + flex-direction: column; + gap: var(--space-medium); + width: 100%; +} + +.content-card__icon { + width: 24px; + height: 24px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--color-icon-primary); +} + +.content-card__icon svg { + width: 24px; + height: 24px; +} + +.content-card__cta { + font-family: var(--font-sans); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + line-height: 1; + letter-spacing: -0.12px; color: var(--color-text-primary); + text-decoration: underline; + text-decoration-skip-ink: none; } -.post-cards__cta .btn-secondary:hover { - background-color: var(--color-button-secondary); - border-color: var(--color-secondary-dark-blue); - color: var(--color-secondary-dark-blue); +.content-card__cta:hover { + text-decoration: underline; } diff --git a/templates/v3/examples/_v3_example_section.html b/templates/v3/examples/_v3_example_section.html index ed234cd1..0992ec14 100644 --- a/templates/v3/examples/_v3_example_section.html +++ b/templates/v3/examples/_v3_example_section.html @@ -122,21 +122,31 @@

    Event cards

    -

    Content event card

    +

    Content event list – clickable cards (link wraps each card)

    -
    -

    Content event card

    -

    Default and contained variants.

    -
    -
    -

    Default

    - {% include "v3/includes/_content_event_card_item.html" with title="Boost 1.83.0 closed for major changes" description="Release closed for major code changes. Still open for serious problem fixes and docs changes without release manager review." date="23/11/23" datetime="2023-11-23" %} -
    -
    -

    Contained

    - {% include "v3/includes/_content_event_card_item.html" with title="Boost 1.83.0 closed for major changes" description="Release closed for major code changes. Still open for serious problem fixes and docs changes without release manager review." date="23/11/23" datetime="2023-11-23" contained=True %} -
    -
    +
    +

    Releases

    +
      +
    • {% include "v3/includes/_content_event_card_item.html" with title="Boost 1.90.0 closed for major changes" description="Release closed for major code changes. Still open for serious problem fixes and docs changes without release manager review." date="29/10/25" datetime="29/10/25" card_url="#event-0" card_aria_label="Boost 1.90.0 closed for major changes" event_card_wrapper=False %}
    • +
    • {% include "v3/includes/_content_event_card_item.html" with title="C++ Now 2025 call for submissions" description="C++ Now conference is accepting talk proposals until March 15. Topics include modern C++, Boost libraries, and tooling." date="12/02/25" datetime="12/02/25" card_url="#event-1" card_aria_label="C++ Now 2025 call for submissions" event_card_wrapper=False %}
    • +
    • {% include "v3/includes/_content_event_card_item.html" with title="Boost 1.89.0 released" description="Boost 1.89.0 is available with updates to Asio, Beast, and several other libraries. See release notes for details." date="15/01/25" datetime="15/01/25" card_url="#event-2" card_aria_label="Boost 1.89.0 released" event_card_wrapper=False %}
    • +
    +
    View all
    +
    +
    +
    + +
    +

    Content event card – clickable cards (link wraps each card)

    +
    +
    +

    Releases

    +
      +
    • {% include "v3/includes/_content_event_card_item.html" with title="Boost 1.90.0 closed for major changes" description="Release closed for major code changes. Still open for serious problem fixes and docs changes without release manager review." date="29/10/25" datetime="29/10/25" card_url="#event-0" card_aria_label="Boost 1.90.0 closed for major changes" event_card_wrapper=True %}
    • +
    • {% include "v3/includes/_content_event_card_item.html" with title="C++ Now 2025 call for submissions" description="C++ Now conference is accepting talk proposals until March 15. Topics include modern C++, Boost libraries, and tooling." date="12/02/25" datetime="12/02/25" card_url="#event-1" card_aria_label="C++ Now 2025 call for submissions" event_card_wrapper=True %}
    • +
    • {% include "v3/includes/_content_event_card_item.html" with title="Boost 1.89.0 released" description="Boost 1.89.0 is available with updates to Asio, Beast, and several other libraries. See release notes for details." date="15/01/25" datetime="15/01/25" card_url="#event-2" card_aria_label="Boost 1.89.0 released" event_card_wrapper=True %}
    • +
    +
    View all
    diff --git a/templates/v3/includes/_content_event_card_item.html b/templates/v3/includes/_content_event_card_item.html index de8508c9..3375908f 100644 --- a/templates/v3/includes/_content_event_card_item.html +++ b/templates/v3/includes/_content_event_card_item.html @@ -1,21 +1,36 @@ {% comment %} - V3 Event content card – single event item (title, description, date). - Uses event-content from static/css/v3/content.css inside event-card from event-cards.css. + V3 Content event card – single event item (title, description, date). + Uses content-card from static/css/v3/content.css; event-card from event-cards.css when wrapped. Variables: - title (required) — event title - description (required) — event description - date (required) — human-readable date (e.g. "29/10/25") - datetime (optional) — value for

    {% endwith %}
    +
    +

    Thread Archive Card

    + {% with basic_card_data as card %} +
    + {% include "v3/includes/_thread_archive_card.html" %} +
    + {% endwith %} +

    Why Boost

    diff --git a/templates/v3/includes/_thread_archive_card.html b/templates/v3/includes/_thread_archive_card.html new file mode 100644 index 00000000..a5f40045 --- /dev/null +++ b/templates/v3/includes/_thread_archive_card.html @@ -0,0 +1,21 @@ +{% comment %} + Thread archive card. Links to the latest mailing list threads, as well as the archive. + + NOTE: This currently contains a placeholder image that should be replaced. +{% endcomment %} +{% load static %} + +
    +
    + Explore The Boost Developers Mailing List +
    +
    +
    + +
    +
    +
    + {% include 'v3/includes/_button.html' with style="primary" url="https://lists.boost.org/archives/list/boost@lists.boost.org/latest" label="Recent Threads" extra_classes="btn-flex" only %} + {% include 'v3/includes/_button.html' with style="secondary" url="https://lists.boost.org/archives/list/boost@lists.boost.org/" label="View Archive" extra_classes="btn-flex" only %} +
    +
    From 5374a04ffd463ab551a912ef0e90489103a1f867 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Thu, 5 Mar 2026 12:53:07 -0500 Subject: [PATCH 25/26] Bug: Correct styling on thread card header --- templates/v3/includes/_thread_archive_card.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/v3/includes/_thread_archive_card.html b/templates/v3/includes/_thread_archive_card.html index a5f40045..4dc8ed23 100644 --- a/templates/v3/includes/_thread_archive_card.html +++ b/templates/v3/includes/_thread_archive_card.html @@ -6,7 +6,7 @@ {% load static %}
    -
    +
    Explore The Boost Developers Mailing List

    From c701fc47a71d0609cd4edc97536ecaa31fc98130 Mon Sep 17 00:00:00 2001 From: Jeremy Childers <30885417+jlchilders11@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:28:22 -0500 Subject: [PATCH 26/26] Story: Add Testimonial Card Component (#2103) --- core/views.py | 42 ++++++++ static/css/v3/components.css | 1 + static/css/v3/testimonial-card.css | 102 ++++++++++++++++++ static/img/v3/demo_page/Avatar.png | Bin 0 -> 3234 bytes static/img/v3/demo_page/Badge.svg | 14 +++ static/js/carousel.js | 2 + .../v3/examples/_v3_example_section.html | 12 ++- templates/v3/includes/_testimonial_card.html | 30 ++++++ .../v3/includes/_testimonial_card_quote.html | 36 +++++++ 9 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 static/css/v3/testimonial-card.css create mode 100644 static/img/v3/demo_page/Avatar.png create mode 100644 static/img/v3/demo_page/Badge.svg create mode 100644 templates/v3/includes/_testimonial_card.html create mode 100644 templates/v3/includes/_testimonial_card_quote.html diff --git a/core/views.py b/core/views.py index 628c81d0..98dc39a3 100644 --- a/core/views.py +++ b/core/views.py @@ -1191,6 +1191,48 @@ def get_context_data(self, **kwargs): }, ] + context["testimonial_data"] = { + "heading": "What Engineers are saying", + "testimonials": [ + { + "quote": "I use Boost daily. I absolutely love it. It's wonderful. I could not do my job w/o it. Much of it is in the new C++11 standard too.", + "author": { + "name": "Name Surname", + "avatar_url": "/static/img/v3/demo_page/Avatar.png", + "role": "Contributor", + "role_badge": "/static/img/v3/demo_page/Badge.svg", + }, + }, + { + "quote": "I use Boost daily. I absolutely love it. It's wonderful. I could not do my job w/o it. Much of it is in the new C++11 standard too.", + "author": { + "name": "Name Surname", + "avatar_url": "/static/img/v3/demo_page/Avatar.png", + "role": "Contributor", + "role_badge": "/static/img/v3/demo_page/Badge.svg", + }, + }, + { + "quote": "I use Boost daily. I absolutely love it. It's wonderful. I could not do my job w/o it. Much of it is in the new C++11 standard too.", + "author": { + "name": "Name Surname", + "avatar_url": "/static/img/v3/demo_page/Avatar.png", + "role": "Contributor", + "role_badge": "/static/img/v3/demo_page/Badge.svg", + }, + }, + { + "quote": "I use Boost daily. I absolutely love it. It's wonderful. I could not do my job w/o it. Much of it is in the new C++11 standard too.", + "author": { + "name": "Name Surname", + "avatar_url": "/static/img/v3/demo_page/Avatar.png", + "role": "Contributor", + "role_badge": "/static/img/v3/demo_page/Badge.svg", + }, + }, + ], + } + latest = Version.objects.most_recent() if latest: lv = ( diff --git a/static/css/v3/components.css b/static/css/v3/components.css index e60dfb93..a275fe91 100644 --- a/static/css/v3/components.css +++ b/static/css/v3/components.css @@ -9,6 +9,7 @@ @import './header.css'; @import './footer.css'; @import './forms.css'; +@import './testimonial-card.css'; @import './card.css'; @import './event-cards.css'; @import './content.css'; diff --git a/static/css/v3/testimonial-card.css b/static/css/v3/testimonial-card.css new file mode 100644 index 00000000..27390cdf --- /dev/null +++ b/static/css/v3/testimonial-card.css @@ -0,0 +1,102 @@ +.testimonial-card { + max-width: 696px; +} + +.testimonial-card__carousel { + display: flex; + padding: 0 var(--space-large, 16px); + flex-direction: column; + align-items: flex-start; + gap: var(--space-large, 16px); + align-self: stretch; +} + +.testimonial-card__text-wrapper { + display: flex; + padding: var(--space-xl, 32px); + border-radius: var(--space-xl, 32px); + background: var(--color-surface-mid, #F7F7F8); +} + +.testimonial-card__text { + color: var(--color-text-primary, #050816); + font-family: var(--font-sans, "Mona Sans VF"); + font-size: var(--font-size-xl, 32px); + font-weight: var(--font-weight-regular); + line-height: var(--line-height-tight); + letter-spacing: var(--letter-spacing-display-regular); +} + +.testimonial-card__text-tail { + width: 32px; + height: var(--space-xl, 32px); + aspect-ratio: 1/1; + fill: var(--color-surface-mid, #F7F7F8); +} + +.testimonial-card__tail-wrapper { + display: flex; + padding: 0 var(--Chat-bubble-tail-left-padding, 60px); + align-items: center; +} + +.testimonial-card__author { + display: flex; + align-items: center; + gap: var(--space-medium, 12px); +} + +.testimonial-card__author-info { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: var(--space-s, 4px); + + + /* Sans/Desktop/Regular/XS/Default */ + font-family: var(--font-sans, "Mona Sans VF"); + font-size: var(--font-size-xs, 12px); + font-style: normal; + font-weight: 400; + line-height: 120%; + /* 14.4px */ + letter-spacing: -0.12px; +} + + +.testimonial-card__author-name-text { + color: var(--color-text-primary, #050816); +} + +.testimonial-card__author-role { + height: 16px; + gap: var(--space-default, 8px); + display: flex; + justify-content: center; + align-items: center; +} + +.testimonial-card__post { + padding: 0; + border: none; + min-width: 0; +} + +.testimonial-card__post .post-cards__item { + max-width: 100%; +} + +.testimonial-card__author-role-text { + color: var(--color-text-secondary, #585A64); +} + +.testimonial-card__author-role-badge { + width: 16px; + height: 16px; + aspect-ratio: 1/1; +} + +.testimonial-card__cards { + max-width: 100%; +} diff --git a/static/img/v3/demo_page/Avatar.png b/static/img/v3/demo_page/Avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..c1335a70aa6828e5d69a774af4ef401a9d301f84 GIT binary patch literal 3234 zcmV;T3|;eyP)(@INAOZoQOZ_5uq{7^P* z*brm1?c2B4wMNx%RCW1J_3NAOTH3E&_t;~Pfm^q3{bwb7*U~_%`jP?hwpNe* zs^5wxA1dKzRLFNN4K#vV{Gfvl3V{zi@W8VF{`<%Co_p>YRv_&1<;%kgl)L`=>&vQD ztIBV`{Z_8J>Z-DS{rb|kZ{PUdh$8K+ts2w4r4qii627%G*a!_(3!}?^`|VdwI_acx z*=3iNrAwCv>L;Ika#%+{)|fbPVyV_D#~yoZSYXPODdp;`uP)Q3O)DEWZY&M9&ed;Y z1!U96>ZV(1uo1A<3YT7bY3ba#bD&BQ-wCzPKKq1%haY};S-pC7Smb~M4v4-hR;(y} z`t&JXx^yX@e){R|SfM`!8XiSB9Os0`9d}&m-o1MudEtc@2D<6MeuSh*6(E9U=;A$L zmn>OQ)~s1mT3cJo?Af!+rcIklqpcwYEDAa8o_p>oXPj|HIsNq0%h5+4-QffJP;9Tg z_KGXPSw$nfhDMtAbLPw`3l=OWuf6tK89H=mjA_IYEtLYj>+M|mOK_jb&p-eCa??#W z1;_EZTEyu!O}XO`GRleW09FNXO>1ZgMbJ~%8YG1?=T^MVU52v9!$_~W{)(Y!0v2@JUxUU;F58a1l)?AbHQ z2@0Ki?z!dr@4pY>AUJjE)KG5Bm@)C~#1l^}mtTH)dGNsp%O{_F5*ZtP_>S_SYuB!! z01Ba$vH}QSe)(lQ*VA@s0qyR)?+#Qsu%d0`$dN%86(l-9?9Myy3{>s7Y;Fjk=p&#$ z7hQBwdFY{s%9U4M8CUOT%$N~Ed+yb%R~a~PV0`Bf-+c4UU$;P7LD6>BS!b1xKKdvS z<^#5m!-o$KG*LvnMU6cbB)G5J2oE`F-`EI%8mX5XmvI3d~ zZ@A%xn2>-_!{>M3eK(ZigkOB|MVUKyZqS6n%50XgZL~kohXS|XetV3$g7dR|s)bRJ-dv9=A8O%aBfA-mDN2pY85cv7$ zpD$m1^;JCEA4C4>r=Jc>de#R$MI}44M<0E(Jonslbw{C&a=NxY`JINtGk#pp(J$29e0F)=)nOgmJtMP(1Mlp(Vta5 z|NQgv!3Q72dw|$C8zY}k)Hw6M@x~hsRif>3)wd_MFrl*c5lV$S4vs3asw^t@LpSRl z{U|Crp$n>*3+?pb^m500@4Xj^Fu|&~?a~Jlkt`=NIuIaB-z?5>IvrRDMX5xWpjTXR zMbJ%|K{0)m9wHe_<~ohEx{J9~$ikvh*6^8-GS2e>yr#g{Uw<7+vyNnDy!(0c=0(m> zKT${oL#5qm4OOFPKw$UIKs7=`%{TeLNd<)E1x`#j6ce@N0uIb7!lpU+31!j(xmLZF z*r@^;h4LP%kWPM!L_huXQ!>=y()tB3RvZdt=v>-Yg8 z_YJ$HKyBlQ_@Ae48oEeGW5np2#P)c4Ub^SRfrJV}0!&I1jG!Si}(sbXAzLETjmX+fB5yMf8r~ z99p8|_#%Dd%V5bXG!6pFTbA?IlDh7ElX8AI&oC@(uSC(d_!{^jNA^33uD`eA0(J z<4M#rSy6}r8SXq2Trz@;iN$>dp83>E=0clP${2kKrf}mA#{1cc>6@o|gq5}31JaMR zfBf;s-zm_HB2dr2YjMP+kY6J09!2tmI=@3m2_a}mC3Q@*53V7xVS+-ac2$T|@Yok+3RZ#VILwJZ;Ae}Xj{UjZNK|r-=5ff0>UReEO z2WAcli&A1gCslDO-V|;W6`hRZm;_Sc+VHb3rP8|AH6JB&gP&0lTk`a~rF!V?M$*8& z?3h;@5{v6YA=Jp0RB$W~Y3M)-2o%UMe&%NB^dD4s<&{?=l(G=#R)Qoj#t@Xhuz3zkdC~pX3TY1+?7Np^|r~Lnh#I z=2ReQouNI-{Yt3*5VCWECZQm25{bvP)-N)}=Y-I9l4hJmGhcAPXPb_10T&jrlSXW^}54qnfI;-@7v7zg35JQnV%jL9)0? z^K&!{a>z{Y`Z%d@bmAL6zxCEzafs{0&8eJmEJs;=(8Af!0}nh<-hA`T_$Jb6gzwR# zM+5?EXAW>`2hjAbcJO*OS5DWb;$WLyRVK46oRTJyFe++f7OdTL>+2*s4{RMha#Gi~ z-+nv3b4CFog|UHGg3HEFJn=*Tl7EU7HuD(M8paA7K=O`E?SE8htE|-+Ns)B$#YGX5*@8dB(2rJUe)bCJQJ42oKxe3P$sx*{(pmoOziZIa}pBI z6>7%I4}^rcXu~P}VOtWD%z)v4EL5@P6k6@ + + + + + + + + + + + + + diff --git a/static/js/carousel.js b/static/js/carousel.js index eefdf5d8..34b3f76e 100644 --- a/static/js/carousel.js +++ b/static/js/carousel.js @@ -128,6 +128,8 @@ if (!root || !root.id) return; const track = root.querySelector('[data-carousel-track]'); const controls = document.getElementById(root.id + '-controls'); + console.log(track) + console.log(controls) if (!track || !controls) return; if (root.hasAttribute('data-carousel-infinite')) { diff --git a/templates/v3/examples/_v3_example_section.html b/templates/v3/examples/_v3_example_section.html index 1d7fdba9..c8226ace 100644 --- a/templates/v3/examples/_v3_example_section.html +++ b/templates/v3/examples/_v3_example_section.html @@ -121,7 +121,7 @@

    Carousel buttons

    -
    + {% comment %}

    Detail card carousel

    {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="post-cards-carousel-demo" heading="Libraries categories" cards=demo_cards_carousel_cards %} @@ -142,7 +142,7 @@

    Detail card carousel with infinite looping and autoplay

    {% include "v3/includes/_cards_carousel_v3.html" with carousel_id="post-cards-carousel-demo-infinite-autoplay" heading="Libraries categories" cards=demo_cards_carousel_cards infinite=True autoplay=True autoplay_delay=5000 %}
    -
    +
    {% endcomment %}

    Post cards list

    @@ -228,6 +228,14 @@

    Card with one button

    {% endwith %}
    + +
    +

    Testimonial Card

    +
    + {% include "v3/includes/_testimonial_card.html" with heading=testimonial_data.heading testimonials=testimonial_data.testimonials %} +
    +
    +

    Thread Archive Card

    {% with basic_card_data as card %} diff --git a/templates/v3/includes/_testimonial_card.html b/templates/v3/includes/_testimonial_card.html new file mode 100644 index 00000000..6ac81e72 --- /dev/null +++ b/templates/v3/includes/_testimonial_card.html @@ -0,0 +1,30 @@ +{% comment %} + Standalone testimonial card + Context: + -Testimonials, a list of testimonial objects, each containing text and an author. + -Heading, the title that should be displayed at the top of the card + Renders only when there is content, only displays fields for which content is provided +{% endcomment %} + +{% with carousel_id='_testimonials_cards_carousel' %} +
    +
    + {{ heading }} + {% include 'v3/includes/_carousel_buttons.html' with carousel_id=carousel_id %} +
    +
    + +
    +{% endwith %} diff --git a/templates/v3/includes/_testimonial_card_quote.html b/templates/v3/includes/_testimonial_card_quote.html new file mode 100644 index 00000000..de1a217b --- /dev/null +++ b/templates/v3/includes/_testimonial_card_quote.html @@ -0,0 +1,36 @@ +{% comment %} +The inner component of a testimonial card, should generally only be used by _testimonial_card + inputs: + avatar_url: Url for the author avatar_url + name: Name of the author + quote: Testimonial quote to be displayed + role: Author role (moderator, maintainer, contributer, etc.) + role_badge: url for an icon representing the role +{% endcomment %} + +
    +
    +
    {{ quote }}
    +
    +
    + + + +
    +
    +
    + {% include "v3/includes/_avatar_v3.html" with src=avatar_url %} +
    + {% if name %} + {{ name }} + {% endif %} + + {% if role %} + {{ role }} + {% endif %} + {% if role_badge %} + + {% endif %} + +
    +