diff --git a/celerybeat-schedule-shm b/celerybeat-schedule-shm new file mode 100644 index 000000000..461441de9 Binary files /dev/null and b/celerybeat-schedule-shm differ diff --git a/celerybeat-schedule-wal b/celerybeat-schedule-wal new file mode 100644 index 000000000..a7a50f1d8 Binary files /dev/null and b/celerybeat-schedule-wal differ diff --git a/core/views.py b/core/views.py index b076de929..98dc39a37 100644 --- a/core/views.py +++ b/core/views.py @@ -1043,7 +1043,46 @@ class V3ComponentDemoView(TemplateView): template_name = "base.html" def get_context_data(self, **kwargs): + from libraries.models import LibraryVersion + from libraries.utils import build_library_intro_context + + 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""" + context = super().get_context_data(**kwargs) + context["code_demo_beast"] = CODE_DEMO_BEAST + context["code_demo_hello"] = CODE_DEMO_HELLO + context["code_demo_install"] = CODE_DEMO_INSTALL context["popular_terms"] = [ {"label": "Networking"}, {"label": "Math"}, @@ -1070,8 +1109,129 @@ def get_context_data(self, **kwargs): {"value": "networking", "label": "Networking"}, ] ) - from libraries.models import LibraryVersion - from libraries.utils import build_library_intro_context + context["basic_card_data"] = { + "title": "Found a Bug?", + "text": "We rely on developers like you to keep Boost solid. Here's how to report issues that help the whole comm", + "primary_button_url": "www.example.com", + "primary_button_label": "Primary Button", + "secondary_button_url": "www.example.com", + "secondary_button_label": "Secondary Button", + } + + context["demo_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"), + }, + ] + + 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: diff --git a/static/css/v3/buttons.css b/static/css/v3/buttons.css index 3c80c804a..21f546ad2 100644 --- a/static/css/v3/buttons.css +++ b/static/css/v3/buttons.css @@ -45,6 +45,12 @@ min-width: 128px; } +.btn-row>.btn-flex { + flex: 1 0 0; + display: flex; + min-width: 128px; +} + .btn-primary { background-color: var(--color-button-secondary, #fff); border-color: var(--color-stroke-strong, #05081640); diff --git a/static/css/v3/card.css b/static/css/v3/card.css new file mode 100644 index 000000000..8ed6c0acd --- /dev/null +++ b/static/css/v3/card.css @@ -0,0 +1,63 @@ +/* + Styles for the basic card, as well as some basic structural members for any children based off the basic card + + card - basic shared styling and layout for cards + card__column - flex column for material in the card + card__center - Centers its children inside the flex parent + card__title - Styles the material bolded for title presentation + card__hr - a Horizontal rule for separating the card and being dark mode responsive + +*/ + +.card { + /*Layout*/ + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--space-large, 16px); + /*Style*/ + width: 100%; + border-radius: var(--border-radius-xl); + border: 1px solid var(--color-stroke-weak, rgba(5, 8, 22, 0.10)); + background: var(--color-surface-weak, #FFF); +} + +.basic-card { + max-width: 458px; +} + +.card .btn-row { + width: 100%; +} + +.card__column { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--space-large, 16px); +} + +.card__header { + align-self: stretch; + display: flex; + padding: 0 var(--space-large, 1rem); +} + +.card__title { + flex: 1 0 0; + color: var(--color-text-primary, #050816); + font-family: var(--font-display, "Mona Sans Display SemiCondensed"); + font-size: var(--font-size-large, 24px); + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: var(--line-height-tight); + letter-spacing: var(--letter-spacing-display-regular); +} + +.card__hr { + width: 100%; + height: 1px; + background: var(--color-stroke-weak, rgba(5, 8, 22, 0.10)); + border: none; + margin: 0; +} diff --git a/static/css/v3/carousel-buttons.css b/static/css/v3/carousel-buttons.css index a6fc016e2..4ece3c982 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/category-tags.css b/static/css/v3/category-tags.css index f4094bb7b..e578b313d 100644 --- a/static/css/v3/category-tags.css +++ b/static/css/v3/category-tags.css @@ -11,6 +11,7 @@ text-decoration: none; cursor: pointer; transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease; + color: var(--color-text-secondary); } .category-tag--default { @@ -22,14 +23,13 @@ } .category-tag--neutral { - background: var(--color-primary-grey-100); + background: var(--color-tag-fill); border-color: var(--color-tag-stroke); - color: var(--color-text-secondary); } .category-tag--neutral:hover, .category-tag--neutral.category-tag--hover-state { - background: var(--color-primary-grey-200); + background: var(--color-tag-fill-hover); border-color: var(--color-tag-stroke); color: var(--color-text-secondary); } @@ -37,40 +37,37 @@ .category-tag--green { background: var(--color-surface-strong-accent-green-default); border-color: var(--color-accent-strong-green); - color: var(--color-text-on-accent); } .category-tag--green:hover, .category-tag--green.category-tag--hover-state { background: var(--color-surface-strong-accent-green-hover); border-color: var(--color-accent-strong-green); - color: var(--color-text-on-accent); + color: var(--color-text-secondary); } .category-tag--yellow { background: var(--color-surface-strong-accent-yellow-default); border-color: var(--color-accent-strong-yellow); - color: var(--color-text-on-accent); } .category-tag--yellow:hover, .category-tag--yellow.category-tag--hover-state { background: var(--color-surface-strong-accent-yellow-hover); border-color: var(--color-accent-strong-yellow); - color: var(--color-text-on-accent); + color: var(--color-text-secondary); } .category-tag--teal { background: var(--color-surface-strong-accent-teal-default); border-color: var(--color-accent-strong-teal); - color: var(--color-text-on-accent); } .category-tag--teal:hover, .category-tag--teal.category-tag--hover-state { background: var(--color-surface-strong-accent-teal-hover); border-color: var(--color-accent-strong-teal); - color: var(--color-text-on-accent); + color: var(--color-text-secondary); } .version-tag { @@ -86,62 +83,53 @@ } .version-tag--default { - background: var(--color-primary-grey-100); + background: var(--color-tag-fill); } .version-tag--hover { - background: var(--color-primary-grey-200); + background: var(--color-tag-fill-hover); } - -.category-cards-demo { +.category-cards { font-family: var(--font-sans); color: var(--color-text-primary); } -.category-cards-demo__heading { +.category-cards__heading { margin: 0 0 var(--space-large); font-size: var(--font-size-medium); font-weight: var(--font-weight-medium); } -.category-cards-demo__list { - display: flex; - flex-wrap: wrap; - gap: var(--space-default); - align-items: center; -} - -.category-cards-demo__block { - margin-bottom: var(--space-large); -} - -.category-cards-demo__block:last-child { - margin-bottom: 0; -} - -.category-cards-demo__label { - display: block; - margin-bottom: var(--space-default); - font-size: var(--font-size-xs); +/* Dark theme: use semantic variables from themes.css only (no raw primitives) */ +html.dark .category-tag--neutral { + background: var(--color-tag-neutral-bg); + border-color: var(--color-tag-neutral-border); color: var(--color-text-secondary); } -.category-cards-demo__row { - display: flex; - flex-wrap: wrap; - gap: 16px; - align-items: center; +html.dark .category-tag--neutral:hover, +html.dark .category-tag--neutral.category-tag--hover-state { + background: var(--color-tag-neutral-bg-hover); + border-color: var(--color-tag-neutral-border); + color: var(--color-text-primary); } -.category-cards-demo__group { - display: flex; - gap: 16px; - align-items: center; +html.dark .category-tag--green, +html.dark .category-tag--yellow, +html.dark .category-tag--teal { + background: var(--color-tag-colored-bg); + border-color: var(--color-tag-colored-border); + color: var(--color-tag-colored-text); } -.category-cards-demo__state-label { - width: 56px; - font-size: var(--font-size-xs); - color: var(--color-text-secondary); +html.dark .category-tag--green:hover, +html.dark .category-tag--yellow:hover, +html.dark .category-tag--teal:hover, +html.dark .category-tag--green.category-tag--hover-state, +html.dark .category-tag--yellow.category-tag--hover-state, +html.dark .category-tag--teal.category-tag--hover-state { + background: var(--color-tag-colored-bg-hover); + border-color: var(--color-tag-colored-border); + color: var(--color-tag-colored-text); } diff --git a/static/css/v3/code-block.css b/static/css/v3/code-block.css index 163cd14e2..cdb7993d0 100644 --- a/static/css/v3/code-block.css +++ b/static/css/v3/code-block.css @@ -6,12 +6,12 @@ --code-block-text: var(--color-syntax-text); --code-block-bg-standalone: var(--color-primary-grey-100); --code-block-border-standalone: var(--color-border); - --code-block-copy-icon-size: var(--space-card); + --code-block-bg-grey: var(--color-primary-grey-100); + --code-block-copy-icon-size: var(--space-icon-copy); --code-block-copy-icon-color: var(--color-icon-secondary); --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); } .code-block--standalone { @@ -27,7 +27,7 @@ } .code-block--grey-bg { - background: var(--color-primary-grey-100); + background: var(--code-block-bg-grey); } .code-block--white-bg { @@ -59,7 +59,7 @@ } .code-block-card .code-block { - margin: 0 var(--space-medium); + margin: 0 var(--space-card); overflow: visible; } @@ -68,9 +68,34 @@ 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 { @@ -78,14 +103,49 @@ 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-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 { 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); @@ -93,9 +153,9 @@ } .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-small); + font-size: var(--font-size-medium); line-height: var(--line-height-relaxed); color: var(--color-text-secondary); } @@ -104,23 +164,6 @@ 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; @@ -161,6 +204,10 @@ color: var(--code-block-copy-icon-color-success); } +.code-block__copy[data-copied="error"] { + color: var(--color-text-error, #d32f2f); +} + .code-block__copy-icon { width: var(--code-block-copy-icon-size); height: var(--code-block-copy-icon-size); diff --git a/static/css/v3/components.css b/static/css/v3/components.css index 44abbeadc..a275fe91a 100644 --- a/static/css/v3/components.css +++ b/static/css/v3/components.css @@ -1,14 +1,16 @@ @import './fonts.css'; @import './foundations.css'; -@import './buttons.css'; @import './button-tooltip.css'; @import './post-cards.css'; +@import './buttons.css'; @import './avatar.css'; @import './carousel-buttons.css'; @import './v3-examples-section.css'; @import './header.css'; @import './footer.css'; @import './forms.css'; +@import './testimonial-card.css'; +@import './card.css'; @import './event-cards.css'; @import './content.css'; @import './why-boost-cards.css'; @@ -16,3 +18,4 @@ @import './code-block.css'; @import './search-card.css'; @import './library-intro-card.css'; +@import './thread-archive-card.css'; diff --git a/static/css/v3/content.css b/static/css/v3/content.css index 5cba3a969..fbbfc4a61 100644 --- a/static/css/v3/content.css +++ b/static/css/v3/content.css @@ -1,41 +1,3 @@ - -.event-content { - font-family: var(--font-sans); - color: var(--color-text-primary); - margin: 0; - padding: 0; - border-radius: var(--card-radius); - box-sizing: border-box; -} - -.event-content--card-contained { - background: var(--color-primary-grey-100); - padding: var(--card-padding); - border: 1px solid 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); @@ -50,6 +12,12 @@ position: relative; box-sizing: border-box; align-self: start; + transition: background-color 0.2s ease, border-color 0.2s ease; +} + +a:hover .content-detail-icon:not(.content-detail-icon--contained) { + background: var(--color-primary-grey-200); + border-color: var(--color-border); } .content-detail-icon__icon { @@ -62,6 +30,12 @@ justify-content: center; width: var(--space-card); height: var(--space-card); + transition: color 0.2s ease; +} + +.content-detail-icon__icon:hover { + color: var(--color-icon-primary); + cursor: pointer; } .content-detail-icon__icon svg { @@ -84,6 +58,11 @@ .content-detail-icon__title-link { color: inherit; text-decoration: none; + transition: color 0.2s ease; +} + +.content-detail-icon__title-link:hover { + color: var(--color-stroke-link-accent, var(--color-text-primary)); } .content-detail-icon__description { @@ -94,6 +73,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/cpp-highlight.css b/static/css/v3/cpp-highlight.css new file mode 100644 index 000000000..ba4a4cffe --- /dev/null +++ b/static/css/v3/cpp-highlight.css @@ -0,0 +1,85 @@ + +/* ========== 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: var(--color-syntax-keyword, #00f); + } + + .code-block code.cpp-highlight .cpp-string, + .code-block .code-block__inner code.cpp-highlight .cpp-string { + color: var(--color-syntax-string, #a31515); + } + + .code-block code.cpp-highlight .cpp-preprocessor, + .code-block .code-block__inner code.cpp-highlight .cpp-preprocessor { + color: var(--color-syntax-preprocessor, #6f008a); + } + + .code-block code.cpp-highlight .cpp-comment, + .code-block .code-block__inner code.cpp-highlight .cpp-comment { + color: var(--color-syntax-comment, #008000); + font-style: italic; + } + + .code-block code.cpp-highlight .cpp-attribute, + .code-block .code-block__inner code.cpp-highlight .cpp-attribute { + color: var(--color-syntax-attribute, #9e9e9e); + } + + .code-block code.cpp-highlight .cpp-number, + .code-block .code-block__inner code.cpp-highlight .cpp-number { + color: var(--color-syntax-number, #098658); + } + + .code-block code.cpp-highlight .cpp-function, + .code-block .code-block__inner code.cpp-highlight .cpp-function { + color: var(--color-syntax-function, #267f99); + } + + /* Light theme – highlight.js classes (same colors as cpp-highlight) */ + .code-block .hljs-keyword, + .code-block .hljs-selector-tag, + .code-block .hljs-addition { + color: var(--color-syntax-keyword, #00f); + } + + .code-block .hljs-string { + color: var(--color-syntax-string, #a31515); + } + + .code-block .hljs-meta, + .code-block .hljs-meta .hljs-keyword { + color: var(--color-syntax-preprocessor, #6f008a); + } + + .code-block .hljs-comment, + .code-block .hljs-quote { + color: var(--color-syntax-comment, #008000); + font-style: italic; + } + + .code-block .hljs-attribute, + .code-block .hljs-variable, + .code-block .hljs-template-variable { + color: var(--color-syntax-attribute, #9e9e9e); + } + + .code-block .hljs-number, + .code-block .hljs-literal { + color: var(--color-syntax-number, #098658); + } + + .code-block .hljs-built_in, + .code-block .hljs-class .hljs-title { + color: var(--color-syntax-function, #267f99); + } diff --git a/static/css/v3/event-cards.css b/static/css/v3/event-cards.css index 020d82a3a..a24cabb7a 100644 --- a/static/css/v3/event-cards.css +++ b/static/css/v3/event-cards.css @@ -1,38 +1,25 @@ - -.event-cards { - font-family: var(--font-sans); - color: var(--color-text-primary); - border-radius: var(--card-radius-lg); - padding: var(--space-large) 0; - width: 100%; - max-width: 458px; - min-width: 0; - box-sizing: border-box; - flex-shrink: 0; -} - .event-cards--white { background: var(--color-bg-secondary); border: 1px solid var(--color-border); } .event-cards--grey { - background: var(--color-primary-grey-100); + background: var(--color-surface-mid); border: 1px solid var(--color-border); } .event-cards--yellow { - background: var(--color-accent-weak-yellow); + background: var(--color-surface-weak-accent-yellow); border: 1px solid var(--color-accent-strong-yellow); } .event-cards--beige { - background: var(--color-accent-weak-green); + background: var(--color-surface-weak-accent-green); border: 1px solid var(--color-accent-strong-green); } .event-cards--teal { - background: var(--color-accent-weak-teal); + background: var(--color-surface-weak-accent-teal); border: 1px solid var(--color-accent-strong-teal); } @@ -50,7 +37,7 @@ padding: 0; display: flex; flex-direction: column; - gap: var(--space-large); + gap: var(--space-default); } .event-cards--white .event-cards__list { @@ -67,40 +54,12 @@ padding-top: var(--space-medium); } -.event-cards--white .event-card { - background: transparent; - box-shadow: none; - margin: 0; - padding: var(--space-medium) var(--space-card); -} - .event-card { background: var(--color-card-bg); border-radius: var(--card-radius); - padding: var(--card-padding); margin: 0 var(--space-card); } -.event-card__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); -} - -.event-card__description { - margin: 0 0 var(--space-default); - font-size: var(--font-size-small); - line-height: var(--line-height-relaxed); - color: var(--color-text-secondary); -} - -.event-card__date { - font-size: var(--font-size-small); - color: var(--color-text-tertiary); -} - .event-cards__buttons { display: flex; gap: var(--button-gap); @@ -108,68 +67,17 @@ padding-top: var(--space-xl); } -.event-cards__btn { +.event-cards__buttons .btn { flex: 1 1 0; - min-width: 0; - padding: var(--space-default) var(--space-default); /* 8px 8px */ - border-radius: var(--button-radius); - font-size: var(--button-font-size); - font-weight: var(--button-font-weight); - line-height: var(--button-line-height); - font-family: var(--button-font-family); + min-width: 128px; + width: auto; text-decoration: none; - text-align: center; - box-sizing: border-box; - color: var(--color-text-primary); -} - -.event-cards--white .event-cards__btn--primary, -.event-cards--grey .event-cards__btn--primary { - background: var(--color-button-primary); - border: none; -} - -.event-cards--white .event-cards__btn--secondary { - background: var(--color-button-secondary); - border: 1px solid var(--color-border); -} - -.event-cards--grey .event-cards__btn--secondary { - background: var(--color-primary-grey-100); - border: 1px solid var(--color-border); -} - -.event-cards--yellow .event-cards__btn--primary { - background: var(--color-surface-strong-accent-yellow-default); - border: none; -} - -.event-cards--yellow .event-cards__btn--secondary { - background: var(--color-accent-weak-yellow); - border: 1px solid var(--color-border); } -.event-cards--beige .event-cards__btn--primary { - background: var(--color-surface-strong-accent-green-default); - border: none; -} - -.event-cards--beige .event-cards__btn--secondary { - background: var(--color-accent-weak-green); - border: 1px solid var(--color-border); -} - -.event-cards--teal .event-cards__btn--primary { - background: var(--color-surface-strong-accent-teal-default); - border: none; -} - -.event-cards--teal .event-cards__btn--secondary { - background: var(--color-accent-weak-teal); - border: 1px solid var(--color-border); +.event-cards__buttons .btn:hover { + text-decoration: none; } -/* Gallery: event cards en fila (wrap) para no apilarse */ .event-cards-gallery { display: flex; flex-wrap: wrap; @@ -184,3 +92,23 @@ min-width: 0; 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{ + background: var(--color-primary-grey-850); + border: 1px solid var(--color-primary-grey-850); +} + +html.dark .event-cards--grey { + background: var(--color-primary-grey-900); +} diff --git a/static/css/v3/index.css b/static/css/v3/index.css index 57d02b4c8..72f750c54 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/post-cards.css b/static/css/v3/post-cards.css index fab7120bc..95b98da13 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); @@ -65,7 +55,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 +93,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 +221,248 @@ 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-surface-weak); + border: 1px solid var(--color-stroke-weak); + border-radius: var(--border-radius-l); +} + +.content-card { + display: flex; + flex-direction: column; + gap: var(--space-medium); + padding: var(--space-card); + font-family: var(--font-sans); + color: var(--color-text-primary); + transition: background-color 0.2s ease, border-color 0.2s ease; +} + +.content-card__title { + margin: 0; + padding: 0; + font-family: var(--font-sans); + font-size: var(--font-size-base); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-default); + letter-spacing: -0.16px; + color: var(--color-text-primary); +} + +.content-card__description { + margin: 0; + padding: 0; + font-family: var(--font-sans); + font-weight: var(--font-weight-regular); + color: var(--color-text-secondary); +} + +.content-card--event .content-card__description { + font-size: var(--font-size-xs); + line-height: var(--line-height-default); + letter-spacing: -0.12px; +} + +.content-card__date { + margin: 0; + font-family: var(--font-sans); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-regular); + line-height: var(--line-height-default); + letter-spacing: -0.12px; + 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; +} + +/* Content / detail cards (title + icon + description + optional CTA) */ +.post-cards--content .post-cards__list { + gap: 0; +} + +.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-detail-icon { + min-width: 300px; +} + +.post-cards--content-card.post-cards--horizontal .post-cards__item { + 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%; } -.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); +.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/css/v3/semantics.css b/static/css/v3/semantics.css index 449a00097..6aa86f988 100644 --- a/static/css/v3/semantics.css +++ b/static/css/v3/semantics.css @@ -98,6 +98,7 @@ TAG SEMANTIC COLORS ============================================ */ --color-tag-fill: var(--color-primary-grey-100); + --color-tag-fill-hover: var(--color-primary-grey-200); --color-tag-stroke: #0508161A; /* ============================================ diff --git a/static/css/v3/spacing.css b/static/css/v3/spacing.css index 8e7040c3f..9e30def27 100644 --- a/static/css/v3/spacing.css +++ b/static/css/v3/spacing.css @@ -20,6 +20,7 @@ --space-xl: 32px; --space-xxl: 60px; --space-avatar: 48px; + --space-icon-copy: 16px; } /* Mobile Spacing Overrides */ @@ -32,6 +33,7 @@ --space-xxl: 48px; --space-avatar: 40px; --space-card: 12px; + --space-icon-copy: 20px; /* xs, s unchanged on mobile */ } } diff --git a/static/css/v3/testimonial-card.css b/static/css/v3/testimonial-card.css new file mode 100644 index 000000000..27390cdfd --- /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/css/v3/themes.css b/static/css/v3/themes.css index 37dcf0210..07b24abad 100644 --- a/static/css/v3/themes.css +++ b/static/css/v3/themes.css @@ -82,11 +82,37 @@ html.dark { /* Tag */ --color-tag-fill: var(--color-primary-grey-900); + --color-tag-fill-hover: var(--color-primary-grey-850); --color-tag-stroke: #FFFFFF1A; + /* Category tag dark: single source for neutral and colored tag surfaces (no raw primitives in category-tags.css) */ + --color-tag-neutral-bg: var(--color-primary-grey-900); + --color-tag-neutral-bg-hover: var(--color-primary-grey-800); + --color-tag-neutral-border: var(--color-primary-grey-900); + --color-tag-colored-bg: var(--color-primary-grey-800); + --color-tag-colored-bg-hover: var(--color-primary-grey-850); + --color-tag-colored-border: var(--color-primary-grey-800); + --color-tag-colored-text: var(--color-text-primary); + + /* Code block (dark backgrounds + contrast so code is readable) */ + --code-block-bg-standalone: var(--color-primary-grey-900); + --code-block-border-standalone: var(--color-border); + --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) */ + --color-syntax-keyword: #c678dd; + --color-syntax-string: #98c379; + --color-syntax-preprocessor: #e06c75; + --color-syntax-comment: #5c6370; + --color-syntax-attribute: #d19a66; + --color-syntax-number: #98c379; + --color-syntax-function: #61afef; + --color-syntax-text: #abb2bf; /* Text */ --color-text-error: var(--color-error-mid); --color-text-link-accent: var(--color-secondary-mid-blue); + --color-text-on-accent: var(--color-primary-white); --color-text-reversed: var(--color-primary-black); --color-text-tertiary: var(--color-primary-grey-600); } diff --git a/static/css/v3/thread-archive-card.css b/static/css/v3/thread-archive-card.css new file mode 100644 index 000000000..b643da002 --- /dev/null +++ b/static/css/v3/thread-archive-card.css @@ -0,0 +1,8 @@ +.thread-archive-card img { + width: 100%; + aspect-ratio: 16/9; +} + +.thread-archive-card .card__column { + width: 100%; +} diff --git a/static/css/v3/v3-examples-section.css b/static/css/v3/v3-examples-section.css index 5c5239889..5783d8049 100644 --- a/static/css/v3/v3-examples-section.css +++ b/static/css/v3/v3-examples-section.css @@ -30,11 +30,15 @@ box-sizing: border-box; width: 100%; padding: var(--space-xl, 32px); - background: var(--color-surface-mid); + background: var(--color-surface-weak); border: 1px solid var(--color-border); border-radius: 8px; } +html.dark .v3-examples-section__example-box { + background: var(--color-surface-mid) +} + .v3-examples-section__tools { display: flex; flex-wrap: wrap; @@ -168,3 +172,56 @@ 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; +.v3-examples-section .category-cards .category-cards__gallery-block { + margin-bottom: var(--space-large); +} + +.v3-examples-section .category-cards .category-cards__gallery-block:last-child { + margin-bottom: 0; +} + +.v3-examples-section .category-cards .category-cards__gallery-label { + display: block; + margin-bottom: var(--space-default); + font-size: var(--font-size-xs); + color: var(--color-text-secondary); +} + +.v3-examples-section .category-cards .category-cards__gallery-row { + display: flex; + flex-wrap: wrap; + gap: var(--space-card, 16px); + align-items: center; +} + +.v3-examples-section .category-cards .category-cards__gallery-group { + display: flex; + gap: var(--space-card, 16px); + align-items: center; +} + +.v3-examples-section .category-cards .category-cards__gallery-state-label { + width: 56px; + font-size: var(--font-size-xs); + color: var(--color-text-secondary); +} diff --git a/static/img/checker.png b/static/img/checker.png new file mode 100644 index 000000000..126b647a1 Binary files /dev/null and b/static/img/checker.png differ diff --git a/static/img/v3/demo_page/Avatar.png b/static/img/v3/demo_page/Avatar.png new file mode 100644 index 000000000..c1335a70a Binary files /dev/null and b/static/img/v3/demo_page/Avatar.png differ diff --git a/static/img/v3/demo_page/Badge.svg b/static/img/v3/demo_page/Badge.svg new file mode 100644 index 000000000..d3a7db143 --- /dev/null +++ b/static/img/v3/demo_page/Badge.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/static/js/carousel.js b/static/js/carousel.js new file mode 100644 index 000000000..34b3f76eb --- /dev/null +++ b/static/js/carousel.js @@ -0,0 +1,171 @@ + +(function () { + + const CAROUSEL_STEP_PX_FALLBACK = 320; + const SCROLL_RESET_EPSILON = 2; + const DEFAULT_AUTOPLAY_MS = 4000; + const CAROUSEL_ITEM_SELECTOR = '[data-carousel-item]'; + + function getStepPx(track) { + const first = track.querySelector(CAROUSEL_ITEM_SELECTOR); + if (first) { + 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 CAROUSEL_STEP_PX_FALLBACK; + } + + function scrollCarousel(track, direction, smooth) { + if (!track) return; + 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' }); + } + + 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(CAROUSEL_ITEM_SELECTOR); + if (items.length === 0) return; + + let setCount = 1; + const appendCloneSet = () => { + const list = track.querySelectorAll(CAROUSEL_ITEM_SELECTOR); + 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'); + console.log(track) + console.log(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) { + prevBtn.addEventListener('click', function () { 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, () => { + 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)); + } + } + + function init() { + document.querySelectorAll('[data-carousel]').forEach(initCarousel); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/static/js/code-block-copy.js b/static/js/code-block-copy.js deleted file mode 100644 index fd02e8878..000000000 --- a/static/js/code-block-copy.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Code block copy: al hacer clic en .code-block__copy, copia el texto del código - * al portapapeles, pone data-copied="true" en el botón y muestra el icono check. - * Tras COPY_FEEDBACK_MS vuelve al estado inicial. - */ -(function () { - var COPY_FEEDBACK_MS = 2000; - - function init() { - document.querySelectorAll(".code-block__copy").forEach(function (btn) { - if (btn.dataset.codeBlockCopyInit) return; - btn.dataset.codeBlockCopyInit = "true"; - btn.addEventListener("click", handleCopyClick); - }); - } - - function handleCopyClick(ev) { - var btn = ev.currentTarget; - var block = btn.closest(".code-block"); - if (!block) return; - var codeEl = block.querySelector(".code-block__inner code"); - if (!codeEl) return; - var text = codeEl.textContent || codeEl.innerText || ""; - - if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(text).then( - function () { - setCopiedState(btn, true); - setTimeout(function () { - setCopiedState(btn, false); - }, COPY_FEEDBACK_MS); - }, - function () { - setCopiedState(btn, true); - setTimeout(function () { - setCopiedState(btn, false); - }, COPY_FEEDBACK_MS); - } - ); - } else { - setCopiedState(btn, true); - setTimeout(function () { - setCopiedState(btn, false); - }, COPY_FEEDBACK_MS); - } - } - - function setCopiedState(btn, copied) { - btn.setAttribute("data-copied", copied ? "true" : "false"); - btn.setAttribute("aria-label", copied ? "Copied" : "Copy code to clipboard"); - } - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", init); - } else { - init(); - } -})(); diff --git a/static/js/code-block.js b/static/js/code-block.js new file mode 100644 index 000000000..10672dc4c --- /dev/null +++ b/static/js/code-block.js @@ -0,0 +1,170 @@ +/** + * Code block copy: on click of .code-block__copy, copies the code text to the clipboard, + * sets data-copied="true" on the button and shows the check icon. + * After COPY_FEEDBACK_MS it reverts to the initial state. + */ +(function () { + const COPY_FEEDBACK_MS = 2000; + + function init() { + document.querySelectorAll(".code-block__copy").forEach(function (btn) { + if (btn.dataset.codeBlockCopyInit) return; + btn.dataset.codeBlockCopyInit = "true"; + btn.addEventListener("click", handleCopyClick); + }); + } + + function fallbackCopy(text) { + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + document.body.appendChild(textarea); + textarea.select(); + let ok = false; + try { + ok = document.execCommand("copy"); + } catch (e) { + console.warn("code-block: execCommand('copy') failed", e); + } + document.body.removeChild(textarea); + return ok; + } + + function handleCopyClick(ev) { + const btn = ev.currentTarget; + const block = btn.closest(".code-block"); + if (!block) return; + const codeEl = block.querySelector(".code-block__inner code"); + if (!codeEl) return; + const text = codeEl.textContent || codeEl.innerText || ""; + + function showCopiedFeedback() { + setCopiedState(btn, "true"); + setTimeout(function () { + setCopiedState(btn, "false"); + }, COPY_FEEDBACK_MS); + } + + function showErrorFeedback() { + setCopiedState(btn, "error"); + setTimeout(function () { + setCopiedState(btn, "false"); + }, COPY_FEEDBACK_MS); + } + + if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text).then( + function () { + showCopiedFeedback(); + }, + function () { + if (fallbackCopy(text)) { + showCopiedFeedback(); + } else { + showErrorFeedback(); + } + } + ); + } else { + if (fallbackCopy(text)) { + showCopiedFeedback(); + } else { + showErrorFeedback(); + } + } + } + + function setCopiedState(btn, state) { + btn.setAttribute("data-copied", state); + const labels = { + true: "Copied", + false: "Copy code to clipboard", + error: "Copy failed" + }; + btn.setAttribute("aria-label", labels[state] || labels.false); + } + + // Replace C++ highlighting AFTER highlight.js processes blocks + // Let hljs work initially, then replace C++ blocks with custom highlighter + function processCppBlocks () { + const CppHighlight = typeof window !== "undefined" && window.CppHighlight; + if (!CppHighlight) return + // Selectors for C++ code blocks that highlight.js has already processed + const 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', + ] + + let 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) + } + }) + + } + + 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") { + document.addEventListener("DOMContentLoaded", function () { + init(); + initHighlight(); + }); + } else { + init(); + initHighlight(); + } + + // Also use MutationObserver to catch dynamically added content + // Process C++ blocks after highlight.js processes new content + if (typeof window.MutationObserver !== 'undefined') { + const observer = new window.MutationObserver(function (mutations) { + let 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 000000000..3de01266b --- /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/account/signup.html b/templates/account/signup.html old mode 100755 new mode 100644 diff --git a/templates/base.html b/templates/base.html index 7a39435ad..44226a278 100644 --- a/templates/base.html +++ b/templates/base.html @@ -45,7 +45,9 @@ {% flag "v3" %} - + + + {% endflag %} {% block extra_head %} @@ -408,6 +410,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 b01c01af9..c8226acef 100644 --- a/templates/v3/examples/_v3_example_section.html +++ b/templates/v3/examples/_v3_example_section.html @@ -112,6 +112,8 @@

Avatar

+ + + {% 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 %} +
+
+ + +
-

Post card (standalone)

+

Detail card carousel with autoplay

- {% 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 author_avatar_url="https://ui-avatars.com/api/?name=Richard+Thomson&size=48" %} - {% include "v3/includes/_post_card_v3.html" %} - {% endwith %} + {% 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 %}
-

Post cards

+

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

@@ -159,6 +175,15 @@

+
+

Detail card (single)

+
+ {% 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 author_avatar_url="https://ui-avatars.com/api/?name=Richard+Thomson&size=48" %} + {% include "v3/includes/_post_card_v3.html" %} + {% endwith %} +
+
+

Search Card

@@ -191,16 +216,52 @@

Form inputs

+ +
+

Basic Card

+ {% with basic_card_data as card %} +
+

Card with two buttons

+ {% include "v3/includes/_basic_card.html" with title=card.title text=card.text primary_button_url=card.primary_button_url primary_button_label=card.primary_button_label secondary_button_url=card.secondary_button_url secondary_button_label=card.secondary_button_label %} +

Card with one button

+ {% include "v3/includes/_basic_card.html" with title=card.title text=card.text primary_button_url=card.primary_button_url primary_button_label=card.primary_button_label %} +
+ {% 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 %} +
+ {% include "v3/includes/_thread_archive_card.html" %} +
+ {% endwith %} +

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="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" %} + {% 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" %}
@@ -221,21 +282,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
@@ -263,7 +334,7 @@

Content detail card

Code blocks

- {% include "v3/includes/_code_blocks_story.html" %} + {% include "v3/includes/_code_blocks_story.html" with code_standalone_1=code_demo_beast code_standalone_2=code_demo_beast code_card_1=code_demo_hello code_card_2=code_demo_beast code_card_3=code_demo_install %}
diff --git a/templates/v3/includes/_basic_card.html b/templates/v3/includes/_basic_card.html new file mode 100644 index 000000000..1bed48c2d --- /dev/null +++ b/templates/v3/includes/_basic_card.html @@ -0,0 +1,27 @@ +{% comment %} + Basic implementation of a card. Can be used on its own, or extended to make other cards + Inputs: + Title: Required, text that appears bolded at the top of the card + Text: Required, text that appears in the middle of the card + Primary Button Url, Label, and Style: Optional. Url is the destination of the button, label is the text of the button, style is from the style list + Secondary Button Url, Label, and Style: Optional, same use as primary +{% endcomment %} + +
+
+ {{ title }} +
+
+
+ {{ text }} +
+
+
+ {% if primary_button_url and primary_button_label %} + {% include 'v3/includes/_button.html' with style=primary_style url=primary_button_url label=primary_button_label extra_classes="btn-flex" only %} + {% endif %} + {% if secondary_button_url and secondary_button_label %} + {% include 'v3/includes/_button.html' with style=secondary_style url=secondary_button_url label=secondary_button_label extra_classes="btn-flex" only %} + {% endif %} +
+
diff --git a/templates/v3/includes/_button.html b/templates/v3/includes/_button.html new file mode 100644 index 000000000..01f892134 --- /dev/null +++ b/templates/v3/includes/_button.html @@ -0,0 +1,26 @@ +{% comment %} + V3 foundation button: style able button. Optional leading icon. + Variables: + label (required), + url (optional), + icon_html (optional; HTML for icon, use with |safe), + type (optional) + style (optional): Choice between + - primary + - secondary + - primary-outline + - secondary-grey + - green + - yellow + - teal + - error + Defaults to primary + extra classes (optional) : Any extra classes that the button should have, as a string + Icon must be wrapped in .... + Usage: {% include "v3/includes/_button.html" with label="Get started" icon_html='' style="secondary" extra_classes="btn-flex" %} +{% endcomment %} +{% if url %} +{% if icon_html %}{{ icon_html|safe }}{% endif %}{{ label }} +{% else %} + +{% endif %} diff --git a/templates/v3/includes/_cards_carousel_v3.html b/templates/v3/includes/_cards_carousel_v3.html new file mode 100644 index 000000000..343626862 --- /dev/null +++ b/templates/v3/includes/_cards_carousel_v3.html @@ -0,0 +1,50 @@ +{% 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 (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). + 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 + + 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 %} + + 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" %} + +{% endwith %} diff --git a/templates/v3/includes/_category_cards.html b/templates/v3/includes/_category_cards.html index db6f465f0..fd6023a15 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). @@ -13,72 +13,74 @@ Usage in style guide (all variants): {% include "v3/includes/_category_cards.html" %} {% endcomment %} -
-

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

+
+

{{ 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 %} -
- Default -
-
+ {% comment %}Variant gallery: Default and Tight, each with neutral / green / yellow / teal and normal + hover state. ul/li for semantics. Styles in v3-examples-section.css.{% endcomment %} + -
+ +
-
+ +
-
+ +
-
+ +
-
- Tight -
-
+ -
+ +
-
+ +
-
+ +
-
+ +
{% if show_version_tags %} -
- Version tags (not clickable) -
-
- Default +