mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-09-27 07:36:49 +00:00
657 lines
20 KiB
SCSS
657 lines
20 KiB
SCSS
// BDS CarouselFeatured Pattern Styles
|
|
// Brand Design System - Featured image carousel with two-column layout
|
|
//
|
|
// Layout:
|
|
// - Desktop (lg+): Two-column layout - Image LEFT (50%), Content RIGHT (50%)
|
|
// - Tablet/Mobile: Single column - Content TOP, Image BOTTOM
|
|
//
|
|
// Naming Convention: BEM with 'bds' namespace
|
|
// .bds-carousel-featured - Base section container
|
|
// .bds-carousel-featured__media-col - Image/media column wrapper
|
|
// .bds-carousel-featured__content-col - Content column wrapper
|
|
// .bds-carousel-featured__content - Content column
|
|
// .bds-carousel-featured__header - Header row (heading + nav)
|
|
// .bds-carousel-featured__heading - Section heading
|
|
// .bds-carousel-featured__nav - Navigation buttons wrapper
|
|
// .bds-carousel-featured__bottom - Bottom section (features + CTA)
|
|
// .bds-carousel-featured__features - Feature list container
|
|
// .bds-carousel-featured__feature - Individual feature item
|
|
// .bds-carousel-featured__feature-title - Feature title
|
|
// .bds-carousel-featured__feature-description - Feature description
|
|
// .bds-carousel-featured__cta - CTA section (buttons + mobile nav)
|
|
// .bds-carousel-featured__buttons - Button group wrapper
|
|
// .bds-carousel-featured__slides - Slides container
|
|
// .bds-carousel-featured__slide-track - Sliding track
|
|
// .bds-carousel-featured__slide - Individual slide
|
|
// .bds-carousel-featured__slide--active - Active slide modifier
|
|
// .bds-carousel-featured__image - Slide image
|
|
//
|
|
// Note: This file is imported within xrpl.scss after Bootstrap and project
|
|
// variables are loaded, so $grid-breakpoints, colors, and mixins are available.
|
|
|
|
// =============================================================================
|
|
// Design Tokens (from Figma)
|
|
// =============================================================================
|
|
// Note: Uses centralized spacing tokens from _spacing.scss.
|
|
|
|
// Spacing
|
|
$bds-carousel-featured-padding-sm: $bds-space-2xl $bds-space-lg; // 24px 16px
|
|
$bds-carousel-featured-padding-md: $bds-space-3xl $bds-space-2xl; // 32px 24px
|
|
$bds-carousel-featured-padding-lg: $bds-space-4xl $bds-space-3xl; // 40px 32px
|
|
|
|
// Content gap between image and content columns
|
|
$bds-carousel-featured-column-gap: $bds-space-sm; // 8px - spacing('sm')
|
|
|
|
// Transition - horizontal wipe (default 'slide' variant)
|
|
$bds-carousel-featured-transition: 400ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
// Transition - in-place crossfade ('fade' variant)
|
|
$bds-carousel-featured-fade-in: 260ms;
|
|
$bds-carousel-featured-fade-out: 200ms;
|
|
$bds-carousel-featured-fade-stagger: 60ms; // Offset between image, features, buttons
|
|
$bds-carousel-featured-fade-timing: cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
// =============================================================================
|
|
// Color Variant Configuration Map
|
|
// =============================================================================
|
|
|
|
// Define all background variants with their color properties (Dark Mode)
|
|
//
|
|
// Note on link colors: the green is picked from the VARIANT BACKGROUND, not the
|
|
// theme mode. Only the "neutral" variant is actually dark in dark mode - "grey"
|
|
// and "yellow" stay light in both modes - so a bright green would be unreadable
|
|
// on them. Light backgrounds get $green-500, the dark background gets $green-300.
|
|
$bds-carousel-featured-variants: (
|
|
"grey": (
|
|
"bg-color": $gray-300,
|
|
"text-color": $black,
|
|
"divider-color": $black,
|
|
"link-color": $green-500,
|
|
"link-hover": $green-400,
|
|
"link-visited": $lilac-400,
|
|
"link-focus": $black,
|
|
"button-variant": "black",
|
|
"button-bg": $black,
|
|
"button-color": $white,
|
|
"button-hover": $gray-500,
|
|
"button-active": $black,
|
|
),
|
|
"neutral": (
|
|
"bg-color": $black,
|
|
"text-color": $white,
|
|
"divider-color": $white,
|
|
"link-color": $green-300,
|
|
"link-hover": $green-200,
|
|
"link-visited": $lilac-300,
|
|
"link-focus": $white,
|
|
"button-variant": "green",
|
|
"button-bg": $green-300,
|
|
"button-color": $black,
|
|
"button-hover": $green-200,
|
|
"button-active": $green-300,
|
|
),
|
|
"yellow": (
|
|
"bg-color": $yellow-100,
|
|
"text-color": $black,
|
|
"divider-color": $black,
|
|
"link-color": $green-500,
|
|
"link-hover": $green-400,
|
|
"link-visited": $lilac-400,
|
|
"link-focus": $black,
|
|
"button-variant": "black",
|
|
"button-bg": $black,
|
|
"button-color": $white,
|
|
"button-hover": $gray-500,
|
|
"button-active": $black,
|
|
),
|
|
);
|
|
|
|
// Define light mode variant overrides
|
|
$bds-carousel-featured-variants-light: (
|
|
"grey": (
|
|
"bg-color": $gray-200,
|
|
"text-color": $black,
|
|
"divider-color": $black,
|
|
"link-color": $green-500,
|
|
"link-hover": $green-400,
|
|
"link-visited": $lilac-400,
|
|
"link-focus": $black,
|
|
),
|
|
"neutral": (
|
|
"bg-color": $white,
|
|
"text-color": $black,
|
|
"divider-color": $black,
|
|
"link-color": $green-500,
|
|
"link-hover": $green-400,
|
|
"link-visited": $lilac-400,
|
|
"link-focus": $black,
|
|
),
|
|
"yellow": (
|
|
"bg-color": $yellow-100,
|
|
"text-color": $black,
|
|
"divider-color": $black,
|
|
"link-color": $green-500,
|
|
"link-hover": $green-400,
|
|
"link-visited": $lilac-400,
|
|
"link-focus": $black,
|
|
),
|
|
);
|
|
|
|
// =============================================================================
|
|
// Mixins: Apply Background Variant Styles
|
|
// =============================================================================
|
|
|
|
// Links inside feature descriptions.
|
|
//
|
|
// Without this, these anchors fall through to the global Bootstrap rule
|
|
// `a { color: rgba(var(--bs-link-color-rgb), ...) }`, where :root sets
|
|
// --bs-link-color-rgb to 255,255,255 - so they render WHITE in both light and
|
|
// dark mode, which is invisible on every carousel background except neutral/dark.
|
|
//
|
|
// The `:not()` chain excludes anchors that already carry their own component
|
|
// styling (BDS links, buttons, card-shaped anchors), and it also lifts our
|
|
// specificity clear of the other global themed link rules in the codebase.
|
|
@mixin carousel-featured-links($config) {
|
|
.bds-carousel-featured__feature-description {
|
|
a:not(.bds-link):not(.bds-btn):not(.bds-card-offgrid):not(.bds-card-icon):not(.xrpl-link) {
|
|
color: map-get($config, "link-color");
|
|
font-weight: 400;
|
|
|
|
&:hover {
|
|
color: map-get($config, "link-hover");
|
|
text-decoration: underline;
|
|
}
|
|
|
|
&:active {
|
|
color: map-get($config, "link-color");
|
|
}
|
|
|
|
&:visited {
|
|
color: map-get($config, "link-visited");
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline: 2px solid map-get($config, "link-focus");
|
|
outline-offset: 1px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// A neutral Link here needs to match THIS PANEL's own text color, not the
|
|
// site theme: "grey" and "yellow" stay light-colored (text stays black) in
|
|
// both light and dark mode, so the sitewide neutral color (black light /
|
|
// white dark) goes white-on-light-yellow in dark mode. Inheriting tracks
|
|
// whichever color the surrounding text already resolves to, correct for
|
|
// every variant, without a per-variant override. Brand links are unaffected
|
|
// -- they already carry their own fixed-per-variant green above.
|
|
//
|
|
// Every state needs its own selector here, not just rest: the sitewide
|
|
// neutral mixin's :hover/:active/:focus/:visited blocks are separate,
|
|
// equally (or more) specific rules that this rest-only override doesn't
|
|
// touch, so without repeating it per state, hover/focus stayed white in dark
|
|
// mode even after rest was fixed.
|
|
.bds-carousel-featured .bds-carousel-featured__feature-description .xrpl-link.xrpl-link--neutral,
|
|
.bds-carousel-featured .bds-carousel-featured__feature-description .xrpl-link.xrpl-link--neutral:hover,
|
|
.bds-carousel-featured .bds-carousel-featured__feature-description .xrpl-link.xrpl-link--neutral:active,
|
|
.bds-carousel-featured .bds-carousel-featured__feature-description .xrpl-link.xrpl-link--neutral:focus,
|
|
.bds-carousel-featured .bds-carousel-featured__feature-description .xrpl-link.xrpl-link--neutral:visited {
|
|
color: inherit;
|
|
}
|
|
|
|
// Same problem, same fix, for the focus ring: .xrpl-link--ctx-on-theme's
|
|
// outline color also flips with the site theme (black light / white dark),
|
|
// not this panel's own fixed-light background. $black matches the brand
|
|
// links' own "link-focus" color for grey/yellow above, so both intentions
|
|
// get the same outline here.
|
|
.bds-carousel-featured .bds-carousel-featured__feature-description .xrpl-link.xrpl-link--ctx-on-theme:focus-visible {
|
|
outline-color: $black;
|
|
}
|
|
|
|
// Full variant mixin (for dark mode with button styles)
|
|
@mixin carousel-featured-variant($variant-name, $config) {
|
|
&--bg-#{$variant-name} {
|
|
background-color: map-get($config, "bg-color");
|
|
|
|
// Text colors
|
|
.bds-carousel-featured__heading,
|
|
.bds-carousel-featured__feature-title,
|
|
.bds-carousel-featured__feature-description {
|
|
color: map-get($config, "text-color");
|
|
}
|
|
|
|
// Divider color
|
|
.bds-divider {
|
|
background-color: map-get($config, "divider-color");
|
|
}
|
|
|
|
// Feature description links
|
|
@include carousel-featured-links($config);
|
|
|
|
// Carousel nav buttons - enabled states only
|
|
// Disabled states are handled by CarouselButton component styles
|
|
.bds-carousel-button--#{map-get($config, "button-variant")} {
|
|
background-color: map-get($config, "button-bg");
|
|
color: map-get($config, "button-color");
|
|
|
|
&:hover:not(:disabled) {
|
|
background-color: map-get($config, "button-hover");
|
|
}
|
|
|
|
&:active:not(:disabled) {
|
|
background-color: map-get($config, "button-active");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Light mode variant mixin (only colors, no button states)
|
|
@mixin carousel-featured-variant-light($variant-name, $config) {
|
|
.bds-carousel-featured--bg-#{$variant-name} {
|
|
background-color: map-get($config, "bg-color");
|
|
|
|
.bds-carousel-featured__heading,
|
|
.bds-carousel-featured__feature-title,
|
|
.bds-carousel-featured__feature-description {
|
|
color: map-get($config, "text-color");
|
|
}
|
|
|
|
.bds-divider {
|
|
background-color: map-get($config, "divider-color");
|
|
}
|
|
|
|
// Feature description links
|
|
@include carousel-featured-links($config);
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Base Container Styles
|
|
// =============================================================================
|
|
|
|
.bds-carousel-featured {
|
|
width: 100%;
|
|
overflow: hidden;
|
|
position: relative;
|
|
// Default background - dark mode default (grey variant)
|
|
background-color: $gray-300;
|
|
|
|
// Mobile (default)
|
|
padding: $bds-carousel-featured-padding-sm;
|
|
|
|
// Tablet
|
|
@include media-breakpoint-up(md) {
|
|
padding: $bds-carousel-featured-padding-md;
|
|
}
|
|
|
|
// Desktop
|
|
@include media-breakpoint-up(lg) {
|
|
padding: $bds-carousel-featured-padding-lg;
|
|
}
|
|
|
|
// Max width constraint
|
|
@include media-breakpoint-up(xl) {
|
|
max-width: 1280px;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
}
|
|
|
|
& > .bds-grid__row {
|
|
gap: $bds-space-2xl;
|
|
@include media-breakpoint-up(md) {
|
|
gap: $bds-space-3xl;
|
|
}
|
|
@include media-breakpoint-up(lg) {
|
|
gap: $bds-space-sm;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Background Color Variants
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Dark Mode (default) - Generate all variant styles using the mixin
|
|
@include bds-theme-mode(dark) {
|
|
@each $variant-name, $config in $bds-carousel-featured-variants {
|
|
@include carousel-featured-variant($variant-name, $config);
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Content Column
|
|
// =============================================================================
|
|
|
|
.bds-carousel-featured__content-col {
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
@include media-breakpoint-up(lg) {
|
|
// Stretch to match image height
|
|
align-self: stretch;
|
|
// Add 8px padding-left to create 16px total gap (8px row gap + 8px padding)
|
|
padding-left: $bds-space-sm; // 8px - spacing('sm')
|
|
}
|
|
}
|
|
|
|
.bds-carousel-featured__content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
gap: 0; // Use space-between instead
|
|
justify-content: space-between; // Header at top, features+CTA at bottom
|
|
|
|
@include media-breakpoint-up(lg) {
|
|
flex: 1;
|
|
min-height: auto; // Reset min-height on desktop
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Header Section (Heading + Nav)
|
|
// =============================================================================
|
|
|
|
.bds-carousel-featured__header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
width: 100%;
|
|
flex-direction: column-reverse;
|
|
|
|
@include media-breakpoint-down(lg) {
|
|
margin-bottom: $bds-space-xl;
|
|
}
|
|
|
|
@include media-breakpoint-up(md) {
|
|
flex-direction: row;
|
|
}
|
|
}
|
|
|
|
.bds-carousel-featured__heading {
|
|
margin: 0;
|
|
// Dark mode default: light text on dark background
|
|
color: $white;
|
|
max-width: 392px;
|
|
|
|
@include wordbreak("break-word");
|
|
}
|
|
|
|
// =============================================================================
|
|
// Bottom Section (Features + CTA grouped together)
|
|
// =============================================================================
|
|
|
|
.bds-carousel-featured__bottom {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $bds-space-2xl; // 24px - spacing('2xl')
|
|
|
|
@include media-breakpoint-up(md) {
|
|
gap: $bds-space-3xl; // 32px - spacing('3xl')
|
|
}
|
|
|
|
@include media-breakpoint-up(lg) {
|
|
gap: $bds-space-4xl; // 40px - spacing('4xl')
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Navigation Buttons
|
|
// =============================================================================
|
|
|
|
.bds-carousel-featured__nav {
|
|
display: flex;
|
|
gap: $bds-space-sm; // 8px - spacing('sm')
|
|
flex-shrink: 0;
|
|
z-index: 2;
|
|
padding: inherit;
|
|
|
|
// @include media-breakpoint-up(lg) {
|
|
// top: 0;
|
|
// bottom: auto;
|
|
// }
|
|
|
|
&.clickable {
|
|
position: absolute;
|
|
right: 0;
|
|
top: 0;
|
|
}
|
|
}
|
|
|
|
.bds-carousel-featured__nav-buttons-placeholder {
|
|
visibility: hidden;
|
|
pointer-events: none;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Feature List
|
|
// =============================================================================
|
|
|
|
.bds-carousel-featured__features {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.bds-carousel-featured__feature {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $bds-space-sm; // 8px - spacing('sm')
|
|
width: 100%;
|
|
|
|
// Spacing between description and next divider
|
|
// Mobile/Tablet: 16px, Desktop: 24px
|
|
&:not(:first-child) {
|
|
padding-top: $bds-space-lg; // 16px - spacing('lg')
|
|
|
|
@include media-breakpoint-up(lg) {
|
|
padding-top: $bds-space-2xl; // 24px - spacing('2xl')
|
|
}
|
|
}
|
|
}
|
|
|
|
.bds-carousel-featured__feature-title {
|
|
margin: 0;
|
|
// Dark mode default: light text on dark background
|
|
color: $white;
|
|
}
|
|
|
|
.bds-carousel-featured__feature-description {
|
|
margin: 0;
|
|
// Dark mode default: muted light text on dark background
|
|
color: $gray-400;
|
|
}
|
|
|
|
// =============================================================================
|
|
// CTA Section (Buttons + Mobile Nav)
|
|
// =============================================================================
|
|
|
|
.bds-carousel-featured__cta {
|
|
display: flex;
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
justify-content: space-between;
|
|
align-items: flex-end;
|
|
width: 100%;
|
|
gap: $bds-space-lg; // 16px - spacing('lg')
|
|
|
|
// Tablet+: no wrap needed
|
|
@include media-breakpoint-up(md) {
|
|
flex-wrap: nowrap;
|
|
gap: 0;
|
|
}
|
|
|
|
// Desktop: nav is hidden, so buttons just align left
|
|
@include media-breakpoint-up(lg) {
|
|
justify-content: flex-start;
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Slides Container
|
|
// =============================================================================
|
|
|
|
.bds-carousel-featured__slides {
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.bds-carousel-featured__slide-track {
|
|
display: flex;
|
|
transition: transform $bds-carousel-featured-transition;
|
|
will-change: transform;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Individual Slides
|
|
// =============================================================================
|
|
|
|
.bds-carousel-featured__slide {
|
|
flex: 0 0 100%;
|
|
width: 100%;
|
|
position: relative;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Fade Variant - In-Place Crossfade
|
|
// =============================================================================
|
|
// Instead of wiping the track sideways, every slide is stacked in a single grid
|
|
// cell and the content crossfades in place. Because all slides still take part in
|
|
// layout, the carousel keeps sizing to the tallest slide exactly as the flex track
|
|
// does - so slides of differing heights don't cause the carousel to jump.
|
|
|
|
.bds-carousel-featured--fade {
|
|
.bds-carousel-featured__slide-track {
|
|
display: grid;
|
|
transition: none;
|
|
will-change: auto;
|
|
}
|
|
|
|
.bds-carousel-featured__slide {
|
|
grid-area: 1 / 1;
|
|
// Inactive slides stay in layout but out of reach. The visibility switch is
|
|
// held until the fade-out finishes so it doesn't cut the transition short.
|
|
visibility: hidden;
|
|
transition: visibility 0ms linear $bds-carousel-featured-fade-out;
|
|
}
|
|
|
|
.bds-carousel-featured__slide--active {
|
|
visibility: visible;
|
|
transition-delay: 0ms;
|
|
}
|
|
|
|
// The parts that crossfade. The outgoing slide fades out as a single unit; the
|
|
// stagger below applies only to the incoming slide.
|
|
.bds-carousel-featured__heading,
|
|
.bds-carousel-featured__media,
|
|
.bds-carousel-featured__features,
|
|
.bds-carousel-featured__cta {
|
|
opacity: 0;
|
|
transition: opacity $bds-carousel-featured-fade-out
|
|
$bds-carousel-featured-fade-timing;
|
|
}
|
|
|
|
.bds-carousel-featured__slide--active {
|
|
.bds-carousel-featured__heading,
|
|
.bds-carousel-featured__media {
|
|
opacity: 1;
|
|
transition: opacity $bds-carousel-featured-fade-in
|
|
$bds-carousel-featured-fade-timing;
|
|
}
|
|
|
|
.bds-carousel-featured__features {
|
|
opacity: 1;
|
|
transition: opacity $bds-carousel-featured-fade-in
|
|
$bds-carousel-featured-fade-timing $bds-carousel-featured-fade-stagger;
|
|
}
|
|
|
|
.bds-carousel-featured__cta {
|
|
opacity: 1;
|
|
transition: opacity $bds-carousel-featured-fade-in
|
|
$bds-carousel-featured-fade-timing
|
|
($bds-carousel-featured-fade-stagger * 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Reduced Motion
|
|
// =============================================================================
|
|
// Both transition styles collapse to an instant swap.
|
|
|
|
@include bds-reduced-motion {
|
|
.bds-carousel-featured__slide-track {
|
|
transition: none;
|
|
}
|
|
|
|
.bds-carousel-featured--fade {
|
|
.bds-carousel-featured__slide,
|
|
.bds-carousel-featured__heading,
|
|
.bds-carousel-featured__media,
|
|
.bds-carousel-featured__features,
|
|
.bds-carousel-featured__cta {
|
|
transition: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
.bds-carousel-featured__slide > .bds-grid__row {
|
|
gap: $bds-space-2xl;
|
|
@include media-breakpoint-up(md) {
|
|
gap: $bds-space-3xl;
|
|
}
|
|
@include media-breakpoint-up(lg) {
|
|
gap: $bds-space-sm;
|
|
}
|
|
}
|
|
|
|
.bds-carousel-featured__media {
|
|
position: relative;
|
|
width: 100%;
|
|
aspect-ratio: 343 / 193;
|
|
|
|
@include media-breakpoint-up(md) {
|
|
aspect-ratio: 16 / 9;
|
|
}
|
|
|
|
@include media-breakpoint-up(lg) {
|
|
aspect-ratio: 1 / 1;
|
|
}
|
|
}
|
|
|
|
.bds-carousel-featured__image {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
pointer-events: none;
|
|
}
|
|
|
|
// =============================================================================
|
|
// LIGHT MODE (html.light) - Color Overrides
|
|
// =============================================================================
|
|
|
|
html.light {
|
|
// Default (no variant class) - Light mode: gray-200 background
|
|
.bds-carousel-featured {
|
|
background-color: $gray-200;
|
|
|
|
.bds-carousel-featured__heading,
|
|
.bds-carousel-featured__feature-title,
|
|
.bds-carousel-featured__feature-description {
|
|
color: $black;
|
|
}
|
|
|
|
.bds-divider {
|
|
background-color: $black;
|
|
}
|
|
}
|
|
|
|
// Generate all light mode variant overrides using the mixin
|
|
@each $variant-name, $config in $bds-carousel-featured-variants-light {
|
|
@include carousel-featured-variant-light($variant-name, $config);
|
|
}
|
|
}
|