`s.
+
+.nav {
+ // scss-docs-start nav-css-vars
+ --#{$prefix}nav-link-padding-x: #{$nav-link-padding-x};
+ --#{$prefix}nav-link-padding-y: #{$nav-link-padding-y};
+ @include rfs($nav-link-font-size, --#{$prefix}nav-link-font-size);
+ --#{$prefix}nav-link-font-weight: #{$nav-link-font-weight};
+ --#{$prefix}nav-link-color: #{$nav-link-color};
+ --#{$prefix}nav-link-hover-color: #{$nav-link-hover-color};
+ --#{$prefix}nav-link-disabled-color: #{$nav-link-disabled-color};
+ // scss-docs-end nav-css-vars
+
+ display: flex;
+ flex-wrap: wrap;
+ padding-inline-start: 0;
+ margin-bottom: 0;
+ list-style: none;
+}
+
+.nav-link {
+ display: block;
+ padding: var(--#{$prefix}nav-link-padding-y) var(--#{$prefix}nav-link-padding-x);
+ @include font-size(var(--#{$prefix}nav-link-font-size));
+ font-weight: var(--#{$prefix}nav-link-font-weight);
+ color: var(--#{$prefix}nav-link-color);
+ text-decoration: if($link-decoration == none, null, none);
+ background: none;
+ border: 0;
+ @include transition($nav-link-transition);
+
+ &:hover,
+ &:focus {
+ color: var(--#{$prefix}nav-link-hover-color);
+ text-decoration: if($link-hover-decoration == underline, none, null);
+ }
+
+ &:focus-visible {
+ outline: 0;
+ box-shadow: $nav-link-focus-box-shadow;
+ }
+
+ // Disabled state lightens text
+ &.disabled,
+ &:disabled {
+ color: var(--#{$prefix}nav-link-disabled-color);
+ pointer-events: none;
+ cursor: default;
+ }
+}
+
+//
+// Tabs
+//
+
+.nav-tabs {
+ // scss-docs-start nav-tabs-css-vars
+ --#{$prefix}nav-tabs-border-width: #{$nav-tabs-border-width};
+ --#{$prefix}nav-tabs-border-color: #{$nav-tabs-border-color};
+ --#{$prefix}nav-tabs-border-radius: #{$nav-tabs-border-radius};
+ --#{$prefix}nav-tabs-link-hover-border-color: #{$nav-tabs-link-hover-border-color};
+ --#{$prefix}nav-tabs-link-active-color: #{$nav-tabs-link-active-color};
+ --#{$prefix}nav-tabs-link-active-bg: #{$nav-tabs-link-active-bg};
+ --#{$prefix}nav-tabs-link-active-border-color: #{$nav-tabs-link-active-border-color};
+ // scss-docs-end nav-tabs-css-vars
+
+ border-bottom: var(--#{$prefix}nav-tabs-border-width) solid var(--#{$prefix}nav-tabs-border-color);
+
+ .nav-link {
+ margin-bottom: calc(-1 * var(--#{$prefix}nav-tabs-border-width)); // stylelint-disable-line function-disallowed-list
+ border: var(--#{$prefix}nav-tabs-border-width) solid transparent;
+ @include border-top-radius(var(--#{$prefix}nav-tabs-border-radius));
+
+ &:hover,
+ &:focus {
+ // Prevents active .nav-link tab overlapping focus outline of previous/next .nav-link
+ isolation: isolate;
+ border-color: var(--#{$prefix}nav-tabs-link-hover-border-color);
+ }
+ }
+
+ .nav-link.active,
+ .nav-item.show .nav-link {
+ color: var(--#{$prefix}nav-tabs-link-active-color);
+ background-color: var(--#{$prefix}nav-tabs-link-active-bg);
+ border-color: var(--#{$prefix}nav-tabs-link-active-border-color);
+ }
+
+ .dropdown-menu {
+ // Make dropdown border overlap tab border
+ margin-top: calc(-1 * var(--#{$prefix}nav-tabs-border-width)); // stylelint-disable-line function-disallowed-list
+ // Remove the top rounded corners here since there is a hard edge above the menu
+ @include border-top-radius(0);
+ }
+}
+
+
+//
+// Pills
+//
+
+.nav-pills {
+ // scss-docs-start nav-pills-css-vars
+ --#{$prefix}nav-pills-border-radius: #{$nav-pills-border-radius};
+ --#{$prefix}nav-pills-link-active-color: #{$nav-pills-link-active-color};
+ --#{$prefix}nav-pills-link-active-bg: #{$nav-pills-link-active-bg};
+ // scss-docs-end nav-pills-css-vars
+
+ .nav-link {
+ @include border-radius(var(--#{$prefix}nav-pills-border-radius));
+ }
+
+ .nav-link.active,
+ .show > .nav-link {
+ color: var(--#{$prefix}nav-pills-link-active-color);
+ @include gradient-bg(var(--#{$prefix}nav-pills-link-active-bg));
+ }
+}
+
+
+//
+// Underline
+//
+
+.nav-underline {
+ // scss-docs-start nav-underline-css-vars
+ --#{$prefix}nav-underline-gap: #{$nav-underline-gap};
+ --#{$prefix}nav-underline-border-width: #{$nav-underline-border-width};
+ --#{$prefix}nav-underline-link-active-color: #{$nav-underline-link-active-color};
+ // scss-docs-end nav-underline-css-vars
+
+ gap: var(--#{$prefix}nav-underline-gap);
+
+ .nav-link {
+ padding-right: 0;
+ padding-left: 0;
+ border-bottom: var(--#{$prefix}nav-underline-border-width) solid transparent;
+
+ &:hover,
+ &:focus {
+ border-bottom-color: currentcolor;
+ }
+ }
+
+ .nav-link.active,
+ .show > .nav-link {
+ font-weight: $font-weight-bold;
+ color: var(--#{$prefix}nav-underline-link-active-color);
+ border-bottom-color: currentcolor;
+ }
+}
+
+
+//
+// Underline border
+//
+
+.nav-underline-border {
+ // scss-docs-start nav-underline-border-css-vars
+ --#{$prefix}nav-underline-border-gap: #{$nav-underline-border-gap};
+ --#{$prefix}nav-underline-border-border-color: #{$nav-underline-border-border-color};
+ --#{$prefix}nav-underline-border-border-width: #{$nav-underline-border-border-width};
+ --#{$prefix}nav-underline-border-link-padding-x: #{$nav-underline-border-link-padding-x};
+ --#{$prefix}nav-underline-border-link-padding-y: #{$nav-underline-border-link-padding-y};
+ --#{$prefix}nav-underline-border-link-color: #{$nav-underline-border-link-color};
+ --#{$prefix}nav-underline-border-link-active-color: #{$nav-underline-border-link-active-color};
+ --#{$prefix}nav-underline-border-link-disabled-color: #{$nav-underline-border-link-disabled-color};
+ // scss-docs-end nav-underline-border-css-vars
+
+ --#{$prefix}nav-link-color: var(--#{$prefix}nav-underline-border-link-color);
+ --#{$prefix}nav-link-disabled-color: var(--#{$prefix}nav-underline-border-link-disabled-color);
+
+ gap: var(--#{$prefix}nav-underline-border-gap);
+ border-bottom: var(--#{$prefix}nav-underline-border-border-width) solid var(--#{$prefix}nav-underline-border-border-color);
+
+ .nav-link {
+ padding: var(--#{$prefix}nav-underline-border-link-padding-y) var(--#{$prefix}nav-underline-border-link-padding-x);
+ margin-bottom: calc(-1 * var(--#{$prefix}nav-underline-border-border-width)); // stylelint-disable-line function-disallowed-list
+ border-bottom: var(--#{$prefix}nav-underline-border-border-width) solid transparent;
+
+ &:hover,
+ &:focus {
+ border-bottom-color: currentcolor;
+ }
+ }
+
+ .nav-link.active,
+ .show > .nav-link {
+ font-weight: $font-weight-bold;
+ color: var(--#{$prefix}nav-underline-border-link-active-color);
+ border-bottom-color: currentcolor;
+ }
+}
+
+
+//
+// Justified variants
+//
+
+.nav-fill {
+ > .nav-link,
+ .nav-item {
+ flex: 1 1 auto;
+ text-align: center;
+ }
+}
+
+.nav-justified {
+ > .nav-link,
+ .nav-item {
+ flex-grow: 1;
+ flex-basis: 0;
+ text-align: center;
+ }
+}
+
+.nav-fill,
+.nav-justified {
+ .nav-item .nav-link {
+ width: 100%; // Make sure button will grow
+ }
+}
+
+
+// Tabbable tabs
+//
+// Hide tabbable panes to start, show them when `.active`
+
+.tab-content {
+ > .tab-pane {
+ display: none;
+ }
+ > .active {
+ display: block;
+ }
+}
diff --git a/src/scss/scss/_navbar.import.scss b/src/scss/scss/_navbar.import.scss
new file mode 100644
index 000000000..f4cd38d2b
--- /dev/null
+++ b/src/scss/scss/_navbar.import.scss
@@ -0,0 +1 @@
+@forward "navbar";
diff --git a/src/scss/scss/_navbar.scss b/src/scss/scss/_navbar.scss
new file mode 100644
index 000000000..e2f915f83
--- /dev/null
+++ b/src/scss/scss/_navbar.scss
@@ -0,0 +1,301 @@
+@use "sass:map";
+@use "functions/escape-svg" as *;
+@use "mixins/border-radius" as *;
+@use "mixins/box-shadow" as *;
+@use "mixins/breakpoints" as *;
+@use "mixins/color-mode" as *;
+@use "mixins/deprecate" as *;
+@use "mixins/gradients" as *;
+@use "mixins/transition" as *;
+@use "vendor/rfs" as *;
+@use "variables" as *;
+
+// Navbar
+//
+// Provide a static navbar from which we expand to create full-width, fixed, and
+// other navbar variations.
+
+.navbar {
+ // scss-docs-start navbar-css-vars
+ --#{$prefix}navbar-padding-x: #{if($navbar-padding-x == null, 0, $navbar-padding-x)};
+ --#{$prefix}navbar-padding-y: #{$navbar-padding-y};
+ --#{$prefix}navbar-color: #{$navbar-light-color};
+ --#{$prefix}navbar-hover-color: #{$navbar-light-hover-color};
+ --#{$prefix}navbar-disabled-color: #{$navbar-light-disabled-color};
+ --#{$prefix}navbar-active-color: #{$navbar-light-active-color};
+ --#{$prefix}navbar-brand-padding-y: #{$navbar-brand-padding-y};
+ --#{$prefix}navbar-brand-margin-end: #{$navbar-brand-margin-end};
+ --#{$prefix}navbar-brand-font-size: #{$navbar-brand-font-size};
+ --#{$prefix}navbar-brand-color: #{$navbar-light-brand-color};
+ --#{$prefix}navbar-brand-hover-color: #{$navbar-light-brand-hover-color};
+ --#{$prefix}navbar-nav-link-padding-x: #{$navbar-nav-link-padding-x};
+ --#{$prefix}navbar-toggler-padding-y: #{$navbar-toggler-padding-y};
+ --#{$prefix}navbar-toggler-padding-x: #{$navbar-toggler-padding-x};
+ --#{$prefix}navbar-toggler-font-size: #{$navbar-toggler-font-size};
+ --#{$prefix}navbar-toggler-icon-bg: #{escape-svg($navbar-light-toggler-icon-bg)};
+ --#{$prefix}navbar-toggler-border-color: #{$navbar-light-toggler-border-color};
+ --#{$prefix}navbar-toggler-border-radius: #{$navbar-toggler-border-radius};
+ --#{$prefix}navbar-toggler-focus-width: #{$navbar-toggler-focus-width};
+ --#{$prefix}navbar-toggler-transition: #{$navbar-toggler-transition};
+ // scss-docs-end navbar-css-vars
+
+ position: relative;
+ display: flex;
+ flex-wrap: wrap; // allow us to do the line break for collapsing content
+ align-items: center;
+ justify-content: space-between; // space out brand from logo
+ padding: var(--#{$prefix}navbar-padding-y) var(--#{$prefix}navbar-padding-x);
+ @include gradient-bg();
+
+ // Because flex properties aren't inherited, we need to redeclare these first
+ // few properties so that content nested within behave properly.
+ // The `flex-wrap` property is inherited to simplify the expanded navbars
+ %container-flex-properties {
+ display: flex;
+ flex-wrap: inherit;
+ align-items: center;
+ justify-content: space-between;
+ }
+
+ > .container,
+ > .container-fluid {
+ @extend %container-flex-properties;
+ }
+
+ @each $breakpoint, $container-max-width in $container-max-widths {
+ > .container#{breakpoint-infix($breakpoint, $container-max-widths)} {
+ @extend %container-flex-properties;
+ }
+ }
+}
+
+
+// Navbar brand
+//
+// Used for brand, project, or site names.
+
+.navbar-brand {
+ padding-top: var(--#{$prefix}navbar-brand-padding-y);
+ padding-bottom: var(--#{$prefix}navbar-brand-padding-y);
+ margin-inline-end: var(--#{$prefix}navbar-brand-margin-end);
+ @include font-size(var(--#{$prefix}navbar-brand-font-size));
+ color: var(--#{$prefix}navbar-brand-color);
+ text-decoration: if($link-decoration == none, null, none);
+ white-space: nowrap;
+
+ &:hover,
+ &:focus {
+ color: var(--#{$prefix}navbar-brand-hover-color);
+ text-decoration: if($link-hover-decoration == underline, none, null);
+ }
+}
+
+
+// Navbar nav
+//
+// Custom navbar navigation (doesn't require `.nav`, but does make use of `.nav-link`).
+
+.navbar-nav {
+ // scss-docs-start navbar-nav-css-vars
+ --#{$prefix}nav-link-padding-x: 0;
+ --#{$prefix}nav-link-padding-y: #{$nav-link-padding-y};
+ @include rfs($nav-link-font-size, --#{$prefix}nav-link-font-size);
+ --#{$prefix}nav-link-font-weight: #{$nav-link-font-weight};
+ --#{$prefix}nav-link-color: var(--#{$prefix}navbar-color);
+ --#{$prefix}nav-link-hover-color: var(--#{$prefix}navbar-hover-color);
+ --#{$prefix}nav-link-disabled-color: var(--#{$prefix}navbar-disabled-color);
+ // scss-docs-end navbar-nav-css-vars
+
+ display: flex;
+ flex-direction: column; // cannot use `inherit` to get the `.navbar`s value
+ padding-inline-start: 0;
+ margin-bottom: 0;
+ list-style: none;
+
+ .nav-link {
+ &.active,
+ &.show {
+ color: var(--#{$prefix}navbar-active-color);
+ }
+ }
+
+ .dropdown-menu {
+ position: static;
+ }
+}
+
+
+// Navbar text
+//
+//
+
+.navbar-text {
+ padding-top: $nav-link-padding-y;
+ padding-bottom: $nav-link-padding-y;
+ color: var(--#{$prefix}navbar-color);
+
+ a,
+ a:hover,
+ a:focus {
+ color: var(--#{$prefix}navbar-active-color);
+ }
+}
+
+
+// Responsive navbar
+//
+// Custom styles for responsive collapsing and toggling of navbar contents.
+// Powered by the collapse Bootstrap JavaScript plugin.
+
+// When collapsed, prevent the toggleable navbar contents from appearing in
+// the default flexbox row orientation. Requires the use of `flex-wrap: wrap`
+// on the `.navbar` parent.
+.navbar-collapse {
+ flex-grow: 1;
+ flex-basis: 100%;
+ // For always expanded or extra full navbars, ensure content aligns itself
+ // properly vertically. Can be easily overridden with flex utilities.
+ align-items: center;
+}
+
+// Button for toggling the navbar when in its collapsed state
+.navbar-toggler {
+ padding: var(--#{$prefix}navbar-toggler-padding-y) var(--#{$prefix}navbar-toggler-padding-x);
+ @include font-size(var(--#{$prefix}navbar-toggler-font-size));
+ line-height: 1;
+ color: var(--#{$prefix}navbar-color);
+ background-color: transparent; // remove default button style
+ border: var(--#{$prefix}border-width) solid var(--#{$prefix}navbar-toggler-border-color); // remove default button style
+ @include border-radius(var(--#{$prefix}navbar-toggler-border-radius));
+ @include transition(var(--#{$prefix}navbar-toggler-transition));
+
+ &:hover {
+ text-decoration: none;
+ }
+
+ &:focus {
+ text-decoration: none;
+ outline: 0;
+ box-shadow: 0 0 0 var(--#{$prefix}navbar-toggler-focus-width);
+ }
+}
+
+// Keep as a separate element so folks can easily override it with another icon
+// or image file as needed.
+.navbar-toggler-icon {
+ display: inline-block;
+ width: 1.5em;
+ height: 1.5em;
+ vertical-align: middle;
+ background-image: var(--#{$prefix}navbar-toggler-icon-bg);
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: 100%;
+}
+
+.navbar-nav-scroll {
+ max-height: var(--#{$prefix}scroll-height, 75vh);
+ overflow-y: auto;
+}
+
+// scss-docs-start navbar-expand-loop
+// Generate series of `.navbar-expand-*` responsive classes for configuring
+// where your navbar collapses.
+.navbar-expand {
+ @each $breakpoint in map.keys($grid-breakpoints) {
+ $next: breakpoint-next($breakpoint, $grid-breakpoints);
+ $infix: breakpoint-infix($next, $grid-breakpoints);
+
+ // stylelint-disable-next-line scss/selector-no-union-class-name
+ {$infix} {
+ @include media-breakpoint-up($next) {
+ flex-wrap: nowrap;
+ justify-content: flex-start;
+
+ .navbar-nav {
+ flex-direction: row;
+
+ .dropdown-menu {
+ position: absolute;
+ }
+
+ .nav-link {
+ padding-right: var(--#{$prefix}navbar-nav-link-padding-x);
+ padding-left: var(--#{$prefix}navbar-nav-link-padding-x);
+ }
+ }
+
+ .navbar-nav-scroll {
+ overflow: visible;
+ }
+
+ .navbar-collapse {
+ display: flex !important; // stylelint-disable-line declaration-no-important
+ flex-basis: auto;
+ }
+
+ .navbar-toggler {
+ display: none;
+ }
+
+ .offcanvas {
+ // stylelint-disable declaration-no-important
+ position: static;
+ z-index: auto;
+ flex-grow: 1;
+ width: auto !important;
+ height: auto !important;
+ visibility: visible !important;
+ background-color: transparent !important;
+ border: 0 !important;
+ transform: none !important;
+ @include box-shadow(none);
+ @include transition(none);
+ // stylelint-enable declaration-no-important
+
+ .offcanvas-header {
+ display: none;
+ }
+
+ .offcanvas-body {
+ display: flex;
+ flex-grow: 0;
+ padding: 0;
+ overflow-y: visible;
+ }
+ }
+ }
+ }
+ }
+}
+// scss-docs-end navbar-expand-loop
+
+// Navbar themes
+//
+// Styles for switching between navbars with light or dark background.
+
+.navbar-light {
+ @include deprecate("`.navbar-light`", "v4.2.6", "v6.0.0", true);
+}
+
+.navbar-dark,
+.navbar[data#{$data-infix}theme="dark"] {
+ // scss-docs-start navbar-dark-css-vars
+ --#{$prefix}navbar-color: #{$navbar-dark-color};
+ --#{$prefix}navbar-hover-color: #{$navbar-dark-hover-color};
+ --#{$prefix}navbar-disabled-color: #{$navbar-dark-disabled-color};
+ --#{$prefix}navbar-active-color: #{$navbar-dark-active-color};
+ --#{$prefix}navbar-brand-color: #{$navbar-dark-brand-color};
+ --#{$prefix}navbar-brand-hover-color: #{$navbar-dark-brand-hover-color};
+ --#{$prefix}navbar-toggler-border-color: #{$navbar-dark-toggler-border-color};
+ --#{$prefix}navbar-toggler-icon-bg: #{escape-svg($navbar-dark-toggler-icon-bg)};
+ // scss-docs-end navbar-dark-css-vars
+}
+
+@if $enable-dark-mode {
+ @include color-mode(dark) {
+ .navbar-toggler-icon {
+ --#{$prefix}navbar-toggler-icon-bg: #{escape-svg($navbar-dark-toggler-icon-bg)};
+ }
+ }
+}
diff --git a/src/scss/scss/_offcanvas.import.scss b/src/scss/scss/_offcanvas.import.scss
new file mode 100644
index 000000000..786f8ee69
--- /dev/null
+++ b/src/scss/scss/_offcanvas.import.scss
@@ -0,0 +1 @@
+@forward "offcanvas";
diff --git a/src/scss/scss/_offcanvas.scss b/src/scss/scss/_offcanvas.scss
new file mode 100644
index 000000000..8d4733ef1
--- /dev/null
+++ b/src/scss/scss/_offcanvas.scss
@@ -0,0 +1,153 @@
+// stylelint-disable function-disallowed-list
+@use "sass:map";
+@use "mixins/backdrop" as *;
+@use "mixins/box-shadow" as *;
+@use "mixins/breakpoints" as *;
+@use "mixins/ltr-rtl" as *;
+@use "mixins/transition" as *;
+@use "variables" as *;
+
+%offcanvas-css-vars {
+ // scss-docs-start offcanvas-css-vars
+ --#{$prefix}offcanvas-zindex: #{$zindex-offcanvas};
+ --#{$prefix}offcanvas-width: #{$offcanvas-horizontal-width};
+ --#{$prefix}offcanvas-height: #{$offcanvas-vertical-height};
+ --#{$prefix}offcanvas-padding-x: #{$offcanvas-padding-x};
+ --#{$prefix}offcanvas-padding-y: #{$offcanvas-padding-y};
+ --#{$prefix}offcanvas-color: #{$offcanvas-color};
+ --#{$prefix}offcanvas-bg: #{$offcanvas-bg-color};
+ --#{$prefix}offcanvas-border-width: #{$offcanvas-border-width};
+ --#{$prefix}offcanvas-border-color: #{$offcanvas-border-color};
+ --#{$prefix}offcanvas-box-shadow: #{$offcanvas-box-shadow};
+ --#{$prefix}offcanvas-transition: #{transform $offcanvas-transition-duration ease-in-out};
+ --#{$prefix}offcanvas-title-line-height: #{$offcanvas-title-line-height};
+ // scss-docs-end offcanvas-css-vars
+}
+
+@each $breakpoint in map.keys($grid-breakpoints) {
+ $next: breakpoint-next($breakpoint, $grid-breakpoints);
+ $infix: breakpoint-infix($next, $grid-breakpoints);
+
+ .offcanvas#{$infix} {
+ @extend %offcanvas-css-vars;
+ }
+}
+
+@each $breakpoint in map.keys($grid-breakpoints) {
+ $next: breakpoint-next($breakpoint, $grid-breakpoints);
+ $infix: breakpoint-infix($next, $grid-breakpoints);
+
+ .offcanvas#{$infix} {
+ @include media-breakpoint-down($next) {
+ position: fixed;
+ bottom: 0;
+ z-index: var(--#{$prefix}offcanvas-zindex);
+ display: flex;
+ flex-direction: column;
+ max-width: 100%;
+ color: var(--#{$prefix}offcanvas-color);
+ visibility: hidden;
+ background-color: var(--#{$prefix}offcanvas-bg);
+ background-clip: padding-box;
+ outline: 0;
+ @include box-shadow(var(--#{$prefix}offcanvas-box-shadow));
+ @include transition(var(--#{$prefix}offcanvas-transition));
+
+ &.offcanvas-start {
+ inset-inline-start: 0;
+ top: 0;
+ width: var(--#{$prefix}offcanvas-width);
+ border-inline-end: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color);
+ @include ltr-rtl-value-only("transform", translateX(-100%), translateX(100%));
+ }
+
+ &.offcanvas-end {
+ inset-inline-end: 0;
+ top: 0;
+ width: var(--#{$prefix}offcanvas-width);
+ border-inline-start: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color);
+ transform: translateX(100%);
+ }
+
+ &.offcanvas-top {
+ top: 0;
+ right: 0;
+ left: 0;
+ height: var(--#{$prefix}offcanvas-height);
+ max-height: 100%;
+ border-bottom: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color);
+ transform: translateY(-100%);
+ }
+
+ &.offcanvas-bottom {
+ right: 0;
+ left: 0;
+ height: var(--#{$prefix}offcanvas-height);
+ max-height: 100%;
+ border-top: var(--#{$prefix}offcanvas-border-width) solid var(--#{$prefix}offcanvas-border-color);
+ transform: translateY(100%);
+ }
+
+ &.showing,
+ &.show:not(.hiding) {
+ transform: none !important; // stylelint-disable-line declaration-no-important
+ }
+
+ &.showing,
+ &.hiding,
+ &.show {
+ visibility: visible;
+ }
+ }
+
+ @if not ($infix == "") {
+ @include media-breakpoint-up($next) {
+ --#{$prefix}offcanvas-height: auto;
+ --#{$prefix}offcanvas-border-width: 0;
+ background-color: transparent !important; // stylelint-disable-line declaration-no-important
+
+ .offcanvas-header {
+ display: none;
+ }
+
+ .offcanvas-body {
+ display: flex;
+ flex-grow: 0;
+ padding: 0;
+ overflow-y: visible;
+ // Reset `background-color` in case `.bg-*` classes are used in offcanvas
+ background-color: transparent !important; // stylelint-disable-line declaration-no-important
+ }
+ }
+ }
+ }
+}
+
+.offcanvas-backdrop {
+ @include overlay-backdrop($zindex-offcanvas-backdrop, var(--#{$prefix}offcanvas-backdrop-bg, $offcanvas-backdrop-bg), $offcanvas-backdrop-opacity);
+}
+
+.offcanvas-header {
+ display: flex;
+ align-items: center;
+ padding: var(--#{$prefix}offcanvas-padding-y) var(--#{$prefix}offcanvas-padding-x);
+
+ .btn-close {
+ padding: calc(var(--#{$prefix}offcanvas-padding-y) * .5) calc(var(--#{$prefix}offcanvas-padding-x) * .5);
+ margin-inline-start: auto;
+ margin-inline-end: calc(-.5 * var(--#{$prefix}offcanvas-padding-x));
+ margin-top: calc(-.5 * var(--#{$prefix}offcanvas-padding-y));
+ margin-bottom: calc(-.5 * var(--#{$prefix}offcanvas-padding-y));
+ }
+}
+
+.offcanvas-title {
+ margin-bottom: 0;
+ line-height: var(--#{$prefix}offcanvas-title-line-height);
+}
+
+.offcanvas-body {
+ flex-grow: 1;
+ padding: var(--#{$prefix}offcanvas-padding-y) var(--#{$prefix}offcanvas-padding-x);
+ overflow-y: auto;
+}
diff --git a/src/scss/scss/_pagination.import.scss b/src/scss/scss/_pagination.import.scss
new file mode 100644
index 000000000..ef891d333
--- /dev/null
+++ b/src/scss/scss/_pagination.import.scss
@@ -0,0 +1 @@
+@forward "pagination";
diff --git a/src/scss/scss/_pagination.scss b/src/scss/scss/_pagination.scss
new file mode 100644
index 000000000..79b3b9a15
--- /dev/null
+++ b/src/scss/scss/_pagination.scss
@@ -0,0 +1,117 @@
+@use "mixins/border-radius" as *;
+@use "mixins/gradients" as *;
+@use "mixins/lists" as *;
+@use "mixins/pagination" as *;
+@use "mixins/transition" as *;
+@use "vendor/rfs" as *;
+@use "variables" as *;
+
+.pagination {
+ // scss-docs-start pagination-css-vars
+ --#{$prefix}pagination-padding-x: #{$pagination-padding-x};
+ --#{$prefix}pagination-padding-y: #{$pagination-padding-y};
+ @include rfs($pagination-font-size, --#{$prefix}pagination-font-size);
+ --#{$prefix}pagination-color: #{$pagination-color};
+ --#{$prefix}pagination-bg: #{$pagination-bg};
+ --#{$prefix}pagination-border-width: #{$pagination-border-width};
+ --#{$prefix}pagination-border-color: #{$pagination-border-color};
+ --#{$prefix}pagination-border-radius: #{$pagination-border-radius};
+ --#{$prefix}pagination-hover-color: #{$pagination-hover-color};
+ --#{$prefix}pagination-hover-bg: #{$pagination-hover-bg};
+ --#{$prefix}pagination-hover-border-color: #{$pagination-hover-border-color};
+ --#{$prefix}pagination-focus-color: #{$pagination-focus-color};
+ --#{$prefix}pagination-focus-bg: #{$pagination-focus-bg};
+ --#{$prefix}pagination-focus-box-shadow: #{$pagination-focus-box-shadow};
+ --#{$prefix}pagination-active-color: #{$pagination-active-color};
+ --#{$prefix}pagination-active-bg: #{$pagination-active-bg};
+ --#{$prefix}pagination-active-border-color: #{$pagination-active-border-color};
+ --#{$prefix}pagination-disabled-color: #{$pagination-disabled-color};
+ --#{$prefix}pagination-disabled-bg: #{$pagination-disabled-bg};
+ --#{$prefix}pagination-disabled-border-color: #{$pagination-disabled-border-color};
+ // scss-docs-end pagination-css-vars
+
+ display: flex;
+ @include list-unstyled();
+}
+
+.page-link {
+ position: relative;
+ display: block;
+ padding: var(--#{$prefix}pagination-padding-y) var(--#{$prefix}pagination-padding-x);
+ @include font-size(var(--#{$prefix}pagination-font-size));
+ color: var(--#{$prefix}pagination-color);
+ text-decoration: if($link-decoration == none, null, none);
+ background-color: var(--#{$prefix}pagination-bg);
+ border: var(--#{$prefix}pagination-border-width) solid var(--#{$prefix}pagination-border-color);
+ @include transition($pagination-transition);
+
+ &:hover {
+ z-index: 2;
+ color: var(--#{$prefix}pagination-hover-color);
+ text-decoration: if($link-hover-decoration == underline, none, null);
+ background-color: var(--#{$prefix}pagination-hover-bg);
+ border-color: var(--#{$prefix}pagination-hover-border-color);
+ }
+
+ &:focus {
+ z-index: 3;
+ color: var(--#{$prefix}pagination-focus-color);
+ background-color: var(--#{$prefix}pagination-focus-bg);
+ outline: $pagination-focus-outline;
+ box-shadow: var(--#{$prefix}pagination-focus-box-shadow);
+ }
+
+ &.active,
+ .active > & {
+ z-index: 3;
+ color: var(--#{$prefix}pagination-active-color);
+ @include gradient-bg(var(--#{$prefix}pagination-active-bg));
+ border-color: var(--#{$prefix}pagination-active-border-color);
+ }
+
+ &.disabled,
+ .disabled > & {
+ color: var(--#{$prefix}pagination-disabled-color);
+ pointer-events: none;
+ background-color: var(--#{$prefix}pagination-disabled-bg);
+ border-color: var(--#{$prefix}pagination-disabled-border-color);
+ }
+}
+
+.page-item {
+ &:not(:first-child) .page-link {
+ margin-inline-start: $pagination-margin-start;
+ }
+
+ @if $pagination-margin-start == calc(-1 * #{$pagination-border-width}) {
+ &:first-child {
+ .page-link {
+ @include border-start-radius(var(--#{$prefix}pagination-border-radius));
+ }
+ }
+
+ &:last-child {
+ .page-link {
+ @include border-end-radius(var(--#{$prefix}pagination-border-radius));
+ }
+ }
+ } @else {
+ // Add border-radius to all pageLinks in case they have left margin
+ .page-link {
+ @include border-radius(var(--#{$prefix}pagination-border-radius));
+ }
+ }
+}
+
+
+//
+// Sizing
+//
+
+.pagination-lg {
+ @include pagination-size($pagination-padding-y-lg, $pagination-padding-x-lg, $font-size-lg, $pagination-border-radius-lg);
+}
+
+.pagination-sm {
+ @include pagination-size($pagination-padding-y-sm, $pagination-padding-x-sm, $font-size-sm, $pagination-border-radius-sm);
+}
diff --git a/src/scss/scss/_placeholders.import.scss b/src/scss/scss/_placeholders.import.scss
new file mode 100644
index 000000000..bc405f24c
--- /dev/null
+++ b/src/scss/scss/_placeholders.import.scss
@@ -0,0 +1 @@
+@forward "placeholders";
diff --git a/src/scss/scss/_placeholders.scss b/src/scss/scss/_placeholders.scss
new file mode 100644
index 000000000..497ec74b2
--- /dev/null
+++ b/src/scss/scss/_placeholders.scss
@@ -0,0 +1,53 @@
+@use "variables" as *;
+
+.placeholder {
+ display: inline-block;
+ min-height: 1em;
+ vertical-align: middle;
+ cursor: wait;
+ background-color: currentcolor;
+ opacity: $placeholder-opacity-max;
+
+ &.btn::before {
+ display: inline-block;
+ content: "";
+ }
+}
+
+// Sizing
+.placeholder-xs {
+ min-height: .6em;
+}
+
+.placeholder-sm {
+ min-height: .8em;
+}
+
+.placeholder-lg {
+ min-height: 1.2em;
+}
+
+// Animation
+.placeholder-glow {
+ .placeholder {
+ animation: placeholder-glow 2s ease-in-out infinite;
+ }
+}
+
+@keyframes placeholder-glow {
+ 50% {
+ opacity: $placeholder-opacity-min;
+ }
+}
+
+.placeholder-wave {
+ mask-image: linear-gradient(130deg, $black 55%, rgba(0, 0, 0, (1 - $placeholder-opacity-min)) 75%, $black 95%);
+ mask-size: 200% 100%;
+ animation: placeholder-wave 2s linear infinite;
+}
+
+@keyframes placeholder-wave {
+ 100% {
+ mask-position: -200% 0%;
+ }
+}
diff --git a/src/scss/scss/_popover.import.scss b/src/scss/scss/_popover.import.scss
new file mode 100644
index 000000000..071aef873
--- /dev/null
+++ b/src/scss/scss/_popover.import.scss
@@ -0,0 +1 @@
+@forward "popover";
diff --git a/src/scss/scss/_popover.scss b/src/scss/scss/_popover.scss
new file mode 100644
index 000000000..042fbc5a0
--- /dev/null
+++ b/src/scss/scss/_popover.scss
@@ -0,0 +1,202 @@
+@use "mixins/border-radius" as *;
+@use "mixins/box-shadow" as *;
+@use "mixins/reset-text" as *;
+@use "vendor/rfs" as *;
+@use "variables" as *;
+
+.popover {
+ // scss-docs-start popover-css-vars
+ --#{$prefix}popover-zindex: #{$zindex-popover};
+ --#{$prefix}popover-max-width: #{$popover-max-width};
+ @include rfs($popover-font-size, --#{$prefix}popover-font-size);
+ --#{$prefix}popover-bg: #{$popover-bg};
+ --#{$prefix}popover-border-width: #{$popover-border-width};
+ --#{$prefix}popover-border-color: #{$popover-border-color};
+ --#{$prefix}popover-border-radius: #{$popover-border-radius};
+ --#{$prefix}popover-inner-border-radius: #{$popover-inner-border-radius};
+ --#{$prefix}popover-box-shadow: #{$popover-box-shadow};
+ --#{$prefix}popover-header-padding-x: #{$popover-header-padding-x};
+ --#{$prefix}popover-header-padding-y: #{$popover-header-padding-y};
+ @include rfs($popover-header-font-size, --#{$prefix}popover-header-font-size);
+ --#{$prefix}popover-header-color: #{$popover-header-color};
+ --#{$prefix}popover-header-bg: #{$popover-header-bg};
+ --#{$prefix}popover-body-padding-x: #{$popover-body-padding-x};
+ --#{$prefix}popover-body-padding-y: #{$popover-body-padding-y};
+ --#{$prefix}popover-body-color: #{$popover-body-color};
+ --#{$prefix}popover-arrow-width: #{$popover-arrow-width};
+ --#{$prefix}popover-arrow-height: #{$popover-arrow-height};
+ --#{$prefix}popover-arrow-border: var(--#{$prefix}popover-border-color);
+ // scss-docs-end popover-css-vars
+
+ z-index: var(--#{$prefix}popover-zindex);
+ display: block;
+ max-width: var(--#{$prefix}popover-max-width);
+ // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.
+ // So reset our font and text properties to avoid inheriting weird values.
+ @include reset-text();
+ @include font-size(var(--#{$prefix}popover-font-size));
+ // Allow breaking very long words so they don't overflow the popover's bounds
+ word-wrap: break-word;
+ background-color: var(--#{$prefix}popover-bg);
+ background-clip: padding-box;
+ border: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-border-color);
+ @include border-radius(var(--#{$prefix}popover-border-radius));
+ @include box-shadow(var(--#{$prefix}popover-box-shadow));
+
+ .popover-arrow {
+ display: block;
+ width: var(--#{$prefix}popover-arrow-width);
+ height: var(--#{$prefix}popover-arrow-height);
+
+ &::before,
+ &::after {
+ position: absolute;
+ display: block;
+ content: "";
+ border-color: transparent;
+ border-style: solid;
+ border-width: 0;
+ }
+ }
+}
+
+.bs-popover-top {
+ > .popover-arrow {
+ bottom: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list
+
+ &::before,
+ &::after {
+ border-width: var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
+ }
+
+ &::before {
+ bottom: 0;
+ border-top-color: var(--#{$prefix}popover-arrow-border);
+ }
+
+ &::after {
+ bottom: var(--#{$prefix}popover-border-width);
+ border-top-color: var(--#{$prefix}popover-bg);
+ }
+ }
+}
+
+/* rtl:begin:ignore */
+.bs-popover-end {
+ > .popover-arrow {
+ left: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list
+ width: var(--#{$prefix}popover-arrow-height);
+ height: var(--#{$prefix}popover-arrow-width);
+
+ &::before,
+ &::after {
+ border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
+ }
+
+ &::before {
+ left: 0;
+ border-right-color: var(--#{$prefix}popover-arrow-border);
+ }
+
+ &::after {
+ left: var(--#{$prefix}popover-border-width);
+ border-right-color: var(--#{$prefix}popover-bg);
+ }
+ }
+}
+
+/* rtl:end:ignore */
+
+.bs-popover-bottom {
+ > .popover-arrow {
+ top: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list
+
+ &::before,
+ &::after {
+ border-width: 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); // stylelint-disable-line function-disallowed-list
+ }
+
+ &::before {
+ top: 0;
+ border-bottom-color: var(--#{$prefix}popover-arrow-border);
+ }
+
+ &::after {
+ top: var(--#{$prefix}popover-border-width);
+ border-bottom-color: var(--#{$prefix}popover-bg);
+ }
+ }
+
+ // This will remove the popover-header's border just below the arrow
+ .popover-header::before {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ display: block;
+ width: var(--#{$prefix}popover-arrow-width);
+ margin-left: calc(-.5 * var(--#{$prefix}popover-arrow-width)); // stylelint-disable-line function-disallowed-list
+ content: "";
+ border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-header-bg);
+ }
+}
+
+/* rtl:begin:ignore */
+.bs-popover-start {
+ > .popover-arrow {
+ right: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); // stylelint-disable-line function-disallowed-list
+ width: var(--#{$prefix}popover-arrow-height);
+ height: var(--#{$prefix}popover-arrow-width);
+
+ &::before,
+ &::after {
+ border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); // stylelint-disable-line function-disallowed-list
+ }
+
+ &::before {
+ right: 0;
+ border-left-color: var(--#{$prefix}popover-arrow-border);
+ }
+
+ &::after {
+ right: var(--#{$prefix}popover-border-width);
+ border-left-color: var(--#{$prefix}popover-bg);
+ }
+ }
+}
+
+/* rtl:end:ignore */
+
+.bs-popover-auto {
+ &[data-popper-placement^="top"] {
+ @extend .bs-popover-top;
+ }
+ &[data-popper-placement^="right"] {
+ @extend .bs-popover-end;
+ }
+ &[data-popper-placement^="bottom"] {
+ @extend .bs-popover-bottom;
+ }
+ &[data-popper-placement^="left"] {
+ @extend .bs-popover-start;
+ }
+}
+
+// Offset the popover to account for the popover arrow
+.popover-header {
+ padding: var(--#{$prefix}popover-header-padding-y) var(--#{$prefix}popover-header-padding-x);
+ margin-bottom: 0; // Reset the default from Reboot
+ @include font-size(var(--#{$prefix}popover-header-font-size));
+ color: var(--#{$prefix}popover-header-color);
+ background-color: var(--#{$prefix}popover-header-bg);
+ border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-border-color);
+ @include border-top-radius(var(--#{$prefix}popover-inner-border-radius));
+
+ &:empty {
+ display: none;
+ }
+}
+
+.popover-body {
+ padding: var(--#{$prefix}popover-body-padding-y) var(--#{$prefix}popover-body-padding-x);
+ color: var(--#{$prefix}popover-body-color);
+}
diff --git a/src/scss/scss/_progress.import.scss b/src/scss/scss/_progress.import.scss
new file mode 100644
index 000000000..c3fc3c81f
--- /dev/null
+++ b/src/scss/scss/_progress.import.scss
@@ -0,0 +1 @@
+@forward "progress";
diff --git a/src/scss/scss/_progress.scss b/src/scss/scss/_progress.scss
new file mode 100644
index 000000000..8b95a0ad3
--- /dev/null
+++ b/src/scss/scss/_progress.scss
@@ -0,0 +1,118 @@
+@use "mixins/border-radius" as *;
+@use "mixins/box-shadow" as *;
+@use "mixins/gradients" as *;
+@use "mixins/transition" as *;
+@use "vendor/rfs" as *;
+@use "variables" as *;
+
+// Disable animation if transitions are disabled
+
+// scss-docs-start progress-keyframes
+@if $enable-transitions {
+ @keyframes progress-bar-stripes {
+ 0% { background-position-x: var(--#{$prefix}progress-height); }
+ }
+}
+// scss-docs-end progress-keyframes
+
+.progress,
+.progress-stacked {
+ // scss-docs-start progress-css-vars
+ --#{$prefix}progress-height: #{$progress-height};
+ @include rfs($progress-font-size, --#{$prefix}progress-font-size);
+ --#{$prefix}progress-bg: #{$progress-bg};
+ --#{$prefix}progress-border-radius: #{$progress-border-radius};
+ --#{$prefix}progress-box-shadow: #{$progress-box-shadow};
+ --#{$prefix}progress-bar-color: #{$progress-bar-color};
+ --#{$prefix}progress-bar-bg: #{$progress-bar-bg};
+ --#{$prefix}progress-bar-transition: #{$progress-bar-transition};
+ // scss-docs-end progress-css-vars
+
+ display: flex;
+ height: var(--#{$prefix}progress-height);
+ overflow: hidden; // force rounded corners by cropping it
+ @include font-size(var(--#{$prefix}progress-font-size));
+ background-color: var(--#{$prefix}progress-bg);
+ @include border-radius(var(--#{$prefix}progress-border-radius));
+ @include box-shadow(var(--#{$prefix}progress-box-shadow));
+}
+
+.progress-bar {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ overflow: hidden;
+ color: var(--#{$prefix}progress-bar-color);
+ text-align: center;
+ white-space: nowrap;
+ background-color: var(--#{$prefix}progress-bar-bg);
+ @include transition(var(--#{$prefix}progress-bar-transition));
+}
+
+.progress-bar-striped {
+ @include gradient-striped();
+ background-size: var(--#{$prefix}progress-height) var(--#{$prefix}progress-height);
+}
+
+.progress-stacked > .progress {
+ overflow: visible;
+}
+
+.progress-stacked > .progress > .progress-bar {
+ width: 100%;
+}
+
+@if $enable-transitions {
+ .progress-bar-animated {
+ animation: $progress-bar-animation-timing progress-bar-stripes;
+
+ @if $enable-reduced-motion {
+ @media (prefers-reduced-motion: reduce) {
+ animation: none;
+ }
+ }
+ }
+}
+
+.progress-thin {
+ height: 4px;
+}
+
+// White progress bar
+.progress.progress-white {
+ background-color: rgba(255, 255, 255, .2);
+ .progress-bar {
+ background-color: $white;
+ }
+}
+
+.progress-group {
+ display: flex;
+ flex-flow: row wrap;
+ margin-bottom: $progress-group-margin-bottom;
+}
+
+.progress-group-prepend {
+ flex: 0 0 100px;
+ align-self: center;
+}
+
+.progress-group-header {
+ display: flex;
+ flex-basis: 100%;
+ align-items: center;
+ margin-bottom: $progress-group-header-margin-bottom;
+}
+
+.progress-group-bars {
+ flex-grow: 1;
+ align-self: center;
+
+ .progress:not(:last-child) {
+ margin-bottom: 2px;
+ }
+}
+
+.progress-group-header + .progress-group-bars {
+ flex-basis: 100%;
+}
diff --git a/src/scss/scss/_reboot.import.scss b/src/scss/scss/_reboot.import.scss
new file mode 100644
index 000000000..bb0353d30
--- /dev/null
+++ b/src/scss/scss/_reboot.import.scss
@@ -0,0 +1 @@
+@forward "reboot";
diff --git a/src/scss/scss/_reboot.scss b/src/scss/scss/_reboot.scss
new file mode 100644
index 000000000..be8f6fe40
--- /dev/null
+++ b/src/scss/scss/_reboot.scss
@@ -0,0 +1,613 @@
+@use "mixins/border-radius" as *;
+@use "vendor/rfs" as *;
+@use "variables" as *;
+
+// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix
+
+
+// Reboot
+//
+// Normalization of HTML elements, manually forked from Normalize.css to remove
+// styles targeting irrelevant browsers while applying new styles.
+//
+// Normalize is licensed MIT. https://github.com/necolas/normalize.css
+
+
+// Document
+//
+// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+
+// Root
+//
+// Ability to the value of the root font sizes, affecting the value of `rem`.
+// null by default, thus nothing is generated.
+
+:root {
+ @if $font-size-root != null {
+ @include font-size(var(--#{$prefix}root-font-size));
+ }
+
+ @if $enable-smooth-scroll {
+ @media (prefers-reduced-motion: no-preference) {
+ scroll-behavior: smooth;
+ }
+ }
+}
+
+
+// Body
+//
+// 1. Remove the margin in all browsers.
+// 2. As a best practice, apply a default `background-color`.
+// 3. Prevent adjustments of font size after orientation changes in iOS.
+// 4. Change the default tap highlight to be completely transparent in iOS.
+
+// scss-docs-start reboot-body-rules
+body {
+ margin: 0; // 1
+ font-family: var(--#{$prefix}body-font-family);
+ @include font-size(var(--#{$prefix}body-font-size));
+ font-weight: var(--#{$prefix}body-font-weight);
+ line-height: var(--#{$prefix}body-line-height);
+ color: var(--#{$prefix}body-color);
+ text-align: var(--#{$prefix}body-text-align);
+ background-color: var(--#{$prefix}body-bg); // 2
+ -webkit-text-size-adjust: 100%; // 3
+ -webkit-tap-highlight-color: rgba($black, 0); // 4
+}
+// scss-docs-end reboot-body-rules
+
+
+// Content grouping
+//
+// 1. Reset Firefox's gray color
+
+hr {
+ margin: $hr-margin-y 0;
+ color: $hr-color; // 1
+ border: 0;
+ border-top: $hr-border-width solid $hr-border-color;
+ opacity: $hr-opacity;
+}
+
+
+// Typography
+//
+// 1. Remove top margins from headings
+// By default, ``-`` all receive top and bottom margins. We nuke the top
+// margin for easier control within type scales as it avoids margin collapsing.
+
+%heading {
+ margin-top: 0; // 1
+ margin-bottom: $headings-margin-bottom;
+ font-family: $headings-font-family;
+ font-style: $headings-font-style;
+ font-weight: $headings-font-weight;
+ line-height: $headings-line-height;
+ color: var(--#{$prefix}heading-color);
+}
+
+h1 {
+ @extend %heading;
+ @include font-size($h1-font-size);
+}
+
+h2 {
+ @extend %heading;
+ @include font-size($h2-font-size);
+}
+
+h3 {
+ @extend %heading;
+ @include font-size($h3-font-size);
+}
+
+h4 {
+ @extend %heading;
+ @include font-size($h4-font-size);
+}
+
+h5 {
+ @extend %heading;
+ @include font-size($h5-font-size);
+}
+
+h6 {
+ @extend %heading;
+ @include font-size($h6-font-size);
+}
+
+
+// Reset margins on paragraphs
+//
+// Similarly, the top margin on ` `s get reset. However, we also reset the
+// bottom margin to use `rem` units instead of `em`.
+
+p {
+ margin-top: 0;
+ margin-bottom: $paragraph-margin-bottom;
+}
+
+
+// Abbreviations
+//
+// 1. Add the correct text decoration in Chrome, Edge, Opera, and Safari.
+// 2. Add explicit cursor to indicate changed behavior.
+// 3. Prevent the text-decoration to be skipped.
+
+abbr[title] {
+ text-decoration: underline dotted; // 1
+ cursor: help; // 2
+ text-decoration-skip-ink: none; // 3
+}
+
+
+// Address
+
+address {
+ margin-bottom: 1rem;
+ font-style: normal;
+ line-height: inherit;
+}
+
+
+// Lists
+
+ol,
+ul {
+ padding-inline-start: 2rem;
+}
+
+ol,
+ul,
+dl {
+ margin-top: 0;
+ margin-bottom: 1rem;
+}
+
+ol ol,
+ul ul,
+ol ul,
+ul ol {
+ margin-bottom: 0;
+}
+
+dt {
+ font-weight: $dt-font-weight;
+}
+
+// 1. Undo browser default
+
+dd {
+ margin-inline-start: 0; // 1
+ margin-bottom: .5rem;
+}
+
+
+// Blockquote
+
+blockquote {
+ margin: 0 0 1rem;
+}
+
+
+// Strong
+//
+// Add the correct font weight in Chrome, Edge, and Safari
+
+b,
+strong {
+ font-weight: $font-weight-bolder;
+}
+
+
+// Small
+//
+// Add the correct font size in all browsers
+
+small {
+ @include font-size($small-font-size);
+}
+
+
+// Mark
+
+mark {
+ padding: $mark-padding;
+ color: var(--#{$prefix}highlight-color);
+ background-color: var(--#{$prefix}highlight-bg);
+}
+
+
+// Sub and Sup
+//
+// Prevent `sub` and `sup` elements from affecting the line height in
+// all browsers.
+
+sub,
+sup {
+ position: relative;
+ @include font-size($sub-sup-font-size);
+ line-height: 0;
+ vertical-align: baseline;
+}
+
+sub { bottom: -.25em; }
+sup { top: -.5em; }
+
+
+// Links
+
+a {
+ color: rgba(var(--#{$prefix}link-color-rgb), var(--#{$prefix}link-opacity, 1));
+ text-decoration: $link-decoration;
+
+ &:hover {
+ --#{$prefix}link-color-rgb: var(--#{$prefix}link-hover-color-rgb);
+ text-decoration: $link-hover-decoration;
+ }
+}
+// And undo these styles for placeholder links/named anchors (without href).
+// It would be more straightforward to just use a[href] in previous block, but that
+// causes specificity issues in many other styles that are too complex to fix.
+// See https://github.com/twbs/bootstrap/issues/19402
+
+a:not([href]):not([class]) {
+ &,
+ &:hover {
+ color: inherit;
+ text-decoration: none;
+ }
+}
+
+
+// Code
+
+pre,
+code,
+kbd,
+samp {
+ font-family: $font-family-code;
+ @include font-size(1em); // Correct the odd `em` font sizing in all browsers.
+}
+
+// 1. Remove browser default top margin
+// 2. Reset browser default of `1em` to use `rem`s
+// 3. Don't allow content to break outside
+
+pre {
+ display: block;
+ margin-top: 0; // 1
+ margin-bottom: 1rem; // 2
+ overflow: auto; // 3
+ @include font-size($code-font-size);
+ color: var(--#{$prefix}pre-color, $pre-color);
+
+ // Account for some code outputs that place code tags in pre tags
+ code {
+ @include font-size(inherit);
+ color: inherit;
+ word-break: normal;
+ }
+}
+
+code {
+ @include font-size($code-font-size);
+ color: var(--#{$prefix}code-color);
+ word-wrap: break-word;
+
+ // Streamline the style when inside anchors to avoid broken underline and more
+ a > & {
+ color: inherit;
+ }
+}
+
+kbd {
+ padding: $kbd-padding-y $kbd-padding-x;
+ @include font-size($kbd-font-size);
+ color: var(--#{$prefix}kbd-color, $kbd-color);
+ background-color: var(--#{$prefix}kbd-bg, $kbd-bg);
+ @include border-radius($border-radius-sm);
+
+ kbd {
+ padding: 0;
+ @include font-size(1em);
+ font-weight: $nested-kbd-font-weight;
+ }
+}
+
+
+// Figures
+//
+// Apply a consistent margin strategy (matches our type styles).
+
+figure {
+ margin: 0 0 1rem;
+}
+
+
+// Images and content
+
+img,
+svg {
+ vertical-align: middle;
+}
+
+
+// Tables
+//
+// Prevent double borders
+
+table {
+ caption-side: bottom;
+ border-collapse: collapse;
+}
+
+caption {
+ padding-top: $table-cell-padding-y;
+ padding-bottom: $table-cell-padding-y;
+ color: var(--#{$prefix}table-caption-color, $table-caption-color);
+ text-align: start;
+}
+
+// 1. Removes font-weight bold by inheriting
+// 2. Matches default `
` alignment by inheriting `text-align`.
+// 3. Fix alignment for Safari
+
+th {
+ font-weight: $table-th-font-weight; // 1
+ text-align: inherit; // 2
+ text-align: -webkit-match-parent; // 3
+}
+
+thead,
+tbody,
+tfoot,
+tr,
+td,
+th {
+ border-color: inherit;
+ border-style: solid;
+ border-width: 0;
+}
+
+
+// Forms
+//
+// 1. Allow labels to use `margin` for spacing.
+
+label {
+ display: inline-block; // 1
+}
+
+// Remove the default `border-radius` that macOS Chrome adds.
+// See https://github.com/twbs/bootstrap/issues/24093
+
+button {
+ // stylelint-disable-next-line property-disallowed-list
+ border-radius: 0;
+}
+
+// Explicitly remove focus outline in Chromium when it shouldn't be
+// visible (e.g. as result of mouse click or touch tap). It already
+// should be doing this automatically, but seems to currently be
+// confused and applies its very visible two-tone outline anyway.
+
+button:focus:not(:focus-visible) {
+ outline: 0;
+}
+
+// 1. Remove the margin in Firefox and Safari
+
+input,
+button,
+select,
+optgroup,
+textarea {
+ margin: 0; // 1
+ font-family: inherit;
+ @include font-size(inherit);
+ line-height: inherit;
+}
+
+// Remove the inheritance of text transform in Firefox
+button,
+select {
+ text-transform: none;
+}
+// Set the cursor for non-`` buttons
+//
+// Details at https://github.com/twbs/bootstrap/pull/30562
+[role="button"] {
+ cursor: pointer;
+}
+
+select {
+ // Remove the inheritance of word-wrap in Safari.
+ // See https://github.com/twbs/bootstrap/issues/24990
+ word-wrap: normal;
+
+ // Undo the opacity change from Chrome
+ &:disabled {
+ opacity: 1;
+ }
+}
+
+// Remove the dropdown arrow only from text type inputs built with datalists in Chrome.
+// See https://stackoverflow.com/a/54997118
+
+[list]:not([type="date"]):not([type="datetime-local"]):not([type="month"]):not([type="week"]):not([type="time"])::-webkit-calendar-picker-indicator {
+ display: none !important;
+}
+
+// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
+// controls in Android 4.
+// 2. Correct the inability to style clickable types in iOS and Safari.
+// 3. Opinionated: add "hand" cursor to non-disabled button elements.
+
+button,
+[type="button"], // 1
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button; // 2
+
+ @if $enable-button-pointers {
+ &:not(:disabled) {
+ cursor: pointer; // 3
+ }
+ }
+}
+
+// Remove inner border and padding from Firefox, but don't restore the outline like Normalize.
+
+::-moz-focus-inner {
+ padding: 0;
+ border-style: none;
+}
+
+// 1. Textareas should really only resize vertically so they don't break their (horizontal) containers.
+
+textarea {
+ resize: vertical; // 1
+}
+
+// 1. Browsers set a default `min-width: min-content;` on fieldsets,
+// unlike e.g. ``s, which have `min-width: 0;` by default.
+// So we reset that to ensure fieldsets behave more like a standard block element.
+// See https://github.com/twbs/bootstrap/issues/12359
+// and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements
+// 2. Reset the default outline behavior of fieldsets so they don't affect page layout.
+
+fieldset {
+ min-width: 0; // 1
+ padding: 0; // 2
+ margin: 0; // 2
+ border: 0; // 2
+}
+
+// 1. By using `float: left`, the legend will behave like a block element.
+// This way the border of a fieldset wraps around the legend if present.
+// 2. Fix wrapping bug.
+// See https://github.com/twbs/bootstrap/issues/29712
+
+legend {
+ float: inline-start; // 1
+ width: 100%;
+ padding: 0;
+ margin-bottom: $legend-margin-bottom;
+ font-weight: $legend-font-weight;
+ line-height: inherit;
+ @include font-size($legend-font-size);
+
+ + * {
+ clear: left; // 2
+ }
+}
+
+// Fix height of inputs with a type of datetime-local, date, month, week, or time
+// See https://github.com/twbs/bootstrap/issues/18842
+
+::-webkit-datetime-edit-fields-wrapper,
+::-webkit-datetime-edit-text,
+::-webkit-datetime-edit-minute,
+::-webkit-datetime-edit-hour-field,
+::-webkit-datetime-edit-day-field,
+::-webkit-datetime-edit-month-field,
+::-webkit-datetime-edit-year-field {
+ padding: 0;
+}
+
+::-webkit-inner-spin-button {
+ height: auto;
+}
+
+// 1. This overrides the extra rounded corners on search inputs in iOS so that our
+// `.form-control` class can properly style them. Note that this cannot simply
+// be added to `.form-control` as it's not specific enough. For details, see
+// https://github.com/twbs/bootstrap/issues/11586.
+// 2. Correct the outline style in Safari.
+
+[type="search"] {
+ -webkit-appearance: textfield; // 1
+ outline-offset: -2px; // 2
+}
+
+// 1. A few input types should stay LTR
+// See https://rtlstyling.com/posts/rtl-styling#form-inputs
+
+*[dir="rtl"] {
+ [type="tel"],
+ [type="url"],
+ [type="email"],
+ [type="number"] {
+ direction: ltr;
+ }
+}
+
+
+// Remove the inner padding in Chrome and Safari on macOS.
+
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+// Remove padding around color pickers in webkit browsers
+
+::-webkit-color-swatch-wrapper {
+ padding: 0;
+}
+
+
+// 1. Inherit font family and line height for file input buttons
+// 2. Correct the inability to style clickable types in iOS and Safari.
+
+::file-selector-button {
+ font: inherit; // 1
+ -webkit-appearance: button; // 2
+}
+
+// Correct element displays
+
+output {
+ display: inline-block;
+}
+
+// Remove border from iframe
+
+iframe {
+ border: 0;
+}
+
+// Summary
+//
+// 1. Add the correct display in all browsers
+
+summary {
+ display: list-item; // 1
+ cursor: pointer;
+}
+
+
+// Progress
+//
+// Add the correct vertical alignment in Chrome, Firefox, and Opera.
+
+progress {
+ vertical-align: baseline;
+}
+
+
+// Hidden attribute
+//
+// Always hide an element with the `hidden` HTML attribute.
+
+[hidden] {
+ display: none !important;
+}
diff --git a/src/scss/scss/_root.import.scss b/src/scss/scss/_root.import.scss
new file mode 100644
index 000000000..bf90eb9b2
--- /dev/null
+++ b/src/scss/scss/_root.import.scss
@@ -0,0 +1 @@
+@use "root";
diff --git a/src/scss/scss/_root.scss b/src/scss/scss/_root.scss
new file mode 100644
index 000000000..a6ca34e81
--- /dev/null
+++ b/src/scss/scss/_root.scss
@@ -0,0 +1,238 @@
+@use "sass:meta";
+@use "functions/color" as *;
+@use "functions/to-rgb" as *;
+@use "mixins/color-mode" as *;
+@use "vendor/rfs" as *;
+@use "maps" as *;
+@use "variables" as *;
+@use "variables-dark" as *;
+
+:root,
+[data#{$data-infix}theme="light"] {
+ // Note: Custom variable values only support SassScript inside `#{}`.
+
+ // Colors
+ //
+ // Generate palettes for full colors, grays, and theme colors
+
+ @each $color, $value in $colors {
+ --#{$prefix}#{$color}: #{$value};
+ }
+
+ @each $color, $value in $grays {
+ --#{$prefix}gray-#{$color}: #{$value};
+ }
+
+ @each $color, $value in $theme-colors {
+ --#{$prefix}#{$color}: #{$value};
+ }
+
+ @each $color, $value in $theme-colors-rgb {
+ --#{$prefix}#{$color}-rgb: #{$value};
+ }
+
+ @each $color, $value in $theme-colors-text {
+ --#{$prefix}#{$color}-text-emphasis: #{$value};
+ }
+
+ @each $color, $value in $theme-colors-bg-subtle {
+ --#{$prefix}#{$color}-bg-subtle: #{$value};
+ }
+
+ @each $color, $value in $theme-colors-border-subtle {
+ --#{$prefix}#{$color}-border-subtle: #{$value};
+ }
+
+ --#{$prefix}white-rgb: #{to-rgb($white)};
+ --#{$prefix}black-rgb: #{to-rgb($black)};
+
+ // Fonts
+
+ // Note: Use `meta.inspect` for lists so that quoted items keep the quotes.
+ // See https://github.com/sass/sass/issues/2383#issuecomment-336349172
+ --#{$prefix}font-sans-serif: #{meta.inspect($font-family-sans-serif)};
+ --#{$prefix}font-monospace: #{meta.inspect($font-family-monospace)};
+ --#{$prefix}gradient: #{$gradient};
+
+ // Root and body
+ // scss-docs-start root-body-variables
+ @if $font-size-root != null {
+ --#{$prefix}root-font-size: #{$font-size-root};
+ }
+ --#{$prefix}body-font-family: #{meta.inspect($font-family-base)};
+ @include rfs($font-size-base, --#{$prefix}body-font-size);
+ --#{$prefix}body-font-weight: #{$font-weight-base};
+ --#{$prefix}body-line-height: #{$line-height-base};
+ @if $body-text-align != null {
+ --#{$prefix}body-text-align: #{$body-text-align};
+ }
+
+ --#{$prefix}body-color: #{$body-color};
+ --#{$prefix}body-color-rgb: #{to-rgb($body-color)};
+ --#{$prefix}body-bg: #{$body-bg};
+ --#{$prefix}body-bg-rgb: #{to-rgb($body-bg)};
+
+ --#{$prefix}emphasis-color: #{$body-emphasis-color};
+ --#{$prefix}emphasis-color-rgb: #{to-rgb($body-emphasis-color)};
+
+ --#{$prefix}secondary-color: #{$body-secondary-color};
+ --#{$prefix}secondary-color-rgb: #{to-rgb($body-secondary-color)};
+ --#{$prefix}secondary-bg: #{$body-secondary-bg};
+ --#{$prefix}secondary-bg-rgb: #{to-rgb($body-secondary-bg)};
+
+ --#{$prefix}tertiary-color: #{$body-tertiary-color};
+ --#{$prefix}tertiary-color-rgb: #{to-rgb($body-tertiary-color)};
+ --#{$prefix}tertiary-bg: #{$body-tertiary-bg};
+ --#{$prefix}tertiary-bg-rgb: #{to-rgb($body-tertiary-bg)};
+
+ --#{$prefix}body-color-dark: #{$body-color-dark};
+ --#{$prefix}body-color-rgb-dark: #{to-rgb($body-color-dark)};
+ --#{$prefix}body-bg-dark: #{$body-bg-dark};
+ --#{$prefix}body-bg-rgb-dark: #{to-rgb($body-bg-dark)};
+
+ --#{$prefix}emphasis-color-dark: #{$body-emphasis-color-dark};
+ --#{$prefix}emphasis-color-rgb-dark: #{to-rgb($body-emphasis-color-dark)};
+
+ --#{$prefix}secondary-color-dark: #{$body-secondary-color-dark};
+ --#{$prefix}secondary-color-rgb-dark: #{to-rgb($body-secondary-color-dark)};
+ --#{$prefix}secondary-bg-dark: #{$body-secondary-bg-dark};
+ --#{$prefix}secondary-bg-rgb-dark: #{to-rgb($body-secondary-bg-dark)};
+
+ --#{$prefix}tertiary-color-dark: #{$body-tertiary-color-dark};
+ --#{$prefix}tertiary-color-rgb-dark: #{to-rgb($body-tertiary-color-dark)};
+ --#{$prefix}tertiary-bg-dark: #{$body-tertiary-bg-dark};
+ --#{$prefix}tertiary-bg-rgb-dark: #{to-rgb($body-tertiary-bg-dark)};
+
+ --#{$prefix}high-emphasis: #{$high-emphasis}; // Deprecated in v5.0.0
+ --#{$prefix}medium-emphasis: #{$medium-emphasis}; // Deprecated in v5.0.0
+ --#{$prefix}disabled: #{$disabled}; // Deprecated in v5.0.0
+
+ --#{$prefix}high-emphasis-inverse: #{$high-emphasis-inverse}; // Deprecated in v5.0.0
+ --#{$prefix}medium-emphasis-inverse: #{$medium-emphasis-inverse}; // Deprecated in v5.0.0
+ --#{$prefix}disabled-inverse: #{$disabled-inverse}; // Deprecated in v5.0.0
+ // scss-docs-end root-body-variables
+
+ --#{$prefix}heading-color: #{$headings-color};
+
+ --#{$prefix}link-color: #{$link-color};
+ --#{$prefix}link-color-rgb: #{to-rgb($link-color)};
+ --#{$prefix}link-decoration: #{$link-decoration};
+
+ --#{$prefix}link-hover-color: #{$link-hover-color};
+ --#{$prefix}link-hover-color-rgb: #{to-rgb($link-hover-color)};
+
+ @if $link-hover-decoration != null {
+ --#{$prefix}link-hover-decoration: #{$link-hover-decoration};
+ }
+
+ --#{$prefix}code-color: #{$code-color};
+ --#{$prefix}highlight-color: #{$mark-color};
+ --#{$prefix}highlight-bg: #{$mark-bg};
+
+ // scss-docs-start root-border-var
+ --#{$prefix}border-width: #{$border-width};
+ --#{$prefix}border-style: #{$border-style};
+ --#{$prefix}border-color: #{$border-color};
+ --#{$prefix}border-color-translucent: #{$border-color-translucent};
+
+ --#{$prefix}border-radius: #{$border-radius};
+ --#{$prefix}border-radius-sm: #{$border-radius-sm};
+ --#{$prefix}border-radius-lg: #{$border-radius-lg};
+ --#{$prefix}border-radius-xl: #{$border-radius-xl};
+ --#{$prefix}border-radius-xxl: #{$border-radius-xxl};
+ --#{$prefix}border-radius-2xl: var(--#{$prefix}border-radius-xxl); // Deprecated in v5.0.0 for consistency
+ --#{$prefix}border-radius-pill: #{$border-radius-pill};
+ // scss-docs-end root-border-var
+
+ --#{$prefix}box-shadow: #{$box-shadow};
+ --#{$prefix}box-shadow-sm: #{$box-shadow-sm};
+ --#{$prefix}box-shadow-lg: #{$box-shadow-lg};
+ --#{$prefix}box-shadow-inset: #{$box-shadow-inset};
+
+ // Focus styles
+ // scss-docs-start root-focus-variables
+ --#{$prefix}focus-ring-width: #{$focus-ring-width};
+ --#{$prefix}focus-ring-opacity: #{$focus-ring-opacity};
+ --#{$prefix}focus-ring-color: #{$focus-ring-color};
+ // scss-docs-end root-focus-variables
+
+ // scss-docs-start root-form-validation-variables
+ --#{$prefix}form-valid-color: #{$form-valid-color};
+ --#{$prefix}form-valid-border-color: #{$form-valid-border-color};
+ --#{$prefix}form-invalid-color: #{$form-invalid-color};
+ --#{$prefix}form-invalid-border-color: #{$form-invalid-border-color};
+ // scss-docs-end root-form-validation-variables
+}
+
+@if $enable-dark-mode {
+ @include color-mode(dark, true) {
+ color-scheme: dark;
+
+ // scss-docs-start root-dark-mode-vars
+ --#{$prefix}body-color: #{$body-color-dark};
+ --#{$prefix}body-color-rgb: #{to-rgb($body-color-dark)};
+ --#{$prefix}body-bg: #{$body-bg-dark};
+ --#{$prefix}body-bg-rgb: #{to-rgb($body-bg-dark)};
+
+ --#{$prefix}emphasis-color: #{$body-emphasis-color-dark};
+ --#{$prefix}emphasis-color-rgb: #{to-rgb($body-emphasis-color-dark)};
+
+ --#{$prefix}secondary-color: #{$body-secondary-color-dark};
+ --#{$prefix}secondary-color-rgb: #{to-rgb($body-secondary-color-dark)};
+ --#{$prefix}secondary-bg: #{$body-secondary-bg-dark};
+ --#{$prefix}secondary-bg-rgb: #{to-rgb($body-secondary-bg-dark)};
+
+ --#{$prefix}tertiary-color: #{$body-tertiary-color-dark};
+ --#{$prefix}tertiary-color-rgb: #{to-rgb($body-tertiary-color-dark)};
+ --#{$prefix}tertiary-bg: #{$body-tertiary-bg-dark};
+ --#{$prefix}tertiary-bg-rgb: #{to-rgb($body-tertiary-bg-dark)};
+
+ --#{$prefix}high-emphasis: #{$high-emphasis-dark}; // Deprecated in v5.0.0
+ --#{$prefix}medium-emphasis: #{$medium-emphasis-dark}; // Deprecated in v5.0.0
+ --#{$prefix}disabled: #{$disabled-dark}; // Deprecated in v5.0.0
+
+ @each $color, $value in $theme-colors-dark {
+ --#{$prefix}#{$color}: #{$value};
+ }
+
+ @each $color, $value in $grays-dark {
+ --#{$prefix}gray-#{$color}: #{$value};
+ }
+
+ @each $color, $value in $theme-colors-rgb-dark {
+ --#{$prefix}#{$color}-rgb: #{$value};
+ }
+
+ @each $color, $value in $theme-colors-text-dark {
+ --#{$prefix}#{$color}-text-emphasis: #{$value};
+ }
+
+ @each $color, $value in $theme-colors-bg-subtle-dark {
+ --#{$prefix}#{$color}-bg-subtle: #{$value};
+ }
+
+ @each $color, $value in $theme-colors-border-subtle-dark {
+ --#{$prefix}#{$color}-border-subtle: #{$value};
+ }
+
+ --#{$prefix}heading-color: #{$headings-color-dark};
+
+ --#{$prefix}link-color: #{$link-color-dark};
+ --#{$prefix}link-hover-color: #{$link-hover-color-dark};
+ --#{$prefix}link-color-rgb: #{to-rgb($link-color-dark)};
+ --#{$prefix}link-hover-color-rgb: #{to-rgb($link-hover-color-dark)};
+
+ --#{$prefix}code-color: #{$code-color-dark};
+ --#{$prefix}highlight-color: #{$mark-color-dark};
+ --#{$prefix}highlight-bg: #{$mark-bg-dark};
+
+ --#{$prefix}border-color: #{$border-color-dark};
+ --#{$prefix}border-color-translucent: #{$border-color-translucent-dark};
+
+ --#{$prefix}form-valid-color: #{$form-valid-color-dark};
+ --#{$prefix}form-valid-border-color: #{$form-valid-border-color-dark};
+ --#{$prefix}form-invalid-color: #{$form-invalid-color-dark};
+ --#{$prefix}form-invalid-border-color: #{$form-invalid-border-color-dark};
+ // scss-docs-end root-dark-mode-vars
+ }
+}
diff --git a/src/scss/scss/_sidebar.import.scss b/src/scss/scss/_sidebar.import.scss
new file mode 100644
index 000000000..4473c264f
--- /dev/null
+++ b/src/scss/scss/_sidebar.import.scss
@@ -0,0 +1,3 @@
+@forward "sidebar/sidebar";
+@forward "sidebar/sidebar-nav";
+@forward "sidebar/sidebar-narrow";
diff --git a/src/scss/scss/_sidebar.scss b/src/scss/scss/_sidebar.scss
new file mode 100644
index 000000000..4473c264f
--- /dev/null
+++ b/src/scss/scss/_sidebar.scss
@@ -0,0 +1,3 @@
+@forward "sidebar/sidebar";
+@forward "sidebar/sidebar-nav";
+@forward "sidebar/sidebar-narrow";
diff --git a/src/scss/scss/_spinners.import.scss b/src/scss/scss/_spinners.import.scss
new file mode 100644
index 000000000..072336a2a
--- /dev/null
+++ b/src/scss/scss/_spinners.import.scss
@@ -0,0 +1 @@
+@forward "spinners";
diff --git a/src/scss/scss/_spinners.scss b/src/scss/scss/_spinners.scss
new file mode 100644
index 000000000..01094b7e3
--- /dev/null
+++ b/src/scss/scss/_spinners.scss
@@ -0,0 +1,87 @@
+@use "variables" as *;
+
+//
+// Rotating border
+//
+
+.spinner-grow,
+.spinner-border {
+ display: inline-block;
+ width: var(--#{$prefix}spinner-width);
+ height: var(--#{$prefix}spinner-height);
+ vertical-align: var(--#{$prefix}spinner-vertical-align);
+ // stylelint-disable-next-line property-disallowed-list
+ border-radius: 50%;
+ animation: var(--#{$prefix}spinner-animation-speed) linear infinite var(--#{$prefix}spinner-animation-name);
+}
+
+// scss-docs-start spinner-border-keyframes
+@keyframes spinner-border {
+ to { transform: rotate(360deg) #{"/* rtl:ignore */"}; }
+}
+// scss-docs-end spinner-border-keyframes
+
+.spinner-border {
+ // scss-docs-start spinner-border-css-vars
+ --#{$prefix}spinner-width: #{$spinner-width};
+ --#{$prefix}spinner-height: #{$spinner-height};
+ --#{$prefix}spinner-vertical-align: #{$spinner-vertical-align};
+ --#{$prefix}spinner-border-width: #{$spinner-border-width};
+ --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed};
+ --#{$prefix}spinner-animation-name: spinner-border;
+ // scss-docs-end spinner-border-css-vars
+
+ border: var(--#{$prefix}spinner-border-width) solid currentcolor;
+ border-right-color: transparent;
+}
+
+.spinner-border-sm {
+ // scss-docs-start spinner-border-sm-css-vars
+ --#{$prefix}spinner-width: #{$spinner-width-sm};
+ --#{$prefix}spinner-height: #{$spinner-height-sm};
+ --#{$prefix}spinner-border-width: #{$spinner-border-width-sm};
+ // scss-docs-end spinner-border-sm-css-vars
+}
+
+//
+// Growing circle
+//
+
+// scss-docs-start spinner-grow-keyframes
+@keyframes spinner-grow {
+ 0% {
+ transform: scale(0);
+ }
+ 50% {
+ opacity: 1;
+ transform: none;
+ }
+}
+// scss-docs-end spinner-grow-keyframes
+
+.spinner-grow {
+ // scss-docs-start spinner-grow-css-vars
+ --#{$prefix}spinner-width: #{$spinner-width};
+ --#{$prefix}spinner-height: #{$spinner-height};
+ --#{$prefix}spinner-vertical-align: #{$spinner-vertical-align};
+ --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed};
+ --#{$prefix}spinner-animation-name: spinner-grow;
+ // scss-docs-end spinner-grow-css-vars
+
+ background-color: currentcolor;
+ opacity: 0;
+}
+
+.spinner-grow-sm {
+ --#{$prefix}spinner-width: #{$spinner-width-sm};
+ --#{$prefix}spinner-height: #{$spinner-height-sm};
+}
+
+@if $enable-reduced-motion {
+ @media (prefers-reduced-motion: reduce) {
+ .spinner-border,
+ .spinner-grow {
+ --#{$prefix}spinner-animation-speed: #{$spinner-animation-speed * 2};
+ }
+ }
+}
diff --git a/src/scss/scss/_tables.import.scss b/src/scss/scss/_tables.import.scss
new file mode 100644
index 000000000..c272d9523
--- /dev/null
+++ b/src/scss/scss/_tables.import.scss
@@ -0,0 +1 @@
+@forward "tables";
diff --git a/src/scss/scss/_tables.scss b/src/scss/scss/_tables.scss
new file mode 100644
index 000000000..21a7a5a01
--- /dev/null
+++ b/src/scss/scss/_tables.scss
@@ -0,0 +1,176 @@
+@use "sass:map";
+@use "mixins/breakpoints" as *;
+@use "mixins/table-variants" as *;
+@use "variables" as *;
+
+//
+// Basic Bootstrap table
+//
+
+.table {
+ // Reset needed for nesting tables
+ --#{$prefix}table-color-type: initial;
+ --#{$prefix}table-bg-type: initial;
+ --#{$prefix}table-color-state: initial;
+ --#{$prefix}table-bg-state: initial;
+ // End of reset
+ --#{$prefix}table-color: #{$table-color};
+ --#{$prefix}table-bg: #{$table-bg};
+ --#{$prefix}table-border-color: #{$table-border-color};
+ --#{$prefix}table-accent-bg: #{$table-accent-bg};
+ --#{$prefix}table-striped-color: #{$table-striped-color};
+ --#{$prefix}table-striped-bg: #{$table-striped-bg};
+ --#{$prefix}table-active-color: #{$table-active-color};
+ --#{$prefix}table-active-bg: #{$table-active-bg};
+ --#{$prefix}table-hover-color: #{$table-hover-color};
+ --#{$prefix}table-hover-bg: #{$table-hover-bg};
+
+ width: 100%;
+ margin-bottom: $spacer;
+ vertical-align: $table-cell-vertical-align;
+ border-color: var(--#{$prefix}table-border-color);
+
+ // Target th & td
+ // We need the child combinator to prevent styles leaking to nested tables which doesn't have a `.table` class.
+ // We use the universal selectors here to simplify the selector (else we would need 6 different selectors).
+ // Another advantage is that this generates less code and makes the selector less specific making it easier to override.
+ // stylelint-disable-next-line selector-max-universal
+ > :not(caption) > * > * {
+ padding: $table-cell-padding-y $table-cell-padding-x;
+ // Following the precept of cascades: https://codepen.io/miriamsuzanne/full/vYNgodb
+ color: var(--#{$prefix}table-color-state, var(--#{$prefix}table-color-type, var(--#{$prefix}table-color)));
+ background-color: var(--#{$prefix}table-bg);
+ border-bottom-width: $table-border-width;
+ box-shadow: inset 0 0 0 9999px var(--#{$prefix}table-bg-state, var(--#{$prefix}table-bg-type, var(--#{$prefix}table-accent-bg)));
+ }
+
+ > tbody {
+ vertical-align: inherit;
+ }
+
+ > thead {
+ vertical-align: bottom;
+ }
+}
+
+.table-group-divider {
+ border-top: calc(#{$table-border-width} * 2) solid $table-group-separator-color; // stylelint-disable-line function-disallowed-list
+}
+
+//
+// Change placement of captions with a class
+//
+
+.caption-top {
+ caption-side: top;
+}
+
+
+//
+// Condensed table w/ half padding
+//
+
+.table-sm {
+ // stylelint-disable-next-line selector-max-universal
+ > :not(caption) > * > * {
+ padding: $table-cell-padding-y-sm $table-cell-padding-x-sm;
+ }
+}
+
+
+// Border versions
+//
+// Add or remove borders all around the table and between all the columns.
+//
+// When borders are added on all sides of the cells, the corners can render odd when
+// these borders do not have the same color or if they are semi-transparent.
+// Therefore we add top and border bottoms to the `tr`s and left and right borders
+// to the `td`s or `th`s
+
+.table-bordered {
+ > :not(caption) > * {
+ border-width: $table-border-width 0;
+
+ // stylelint-disable-next-line selector-max-universal
+ > * {
+ border-width: 0 $table-border-width;
+ }
+ }
+}
+
+.table-borderless {
+ // stylelint-disable-next-line selector-max-universal
+ > :not(caption) > * > * {
+ border-bottom-width: 0;
+ }
+
+ > :not(:first-child) {
+ border-top-width: 0;
+ }
+}
+
+// Zebra-striping
+//
+// Default zebra-stripe styles (alternating gray and transparent backgrounds)
+
+// For rows
+.table-striped {
+ > tbody > tr:nth-of-type(#{$table-striped-order}) > * {
+ --#{$prefix}table-color-type: var(--#{$prefix}table-striped-color);
+ --#{$prefix}table-bg-type: var(--#{$prefix}table-striped-bg);
+ }
+}
+
+// For columns
+.table-striped-columns {
+ > :not(caption) > tr > :nth-child(#{$table-striped-columns-order}) {
+ --#{$prefix}table-color-type: var(--#{$prefix}table-striped-color);
+ --#{$prefix}table-bg-type: var(--#{$prefix}table-striped-bg);
+ }
+}
+
+// Active table
+//
+// The `.table-active` class can be added to highlight rows or cells
+
+.table-active {
+ --#{$prefix}table-color-state: var(--#{$prefix}table-active-color);
+ --#{$prefix}table-bg-state: var(--#{$prefix}table-active-bg);
+}
+
+// Hover effect
+//
+// Placed here since it has to come after the potential zebra striping
+
+.table-hover {
+ > tbody > tr:hover > * {
+ --#{$prefix}table-color-state: var(--#{$prefix}table-hover-color);
+ --#{$prefix}table-bg-state: var(--#{$prefix}table-hover-bg);
+ }
+}
+
+
+// Table variants
+//
+// Table variants set the table cell backgrounds, border colors
+// and the colors of the striped, hovered & active tables
+
+@each $color, $value in $table-variants {
+ @include table-variant($color, $value);
+}
+
+// Responsive tables
+//
+// Generate series of `.table-responsive-*` classes for configuring the screen
+// size of where your table will overflow.
+
+@each $breakpoint in map.keys($grid-breakpoints) {
+ $infix: breakpoint-infix($breakpoint, $grid-breakpoints);
+
+ @include media-breakpoint-down($breakpoint) {
+ .table-responsive#{$infix} {
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+ }
+}
diff --git a/src/scss/scss/_toasts.import.scss b/src/scss/scss/_toasts.import.scss
new file mode 100644
index 000000000..882c22d46
--- /dev/null
+++ b/src/scss/scss/_toasts.import.scss
@@ -0,0 +1 @@
+@forward "toasts";
diff --git a/src/scss/scss/_toasts.scss b/src/scss/scss/_toasts.scss
new file mode 100644
index 000000000..4be77dc87
--- /dev/null
+++ b/src/scss/scss/_toasts.scss
@@ -0,0 +1,76 @@
+@use "mixins/border-radius" as *;
+@use "vendor/rfs" as *;
+@use "variables" as *;
+
+.toast {
+ // scss-docs-start toast-css-vars
+ --#{$prefix}toast-zindex: #{$zindex-toast};
+ --#{$prefix}toast-padding-x: #{$toast-padding-x};
+ --#{$prefix}toast-padding-y: #{$toast-padding-y};
+ --#{$prefix}toast-spacing: #{$toast-spacing};
+ --#{$prefix}toast-max-width: #{$toast-max-width};
+ @include rfs($toast-font-size, --#{$prefix}toast-font-size);
+ --#{$prefix}toast-color: #{$toast-color};
+ --#{$prefix}toast-bg: #{$toast-background-color};
+ --#{$prefix}toast-border-width: #{$toast-border-width};
+ --#{$prefix}toast-border-color: #{$toast-border-color};
+ --#{$prefix}toast-border-radius: #{$toast-border-radius};
+ --#{$prefix}toast-box-shadow: #{$toast-box-shadow};
+ --#{$prefix}toast-header-color: #{$toast-header-color};
+ --#{$prefix}toast-header-bg: #{$toast-header-background-color};
+ --#{$prefix}toast-header-border-color: #{$toast-header-border-color};
+ // scss-docs-end toast-css-vars
+
+ width: var(--#{$prefix}toast-max-width);
+ max-width: 100%;
+ @include font-size(var(--#{$prefix}toast-font-size));
+ color: var(--#{$prefix}toast-color);
+ pointer-events: auto;
+ background-color: var(--#{$prefix}toast-bg);
+ background-clip: padding-box;
+ border: var(--#{$prefix}toast-border-width) solid var(--#{$prefix}toast-border-color);
+ box-shadow: var(--#{$prefix}toast-box-shadow);
+ @include border-radius(var(--#{$prefix}toast-border-radius));
+
+ &.showing {
+ opacity: 0;
+ }
+
+ &:not(.show) {
+ display: none;
+ }
+}
+
+.toast-container {
+ --#{$prefix}toast-zindex: #{$zindex-toast};
+
+ position: absolute;
+ z-index: var(--#{$prefix}toast-zindex);
+ width: max-content;
+ max-width: 100%;
+ pointer-events: none;
+
+ > :not(:last-child) {
+ margin-bottom: var(--#{$prefix}toast-spacing);
+ }
+}
+
+.toast-header {
+ display: flex;
+ align-items: center;
+ padding: var(--#{$prefix}toast-padding-y) var(--#{$prefix}toast-padding-x);
+ color: var(--#{$prefix}toast-header-color);
+ background-color: var(--#{$prefix}toast-header-bg);
+ background-clip: padding-box;
+ border-bottom: var(--#{$prefix}toast-border-width) solid var(--#{$prefix}toast-header-border-color);
+ @include border-top-radius(calc(var(--#{$prefix}toast-border-radius) - var(--#{$prefix}toast-border-width)));
+
+ .btn-close {
+ margin-inline: var(--#{$prefix}toast-padding-x) calc(-.5 * var(--#{$prefix}toast-padding-x)); // stylelint-disable-line function-disallowed-list
+ }
+}
+
+.toast-body {
+ padding: var(--#{$prefix}toast-padding-x);
+ word-wrap: break-word;
+}
diff --git a/src/scss/scss/_tooltip.import.scss b/src/scss/scss/_tooltip.import.scss
new file mode 100644
index 000000000..5ee0266aa
--- /dev/null
+++ b/src/scss/scss/_tooltip.import.scss
@@ -0,0 +1 @@
+@forward "tooltip";
diff --git a/src/scss/scss/_tooltip.scss b/src/scss/scss/_tooltip.scss
new file mode 100644
index 000000000..34b2e87ea
--- /dev/null
+++ b/src/scss/scss/_tooltip.scss
@@ -0,0 +1,125 @@
+@use "mixins/border-radius" as *;
+@use "mixins/deprecate" as *;
+@use "mixins/reset-text" as *;
+@use "vendor/rfs" as *;
+@use "variables" as *;
+
+// Base class
+.tooltip {
+ // scss-docs-start tooltip-css-vars
+ --#{$prefix}tooltip-zindex: #{$zindex-tooltip};
+ --#{$prefix}tooltip-max-width: #{$tooltip-max-width};
+ --#{$prefix}tooltip-padding-x: #{$tooltip-padding-x};
+ --#{$prefix}tooltip-padding-y: #{$tooltip-padding-y};
+ --#{$prefix}tooltip-margin: #{$tooltip-margin};
+ @include rfs($tooltip-font-size, --#{$prefix}tooltip-font-size);
+ --#{$prefix}tooltip-color: #{$tooltip-color};
+ --#{$prefix}tooltip-bg: #{$tooltip-bg};
+ --#{$prefix}tooltip-border-radius: #{$tooltip-border-radius};
+ --#{$prefix}tooltip-opacity: #{$tooltip-opacity};
+ --#{$prefix}tooltip-arrow-width: #{$tooltip-arrow-width};
+ --#{$prefix}tooltip-arrow-height: #{$tooltip-arrow-height};
+ // scss-docs-end tooltip-css-vars
+
+ z-index: var(--#{$prefix}tooltip-zindex);
+ display: block;
+ margin: var(--#{$prefix}tooltip-margin);
+ @include deprecate("`$tooltip-margin`", "v4", "v4.x", true);
+ // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.
+ // So reset our font and text properties to avoid inheriting weird values.
+ @include reset-text();
+ @include font-size(var(--#{$prefix}tooltip-font-size));
+ // Allow breaking very long words so they don't overflow the tooltip's bounds
+ word-wrap: break-word;
+ opacity: 0;
+
+ &.show { opacity: var(--#{$prefix}tooltip-opacity); }
+
+ .tooltip-arrow {
+ display: block;
+ width: var(--#{$prefix}tooltip-arrow-width);
+ height: var(--#{$prefix}tooltip-arrow-height);
+
+ &::before {
+ position: absolute;
+ content: "";
+ border-color: transparent;
+ border-style: solid;
+ }
+ }
+}
+
+.bs-tooltip-top .tooltip-arrow {
+ bottom: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list
+
+ &::before {
+ top: -1px;
+ border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
+ border-top-color: var(--#{$prefix}tooltip-bg);
+ }
+}
+
+/* rtl:begin:ignore */
+.bs-tooltip-end .tooltip-arrow {
+ left: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list
+ width: var(--#{$prefix}tooltip-arrow-height);
+ height: var(--#{$prefix}tooltip-arrow-width);
+
+ &::before {
+ right: -1px;
+ border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; // stylelint-disable-line function-disallowed-list
+ border-right-color: var(--#{$prefix}tooltip-bg);
+ }
+}
+
+/* rtl:end:ignore */
+
+.bs-tooltip-bottom .tooltip-arrow {
+ top: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list
+
+ &::before {
+ bottom: -1px;
+ border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list
+ border-bottom-color: var(--#{$prefix}tooltip-bg);
+ }
+}
+
+/* rtl:begin:ignore */
+.bs-tooltip-start .tooltip-arrow {
+ right: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); // stylelint-disable-line function-disallowed-list
+ width: var(--#{$prefix}tooltip-arrow-height);
+ height: var(--#{$prefix}tooltip-arrow-width);
+
+ &::before {
+ left: -1px;
+ border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); // stylelint-disable-line function-disallowed-list
+ border-left-color: var(--#{$prefix}tooltip-bg);
+ }
+}
+
+/* rtl:end:ignore */
+
+.bs-tooltip-auto {
+ &[data-popper-placement^="top"] {
+ @extend .bs-tooltip-top;
+ }
+ &[data-popper-placement^="right"] {
+ @extend .bs-tooltip-end;
+ }
+ &[data-popper-placement^="bottom"] {
+ @extend .bs-tooltip-bottom;
+ }
+ &[data-popper-placement^="left"] {
+ @extend .bs-tooltip-start;
+ }
+}
+
+// Wrapper for the tooltip content
+.tooltip-inner {
+ max-width: var(--#{$prefix}tooltip-max-width);
+ padding: var(--#{$prefix}tooltip-padding-y) var(--#{$prefix}tooltip-padding-x);
+ color: var(--#{$prefix}tooltip-color);
+ text-align: center;
+ background-color: var(--#{$prefix}tooltip-bg);
+ @include border-radius(var(--#{$prefix}tooltip-border-radius));
+}
diff --git a/src/scss/scss/_transitions.import.scss b/src/scss/scss/_transitions.import.scss
new file mode 100644
index 000000000..c7572dfc7
--- /dev/null
+++ b/src/scss/scss/_transitions.import.scss
@@ -0,0 +1 @@
+@forward "transitions";
diff --git a/src/scss/scss/_transitions.scss b/src/scss/scss/_transitions.scss
new file mode 100644
index 000000000..8a562c488
--- /dev/null
+++ b/src/scss/scss/_transitions.scss
@@ -0,0 +1,30 @@
+@use "mixins/transition" as *;
+@use "variables" as *;
+
+.fade {
+ @include transition($transition-fade);
+
+ &:not(.show) {
+ opacity: 0;
+ }
+}
+
+// scss-docs-start collapse-classes
+.collapse {
+ &:not(.show) {
+ display: none;
+ }
+}
+
+.collapsing {
+ height: 0;
+ overflow: hidden;
+ @include transition($transition-collapse);
+
+ &.collapse-horizontal {
+ width: 0;
+ height: auto;
+ @include transition($transition-collapse-width);
+ }
+}
+// scss-docs-end collapse-classes
diff --git a/src/scss/scss/_type.import.scss b/src/scss/scss/_type.import.scss
new file mode 100644
index 000000000..c002d0d21
--- /dev/null
+++ b/src/scss/scss/_type.import.scss
@@ -0,0 +1 @@
+@forward "type";
diff --git a/src/scss/scss/_type.scss b/src/scss/scss/_type.scss
new file mode 100644
index 000000000..e3aaff9cb
--- /dev/null
+++ b/src/scss/scss/_type.scss
@@ -0,0 +1,111 @@
+@use "mixins/lists" as *;
+@use "vendor/rfs" as *;
+@use "reboot" as *;
+@use "variables" as *;
+
+//
+// Headings
+//
+.h1 {
+ @extend h1;
+}
+
+.h2 {
+ @extend h2;
+}
+
+.h3 {
+ @extend h3;
+}
+
+.h4 {
+ @extend h4;
+}
+
+.h5 {
+ @extend h5;
+}
+
+.h6 {
+ @extend h6;
+}
+
+
+.lead {
+ @include font-size($lead-font-size);
+ font-weight: $lead-font-weight;
+}
+
+// Type display classes
+@each $display, $font-size in $display-font-sizes {
+ .display-#{$display} {
+ font-family: $display-font-family;
+ font-style: $display-font-style;
+ font-weight: $display-font-weight;
+ line-height: $display-line-height;
+ @include font-size($font-size);
+ }
+}
+
+//
+// Emphasis
+//
+.small {
+ @extend small;
+}
+
+.mark {
+ @extend mark;
+}
+
+//
+// Lists
+//
+
+.list-unstyled {
+ @include list-unstyled();
+}
+
+// Inline turns list items into inline-block
+.list-inline {
+ @include list-unstyled();
+}
+.list-inline-item {
+ display: inline-block;
+
+ &:not(:last-child) {
+ margin-inline-end: $list-inline-padding;
+ }
+}
+
+
+//
+// Misc
+//
+
+// Builds on `abbr`
+.initialism {
+ @include font-size($initialism-font-size);
+ text-transform: uppercase;
+}
+
+// Blockquotes
+.blockquote {
+ margin-bottom: $blockquote-margin-y;
+ @include font-size($blockquote-font-size);
+
+ > :last-child {
+ margin-bottom: 0;
+ }
+}
+
+.blockquote-footer {
+ margin-top: -$blockquote-margin-y;
+ margin-bottom: $blockquote-margin-y;
+ @include font-size($blockquote-footer-font-size);
+ color: $blockquote-footer-color;
+
+ &::before {
+ content: "\2014\00A0"; // em dash, nbsp
+ }
+}
diff --git a/src/scss/scss/_utilities.import.scss b/src/scss/scss/_utilities.import.scss
new file mode 100644
index 000000000..d94f1f77b
--- /dev/null
+++ b/src/scss/scss/_utilities.import.scss
@@ -0,0 +1 @@
+@forward "utilities";
diff --git a/src/scss/scss/_utilities.scss b/src/scss/scss/_utilities.scss
new file mode 100644
index 000000000..4f4c69892
--- /dev/null
+++ b/src/scss/scss/_utilities.scss
@@ -0,0 +1,865 @@
+@use "sass:map";
+@use "functions/maps" as *;
+@use "maps" as *;
+@use "variables" as *;
+
+// Utilities
+
+$utilities: () !default;
+// stylelint-disable-next-line scss/dollar-variable-default
+$utilities: map.merge(
+ (
+ // scss-docs-start utils-vertical-align
+ "align": (
+ property: vertical-align,
+ class: align,
+ values: baseline top middle bottom text-bottom text-top
+ ),
+ // scss-docs-end utils-vertical-align
+ // scss-docs-start utils-float
+ "float": (
+ responsive: true,
+ property: float,
+ values: (
+ start: inline-start,
+ end: inline-end,
+ none: none,
+ ),
+ ),
+ // scss-docs-end utils-float
+ // Object Fit utilities
+ // scss-docs-start utils-object-fit
+ "object-fit": (
+ responsive: true,
+ property: object-fit,
+ values: (
+ contain: contain,
+ cover: cover,
+ fill: fill,
+ scale: scale-down,
+ none: none,
+ )
+ ),
+ // scss-docs-end utils-object-fit
+ // Opacity utilities
+ // scss-docs-start utils-opacity
+ "opacity": (
+ property: opacity,
+ values: (
+ 0: 0,
+ 25: .25,
+ 50: .5,
+ 75: .75,
+ 100: 1,
+ )
+ ),
+ // scss-docs-end utils-opacity
+ // scss-docs-start utils-overflow
+ "overflow": (
+ property: overflow,
+ values: auto hidden visible scroll,
+ ),
+ "overflow-x": (
+ property: overflow-x,
+ values: auto hidden visible scroll,
+ ),
+ "overflow-y": (
+ property: overflow-y,
+ values: auto hidden visible scroll,
+ ),
+ // scss-docs-end utils-overflow
+ // scss-docs-start utils-display
+ "display": (
+ responsive: true,
+ print: true,
+ property: display,
+ class: d,
+ values: inline inline-block block grid inline-grid table table-row table-cell flex inline-flex none
+ ),
+ // scss-docs-end utils-display
+ // scss-docs-start utils-shadow
+ "shadow": (
+ property: box-shadow,
+ class: shadow,
+ values: (
+ null: var(--#{$prefix}box-shadow),
+ sm: var(--#{$prefix}box-shadow-sm),
+ lg: var(--#{$prefix}box-shadow-lg),
+ none: none,
+ )
+ ),
+ // scss-docs-end utils-shadow
+ // scss-docs-start utils-focus-ring
+ "focus-ring": (
+ css-var: true,
+ css-variable-name: focus-ring-color,
+ class: focus-ring,
+ values: map-loop($theme-colors-rgb, rgba-css-var, "$prefix", "$key", "focus-ring")
+ ),
+ // scss-docs-end utils-focus-ring
+ // scss-docs-start utils-position
+ "position": (
+ property: position,
+ values: static relative absolute fixed sticky
+ ),
+ "top": (
+ property: top,
+ values: $position-values
+ ),
+ "bottom": (
+ property: bottom,
+ values: $position-values
+ ),
+ "start": (
+ property: inset-inline-start,
+ class: start,
+ values: $position-values
+ ),
+ "end": (
+ property: inset-inline-end,
+ class: end,
+ values: $position-values
+ ),
+ "translate-middle": (
+ property: transform,
+ class: translate-middle,
+ values: (
+ null: ("ltr": translate(-50%, -50%), "rtl": translate(50%, -50%)),
+ x: ("ltr": translateX(-50%), "rtl": translateX(50%)),
+ y: translateY(-50%),
+ ),
+ rtl: true
+ ),
+ // scss-docs-end utils-position
+ // scss-docs-start utils-borders
+ "border": (
+ property: border,
+ values: (
+ null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),
+ 0: 0,
+ )
+ ),
+ "border-top": (
+ property: border-top,
+ values: (
+ null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),
+ 0: 0,
+ )
+ ),
+ "border-end": (
+ property: border-inline-end,
+ class: border-end,
+ values: (
+ null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),
+ 0: 0,
+ ),
+ ),
+ "border-bottom": (
+ property: border-bottom,
+ values: (
+ null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),
+ 0: 0,
+ )
+ ),
+ "border-start": (
+ property: border-inline-start,
+ class: border-start,
+ values: (
+ null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),
+ 0: 0,
+ ),
+ ),
+ "border-color": (
+ property: border-color,
+ class: border,
+ local-vars: (
+ "border-opacity": 1
+ ),
+ values: $utilities-border-colors
+ ),
+ "border-top-color": (
+ property: border-top-color,
+ class: border-top,
+ values: map.merge($theme-colors, ("white": $white)),
+ vars: true
+ ),
+ "border-end-color": (
+ property: border-inline-end-color,
+ class: border-end,
+ values: map.merge($theme-colors, ("white": $white)),
+ vars: true,
+ ),
+ "border-bottom-color": (
+ property: border-bottom-color,
+ class: border-bottom,
+ values: map.merge($theme-colors, ("white": $white)),
+ vars: true
+ ),
+ "border-start-color": (
+ property: border-inline-start-color,
+ class: border-start,
+ values: map.merge($theme-colors, ("white": $white)),
+ vars: true,
+ ),
+ "border-width": (
+ property: border-width,
+ class: border,
+ values: $border-widths
+ ),
+ "border-top-width": (
+ property: border-top-width,
+ class: border-top,
+ values: $border-widths
+ ),
+ "border-end-width": (
+ property: border-inline-end-width,
+ class: border-end,
+ values: $border-widths,
+ ),
+ "border-bottom-width": (
+ property: border-bottom-width,
+ class: border-bottom,
+ values: $border-widths
+ ),
+ "border-start-width": (
+ property: border-inline-start-width,
+ class: border-start,
+ values: $border-widths,
+ ),
+ "subtle-border-color": (
+ property: border-color,
+ class: border,
+ values: $utilities-border-subtle
+ ),
+ "border-opacity": (
+ css-var: true,
+ class: border-opacity,
+ values: (
+ 10: .1,
+ 25: .25,
+ 50: .5,
+ 75: .75,
+ 100: 1
+ )
+ ),
+ // scss-docs-end utils-borders
+ // Sizing utilities
+ // scss-docs-start utils-sizing
+ "width": (
+ property: width,
+ class: w,
+ values: (
+ 25: 25%,
+ 50: 50%,
+ 75: 75%,
+ 100: 100%,
+ auto: auto
+ )
+ ),
+ "max-width": (
+ property: max-width,
+ class: mw,
+ values: (100: 100%)
+ ),
+ "viewport-width": (
+ property: width,
+ class: vw,
+ values: (100: 100vw)
+ ),
+ "min-viewport-width": (
+ property: min-width,
+ class: min-vw,
+ values: (100: 100vw)
+ ),
+ "height": (
+ property: height,
+ class: h,
+ values: (
+ 25: 25%,
+ 50: 50%,
+ 75: 75%,
+ 100: 100%,
+ auto: auto
+ )
+ ),
+ "max-height": (
+ property: max-height,
+ class: mh,
+ values: (100: 100%)
+ ),
+ "viewport-height": (
+ property: height,
+ class: vh,
+ values: (100: 100vh)
+ ),
+ "min-viewport-height": (
+ property: min-height,
+ class: min-vh,
+ values: (100: 100vh)
+ ),
+ // scss-docs-end utils-sizing
+ // Flex utilities
+ // scss-docs-start utils-flex
+ "flex": (
+ responsive: true,
+ property: flex,
+ values: (fill: 1 1 auto)
+ ),
+ "flex-direction": (
+ responsive: true,
+ property: flex-direction,
+ class: flex,
+ values: row column row-reverse column-reverse
+ ),
+ "flex-grow": (
+ responsive: true,
+ property: flex-grow,
+ class: flex,
+ values: (
+ grow-0: 0,
+ grow-1: 1,
+ )
+ ),
+ "flex-shrink": (
+ responsive: true,
+ property: flex-shrink,
+ class: flex,
+ values: (
+ shrink-0: 0,
+ shrink-1: 1,
+ )
+ ),
+ "flex-wrap": (
+ responsive: true,
+ property: flex-wrap,
+ class: flex,
+ values: wrap nowrap wrap-reverse
+ ),
+ "justify-content": (
+ responsive: true,
+ property: justify-content,
+ values: (
+ start: flex-start,
+ end: flex-end,
+ center: center,
+ between: space-between,
+ around: space-around,
+ evenly: space-evenly,
+ )
+ ),
+ "align-items": (
+ responsive: true,
+ property: align-items,
+ values: (
+ start: flex-start,
+ end: flex-end,
+ center: center,
+ baseline: baseline,
+ stretch: stretch,
+ )
+ ),
+ "align-content": (
+ responsive: true,
+ property: align-content,
+ values: (
+ start: flex-start,
+ end: flex-end,
+ center: center,
+ between: space-between,
+ around: space-around,
+ stretch: stretch,
+ )
+ ),
+ "align-self": (
+ responsive: true,
+ property: align-self,
+ values: (
+ auto: auto,
+ start: flex-start,
+ end: flex-end,
+ center: center,
+ baseline: baseline,
+ stretch: stretch,
+ )
+ ),
+ "order": (
+ responsive: true,
+ property: order,
+ values: (
+ first: -1,
+ 0: 0,
+ 1: 1,
+ 2: 2,
+ 3: 3,
+ 4: 4,
+ 5: 5,
+ last: 6,
+ ),
+ ),
+ // scss-docs-end utils-flex
+ // Margin utilities
+ // scss-docs-start utils-spacing
+ "margin": (
+ responsive: true,
+ property: margin,
+ class: m,
+ values: map.merge($spacers, (auto: auto))
+ ),
+ "margin-x": (
+ responsive: true,
+ property: margin-right margin-left,
+ class: mx,
+ values: map.merge($spacers, (auto: auto))
+ ),
+ "margin-y": (
+ responsive: true,
+ property: margin-top margin-bottom,
+ class: my,
+ values: map.merge($spacers, (auto: auto))
+ ),
+ "margin-top": (
+ responsive: true,
+ property: margin-top,
+ class: mt,
+ values: map.merge($spacers, (auto: auto))
+ ),
+ "margin-end": (
+ responsive: true,
+ property: margin-inline-end,
+ class: me,
+ values: map.merge($spacers, (auto: auto)),
+ ),
+ "margin-bottom": (
+ responsive: true,
+ property: margin-bottom,
+ class: mb,
+ values: map.merge($spacers, (auto: auto))
+ ),
+ "margin-start": (
+ responsive: true,
+ property: margin-inline-start,
+ class: ms,
+ values: map.merge($spacers, (auto: auto)),
+ ),
+ // Negative margin utilities
+ "negative-margin": (
+ responsive: true,
+ property: margin,
+ class: m,
+ values: $negative-spacers
+ ),
+ "negative-margin-x": (
+ responsive: true,
+ property: margin-right margin-left,
+ class: mx,
+ values: $negative-spacers
+ ),
+ "negative-margin-y": (
+ responsive: true,
+ property: margin-top margin-bottom,
+ class: my,
+ values: $negative-spacers
+ ),
+ "negative-margin-top": (
+ responsive: true,
+ property: margin-top,
+ class: mt,
+ values: $negative-spacers
+ ),
+ "negative-margin-end": (
+ responsive: true,
+ property: margin-inline-end,
+ class: me,
+ values: $negative-spacers
+ ),
+ "negative-margin-bottom": (
+ responsive: true,
+ property: margin-bottom,
+ class: mb,
+ values: $negative-spacers
+ ),
+ "negative-margin-start": (
+ responsive: true,
+ property: margin-inline-start,
+ class: ms,
+ values: $negative-spacers
+ ),
+ // Padding utilities
+ "padding": (
+ responsive: true,
+ property: padding,
+ class: p,
+ values: $spacers
+ ),
+ "padding-x": (
+ responsive: true,
+ property: padding-right padding-left,
+ class: px,
+ values: $spacers
+ ),
+ "padding-y": (
+ responsive: true,
+ property: padding-top padding-bottom,
+ class: py,
+ values: $spacers
+ ),
+ "padding-top": (
+ responsive: true,
+ property: padding-top,
+ class: pt,
+ values: $spacers
+ ),
+ "padding-end": (
+ responsive: true,
+ property: padding-inline-end,
+ class: pe,
+ values: $spacers
+ ),
+ "padding-bottom": (
+ responsive: true,
+ property: padding-bottom,
+ class: pb,
+ values: $spacers
+ ),
+ "padding-start": (
+ responsive: true,
+ property: padding-inline-start,
+ class: ps,
+ values: $spacers
+ ),
+ // Gap utility
+ "gap": (
+ responsive: true,
+ property: gap,
+ class: gap,
+ values: $spacers
+ ),
+ "row-gap": (
+ responsive: true,
+ property: row-gap,
+ class: row-gap,
+ values: $spacers
+ ),
+ "column-gap": (
+ responsive: true,
+ property: column-gap,
+ class: column-gap,
+ values: $spacers
+ ),
+ // scss-docs-end utils-spacing
+ // Text
+ // scss-docs-start utils-text
+ "font-family": (
+ property: font-family,
+ class: font,
+ values: (monospace: var(--#{$prefix}font-monospace))
+ ),
+ "font-size": (
+ rfs: true,
+ property: font-size,
+ class: fs,
+ values: $font-sizes
+ ),
+ "font-style": (
+ property: font-style,
+ class: fst,
+ values: italic normal
+ ),
+ "font-weight": (
+ property: font-weight,
+ class: fw,
+ values: (
+ lighter: $font-weight-lighter,
+ light: $font-weight-light,
+ normal: $font-weight-normal,
+ medium: $font-weight-medium,
+ semibold: $font-weight-semibold,
+ bold: $font-weight-bold,
+ bolder: $font-weight-bolder
+ )
+ ),
+ "line-height": (
+ property: line-height,
+ class: lh,
+ values: (
+ 1: 1,
+ sm: $line-height-sm,
+ base: $line-height-base,
+ lg: $line-height-lg,
+ )
+ ),
+ "text-align": (
+ responsive: true,
+ property: text-align,
+ class: text,
+ values: (
+ start: start,
+ end: end,
+ center: center,
+ )
+ ),
+ "text-decoration": (
+ property: text-decoration,
+ values: none underline line-through
+ ),
+ "text-transform": (
+ property: text-transform,
+ class: text,
+ values: lowercase uppercase capitalize
+ ),
+ "white-space": (
+ property: white-space,
+ class: text,
+ values: (
+ wrap: normal,
+ nowrap: nowrap,
+ )
+ ),
+ "word-wrap": (
+ property: word-wrap word-break,
+ class: text,
+ values: (break: break-word),
+ rtl: false
+ ),
+ // scss-docs-end utils-text
+ // scss-docs-start utils-color
+ "color": (
+ property: color,
+ class: text,
+ dark-mode: true,
+ local-vars: (
+ "text-opacity": 1
+ ),
+ values: map.merge(
+ $utilities-text-colors,
+ (
+ "muted": var(--#{$prefix}secondary-color), // deprecated
+ "black-50": rgba($black, .5), // deprecated
+ "white-50": rgba($white, .5), // deprecated
+ "body-secondary": var(--#{$prefix}secondary-color),
+ "body-tertiary": var(--#{$prefix}tertiary-color),
+ "body-emphasis": var(--#{$prefix}emphasis-color),
+ "reset": inherit,
+ "high-emphasis-inverse": var(--#{$prefix}high-emphasis-inverse), // deprecated
+ "medium-emphasis-inverse": var(--#{$prefix}medium-emphasis-inverse), // deprecated
+ "disabled-inverse": var(--#{$prefix}disabled-inverse), // deprecated
+ "high-emphasis": var(--#{$prefix}high-emphasis), // deprecated
+ "medium-emphasis": var(--#{$prefix}medium-emphasis), // deprecated
+ "disabled": var(--#{$prefix}disabled)
+ )
+ )
+ ),
+ "text-opacity": (
+ css-var: true,
+ class: text-opacity,
+ values: (
+ 25: .25,
+ 50: .5,
+ 75: .75,
+ 100: 1
+ )
+ ),
+ "text-color": (
+ property: color,
+ class: text,
+ values: $utilities-text-emphasis-colors
+ ),
+ // scss-docs-end utils-color
+ // scss-docs-start utils-links
+ "link-opacity": (
+ css-var: true,
+ class: link-opacity,
+ state: hover,
+ values: (
+ 10: .1,
+ 25: .25,
+ 50: .5,
+ 75: .75,
+ 100: 1
+ )
+ ),
+ "link-offset": (
+ property: text-underline-offset,
+ class: link-offset,
+ state: hover,
+ values: (
+ 1: .125em,
+ 2: .25em,
+ 3: .375em,
+ )
+ ),
+ "link-underline": (
+ property: text-decoration-color,
+ class: link-underline,
+ local-vars: (
+ "link-underline-opacity": 1
+ ),
+ values: map.merge(
+ $utilities-links-underline,
+ (
+ null: rgba(var(--#{$prefix}link-color-rgb), var(--#{$prefix}link-underline-opacity, 1)),
+ )
+ )
+ ),
+ "link-underline-opacity": (
+ css-var: true,
+ class: link-underline-opacity,
+ state: hover,
+ values: (
+ 0: 0,
+ 10: .1,
+ 25: .25,
+ 50: .5,
+ 75: .75,
+ 100: 1
+ ),
+ ),
+ // scss-docs-end utils-links
+ // scss-docs-start utils-bg-color
+ "background-color": (
+ property: background-color,
+ class: bg,
+ dark-mode: true,
+ local-vars: (
+ "bg-opacity": 1
+ ),
+ values: map.merge(
+ $utilities-bg-colors,
+ (
+ "transparent": transparent,
+ "body-secondary": rgba(var(--#{$prefix}secondary-bg-rgb), var(--#{$prefix}bg-opacity)),
+ "body-tertiary": rgba(var(--#{$prefix}tertiary-bg-rgb), var(--#{$prefix}bg-opacity)),
+ )
+ )
+ ),
+ "bg-opacity": (
+ css-var: true,
+ class: bg-opacity,
+ values: (
+ 10: .1,
+ 25: .25,
+ 50: .5,
+ 75: .75,
+ 100: 1
+ )
+ ),
+ "subtle-background-color": (
+ property: background-color,
+ class: bg,
+ dark-mode: true,
+ values: $utilities-bg-subtle
+ ),
+ // scss-docs-end utils-bg-color
+ "gradient": (
+ property: background-image,
+ class: bg,
+ values: (gradient: var(--#{$prefix}gradient))
+ ),
+ // scss-docs-start utils-interaction
+ "user-select": (
+ property: user-select,
+ values: all auto none
+ ),
+ "pointer-events": (
+ property: pointer-events,
+ class: pe,
+ values: none auto,
+ ),
+ // scss-docs-end utils-interaction
+ // scss-docs-start utils-border-radius
+ "rounded": (
+ property: border-radius,
+ class: rounded,
+ values: (
+ null: var(--#{$prefix}border-radius),
+ 0: 0,
+ 1: var(--#{$prefix}border-radius-sm),
+ 2: var(--#{$prefix}border-radius),
+ 3: var(--#{$prefix}border-radius-lg),
+ 4: var(--#{$prefix}border-radius-xl),
+ 5: var(--#{$prefix}border-radius-xxl),
+ circle: 50%,
+ pill: var(--#{$prefix}border-radius-pill)
+ )
+ ),
+ "rounded-top": (
+ property: border-top-left-radius border-top-right-radius,
+ class: rounded-top,
+ values: (
+ null: var(--#{$prefix}border-radius),
+ 0: 0,
+ 1: var(--#{$prefix}border-radius-sm),
+ 2: var(--#{$prefix}border-radius),
+ 3: var(--#{$prefix}border-radius-lg),
+ 4: var(--#{$prefix}border-radius-xl),
+ 5: var(--#{$prefix}border-radius-xxl),
+ circle: 50%,
+ pill: var(--#{$prefix}border-radius-pill)
+ )
+ ),
+ "rounded-end": (
+ property: border-start-end-radius border-end-end-radius,
+ class: rounded-end,
+ values: (
+ null: var(--#{$prefix}border-radius),
+ 0: 0,
+ 1: var(--#{$prefix}border-radius-sm),
+ 2: var(--#{$prefix}border-radius),
+ 3: var(--#{$prefix}border-radius-lg),
+ 4: var(--#{$prefix}border-radius-xl),
+ 5: var(--#{$prefix}border-radius-xxl),
+ circle: 50%,
+ pill: var(--#{$prefix}border-radius-pill)
+ )
+ ),
+ "rounded-bottom": (
+ property: border-bottom-right-radius border-bottom-left-radius,
+ class: rounded-bottom,
+ values: (
+ null: var(--#{$prefix}border-radius),
+ 0: 0,
+ 1: var(--#{$prefix}border-radius-sm),
+ 2: var(--#{$prefix}border-radius),
+ 3: var(--#{$prefix}border-radius-lg),
+ 4: var(--#{$prefix}border-radius-xl),
+ 5: var(--#{$prefix}border-radius-xxl),
+ circle: 50%,
+ pill: var(--#{$prefix}border-radius-pill)
+ )
+ ),
+ "rounded-start": (
+ property: border-end-start-radius border-start-start-radius,
+ class: rounded-start,
+ values: (
+ null: var(--#{$prefix}border-radius),
+ 0: 0,
+ 1: var(--#{$prefix}border-radius-sm),
+ 2: var(--#{$prefix}border-radius),
+ 3: var(--#{$prefix}border-radius-lg),
+ 4: var(--#{$prefix}border-radius-xl),
+ 5: var(--#{$prefix}border-radius-xxl),
+ circle: 50%,
+ pill: var(--#{$prefix}border-radius-pill)
+ )
+ ),
+ // scss-docs-end utils-border-radius
+ // scss-docs-start utils-visibility
+ "visibility": (
+ property: visibility,
+ class: null,
+ values: (
+ visible: visible,
+ invisible: hidden,
+ )
+ ),
+ // scss-docs-end utils-visibility
+ // scss-docs-start utils-zindex
+ "z-index": (
+ property: z-index,
+ class: z,
+ values: $zindex-levels,
+ )
+ // scss-docs-end utils-zindex
+ ),
+ $utilities
+);
diff --git a/src/scss/scss/_variables-dark.import.scss b/src/scss/scss/_variables-dark.import.scss
new file mode 100644
index 000000000..173db3b9a
--- /dev/null
+++ b/src/scss/scss/_variables-dark.import.scss
@@ -0,0 +1 @@
+@forward "variables-dark";
diff --git a/src/scss/scss/_variables-dark.scss b/src/scss/scss/_variables-dark.scss
new file mode 100644
index 000000000..313cdc5a4
--- /dev/null
+++ b/src/scss/scss/_variables-dark.scss
@@ -0,0 +1,165 @@
+@use "sass:color";
+@use "functions/color" as *;
+@use "variables" as *;
+
+// Dark color mode variables
+//
+// Custom variables for the `[data#{$data-infix}theme="dark"]` theme. Use this as a starting point for your own custom color modes by creating a new theme-specific file like `_variables-dark.scss` and adding the variables you need.
+
+//
+// Global colors
+//
+
+// scss-docs-start sass-dark-mode-vars
+// scss-docs-start gray-color-dark-variables
+$gray-100-dark: $gray-100 !default;
+$gray-200-dark: $gray-200 !default;
+$gray-300-dark: $gray-300 !default;
+$gray-400-dark: $gray-400 !default;
+$gray-500-dark: $gray-500 !default;
+$gray-600-dark: $gray-600 !default;
+$gray-700-dark: $gray-700 !default;
+$gray-800-dark: $gray-800 !default;
+$gray-900-dark: $gray-900 !default;
+// scss-docs-end gray-color-dark-variables
+
+// fusv-disable
+// scss-docs-start gray-colors-dark-map
+$grays-dark: (
+ "100": $gray-100-dark,
+ "200": $gray-200-dark,
+ "300": $gray-300-dark,
+ "400": $gray-400-dark,
+ "500": $gray-500-dark,
+ "600": $gray-600-dark,
+ "700": $gray-700-dark,
+ "800": $gray-800-dark,
+ "900": $gray-900-dark
+) !default;
+// scss-docs-end gray-colors-dark-map
+// fusv-enable
+
+// fusv-disable
+$high-emphasis-dark: rgba($white, .87) !default; // Deprecated in v5.0.0
+$medium-emphasis-dark: rgba($white, .6) !default; // Deprecated in v5.0.0
+$disabled-dark: rgba($white, .38) !default; // Deprecated in v5.0.0
+// fusv-enable
+
+// scss-docs-start theme-color-dark-variables
+$primary-dark: color.scale($primary, $saturation: -10%) !default; // stylelint-disable-line scss/at-function-named-arguments
+$secondary-dark: $secondary !default;
+$success-dark: color.scale($success, $saturation: -10%) !default; // stylelint-disable-line scss/at-function-named-arguments
+$info-dark: color.scale($info, $saturation: -10%) !default; // stylelint-disable-line scss/at-function-named-arguments
+$warning-dark: color.scale($warning, $saturation: -10%) !default; // stylelint-disable-line scss/at-function-named-arguments
+$danger-dark: color.scale($danger, $saturation: -10%) !default; // stylelint-disable-line scss/at-function-named-arguments
+$light-dark: $light !default;
+$dark-dark: $dark !default;
+// scss-docs-end theme-color-dark-variables
+
+// scss-docs-start theme-colors-dark-map
+$theme-colors-dark: (
+ "primary": $primary-dark,
+ "secondary": $secondary-dark,
+ "success": $success-dark,
+ "info": $info-dark,
+ "warning": $warning-dark,
+ "danger": $danger-dark,
+ "light": $light-dark,
+ "dark": $dark-dark
+) !default;
+// scss-docs-end theme-colors-dark-map
+
+// scss-docs-start theme-text-dark-variables
+$primary-text-emphasis-dark: color.scale($primary-text-emphasis, $saturation: -10%) !default; // stylelint-disable-line scss/at-function-named-arguments
+$secondary-text-emphasis-dark: $secondary-text-emphasis !default;
+$success-text-emphasis-dark: color.scale($success-text-emphasis, $saturation: -10%) !default; // stylelint-disable-line scss/at-function-named-arguments
+$info-text-emphasis-dark: color.scale($info-text-emphasis, $saturation: -10%) !default; // stylelint-disable-line scss/at-function-named-arguments
+$warning-text-emphasis-dark: color.scale($warning-text-emphasis, $saturation: -10%) !default; // stylelint-disable-line scss/at-function-named-arguments
+$danger-text-emphasis-dark: color.scale($danger-text-emphasis, $saturation: -10%) !default; // stylelint-disable-line scss/at-function-named-arguments
+$light-text-emphasis-dark: $gray-100-dark !default;
+$dark-text-emphasis-dark: $gray-300-dark !default;
+// scss-docs-end theme-text-dark-variables
+
+// scss-docs-start theme-bg-subtle-dark-variables
+$primary-bg-subtle-dark: $primary-bg-subtle !default;
+$secondary-bg-subtle-dark: $secondary-bg-subtle !default;
+$success-bg-subtle-dark: $success-bg-subtle !default;
+$info-bg-subtle-dark: $info-bg-subtle !default;
+$warning-bg-subtle-dark: $warning-bg-subtle !default;
+$danger-bg-subtle-dark: $danger-bg-subtle !default;
+$light-bg-subtle-dark: $gray-800-dark !default;
+$dark-bg-subtle-dark: color.mix($gray-800-dark, $black) !default;
+// scss-docs-end theme-bg-subtle-dark-variables
+
+// scss-docs-start theme-border-subtle-dark-variables
+$primary-border-subtle-dark: $primary-border-subtle !default;
+$secondary-border-subtle-dark: $secondary-border-subtle !default;
+$success-border-subtle-dark: $success-border-subtle !default;
+$info-border-subtle-dark: $info-border-subtle !default;
+$warning-border-subtle-dark: $warning-border-subtle !default;
+$danger-border-subtle-dark: $danger-border-subtle !default;
+$light-border-subtle-dark: $gray-700-dark !default;
+$dark-border-subtle-dark: $gray-800-dark !default;
+// scss-docs-end theme-border-subtle-dark-variables
+
+$body-color-dark: rgba($white, .87) !default;
+$body-bg-dark: $gray-900-dark !default;
+$body-secondary-color-dark: rgba($white, .6) !default;
+$body-secondary-bg-dark: $gray-800-dark !default;
+$body-tertiary-color-dark: rgba($white, .38) !default;
+$body-tertiary-bg-dark: color.mix($gray-800-dark, #212631, 50%) !default;
+$body-emphasis-color-dark: $white !default;
+$border-color-dark: $gray-800-dark !default;
+$border-color-translucent-dark: rgba($white, .1) !default;
+$headings-color-dark: inherit !default;
+$link-color-dark: $primary-dark !default;
+$link-hover-color-dark: shift-color($link-color-dark, -$link-shade-percentage) !default;
+$code-color-dark: tint-color($code-color, 40%) !default;
+$mark-color-dark: $body-color-dark !default;
+$mark-bg-dark: $yellow-800 !default;
+
+
+//
+// Forms
+//
+
+$form-select-indicator-color-dark: $body-color-dark !default;
+$form-select-indicator-dark: url("data:image/svg+xml,
") !default;
+
+$form-switch-color-dark: rgba($white, .25) !default;
+$form-switch-bg-image-dark: url("data:image/svg+xml,
") !default;
+
+// scss-docs-start form-validation-colors-dark
+$form-valid-color-dark: $green-300 !default;
+$form-valid-border-color-dark: $green-300 !default;
+$form-invalid-color-dark: $red-300 !default;
+$form-invalid-border-color-dark: $red-300 !default;
+// scss-docs-end form-validation-colors-dark
+
+
+//
+// Accordion
+//
+
+$accordion-icon-color-dark: $body-color-dark !default;
+$accordion-icon-active-color-dark: $primary-text-emphasis-dark !default;
+
+$accordion-button-icon-dark: url("data:image/svg+xml,
") !default;
+$accordion-button-active-icon-dark: url("data:image/svg+xml,
") !default;
+
+
+//
+// Carousel
+//
+
+$carousel-indicator-active-bg-dark: $carousel-dark-indicator-active-bg !default;
+$carousel-caption-color-dark: $carousel-dark-caption-color !default;
+$carousel-control-icon-filter-dark: $carousel-dark-control-icon-filter !default;
+
+
+//
+// Close button
+//
+
+$btn-close-filter-dark: $btn-close-white-filter !default;
+// scss-docs-end sass-dark-mode-vars
diff --git a/src/scss/scss/_variables.import.scss b/src/scss/scss/_variables.import.scss
new file mode 100644
index 000000000..eaab32235
--- /dev/null
+++ b/src/scss/scss/_variables.import.scss
@@ -0,0 +1 @@
+@forward "variables";
diff --git a/src/scss/scss/_variables.scss b/src/scss/scss/_variables.scss
new file mode 100644
index 000000000..ef084e9ad
--- /dev/null
+++ b/src/scss/scss/_variables.scss
@@ -0,0 +1,2070 @@
+@use "sass:color";
+@use "sass:string";
+@use "functions/assert-ascending" as *;
+@use "functions/assert-starts-at-zero" as *;
+@use "functions/color" as *;
+@use "functions/color-contrast-variables" as *;
+@use "functions/math" as *;
+@use "functions/to-rgb" as *;
+
+// Variables
+//
+// Variables should follow the `$component-state-property-size` formula for
+// consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs.
+
+// Color system
+
+// scss-docs-start gray-color-variables
+$white: #fff !default;
+$gray-base: #323a49 !default;
+$gray-100: #f3f4f7 !default;
+$gray-200: #e7eaee !default;
+$gray-300: #dbdfe6 !default;
+$gray-400: #cfd4de !default;
+$gray-500: #aab3c5 !default;
+$gray-600: #6d7d9c !default;
+$gray-700: #4a566d !default;
+$gray-800: #323a49 !default;
+$gray-900: #212631 !default;
+$black: #080a0c !default;
+// scss-docs-end gray-color-variables
+
+// fusv-disable
+// scss-docs-start gray-colors-map
+$grays: (
+ "100": $gray-100,
+ "200": $gray-200,
+ "300": $gray-300,
+ "400": $gray-400,
+ "500": $gray-500,
+ "600": $gray-600,
+ "700": $gray-700,
+ "800": $gray-800,
+ "900": $gray-900
+) !default;
+// scss-docs-end gray-colors-map
+// fusv-enable
+
+// fusv-disable
+$high-emphasis: rgba(shift-color($gray-base, +26%), .95) !default; // Deprecated in 5.0.0
+$medium-emphasis: rgba(shift-color($gray-base, +26%), .681) !default; // Deprecated in 5.0.0
+$disabled: rgba(shift-color($gray-base, +26%), .38) !default; // Deprecated in 5.0.0
+
+$high-emphasis-inverse: rgba($white, .87) !default; // Deprecated in 5.0.0
+$medium-emphasis-inverse: rgba($white, .6) !default; // Deprecated in 5.0.0
+$disabled-inverse: rgba($white, .38) !default; // Deprecated in 5.0.0
+// fusv-enable
+
+// scss-docs-start color-variables
+$blue: #0d6efd !default;
+$indigo: #6610f2 !default;
+$purple: #6f42c1 !default;
+$pink: #d63384 !default;
+$red: #dc3545 !default;
+$orange: #fd7e14 !default;
+$yellow: #ffc107 !default;
+$green: #198754 !default;
+$teal: #20c997 !default;
+$cyan: #0dcaf0 !default;
+// scss-docs-end color-variables
+
+// scss-docs-start colors-map
+$colors: (
+ "blue": $blue,
+ "indigo": $indigo,
+ "purple": $purple,
+ "pink": $pink,
+ "red": $red,
+ "orange": $orange,
+ "yellow": $yellow,
+ "green": $green,
+ "teal": $teal,
+ "cyan": $cyan,
+ "black": $black,
+ "white": $white,
+ "gray": $gray-600,
+ "gray-dark": $gray-800
+) !default;
+// scss-docs-end colors-map
+
+// The contrast ratio to reach against white, to determine if color changes from "light" to "dark". Acceptable values for WCAG 2.2 are 3, 4.5 and 7.
+// See https://www.w3.org/TR/WCAG/#contrast-minimum
+$min-contrast-ratio: 4.5 !default;
+
+// Customize the light and dark text colors for use in our color contrast function.
+$color-contrast-dark: $black !default;
+$color-contrast-light: $white !default;
+
+
+// fusv-disable
+$blue-100: tint-color($blue, 80%) !default;
+$blue-200: tint-color($blue, 60%) !default;
+$blue-300: tint-color($blue, 40%) !default;
+$blue-400: tint-color($blue, 20%) !default;
+$blue-500: $blue !default;
+$blue-600: shade-color($blue, 20%) !default;
+$blue-700: shade-color($blue, 40%) !default;
+$blue-800: shade-color($blue, 60%) !default;
+$blue-900: shade-color($blue, 80%) !default;
+
+$indigo-100: tint-color($indigo, 80%) !default;
+$indigo-200: tint-color($indigo, 60%) !default;
+$indigo-300: tint-color($indigo, 40%) !default;
+$indigo-400: tint-color($indigo, 20%) !default;
+$indigo-500: $indigo !default;
+$indigo-600: shade-color($indigo, 20%) !default;
+$indigo-700: shade-color($indigo, 40%) !default;
+$indigo-800: shade-color($indigo, 60%) !default;
+$indigo-900: shade-color($indigo, 80%) !default;
+
+$purple-100: tint-color($purple, 80%) !default;
+$purple-200: tint-color($purple, 60%) !default;
+$purple-300: tint-color($purple, 40%) !default;
+$purple-400: tint-color($purple, 20%) !default;
+$purple-500: $purple !default;
+$purple-600: shade-color($purple, 20%) !default;
+$purple-700: shade-color($purple, 40%) !default;
+$purple-800: shade-color($purple, 60%) !default;
+$purple-900: shade-color($purple, 80%) !default;
+
+$pink-100: tint-color($pink, 80%) !default;
+$pink-200: tint-color($pink, 60%) !default;
+$pink-300: tint-color($pink, 40%) !default;
+$pink-400: tint-color($pink, 20%) !default;
+$pink-500: $pink !default;
+$pink-600: shade-color($pink, 20%) !default;
+$pink-700: shade-color($pink, 40%) !default;
+$pink-800: shade-color($pink, 60%) !default;
+$pink-900: shade-color($pink, 80%) !default;
+
+$red-100: tint-color($red, 80%) !default;
+$red-200: tint-color($red, 60%) !default;
+$red-300: tint-color($red, 40%) !default;
+$red-400: tint-color($red, 20%) !default;
+$red-500: $red !default;
+$red-600: shade-color($red, 20%) !default;
+$red-700: shade-color($red, 40%) !default;
+$red-800: shade-color($red, 60%) !default;
+$red-900: shade-color($red, 80%) !default;
+
+$orange-100: tint-color($orange, 80%) !default;
+$orange-200: tint-color($orange, 60%) !default;
+$orange-300: tint-color($orange, 40%) !default;
+$orange-400: tint-color($orange, 20%) !default;
+$orange-500: $orange !default;
+$orange-600: shade-color($orange, 20%) !default;
+$orange-700: shade-color($orange, 40%) !default;
+$orange-800: shade-color($orange, 60%) !default;
+$orange-900: shade-color($orange, 80%) !default;
+
+$yellow-100: tint-color($yellow, 80%) !default;
+$yellow-200: tint-color($yellow, 60%) !default;
+$yellow-300: tint-color($yellow, 40%) !default;
+$yellow-400: tint-color($yellow, 20%) !default;
+$yellow-500: $yellow !default;
+$yellow-600: shade-color($yellow, 20%) !default;
+$yellow-700: shade-color($yellow, 40%) !default;
+$yellow-800: shade-color($yellow, 60%) !default;
+$yellow-900: shade-color($yellow, 80%) !default;
+
+$green-100: tint-color($green, 80%) !default;
+$green-200: tint-color($green, 60%) !default;
+$green-300: tint-color($green, 40%) !default;
+$green-400: tint-color($green, 20%) !default;
+$green-500: $green !default;
+$green-600: shade-color($green, 20%) !default;
+$green-700: shade-color($green, 40%) !default;
+$green-800: shade-color($green, 60%) !default;
+$green-900: shade-color($green, 80%) !default;
+
+$teal-100: tint-color($teal, 80%) !default;
+$teal-200: tint-color($teal, 60%) !default;
+$teal-300: tint-color($teal, 40%) !default;
+$teal-400: tint-color($teal, 20%) !default;
+$teal-500: $teal !default;
+$teal-600: shade-color($teal, 20%) !default;
+$teal-700: shade-color($teal, 40%) !default;
+$teal-800: shade-color($teal, 60%) !default;
+$teal-900: shade-color($teal, 80%) !default;
+
+$cyan-100: tint-color($cyan, 80%) !default;
+$cyan-200: tint-color($cyan, 60%) !default;
+$cyan-300: tint-color($cyan, 40%) !default;
+$cyan-400: tint-color($cyan, 20%) !default;
+$cyan-500: $cyan !default;
+$cyan-600: shade-color($cyan, 20%) !default;
+$cyan-700: shade-color($cyan, 40%) !default;
+$cyan-800: shade-color($cyan, 60%) !default;
+$cyan-900: shade-color($cyan, 80%) !default;
+
+$blues: (
+ "blue-100": $blue-100,
+ "blue-200": $blue-200,
+ "blue-300": $blue-300,
+ "blue-400": $blue-400,
+ "blue-500": $blue-500,
+ "blue-600": $blue-600,
+ "blue-700": $blue-700,
+ "blue-800": $blue-800,
+ "blue-900": $blue-900
+) !default;
+
+$indigos: (
+ "indigo-100": $indigo-100,
+ "indigo-200": $indigo-200,
+ "indigo-300": $indigo-300,
+ "indigo-400": $indigo-400,
+ "indigo-500": $indigo-500,
+ "indigo-600": $indigo-600,
+ "indigo-700": $indigo-700,
+ "indigo-800": $indigo-800,
+ "indigo-900": $indigo-900
+) !default;
+
+$purples: (
+ "purple-100": $purple-100,
+ "purple-200": $purple-200,
+ "purple-300": $purple-300,
+ "purple-400": $purple-400,
+ "purple-500": $purple-500,
+ "purple-600": $purple-600,
+ "purple-700": $purple-700,
+ "purple-800": $purple-800,
+ "purple-900": $purple-900
+) !default;
+
+$pinks: (
+ "pink-100": $pink-100,
+ "pink-200": $pink-200,
+ "pink-300": $pink-300,
+ "pink-400": $pink-400,
+ "pink-500": $pink-500,
+ "pink-600": $pink-600,
+ "pink-700": $pink-700,
+ "pink-800": $pink-800,
+ "pink-900": $pink-900
+) !default;
+
+$reds: (
+ "red-100": $red-100,
+ "red-200": $red-200,
+ "red-300": $red-300,
+ "red-400": $red-400,
+ "red-500": $red-500,
+ "red-600": $red-600,
+ "red-700": $red-700,
+ "red-800": $red-800,
+ "red-900": $red-900
+) !default;
+
+$oranges: (
+ "orange-100": $orange-100,
+ "orange-200": $orange-200,
+ "orange-300": $orange-300,
+ "orange-400": $orange-400,
+ "orange-500": $orange-500,
+ "orange-600": $orange-600,
+ "orange-700": $orange-700,
+ "orange-800": $orange-800,
+ "orange-900": $orange-900
+) !default;
+
+$yellows: (
+ "yellow-100": $yellow-100,
+ "yellow-200": $yellow-200,
+ "yellow-300": $yellow-300,
+ "yellow-400": $yellow-400,
+ "yellow-500": $yellow-500,
+ "yellow-600": $yellow-600,
+ "yellow-700": $yellow-700,
+ "yellow-800": $yellow-800,
+ "yellow-900": $yellow-900
+) !default;
+
+$greens: (
+ "green-100": $green-100,
+ "green-200": $green-200,
+ "green-300": $green-300,
+ "green-400": $green-400,
+ "green-500": $green-500,
+ "green-600": $green-600,
+ "green-700": $green-700,
+ "green-800": $green-800,
+ "green-900": $green-900
+) !default;
+
+$teals: (
+ "teal-100": $teal-100,
+ "teal-200": $teal-200,
+ "teal-300": $teal-300,
+ "teal-400": $teal-400,
+ "teal-500": $teal-500,
+ "teal-600": $teal-600,
+ "teal-700": $teal-700,
+ "teal-800": $teal-800,
+ "teal-900": $teal-900
+) !default;
+
+$cyans: (
+ "cyan-100": $cyan-100,
+ "cyan-200": $cyan-200,
+ "cyan-300": $cyan-300,
+ "cyan-400": $cyan-400,
+ "cyan-500": $cyan-500,
+ "cyan-600": $cyan-600,
+ "cyan-700": $cyan-700,
+ "cyan-800": $cyan-800,
+ "cyan-900": $cyan-900
+) !default;
+// fusv-enable
+
+// scss-docs-start theme-color-variables
+$primary: #5856d6 !default;
+$secondary: #6b7785 !default;
+$success: #1b9e3e !default;
+$info: #39f !default;
+$warning: #f9b115 !default;
+$danger: #e55353 !default;
+$light: $gray-100 !default;
+$dark: $gray-900 !default;
+// scss-docs-end theme-color-variables
+
+// scss-docs-start theme-colors-map
+$theme-colors: (
+ "primary": $primary,
+ "secondary": $secondary,
+ "success": $success,
+ "info": $info,
+ "warning": $warning,
+ "danger": $danger,
+ "light": $light,
+ "dark": $dark
+) !default;
+// scss-docs-end theme-colors-map
+
+// scss-docs-start theme-text-variables
+$primary-text-emphasis: #3634a3 !default;
+$secondary-text-emphasis: #212233 !default;
+$success-text-emphasis: #0f5722 !default;
+$info-text-emphasis: #184c77 !default;
+$warning-text-emphasis: #764705 !default;
+$danger-text-emphasis: #671414 !default;
+$light-text-emphasis: $gray-700 !default;
+$dark-text-emphasis: $gray-800 !default;
+// scss-docs-end theme-text-variables
+
+// scss-docs-start theme-bg-subtle-variables
+$primary-bg-subtle: #cfc7f3 !default;
+$secondary-bg-subtle: #ced2d8 !default;
+$success-bg-subtle: #cbedd6 !default;
+$info-bg-subtle: #c0e6ff !default;
+$warning-bg-subtle: #feecc5 !default;
+$danger-bg-subtle: #f9d4d4 !default;
+$light-bg-subtle: color.mix($gray-100, $white) !default;
+$dark-bg-subtle: $gray-400 !default;
+// scss-docs-end theme-bg-subtle-variables
+
+// scss-docs-start theme-border-subtle-variables
+$primary-border-subtle: #9d92e6 !default;
+$secondary-border-subtle: #9da5b1 !default;
+$success-border-subtle: #96dbad !default;
+$info-border-subtle: #80c6ff !default;
+$warning-border-subtle: #fcd88a !default;
+$danger-border-subtle: #f2a9a9 !default;
+$light-border-subtle: $gray-200 !default;
+$dark-border-subtle: $gray-500 !default;
+// scss-docs-end theme-border-subtle-variables
+
+// Characters which are escaped by the escape-svg function
+$escaped-characters: (
+ ("<", "%3c"),
+ (">", "%3e"),
+ ("#", "%23"),
+ ("(", "%28"),
+ (")", "%29"),
+) !default;
+
+// Options
+//
+// Quickly modify global styling by enabling or disabling optional features.
+
+$enable-caret: true !default;
+$enable-rounded: true !default;
+$enable-shadows: false !default;
+$enable-gradients: false !default;
+$enable-transitions: true !default;
+$enable-reduced-motion: true !default;
+$enable-smooth-scroll: true !default;
+$enable-grid-classes: true !default;
+$enable-container-classes: true !default;
+$enable-cssgrid: false !default;
+$enable-button-pointers: true !default;
+$enable-rfs: true !default;
+$enable-validation-icons: true !default;
+$enable-negative-margins: false !default;
+$enable-deprecation-messages: true !default;
+$enable-important-utilities: true !default;
+$enable-ltr: true !default;
+$enable-rtl: false !default;
+$enable-container-queries: false !default;
+
+$enable-dark-mode: true !default;
+$color-mode-type: data !default; // `data` or `media-query`
+
+// Prefix for :root CSS variables
+
+$variable-prefix: cui- !default; // Deprecated in v4.2.6 for the shorter `$prefix`
+$prefix: $variable-prefix !default;
+
+// Prefix for data attributes
+
+$data-infix: -coreui- !default;
+
+// Set mobile breakpoint
+
+$mobile-breakpoint: lg !default;
+
+// Gradient
+//
+// The gradient which is added to components if `$enable-gradients` is `true`
+// This gradient is also added to elements with `.bg-gradient`
+// scss-docs-start variable-gradient
+$gradient: linear-gradient(180deg, rgba($white, .15), rgba($white, 0)) !default;
+// scss-docs-end variable-gradient
+
+// Spacing
+//
+// Control the default styling of most Bootstrap elements by modifying these
+// variables. Mostly focused on spacing.
+// You can add more entries to the $spacers map, should you need more variation.
+
+// scss-docs-start spacer-variables-maps
+$spacer: 1rem !default;
+$spacers: (
+ 0: 0,
+ 1: $spacer * .25,
+ 2: $spacer * .5,
+ 3: $spacer,
+ 4: $spacer * 1.5,
+ 5: $spacer * 3,
+) !default;
+// scss-docs-end spacer-variables-maps
+
+// Position
+//
+// Define the edge positioning anchors of the position utilities.
+
+// scss-docs-start position-map
+$position-values: (
+ 0: 0,
+ 50: 50%,
+ 100: 100%
+) !default;
+// scss-docs-end position-map
+
+// Body
+//
+// Settings for the `` element.
+
+$body-text-align: null !default;
+$body-color: rgba(shift-color($gray-base, +26%), .95) !default;
+$body-bg: $white !default;
+
+$body-secondary-color: rgba(shift-color($gray-base, +26%), .681) !default;
+$body-secondary-bg: $gray-200 !default;
+
+$body-tertiary-color: rgba(shift-color($gray-base, +26%), .38) !default;
+$body-tertiary-bg: $gray-100 !default;
+
+$body-emphasis-color: $black !default;
+
+// Links
+//
+// Style anchor elements.
+
+$link-color: $primary !default;
+$link-decoration: underline !default;
+$link-shade-percentage: 20% !default;
+$link-hover-color: shift-color($link-color, $link-shade-percentage) !default;
+$link-hover-decoration: null !default;
+
+$stretched-link-pseudo-element: after !default;
+$stretched-link-z-index: 1 !default;
+
+// Icon links
+// scss-docs-start icon-link-variables
+$icon-link-gap: .375rem !default;
+$icon-link-underline-offset: .25em !default;
+$icon-link-icon-size: 1em !default;
+$icon-link-icon-transition: .2s ease-in-out transform !default;
+$icon-link-icon-transform: translate3d(.25em, 0, 0) !default;
+// scss-docs-end icon-link-variables
+
+// Paragraphs
+//
+// Style p element.
+
+$paragraph-margin-bottom: 1rem !default;
+
+
+// Grid breakpoints
+//
+// Define the minimum dimensions at which your layout will change,
+// adapting to different screen sizes, for use in media queries.
+
+// scss-docs-start grid-breakpoints
+$grid-breakpoints: (
+ xs: 0,
+ sm: 576px,
+ md: 768px,
+ lg: 992px,
+ xl: 1200px,
+ xxl: 1400px
+) !default;
+// scss-docs-end grid-breakpoints
+
+@include assert-ascending($grid-breakpoints, "$grid-breakpoints");
+@include assert-starts-at-zero($grid-breakpoints, "$grid-breakpoints");
+
+// scss-docs-start cq-grid-breakpoints
+$cq-grid-breakpoints: (
+ xs: 0,
+ sm: 576px,
+ md: 768px,
+ lg: 992px,
+ xl: 1200px,
+ xxl: 1400px
+) !default;
+
+@include assert-ascending($cq-grid-breakpoints, "$cq-grid-breakpoints");
+@include assert-starts-at-zero($cq-grid-breakpoints, "$cq-grid-breakpoints");
+// scss-docs-end cq-grid-breakpoints
+
+// Grid containers
+//
+// Define the maximum width of `.container` for different screen sizes.
+
+// scss-docs-start container-max-widths
+$container-max-widths: (
+ sm: 540px,
+ md: 720px,
+ lg: 960px,
+ xl: 1140px,
+ xxl: 1320px
+) !default;
+// scss-docs-end container-max-widths
+
+@include assert-ascending($container-max-widths, "$container-max-widths");
+
+
+// Grid columns
+//
+// Set the number of columns and specify the width of the gutters.
+
+$grid-columns: 12 !default;
+$grid-gutter-width: 1.5rem !default;
+$grid-row-columns: 6 !default;
+
+// Container padding
+
+$container-padding-x: $grid-gutter-width !default;
+
+
+// Components
+//
+// Define common padding and border radius sizes and more.
+
+// scss-docs-start border-variables
+$border-width: 1px !default;
+$border-widths: (
+ 1: 1px,
+ 2: 2px,
+ 3: 3px,
+ 4: 4px,
+ 5: 5px
+) !default;
+
+$border-style: solid !default;
+$border-color: $gray-300 !default;
+$border-color-translucent: rgba($black, .175) !default;
+// scss-docs-end border-variables
+
+// scss-docs-start border-radius-variables
+$border-radius: .375rem !default;
+$border-radius-sm: .25rem !default;
+$border-radius-lg: .5rem !default;
+$border-radius-xl: 1rem !default;
+$border-radius-xxl: 2rem !default;
+$border-radius-pill: 50rem !default;
+// scss-docs-end border-radius-variables
+// fusv-disable
+$border-radius-2xl: $border-radius-xxl !default; // Deprecated in v5.0.0
+// fusv-enable
+
+// scss-docs-start box-shadow-variables
+$box-shadow: 0 .5rem 1rem rgba($black, .15) !default;
+$box-shadow-sm: 0 .125rem .25rem rgba($black, .075) !default;
+$box-shadow-lg: 0 1rem 3rem rgba($black, .175) !default;
+$box-shadow-inset: inset 0 1px 2px rgba($black, .075) !default;
+// scss-docs-end box-shadow-variables
+
+$component-active-color: rgba($white, .87) !default;
+$component-active-bg: var(--#{$prefix}primary) !default;
+
+// scss-docs-start focus-ring-variables
+$focus-ring-width: .25rem !default;
+$focus-ring-opacity: .25 !default;
+$focus-ring-color: rgba($primary, $focus-ring-opacity) !default;
+$focus-ring-blur: 0 !default;
+$focus-ring-box-shadow: 0 0 $focus-ring-blur $focus-ring-width $focus-ring-color !default;
+// scss-docs-end focus-ring-variables
+
+// scss-docs-start caret-variables
+$caret-width: .3em !default;
+$caret-vertical-align: $caret-width * .85 !default;
+$caret-spacing: $caret-width * .85 !default;
+// scss-docs-end caret-variables
+
+$transition-base: all .2s ease-in-out !default;
+$transition-fade: opacity .15s linear !default;
+// scss-docs-start collapse-transition
+$transition-collapse: height .35s ease !default;
+$transition-collapse-width: width .35s ease !default;
+// scss-docs-end collapse-transition
+
+// stylelint-disable function-disallowed-list
+// scss-docs-start aspect-ratios
+$aspect-ratios: (
+ "1x1": 100%,
+ "4x3": calc(3 / 4 * 100%),
+ "16x9": calc(9 / 16 * 100%),
+ "21x9": calc(9 / 21 * 100%)
+) !default;
+// scss-docs-end aspect-ratios
+// stylelint-enable function-disallowed-list
+
+// Typography
+//
+// Font, line-height, and color for body text, headings, and more.
+
+// scss-docs-start font-variables
+// stylelint-disable value-keyword-case
+$font-family-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default;
+$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default;
+// stylelint-enable value-keyword-case
+$font-family-base: var(--#{$prefix}font-sans-serif) !default;
+$font-family-code: var(--#{$prefix}font-monospace) !default;
+
+// $font-size-root affects the value of `rem`, which is used for as well font sizes, paddings, and margins
+// $font-size-base affects the font size of the body text
+$font-size-root: null !default;
+$font-size-base: 1rem !default; // Assumes the browser default, typically `16px`
+$font-size-sm: $font-size-base * .875 !default;
+$font-size-lg: $font-size-base * 1.25 !default;
+
+$font-weight-lighter: lighter !default;
+$font-weight-light: 300 !default;
+$font-weight-normal: 400 !default;
+$font-weight-medium: 500 !default;
+$font-weight-semibold: 600 !default;
+$font-weight-bold: 700 !default;
+$font-weight-bolder: bolder !default;
+
+$font-weight-base: $font-weight-normal !default;
+
+$line-height-base: 1.5 !default;
+$line-height-sm: 1.25 !default;
+$line-height-lg: 2 !default;
+
+$h1-font-size: $font-size-base * 2.5 !default;
+$h2-font-size: $font-size-base * 2 !default;
+$h3-font-size: $font-size-base * 1.75 !default;
+$h4-font-size: $font-size-base * 1.5 !default;
+$h5-font-size: $font-size-base * 1.25 !default;
+$h6-font-size: $font-size-base !default;
+// scss-docs-end font-variables
+
+// scss-docs-start font-sizes
+$font-sizes: (
+ 1: $h1-font-size,
+ 2: $h2-font-size,
+ 3: $h3-font-size,
+ 4: $h4-font-size,
+ 5: $h5-font-size,
+ 6: $h6-font-size
+) !default;
+// scss-docs-end font-sizes
+
+// scss-docs-start headings-variables
+$headings-margin-bottom: $spacer * .5 !default;
+$headings-font-family: null !default;
+$headings-font-style: null !default;
+$headings-font-weight: 500 !default;
+$headings-line-height: 1.2 !default;
+$headings-color: inherit !default;
+// scss-docs-end headings-variables
+
+// scss-docs-start display-headings
+$display-font-sizes: (
+ 1: 5rem,
+ 2: 4.5rem,
+ 3: 4rem,
+ 4: 3.5rem,
+ 5: 3rem,
+ 6: 2.5rem
+) !default;
+
+$display-font-family: null !default;
+$display-font-style: null !default;
+$display-font-weight: 300 !default;
+$display-line-height: $headings-line-height !default;
+// scss-docs-end display-headings
+
+// scss-docs-start type-variables
+$lead-font-size: $font-size-base * 1.25 !default;
+$lead-font-weight: 300 !default;
+
+$small-font-size: .875em !default;
+
+$sub-sup-font-size: .75em !default;
+
+// fusv-disable
+$text-muted: var(--#{$prefix}secondary-color) !default; // Deprecated in 5.0.0
+// fusv-enable
+
+$initialism-font-size: $small-font-size !default;
+
+$blockquote-margin-y: $spacer !default;
+$blockquote-font-size: $font-size-base * 1.25 !default;
+$blockquote-footer-color: $gray-600 !default;
+$blockquote-footer-font-size: $small-font-size !default;
+
+$hr-margin-y: $spacer !default;
+$hr-color: inherit !default;
+
+// fusv-disable
+$hr-bg-color: null !default; // Deprecated in v4.2.6
+$hr-height: null !default; // Deprecated in v4.2.6
+// fusv-enable
+
+$hr-border-color: null !default; // Allows for inherited colors
+$hr-border-width: var(--#{$prefix}border-width) !default;
+$hr-opacity: .25 !default;
+
+// scss-docs-start vr-variables
+$vr-border-width: var(--#{$prefix}border-width) !default;
+// scss-docs-end vr-variables
+
+$legend-margin-bottom: .5rem !default;
+$legend-font-size: 1.5rem !default;
+$legend-font-weight: null !default;
+
+$dt-font-weight: $font-weight-bold !default;
+
+$list-inline-padding: .5rem !default;
+
+$mark-padding: .1875em !default;
+$mark-color: $body-color !default;
+$mark-bg: $yellow-100 !default;
+// scss-docs-end type-variables
+
+// Icons
+$icon-size-base: 1rem !default;
+$icon-size-sm: $icon-size-base * .875 !default;
+$icon-size-lg: $icon-size-base * 1.25 !default;
+$icon-size-xl: $icon-size-base * 1.5 !default;
+$icon-size-xxl: $icon-size-base * 2 !default;
+
+
+// Tables
+//
+// Customizes the `.table` component with basic values, each used across all table variations.
+
+// scss-docs-start table-variables
+$table-cell-padding-y: .5rem !default;
+$table-cell-padding-x: .5rem !default;
+$table-cell-padding-y-sm: .25rem !default;
+$table-cell-padding-x-sm: .25rem !default;
+
+$table-cell-vertical-align: top !default;
+
+$table-color: var(--#{$prefix}emphasis-color) !default;
+$table-bg: var(--#{$prefix}body-bg) !default;
+$table-accent-bg: transparent !default;
+
+$table-th-font-weight: null !default;
+
+$table-striped-color: $table-color !default;
+$table-striped-bg-factor: .05 !default;
+$table-striped-bg: rgba(var(--#{$prefix}emphasis-color-rgb), $table-striped-bg-factor) !default;
+
+$table-active-color: $table-color !default;
+$table-active-bg-factor: .1 !default;
+$table-active-bg: rgba(var(--#{$prefix}emphasis-color-rgb), $table-active-bg-factor) !default;
+
+$table-hover-color: $table-color !default;
+$table-hover-bg-factor: .075 !default;
+$table-hover-bg: rgba(var(--#{$prefix}emphasis-color-rgb), $table-hover-bg-factor) !default;
+
+$table-border-factor: .2 !default;
+$table-border-width: var(--#{$prefix}border-width) !default;
+$table-border-color: var(--#{$prefix}border-color) !default;
+
+$table-striped-order: odd !default;
+$table-striped-columns-order: even !default;
+
+$table-group-separator-color: currentcolor !default;
+
+$table-caption-color: var(--#{$prefix}secondary-color) !default;
+
+$table-bg-scale: -80% !default;
+// scss-docs-end table-variables
+
+// scss-docs-start table-loop
+$table-variants: (
+ "primary": shift-color($primary, $table-bg-scale),
+ "secondary": shift-color($secondary, $table-bg-scale),
+ "success": shift-color($success, $table-bg-scale),
+ "info": shift-color($info, $table-bg-scale),
+ "warning": shift-color($warning, $table-bg-scale),
+ "danger": shift-color($danger, $table-bg-scale),
+ "light": $light,
+ "dark": $dark,
+) !default;
+// scss-docs-end table-loop
+
+
+// Buttons + Forms
+//
+// Shared variables that are reassigned to `$input-` and `$btn-` specific variables.
+
+// scss-docs-start input-btn-variables
+$input-btn-padding-y: .375rem !default;
+$input-btn-padding-x: .75rem !default;
+$input-btn-font-family: null !default;
+$input-btn-font-size: $font-size-base !default;
+$input-btn-line-height: $line-height-base !default;
+
+$input-btn-focus-width: $focus-ring-width !default;
+$input-btn-focus-color-opacity: $focus-ring-opacity !default;
+$input-btn-focus-color: $focus-ring-color !default;
+$input-btn-focus-blur: $focus-ring-blur !default;
+$input-btn-focus-box-shadow: $focus-ring-box-shadow !default;
+
+$input-btn-padding-y-sm: .25rem !default;
+$input-btn-padding-x-sm: .5rem !default;
+$input-btn-font-size-sm: $font-size-sm !default;
+
+$input-btn-padding-y-lg: .5rem !default;
+$input-btn-padding-x-lg: 1rem !default;
+$input-btn-font-size-lg: $font-size-lg !default;
+
+$input-btn-border-width: var(--#{$prefix}border-width) !default;
+// scss-docs-end input-btn-variables
+
+
+// Buttons
+//
+// For each of Bootstrap's buttons, define text, background, and border color.
+
+// scss-docs-start btn-variables
+$btn-color: var(--#{$prefix}body-color) !default;
+$btn-padding-y: $input-btn-padding-y !default;
+$btn-padding-x: $input-btn-padding-x !default;
+$btn-font-family: $input-btn-font-family !default;
+$btn-font-size: $input-btn-font-size !default;
+$btn-line-height: $input-btn-line-height !default;
+$btn-white-space: null !default; // Set to `nowrap` to prevent text wrapping
+
+$btn-padding-y-sm: $input-btn-padding-y-sm !default;
+$btn-padding-x-sm: $input-btn-padding-x-sm !default;
+$btn-font-size-sm: $input-btn-font-size-sm !default;
+
+$btn-padding-y-lg: $input-btn-padding-y-lg !default;
+$btn-padding-x-lg: $input-btn-padding-x-lg !default;
+$btn-font-size-lg: $input-btn-font-size-lg !default;
+
+$btn-border-width: $input-btn-border-width !default;
+
+$btn-font-weight: $font-weight-normal !default;
+$btn-box-shadow: inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default;
+$btn-focus-width: $input-btn-focus-width !default;
+$btn-focus-box-shadow: $input-btn-focus-box-shadow !default;
+$btn-disabled-opacity: .65 !default;
+$btn-active-box-shadow: inset 0 3px 5px rgba($black, .125) !default;
+
+$btn-link-color: var(--#{$prefix}link-color) !default;
+$btn-link-hover-color: var(--#{$prefix}link-hover-color) !default;
+$btn-link-disabled-color: $gray-600 !default;
+$btn-link-focus-shadow-rgb: to-rgb(color.mix(color-contrast-variables($link-color, $color-contrast-dark, $color-contrast-light, $white, $black, $min-contrast-ratio), $link-color, 15%)) !default;
+
+// Allows for customizing button radius independently from global border radius
+$btn-border-radius: var(--#{$prefix}border-radius) !default;
+$btn-border-radius-sm: var(--#{$prefix}border-radius-sm) !default;
+$btn-border-radius-lg: var(--#{$prefix}border-radius-lg) !default;
+
+$btn-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
+
+$btn-hover-bg-shade-amount: 15% !default;
+$btn-hover-bg-tint-amount: 15% !default;
+$btn-hover-border-shade-amount: 20% !default;
+$btn-hover-border-tint-amount: 10% !default;
+$btn-active-bg-shade-amount: 20% !default;
+$btn-active-bg-tint-amount: 20% !default;
+$btn-active-border-shade-amount: 25% !default;
+$btn-active-border-tint-amount: 10% !default;
+// scss-docs-end btn-variables
+
+
+// Forms
+
+// scss-docs-start form-text-variables
+$form-text-margin-top: .25rem !default;
+$form-text-font-size: $small-font-size !default;
+$form-text-font-style: null !default;
+$form-text-font-weight: null !default;
+$form-text-color: var(--#{$prefix}secondary-color) !default;
+// scss-docs-end form-text-variables
+
+// scss-docs-start form-label-variables
+$form-label-margin-bottom: .5rem !default;
+$form-label-font-size: null !default;
+$form-label-font-style: null !default;
+$form-label-font-weight: null !default;
+$form-label-color: null !default;
+// scss-docs-end form-label-variables
+
+// scss-docs-start form-input-variables
+$input-padding-y: $input-btn-padding-y !default;
+$input-padding-x: $input-btn-padding-x !default;
+$input-font-family: $input-btn-font-family !default;
+$input-font-size: $input-btn-font-size !default;
+$input-font-weight: $font-weight-base !default;
+$input-line-height: $input-btn-line-height !default;
+
+$input-padding-y-sm: $input-btn-padding-y-sm !default;
+$input-padding-x-sm: $input-btn-padding-x-sm !default;
+$input-font-size-sm: $input-btn-font-size-sm !default;
+
+$input-padding-y-lg: $input-btn-padding-y-lg !default;
+$input-padding-x-lg: $input-btn-padding-x-lg !default;
+$input-font-size-lg: $input-btn-font-size-lg !default;
+
+$input-bg: var(--#{$prefix}body-bg) !default;
+$input-disabled-color: null !default;
+$input-disabled-bg: var(--#{$prefix}secondary-bg) !default;
+$input-disabled-border-color: null !default;
+
+$input-color: var(--#{$prefix}body-color) !default;
+$input-border-color: var(--#{$prefix}border-color) !default;
+$input-border-width: $input-btn-border-width !default;
+$input-box-shadow: var(--#{$prefix}box-shadow-inset) !default;
+
+$input-border-radius: var(--#{$prefix}border-radius) !default;
+$input-border-radius-sm: var(--#{$prefix}border-radius-sm) !default;
+$input-border-radius-lg: var(--#{$prefix}border-radius-lg) !default;
+
+$input-focus-bg: $input-bg !default;
+$input-focus-border-color: tint-color($primary, 50%) !default;
+$input-focus-color: $input-color !default;
+$input-focus-width: $input-btn-focus-width !default;
+$input-focus-box-shadow: $input-btn-focus-box-shadow !default;
+
+$input-placeholder-color: var(--#{$prefix}secondary-color) !default;
+$input-plaintext-color: var(--#{$prefix}body-color) !default;
+
+$input-height-border: calc(#{$input-border-width} * 2) !default; // stylelint-disable-line function-disallowed-list
+
+$input-height-inner: add($input-line-height * 1em, $input-padding-y * 2) !default;
+$input-height-inner-half: add($input-line-height * .5em, $input-padding-y) !default;
+$input-height-inner-quarter: add($input-line-height * .25em, $input-padding-y * .5) !default;
+
+$input-height: add($input-line-height * 1em, add($input-padding-y * 2, $input-height-border, false)) !default;
+$input-height-sm: add($input-line-height * 1em, add($input-padding-y-sm * 2, $input-height-border, false)) !default;
+$input-height-lg: add($input-line-height * 1em, add($input-padding-y-lg * 2, $input-height-border, false)) !default;
+
+$input-transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
+
+$form-color-width: 3rem !default;
+// scss-docs-end form-input-variables
+
+// scss-docs-start form-check-variables
+$form-check-input-width: 1em !default;
+$form-check-min-height: $font-size-base * $line-height-base !default;
+$form-check-padding-start: $form-check-input-width + .5em !default;
+$form-check-margin-bottom: .125rem !default;
+$form-check-label-color: null !default;
+$form-check-label-cursor: null !default;
+$form-check-transition: null !default;
+
+$form-check-input-active-filter: brightness(90%) !default;
+
+$form-check-input-bg: $input-bg !default;
+$form-check-input-border: var(--#{$prefix}border-width) solid var(--#{$prefix}border-color) !default;
+$form-check-input-border-radius: .25em !default;
+$form-check-radio-border-radius: 50% !default;
+$form-check-input-focus-border: $input-focus-border-color !default;
+$form-check-input-focus-box-shadow: $focus-ring-box-shadow !default;
+
+$form-check-input-checked-color: $component-active-color !default;
+$form-check-input-checked-bg-color: $component-active-bg !default;
+$form-check-input-checked-border-color: $form-check-input-checked-bg-color !default;
+$form-check-input-checked-bg-image: url("data:image/svg+xml,
") !default;
+$form-check-radio-checked-bg-image: url("data:image/svg+xml,
") !default;
+
+$form-check-input-indeterminate-color: $component-active-color !default;
+$form-check-input-indeterminate-bg-color: $component-active-bg !default;
+$form-check-input-indeterminate-border-color: $form-check-input-indeterminate-bg-color !default;
+$form-check-input-indeterminate-bg-image: url("data:image/svg+xml,
") !default;
+
+$form-check-input-disabled-opacity: .5 !default;
+$form-check-label-disabled-opacity: $form-check-input-disabled-opacity !default;
+$form-check-btn-check-disabled-opacity: $btn-disabled-opacity !default;
+
+$form-check-inline-margin-end: 1rem !default;
+// scss-docs-end form-check-variables
+
+// scss-docs-start form-switch-variables
+$form-switch-color: rgba($black, .25) !default;
+$form-switch-width: 2em !default;
+$form-switch-padding-start: $form-switch-width + .5em !default;
+$form-switch-bg-image: url("data:image/svg+xml,
") !default;
+$form-switch-border-radius: $form-switch-width !default;
+$form-switch-transition: background-position .15s ease-in-out !default;
+
+$form-switch-focus-color: $input-focus-border-color !default;
+$form-switch-focus-bg-image: url("data:image/svg+xml,
") !default;
+
+$form-switch-checked-color: $component-active-color !default;
+$form-switch-checked-bg-image: url("data:image/svg+xml,
") !default;
+$form-switch-checked-bg-position: right center !default;
+
+$form-switch-widths: (
+ lg: (
+ width: 2.5em,
+ height: 1.25em
+ ),
+ xl: (
+ width: 3em,
+ height: 1.5em
+ )
+) !default;
+// scss-docs-end form-switch-variables
+
+$form-check-inline-margin-end: 1rem !default;
+
+// scss-docs-start input-group-variables
+$input-group-addon-padding-y: $input-padding-y !default;
+$input-group-addon-padding-x: $input-padding-x !default;
+$input-group-addon-font-weight: $input-font-weight !default;
+$input-group-addon-color: $input-color !default;
+$input-group-addon-bg: var(--#{$prefix}tertiary-bg) !default;
+$input-group-addon-border-color: $input-border-color !default;
+// scss-docs-end input-group-variables
+
+// scss-docs-start form-select-variables
+$form-select-padding-y: $input-padding-y !default;
+$form-select-padding-x: $input-padding-x !default;
+$form-select-font-family: $input-font-family !default;
+$form-select-font-size: $input-font-size !default;
+$form-select-indicator-padding: $form-select-padding-x * 3 !default; // Extra padding for background-image
+$form-select-font-weight: $input-font-weight !default;
+$form-select-line-height: $input-line-height !default;
+$form-select-color: $input-color !default;
+$form-select-bg: $input-bg !default;
+$form-select-disabled-color: null !default;
+$form-select-disabled-bg: $input-disabled-bg !default;
+$form-select-disabled-border-color: $input-disabled-border-color !default;
+$form-select-bg-position: right $form-select-padding-x center !default;
+$form-select-bg-size: 16px 12px !default; // In pixels because image dimensions
+$form-select-indicator-color: $gray-800 !default;
+$form-select-indicator: url("data:image/svg+xml,
") !default;
+
+$form-select-feedback-icon-padding-end: $form-select-padding-x * 2.5 + $form-select-indicator-padding !default;
+$form-select-feedback-icon-position: center right $form-select-indicator-padding !default;
+$form-select-feedback-icon-size: $input-height-inner-half $input-height-inner-half !default;
+
+$form-select-border-width: $input-border-width !default;
+$form-select-border-color: $input-border-color !default;
+$form-select-border-radius: $input-border-radius !default;
+$form-select-box-shadow: var(--#{$prefix}box-shadow-inset) !default;
+
+$form-select-focus-border-color: $input-focus-border-color !default;
+$form-select-focus-width: $input-focus-width !default;
+$form-select-focus-box-shadow: 0 0 0 $form-select-focus-width $input-btn-focus-color !default;
+
+$form-select-padding-y-sm: $input-padding-y-sm !default;
+$form-select-padding-x-sm: $input-padding-x-sm !default;
+$form-select-font-size-sm: $input-font-size-sm !default;
+$form-select-border-radius-sm: $input-border-radius-sm !default;
+
+$form-select-padding-y-lg: $input-padding-y-lg !default;
+$form-select-padding-x-lg: $input-padding-x-lg !default;
+$form-select-font-size-lg: $input-font-size-lg !default;
+$form-select-border-radius-lg: $input-border-radius-lg !default;
+
+$form-select-transition: $input-transition !default;
+// scss-docs-end form-select-variables
+
+// scss-docs-start form-range-variables
+$form-range-track-width: 100% !default;
+$form-range-track-height: .5rem !default;
+$form-range-track-cursor: pointer !default;
+$form-range-track-bg: var(--#{$prefix}secondary-bg) !default;
+$form-range-track-border-radius: 1rem !default;
+$form-range-track-box-shadow: var(--#{$prefix}box-shadow-inset) !default;
+
+$form-range-thumb-width: 1rem !default;
+$form-range-thumb-height: $form-range-thumb-width !default;
+$form-range-thumb-bg: $component-active-bg !default;
+$form-range-thumb-border: 0 !default;
+$form-range-thumb-border-radius: 1rem !default;
+$form-range-thumb-box-shadow: 0 .1rem .25rem rgba($black, .1) !default;
+$form-range-thumb-focus-box-shadow: 0 0 0 1px $body-bg, $input-focus-box-shadow !default;
+$form-range-thumb-focus-box-shadow-width: $input-focus-width !default; // For focus box shadow issue in Edge
+$form-range-thumb-active-bg: tint-color($primary, 70%) !default;
+$form-range-thumb-disabled-bg: var(--#{$prefix}secondary-color) !default;
+$form-range-thumb-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
+// scss-docs-end form-range-variables
+
+// scss-docs-start form-file-variables
+$form-file-button-color: $input-color !default;
+$form-file-button-bg: var(--#{$prefix}tertiary-bg) !default;
+$form-file-button-hover-bg: var(--#{$prefix}secondary-bg) !default;
+// scss-docs-end form-file-variables
+
+// scss-docs-start form-floating-variables
+$form-floating-height: add(3.5rem, $input-height-border) !default;
+$form-floating-line-height: 1.25 !default;
+$form-floating-padding-x: $input-padding-x !default;
+$form-floating-padding-y: 1rem !default;
+$form-floating-input-padding-t: 1.625rem !default;
+$form-floating-input-padding-b: .625rem !default;
+$form-floating-label-height: 1.5em !default;
+$form-floating-label-opacity: .65 !default;
+$form-floating-label-transform: scale(.85) translateY(-.5rem) translateX(.15rem) !default;
+$form-floating-label-disabled-color: $gray-600 !default;
+$form-floating-transition: opacity .1s ease-in-out, transform .1s ease-in-out !default;
+// scss-docs-end form-floating-variables
+
+// Form validation
+
+// scss-docs-start form-feedback-variables
+$form-feedback-margin-top: $form-text-margin-top !default;
+$form-feedback-font-size: $form-text-font-size !default;
+$form-feedback-font-style: $form-text-font-style !default;
+$form-feedback-valid-color: $success !default;
+$form-feedback-invalid-color: $danger !default;
+
+$form-feedback-icon-valid-color: $form-feedback-valid-color !default;
+$form-feedback-icon-valid: url("data:image/svg+xml,
") !default;
+$form-feedback-icon-invalid-color: $form-feedback-invalid-color !default;
+$form-feedback-icon-invalid: url("data:image/svg+xml,
") !default;
+// scss-docs-end form-feedback-variables
+
+// scss-docs-start form-validation-colors
+$form-valid-color: $form-feedback-valid-color !default;
+$form-valid-border-color: $form-feedback-valid-color !default;
+$form-invalid-color: $form-feedback-invalid-color !default;
+$form-invalid-border-color: $form-feedback-invalid-color !default;
+// scss-docs-end form-validation-colors
+
+// scss-docs-start form-validation-states
+$form-validation-states: (
+ "valid": (
+ "color": var(--#{$prefix}form-valid-color),
+ "icon": $form-feedback-icon-valid,
+ "tooltip-color": #fff,
+ "tooltip-bg-color": var(--#{$prefix}success),
+ "focus-box-shadow": 0 0 $input-btn-focus-blur $input-focus-width rgba(var(--#{$prefix}success-rgb), $input-btn-focus-color-opacity),
+ "border-color": var(--#{$prefix}form-valid-border-color),
+ ),
+ "invalid": (
+ "color": var(--#{$prefix}form-invalid-color),
+ "icon": $form-feedback-icon-invalid,
+ "tooltip-color": #fff,
+ "tooltip-bg-color": var(--#{$prefix}danger),
+ "focus-box-shadow": 0 0 $input-btn-focus-blur $input-focus-width rgba(var(--#{$prefix}danger-rgb), $input-btn-focus-color-opacity),
+ "border-color": var(--#{$prefix}form-invalid-border-color),
+ )
+) !default;
+// scss-docs-end form-validation-states
+
+// Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+
+// scss-docs-start zindex-stack
+$zindex-dropdown: 1000 !default;
+$zindex-sticky: 1020 !default;
+$zindex-fixed: 1030 !default;
+$zindex-sidebar-backdrop: 1034 !default;
+$zindex-sidebar: 1035 !default;
+$zindex-offcanvas-backdrop: 1040 !default;
+$zindex-offcanvas: 1045 !default;
+$zindex-modal-backdrop: 1050 !default;
+$zindex-modal: 1055 !default;
+$zindex-popover: 1070 !default;
+$zindex-tooltip: 1080 !default;
+$zindex-toast: 1090 !default;
+// scss-docs-end zindex-stack
+
+// scss-docs-start zindex-levels-map
+$zindex-levels: (
+ n1: -1,
+ 0: 0,
+ 1: 1,
+ 2: 2,
+ 3: 3
+) !default;
+// scss-docs-end zindex-levels-map
+
+
+// Navs
+
+// scss-docs-start nav-variables
+$nav-link-padding-y: .5rem !default;
+$nav-link-padding-x: 1rem !default;
+$nav-link-font-size: null !default;
+$nav-link-font-weight: null !default;
+$nav-link-color: var(--#{$prefix}link-color) !default;
+$nav-link-hover-color: var(--#{$prefix}link-hover-color) !default;
+$nav-link-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out !default;
+$nav-link-disabled-color: var(--#{$prefix}secondary-color) !default;
+$nav-link-focus-box-shadow: $focus-ring-box-shadow !default;
+
+$nav-tabs-border-color: var(--#{$prefix}border-color) !default;
+$nav-tabs-border-width: var(--#{$prefix}border-width) !default;
+$nav-tabs-border-radius: var(--#{$prefix}border-radius) !default;
+$nav-tabs-link-hover-border-color: var(--#{$prefix}secondary-bg) var(--#{$prefix}secondary-bg) $nav-tabs-border-color !default;
+$nav-tabs-link-active-color: var(--#{$prefix}emphasis-color) !default;
+$nav-tabs-link-active-bg: var(--#{$prefix}body-bg) !default;
+$nav-tabs-link-active-border-color: var(--#{$prefix}border-color) var(--#{$prefix}border-color) $nav-tabs-link-active-bg !default;
+
+$nav-pills-border-radius: var(--#{$prefix}border-radius) !default;
+$nav-pills-link-active-color: $component-active-color !default;
+$nav-pills-link-active-bg: $component-active-bg !default;
+
+$nav-underline-gap: 1rem !default;
+$nav-underline-border-width: .125rem !default;
+$nav-underline-link-active-color: var(--#{$prefix}emphasis-color) !default;
+
+$nav-underline-border-gap: .5rem !default;
+$nav-underline-border-border-color: var(--#{$prefix}border-color) !default;
+$nav-underline-border-border-width: .125rem !default;
+$nav-underline-border-link-padding-y: .5rem !default;
+$nav-underline-border-link-padding-x: .5rem !default;
+$nav-underline-border-link-color: var(--#{$prefix}secondary-color) !default;
+$nav-underline-border-link-active-color: var(--#{$prefix}primary) !default;
+$nav-underline-border-link-disabled-color: var(--#{$prefix}tertiary-color) !default;
+// scss-docs-end nav-variables
+
+
+// Navbar
+
+// scss-docs-start navbar-variables
+$navbar-padding-y: $spacer * .5 !default;
+$navbar-padding-x: null !default;
+
+$navbar-nav-link-padding-x: .5rem !default;
+
+$navbar-brand-font-size: $font-size-lg !default;
+// Compute the navbar-brand padding-y so the navbar-brand will have the same height as navbar-text and nav-link
+$nav-link-height: $font-size-base * $line-height-base + $nav-link-padding-y * 2 !default;
+$navbar-brand-height: $navbar-brand-font-size * $line-height-base !default;
+$navbar-brand-padding-y: ($nav-link-height - $navbar-brand-height) * .5 !default;
+$navbar-brand-margin-end: 1rem !default;
+
+$navbar-toggler-padding-y: .25rem !default;
+$navbar-toggler-padding-x: .75rem !default;
+$navbar-toggler-font-size: $font-size-lg !default;
+$navbar-toggler-border-radius: $btn-border-radius !default;
+$navbar-toggler-focus-width: $btn-focus-width !default;
+$navbar-toggler-transition: box-shadow .15s ease-in-out !default;
+
+$navbar-light-color: rgba(var(--#{$prefix}emphasis-color-rgb), .65) !default;
+$navbar-light-hover-color: rgba(var(--#{$prefix}emphasis-color-rgb), .8) !default;
+$navbar-light-active-color: rgba(var(--#{$prefix}emphasis-color-rgb), 1) !default;
+$navbar-light-disabled-color: rgba(var(--#{$prefix}emphasis-color-rgb), .3) !default;
+$navbar-light-icon-color: rgba($body-color, .75) !default;
+$navbar-light-toggler-icon-bg: url("data:image/svg+xml,
") !default;
+$navbar-light-toggler-border-color: rgba(var(--#{$prefix}emphasis-color-rgb), .15) !default;
+$navbar-light-brand-color: $navbar-light-active-color !default;
+$navbar-light-brand-hover-color: $navbar-light-active-color !default;
+// scss-docs-end navbar-variables
+
+// scss-docs-start navbar-dark-variables
+$navbar-dark-color: rgba($white, .55) !default;
+$navbar-dark-hover-color: rgba($white, .75) !default;
+$navbar-dark-active-color: $white !default;
+$navbar-dark-disabled-color: rgba($white, .25) !default;
+$navbar-dark-icon-color: $navbar-dark-color !default;
+$navbar-dark-toggler-icon-bg: url("data:image/svg+xml,
") !default;
+$navbar-dark-toggler-border-color: rgba($white, .1) !default;$navbar-dark-toggler-border-color: rgba($white, .1) !default;
+$navbar-dark-brand-color: $navbar-dark-active-color !default;
+$navbar-dark-brand-hover-color: $navbar-dark-active-color !default;
+// scss-docs-end navbar-dark-variables
+
+
+// Dropdowns
+//
+// Dropdown menu container and contents.
+
+// scss-docs-start dropdown-variables
+$dropdown-min-width: 10rem !default;
+$dropdown-padding-x: 0 !default;
+$dropdown-padding-y: .5rem !default;
+$dropdown-spacer: .125rem !default;
+$dropdown-font-size: $font-size-base !default;
+$dropdown-color: var(--#{$prefix}body-color) !default;
+$dropdown-bg: var(--#{$prefix}body-bg) !default;
+$dropdown-border-color: var(--#{$prefix}border-color-translucent) !default;
+$dropdown-border-radius: var(--#{$prefix}border-radius) !default;
+$dropdown-border-width: var(--#{$prefix}border-width) !default;
+$dropdown-inner-border-radius: calc(#{$dropdown-border-radius} - #{$dropdown-border-width}) !default; // stylelint-disable-line function-disallowed-list
+$dropdown-divider-bg: $dropdown-border-color !default;
+$dropdown-divider-margin-y: $spacer * .5 !default;
+$dropdown-box-shadow: var(--#{$prefix}box-shadow) !default;
+
+$dropdown-link-color: var(--#{$prefix}body-color) !default;
+$dropdown-link-hover-color: $dropdown-link-color !default;
+$dropdown-link-hover-bg: var(--#{$prefix}tertiary-bg) !default;
+
+$dropdown-link-active-color: $component-active-color !default;
+$dropdown-link-active-bg: $component-active-bg !default;
+
+$dropdown-link-disabled-color: var(--#{$prefix}tertiary-color) !default;
+
+$dropdown-item-padding-y: $spacer * .25 !default;
+$dropdown-item-padding-x: $spacer !default;
+
+$dropdown-header-color: $gray-600 !default;
+$dropdown-header-padding-x: $dropdown-item-padding-x !default;
+$dropdown-header-padding-y: $dropdown-padding-y !default;
+// fusv-disable
+$dropdown-header-padding: $dropdown-header-padding-y $dropdown-header-padding-x !default; // Deprecated in v4.2.6
+// fusv-enable
+// scss-docs-end dropdown-variables
+
+// scss-docs-start dropdown-dark-variables
+$dropdown-dark-color: $gray-300 !default;
+$dropdown-dark-bg: $gray-800 !default;
+$dropdown-dark-border-color: $dropdown-border-color !default;
+$dropdown-dark-divider-bg: $dropdown-divider-bg !default;
+$dropdown-dark-box-shadow: null !default;
+$dropdown-dark-link-color: $dropdown-dark-color !default;
+$dropdown-dark-link-hover-color: $white !default;
+$dropdown-dark-link-hover-bg: rgba($white, .15) !default;
+$dropdown-dark-link-active-color: $dropdown-link-active-color !default;
+$dropdown-dark-link-active-bg: $dropdown-link-active-bg !default;
+$dropdown-dark-link-disabled-color: $gray-500 !default;
+$dropdown-dark-header-color: $gray-500 !default;
+// scss-docs-end dropdown-dark-variables
+
+
+// Pagination
+
+// scss-docs-start pagination-variables
+$pagination-padding-y: .375rem !default;
+$pagination-padding-x: .75rem !default;
+$pagination-padding-y-sm: .25rem !default;
+$pagination-padding-x-sm: .5rem !default;
+$pagination-padding-y-lg: .75rem !default;
+$pagination-padding-x-lg: 1.5rem !default;
+
+$pagination-font-size: $font-size-base !default;
+
+$pagination-color: var(--#{$prefix}link-color) !default;
+$pagination-bg: var(--#{$prefix}body-bg) !default;
+$pagination-border-radius: var(--#{$prefix}border-radius) !default;
+$pagination-border-width: var(--#{$prefix}border-width) !default;
+$pagination-margin-start: calc(-1 * #{$pagination-border-width}) !default; // stylelint-disable-line function-disallowed-list
+$pagination-border-color: var(--#{$prefix}border-color) !default;
+
+$pagination-focus-color: var(--#{$prefix}link-hover-color) !default;
+$pagination-focus-bg: var(--#{$prefix}secondary-bg) !default;
+$pagination-focus-box-shadow: $focus-ring-box-shadow !default;
+$pagination-focus-outline: 0 !default;
+
+$pagination-hover-color: var(--#{$prefix}link-hover-color) !default;
+$pagination-hover-bg: var(--#{$prefix}tertiary-bg) !default;
+$pagination-hover-border-color: var(--#{$prefix}border-color) !default; // Todo in v6: remove this?
+
+$pagination-active-color: $component-active-color !default;
+$pagination-active-bg: $component-active-bg !default;
+$pagination-active-border-color: $component-active-bg !default;
+
+$pagination-disabled-color: var(--#{$prefix}secondary-color) !default;
+$pagination-disabled-bg: var(--#{$prefix}secondary-bg) !default;
+$pagination-disabled-border-color: var(--#{$prefix}border-color) !default;
+
+$pagination-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
+
+$pagination-border-radius-sm: var(--#{$prefix}border-radius-sm) !default;
+$pagination-border-radius-lg: var(--#{$prefix}border-radius-lg) !default;
+// scss-docs-end pagination-variables
+
+
+// Placeholders
+
+// scss-docs-start placeholders
+$placeholder-opacity-max: .5 !default;
+$placeholder-opacity-min: .2 !default;
+// scss-docs-end placeholders
+
+// Cards
+
+// scss-docs-start card-variables
+$card-spacer-y: $spacer !default;
+$card-spacer-x: $spacer !default;
+$card-title-spacer-y: $spacer * .5 !default;
+$card-title-color: null !default;
+$card-subtitle-color: null !default;
+$card-border-width: var(--#{$prefix}border-width) !default;
+$card-border-color: var(--#{$prefix}border-color-translucent) !default;
+$card-border-radius: var(--#{$prefix}border-radius) !default;
+$card-box-shadow: null !default;
+$card-inner-border-radius: subtract($card-border-radius, $card-border-width) !default;
+$card-cap-padding-y: $card-spacer-y * .5 !default;
+$card-cap-padding-x: $card-spacer-x !default;
+$card-cap-bg: rgba(var(--#{$prefix}body-color-rgb), .03) !default;
+$card-cap-color: null !default;
+$card-height: null !default;
+$card-color: null !default;
+$card-bg: var(--#{$prefix}body-bg) !default;
+$card-img-overlay-padding: $spacer !default;
+$card-group-margin: $grid-gutter-width * .5 !default;
+// scss-docs-end card-variables
+
+// Accordion
+
+// scss-docs-start accordion-variables
+$accordion-padding-y: 1rem !default;
+$accordion-padding-x: 1.25rem !default;
+$accordion-color: var(--#{$prefix}body-color) !default;
+$accordion-bg: var(--#{$prefix}body-bg) !default;
+$accordion-border-width: var(--#{$prefix}border-width) !default;
+$accordion-border-color: var(--#{$prefix}border-color) !default;
+$accordion-border-radius: var(--#{$prefix}border-radius) !default;
+$accordion-inner-border-radius: subtract($accordion-border-radius, $accordion-border-width) !default;
+
+$accordion-body-padding-y: $accordion-padding-y !default;
+$accordion-body-padding-x: $accordion-padding-x !default;
+
+$accordion-button-padding-y: $accordion-padding-y !default;
+$accordion-button-padding-x: $accordion-padding-x !default;
+$accordion-button-color: var(--#{$prefix}body-color) !default;
+$accordion-button-bg: var(--#{$prefix}accordion-bg) !default;
+$accordion-transition: $btn-transition, border-radius .15s ease !default;
+$accordion-button-active-bg: var(--#{$prefix}primary-bg-subtle) !default;
+$accordion-button-active-color: var(--#{$prefix}primary-text-emphasis) !default;
+
+// fusv-disable
+$accordion-button-focus-border-color: $input-focus-border-color !default; // Deprecated in v5.0.0
+// fusv-enable
+$accordion-button-focus-box-shadow: $btn-focus-box-shadow !default;
+
+$accordion-icon-width: 1.25rem !default;
+$accordion-icon-color: $body-color !default;
+$accordion-icon-active-color: $primary-text-emphasis !default;
+$accordion-icon-transition: transform .2s ease-in-out !default;
+$accordion-icon-transform: rotate(-180deg) !default;
+
+$accordion-button-icon: url("data:image/svg+xml,
") !default;
+$accordion-button-active-icon: url("data:image/svg+xml,
") !default;
+// scss-docs-end accordion-variables
+
+// Tooltips
+
+// scss-docs-start tooltip-variables
+$tooltip-font-size: $font-size-sm !default;
+$tooltip-max-width: 200px !default;
+$tooltip-color: var(--#{$prefix}body-bg) !default;
+$tooltip-bg: var(--#{$prefix}emphasis-color) !default;
+$tooltip-border-radius: var(--#{$prefix}border-radius) !default;
+$tooltip-opacity: .9 !default;
+$tooltip-padding-y: $spacer * .25 !default;
+$tooltip-padding-x: $spacer * .5 !default;
+$tooltip-margin: null !default; // TODO: remove this in v6
+
+$tooltip-arrow-width: .8rem !default;
+$tooltip-arrow-height: .4rem !default;
+// fusv-disable
+$tooltip-arrow-color: null !default; // Deprecated in 4.2.0 for CSS variables
+// fusv-enable
+// scss-docs-end tooltip-variables
+
+// Form tooltips must come after regular tooltips
+// scss-docs-start tooltip-feedback-variables
+$form-feedback-tooltip-padding-y: $tooltip-padding-y !default;
+$form-feedback-tooltip-padding-x: $tooltip-padding-x !default;
+$form-feedback-tooltip-font-size: $tooltip-font-size !default;
+$form-feedback-tooltip-line-height: null !default;
+$form-feedback-tooltip-opacity: $tooltip-opacity !default;
+$form-feedback-tooltip-border-radius: $tooltip-border-radius !default;
+// scss-docs-end tooltip-feedback-variables
+
+
+// Popovers
+
+// scss-docs-start popover-variables
+$popover-font-size: $font-size-sm !default;
+$popover-bg: var(--#{$prefix}body-bg) !default;
+$popover-max-width: 276px !default;
+$popover-border-width: var(--#{$prefix}border-width) !default;
+$popover-border-color: var(--#{$prefix}border-color-translucent) !default;
+$popover-border-radius: var(--#{$prefix}border-radius-lg) !default;
+$popover-inner-border-radius: calc(#{$popover-border-radius} - #{$popover-border-width}) !default; // stylelint-disable-line function-disallowed-list
+$popover-box-shadow: var(--#{$prefix}box-shadow) !default;
+
+$popover-header-font-size: $font-size-base !default;
+$popover-header-bg: var(--#{$prefix}secondary-bg) !default;
+$popover-header-color: $headings-color !default;
+$popover-header-padding-y: .5rem !default;
+$popover-header-padding-x: $spacer !default;
+
+$popover-body-color: var(--#{$prefix}body-color) !default;
+$popover-body-padding-y: $spacer !default;
+$popover-body-padding-x: $spacer !default;
+
+$popover-arrow-width: 1rem !default;
+$popover-arrow-height: .5rem !default;
+// scss-docs-end popover-variables
+
+// fusv-disable
+// Deprecated in 4.2.0 for CSS variables
+$popover-arrow-color: $popover-bg !default;
+$popover-arrow-outer-color: var(--#{$prefix}border-color-translucent) !default;
+// fusv-enable
+
+
+// Toasts
+
+// scss-docs-start toast-variables
+$toast-max-width: 350px !default;
+$toast-padding-x: .75rem !default;
+$toast-padding-y: .5rem !default;
+$toast-font-size: .875rem !default;
+$toast-color: null !default;
+$toast-background-color: rgba(var(--#{$prefix}body-bg-rgb), .85) !default;
+$toast-border-width: var(--#{$prefix}border-width) !default;
+$toast-border-color: var(--#{$prefix}border-color-translucent) !default;
+$toast-border-radius: var(--#{$prefix}border-radius) !default;
+$toast-box-shadow: var(--#{$prefix}box-shadow) !default;
+$toast-spacing: $container-padding-x !default;
+
+$toast-header-color: var(--#{$prefix}secondary-color) !default;
+$toast-header-background-color: rgba(var(--#{$prefix}body-bg-rgb), .85) !default;
+$toast-header-border-color: $toast-border-color !default;
+// scss-docs-end toast-variables
+
+
+// Badges
+
+// scss-docs-start badge-variables
+$badge-font-size: .75em !default;
+$badge-font-weight: $font-weight-bold !default;
+$badge-color: $white !default;
+$badge-padding-y: .35em !default;
+$badge-padding-x: .65em !default;
+$badge-border-radius: var(--#{$prefix}border-radius) !default;
+
+$badge-font-size-sm: .65em !default;
+$badge-padding-y-sm: .3em !default;
+$badge-padding-x-sm: .5em !default;
+// scss-docs-end badge-variables
+
+
+// Modals
+
+// scss-docs-start modal-variables
+$modal-inner-padding: $spacer !default;
+
+$modal-footer-margin-between: .5rem !default;
+
+$modal-dialog-margin: .5rem !default;
+$modal-dialog-margin-y-sm-up: 1.75rem !default;
+
+$modal-title-line-height: $line-height-base !default;
+
+$modal-content-color: var(--#{$prefix}body-color) !default;
+$modal-content-bg: var(--#{$prefix}body-bg) !default;
+$modal-content-border-color: var(--#{$prefix}border-color-translucent) !default;
+$modal-content-border-width: var(--#{$prefix}border-width) !default;
+$modal-content-border-radius: var(--#{$prefix}border-radius-lg) !default;
+$modal-content-inner-border-radius: subtract($modal-content-border-radius, $modal-content-border-width) !default;
+$modal-content-box-shadow-xs: var(--#{$prefix}box-shadow-sm) !default;
+$modal-content-box-shadow-sm-up: var(--#{$prefix}box-shadow) !default;
+
+$modal-backdrop-bg: $black !default;
+$modal-backdrop-opacity: .5 !default;
+
+$modal-header-border-color: var(--#{$prefix}border-color) !default;
+$modal-header-border-width: $modal-content-border-width !default;
+$modal-header-padding-y: $modal-inner-padding !default;
+$modal-header-padding-x: $modal-inner-padding !default;
+$modal-header-padding: $modal-header-padding-y $modal-header-padding-x !default; // Keep this for backwards compatibility
+
+$modal-footer-bg: null !default;
+$modal-footer-border-color: $modal-header-border-color !default;
+$modal-footer-border-width: $modal-header-border-width !default;
+
+$modal-sm: 300px !default;
+$modal-md: 500px !default;
+$modal-lg: 800px !default;
+$modal-xl: 1140px !default;
+
+$modal-fade-transform: translate(0, -50px) !default;
+$modal-show-transform: none !default;
+$modal-transition: transform .3s ease-out !default;
+$modal-scale-transform: scale(1.02) !default;
+// scss-docs-end modal-variables
+
+
+// Avatars
+// scss-docs-start avatar-variables
+$avatar-width: 2rem !default;
+$avatar-height: 2rem !default;
+$avatar-font-size: .8rem !default;
+$avatar-border-radius: 50em !default;
+$avatar-status-width: .5rem !default;
+$avatar-status-height: .5rem !default;
+$avatar-status-border-radius: 50em !default;
+$avatar-transition: margin .15s !default;
+
+$avatar-sizes: (
+ sm: (
+ width: 1.5rem,
+ height: 1.5rem,
+ font-size: .6rem,
+ status-width: .4rem,
+ status-height: .4rem
+ ),
+ md: (
+ width: 2.5rem,
+ height: 2.5rem,
+ font-size: 1rem,
+ status-width: .7rem,
+ status-height: .7rem
+ ),
+ lg: (
+ width: 3rem,
+ height: 3rem,
+ font-size: 1.2rem,
+ status-width: .8rem,
+ status-height: .8rem
+ ),
+ xl: (
+ width: 4rem,
+ height: 4rem,
+ font-size: 1.6rem,
+ status-width: 1rem,
+ status-height: 1rem
+ ),
+) !default;
+// scss-docs-end avatar-variables
+
+// fusv-disable
+// Deprecated in 5.1.0 for CSS variables
+$avatar-widths: (
+ sm: 1.5rem,
+ md: 2.5rem,
+ lg: 3rem,
+ xl: 4rem
+) !default;
+// fusv-enable
+
+// Alerts
+//
+// Define alert colors, border radius, and padding.
+
+// scss-docs-start alert-variables
+$alert-padding-y: $spacer !default;
+$alert-padding-x: $spacer !default;
+$alert-margin-bottom: 1rem !default;
+$alert-border-radius: var(--#{$prefix}border-radius) !default;
+$alert-link-font-weight: $font-weight-bold !default;
+$alert-border-width: var(--#{$prefix}border-width) !default;
+$alert-dismissible-padding-r: $alert-padding-x * 3 !default; // 3x covers width of x plus default padding on either side
+// scss-docs-end alert-variables
+
+// fusv-disable
+$alert-bg-scale: -80% !default; // Deprecated in v4.2.0, to be removed in v6
+$alert-border-scale: -70% !default; // Deprecated in v4.2.0, to be removed in v6
+$alert-color-scale: 40% !default; // Deprecated in v4.2.0, to be removed in v6
+// fusv-enable
+
+// Callouts
+// scss-docs-start callout-variables
+$callout-padding-y: $spacer !default;
+$callout-padding-x: $spacer !default;
+$callout-margin-y: $spacer !default;
+$callout-margin-x: 0 !default;
+$callout-border-radius: var(--#{$prefix}border-radius) !default;
+$callout-border-width: var(--#{$prefix}border-width) !default;
+$callout-border-color: var(--#{$prefix}border-color) !default;
+$callout-border-left-width: calc(#{$callout-border-width} * 4) !default; // stylelint-disable-line function-disallowed-list
+
+$callout-variants: (
+ "primary": $primary,
+ "secondary": $secondary,
+ "success": $success,
+ "danger": $danger,
+ "warning": $warning,
+ "info": $info,
+ "light": $light,
+ "dark": $dark
+) !default;
+// scss-docs-end callout-variables
+
+
+// Progress bars
+
+// scss-docs-start progress-variables
+$progress-height: 1rem !default;
+$progress-font-size: $font-size-base * .75 !default;
+$progress-bg: var(--#{$prefix}secondary-bg) !default;
+$progress-border-radius: var(--#{$prefix}border-radius) !default;
+$progress-box-shadow: var(--#{$prefix}box-shadow-inset) !default;
+$progress-bar-color: $white !default;
+$progress-bar-bg: var(--#{$prefix}primary) !default;
+$progress-bar-animation-timing: 1s linear infinite !default;
+$progress-bar-transition: width .6s ease !default;
+
+// TODO: clean-up ???
+$progress-group-margin-bottom: $spacer !default;
+$progress-group-header-margin-bottom: $spacer * .25 !default;
+// scss-docs-end progress-variables
+
+// List group
+// scss-docs-start list-group-variables
+$list-group-color: var(--#{$prefix}body-color) !default;
+$list-group-bg: var(--#{$prefix}body-bg) !default;
+$list-group-border-color: var(--#{$prefix}border-color) !default;
+$list-group-border-width: var(--#{$prefix}border-width) !default;
+$list-group-border-radius: var(--#{$prefix}border-radius) !default;
+
+$list-group-item-padding-y: $spacer * .5 !default;
+$list-group-item-padding-x: $spacer !default;
+// fusv-disable
+$list-group-item-bg-scale: -80% !default; // Deprecated in v5.0.0
+$list-group-item-color-scale: 40% !default; // Deprecated in v5.0.0
+// fusv-enable
+
+$list-group-hover-bg: var(--#{$prefix}tertiary-bg) !default;
+$list-group-active-color: $component-active-color !default;
+$list-group-active-bg: $component-active-bg !default;
+$list-group-active-border-color: $list-group-active-bg !default;
+
+$list-group-disabled-color: var(--#{$prefix}secondary-color) !default;
+$list-group-disabled-bg: $list-group-bg !default;
+
+$list-group-action-color: var(--#{$prefix}secondary-color) !default;
+$list-group-action-hover-color: var(--#{$prefix}emphasis-color) !default;
+
+$list-group-action-active-color: var(--#{$prefix}body-color) !default;
+$list-group-action-active-bg: var(--#{$prefix}secondary-bg) !default;
+// scss-docs-end list-group-variables
+
+
+// Header
+// scss-docs-start header-variables
+$header-padding-y: $spacer * .5 !default;
+$header-padding-x: $spacer * .5 !default;
+$header-brand-font-size: $font-size-lg !default;
+$header-color: rgba(var(--#{$prefix}emphasis-color-rgb), .65) !default;
+$header-bg: var(--#{$prefix}body-bg) !default;
+$header-border-color: var(--#{$prefix}border-color) !default;
+$header-border-width: var(--#{$prefix}border-width) !default;
+$header-hover-color: rgba(var(--#{$prefix}emphasis-color-rgb), .8) !default;
+$header-active-color: rgba(var(--#{$prefix}emphasis-color-rgb), 1) !default;
+$header-disabled-color: rgba(var(--#{$prefix}emphasis-color-rgb), .3) !default;
+$header-transition: box-shadow .15s ease-in-out !default;
+
+// Compute the header-brand padding-y so the header-brand will have the same height as header-text and nav-link
+$nav-link-height: $font-size-base * $line-height-base + $nav-link-padding-y * 2 !default;
+$header-brand-height: $header-brand-font-size * $line-height-base !default;
+$header-brand-padding-y: ($nav-link-height - $header-brand-height) * .5 !default;
+$header-brand-margin-end: 1rem !default;
+$header-brand-font-size: $font-size-lg !default;
+$header-brand-color: $gray-900 !default;
+$header-brand-hover-color: shade-color($gray-900, 10%) !default;
+
+$header-toggler-padding-y: .25rem !default;
+$header-toggler-padding-x: .75rem !default;
+$header-toggler-font-size: $font-size-lg !default;
+$header-toggler-color: rgba(var(--#{$prefix}emphasis-color-rgb), .65) !default;
+$header-toggler-bg: transparent !default;
+$header-toggler-border-radius: $btn-border-radius !default;
+$header-toggler-hover-color: rgba(var(--#{$prefix}emphasis-color-rgb), 1) !default;
+
+$header-toggler-icon-bg: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$header-color}' stroke-width='2.25' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E") !default;
+$header-toggler-hover-icon-bg: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$header-hover-color}' stroke-width='2.25' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E") !default;
+
+$header-nav-link-padding-x: .5rem !default;
+$header-nav-link-padding-y: .5rem !default;
+
+$header-divider-border-width: var(--#{$prefix}border-width) !default;
+$header-divider-border-color: var(--#{$prefix}border-color) !default;
+// scss-docs-end header-variables
+
+
+// Image thumbnails
+
+// scss-docs-start thumbnail-variables
+$thumbnail-padding: .25rem !default;
+$thumbnail-bg: var(--#{$prefix}body-bg) !default;
+$thumbnail-border-width: var(--#{$prefix}border-width) !default;
+$thumbnail-border-color: var(--#{$prefix}border-color) !default;
+$thumbnail-border-radius: var(--#{$prefix}border-radius) !default;
+$thumbnail-box-shadow: var(--#{$prefix}box-shadow-sm) !default;
+// scss-docs-end thumbnail-variables
+
+
+// Figures
+
+// scss-docs-start figure-variables
+$figure-caption-font-size: $small-font-size !default;
+$figure-caption-color: var(--#{$prefix}secondary-color) !default;
+// scss-docs-end figure-variables
+
+
+// Breadcrumbs
+
+// scss-docs-start breadcrumb-variables
+$breadcrumb-font-size: null !default;
+$breadcrumb-padding-y: 0 !default;
+$breadcrumb-padding-x: 0 !default;
+$breadcrumb-item-padding-x: .5rem !default;
+$breadcrumb-margin-bottom: 1rem !default;
+$breadcrumb-bg: null !default;
+$breadcrumb-divider-color: var(--#{$prefix}secondary-color) !default;
+$breadcrumb-active-color: var(--#{$prefix}secondary-color) !default;
+$breadcrumb-divider: string.quote("/") !default;
+$breadcrumb-divider-flipped: $breadcrumb-divider !default;
+$breadcrumb-border-radius: null !default;
+// scss-docs-end breadcrumb-variables
+
+// Carousel
+
+// scss-docs-start carousel-variables
+$carousel-control-color: $white !default;
+$carousel-control-width: 15% !default;
+$carousel-control-opacity: .5 !default;
+$carousel-control-hover-opacity: .9 !default;
+$carousel-control-transition: opacity .15s ease !default;
+$carousel-control-icon-filter: null !default;
+
+$carousel-indicator-width: 30px !default;
+$carousel-indicator-height: 3px !default;
+$carousel-indicator-hit-area-height: 10px !default;
+$carousel-indicator-spacer: 3px !default;
+$carousel-indicator-opacity: .5 !default;
+$carousel-indicator-active-bg: $white !default;
+$carousel-indicator-active-opacity: 1 !default;
+$carousel-indicator-transition: opacity .6s ease !default;
+
+$carousel-caption-width: 70% !default;
+$carousel-caption-color: $white !default;
+$carousel-caption-padding-y: 1.25rem !default;
+$carousel-caption-spacer: 1.25rem !default;
+
+$carousel-control-icon-width: 2rem !default;
+
+$carousel-control-prev-icon-bg: url("data:image/svg+xml,
") !default;
+$carousel-control-next-icon-bg: url("data:image/svg+xml,
") !default;
+
+$carousel-transition-duration: .6s !default;
+$carousel-transition: transform $carousel-transition-duration ease-in-out !default; // Define transform transition first if using multiple transitions (e.g., `transform 2s ease, opacity .5s ease-out`)
+// scss-docs-end carousel-variables
+
+// scss-docs-start carousel-dark-variables
+$carousel-dark-indicator-active-bg: $black !default; // Deprecated in v5.3.2
+$carousel-dark-caption-color: $black !default; // Deprecated in v5.3.2
+$carousel-dark-control-icon-filter: invert(1) grayscale(100) !default; // Deprecated in v5.3.2
+// scss-docs-end carousel-dark-variables
+
+// scss-docs-start sidebar-variables
+$sidebar-width: 16rem !default;
+$sidebar-widths: (
+ sm: 12rem,
+ lg: 20rem,
+ xl: 24rem
+) !default;
+$sidebar-narrow-width: 4rem !default;
+$sidebar-padding-y: $spacer !default;
+$sidebar-padding-x: $spacer !default;
+$sidebar-color: var(--#{$prefix}body-color) !default;
+$sidebar-bg: var(--#{$prefix}body-bg) !default;
+$sidebar-transition: margin-left .15s, margin-right .15s, box-shadow .075s, transform .15s, width .15s, z-index 0s ease .15s !default;
+$sidebar-brand-color: var(--#{$prefix}body-color) !default;
+$sidebar-brand-bg: rgba($black, .2) !default;
+$sidebar-backdrop-bg: $black !default;
+$sidebar-backdrop-opacity: .5 !default;
+$sidebar-overlaid-box-shadow: var(--#{$prefix}box-shadow) !default;
+$sidebar-narrow-unfoldable-box-shadow: var(--#{$prefix}box-shadow) !default;
+// scss-docs-end sidebar-variables
+
+// scss-docs-start sidebar-nav-variables
+$sidebar-nav-padding-y: $sidebar-padding-y * .5 !default;
+$sidebar-nav-padding-x: $sidebar-padding-x * .5 !default;
+$sidebar-nav-gap: 1px !default;
+
+$sidebar-nav-title-padding-y: .75rem !default;
+$sidebar-nav-title-padding-x: 1rem !default;
+$sidebar-nav-title-margin-top: 1rem !default;
+$sidebar-nav-title-color: var(--#{$prefix}tertiary-color) !default;
+$sidebar-nav-title-transition: height .15s, margin .15s !default;
+
+$sidebar-nav-link-padding-y: .75rem !default;
+$sidebar-nav-link-padding-x: 1rem !default;
+$sidebar-nav-link-color: var(--#{$prefix}body-color) !default;
+$sidebar-nav-link-bg: transparent !default;
+$sidebar-nav-link-border-width: 0 !default;
+$sidebar-nav-link-border-color: transparent !default;
+$sidebar-nav-link-border-radius: var(--#{$prefix}border-radius) !default;
+$sidebar-nav-link-transition: background .15s ease, color .15s ease, gap .15s ease !default;
+
+$sidebar-compact-nav-link-padding-y: .5625rem !default;
+
+$sidebar-narrow-nav-link-padding-y: .75rem !default;
+$sidebar-narrow-nav-link-padding-x: .5rem !default;
+
+$sidebar-nav-link-icon-margin: .75rem !default;
+$sidebar-nav-link-icon-color: var(--#{$prefix}tertiary-color) !default;
+$sidebar-nav-link-icon-width: 1.25rem !default;
+$sidebar-nav-link-icon-height: 1.25rem !default;
+$sidebar-nav-link-icon-font-size: $sidebar-nav-link-icon-height !default;
+
+$sidebar-nav-link-icon-bullet-size: .3125rem !default;
+$sidebar-nav-link-icon-bullet-bg: transparent !default;
+$sidebar-nav-link-icon-bullet-border-width: 1px !default;
+$sidebar-nav-link-icon-bullet-border-color: var(--#{$prefix}tertiary-color) !default;
+$sidebar-nav-link-icon-bullet-border-radius: 50rem !default;
+
+$sidebar-nav-link-hover-color: var(--#{$prefix}emphasis-color) !default;
+$sidebar-nav-link-hover-bg: var(--#{$prefix}tertiary-bg) !default;
+$sidebar-nav-link-hover-icon-color: var(--#{$prefix}body-color) !default;
+$sidebar-nav-link-hover-icon-bullet-bg: transparent !default;
+$sidebar-nav-link-hover-icon-bullet-border-color: var(--#{$prefix}body-color) !default;
+
+$sidebar-nav-link-active-color: var(--#{$prefix}emphasis-color) !default;
+$sidebar-nav-link-active-bg: var(--#{$prefix}tertiary-bg) !default;
+$sidebar-nav-link-active-icon-color: var(--#{$prefix}emphasis-color) !default;
+$sidebar-nav-link-active-icon-bullet-bg: transparent !default;
+$sidebar-nav-link-active-icon-bullet-border-color: var(--#{$prefix}emphasis-color) !default;
+
+$sidebar-nav-link-disabled-color: var(--#{$prefix}tertiary-color) !default;
+$sidebar-nav-link-disabled-icon-color: var(--#{$prefix}tertiary-color) !default;
+$sidebar-nav-link-disabled-icon-bullet-bg: transparent !default;
+$sidebar-nav-link-disabled-icon-bullet-border-color: var(--#{$prefix}tertiary-color) !default;
+
+$sidebar-nav-group-bg: transparent !default;
+$sidebar-nav-group-border-width: 0 !default;
+$sidebar-nav-group-border-color: transparent !default;
+$sidebar-nav-group-border-radius: var(--#{$prefix}border-radius) !default;
+$sidebar-nav-group-transition: background .15s ease-in-out !default;
+$sidebar-nav-group-toggle-show-color: $sidebar-nav-link-color !default;
+
+$sidebar-nav-group-items-padding-y: 0 !default;
+$sidebar-nav-group-items-padding-x: 0 !default;
+$sidebar-nav-group-items-transition: height .15s ease !default;
+
+$sidebar-nav-group-indicator-color: var(--#{$prefix}tertiary-color) !default;
+$sidebar-nav-group-indicator-icon: url("data:image/svg+xml,
") !default;
+$sidebar-nav-group-indicator-hover-color: $sidebar-nav-link-hover-color !default;
+$sidebar-nav-group-indicator-hover-icon: $sidebar-nav-group-indicator-icon !default;
+$sidebar-nav-group-indicator-transition: transform .15s !default;
+// scss-docs-end sidebar-nav-variables
+
+// scss-docs-start sidebar-toggler
+$sidebar-toggler-width: .5rem !default;
+$sidebar-toggler-height: .5rem !default;
+$sidebar-toggler-padding-x: .25rem !default;
+$sidebar-toggler-padding-y: .25rem !default;
+$sidebar-toggler-bg: transparent !default;
+$sidebar-toggler-color: var(--#{$prefix}tertiary-color) !default;
+$sidebar-toggler-icon: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cg xmlns='http://www.w3.org/2000/svg' transform='matrix(-1 0 0 -1 512 512)'%3E%3Cpath fill='%23000' d='M472,16H40A24.028,24.028,0,0,0,16,40V200H48V48H464V464H48V304H16V472a24.028,24.028,0,0,0,24,24H472a24.028,24.028,0,0,0,24-24V40A24.028,24.028,0,0,0,472,16Z'/%3E%3Cpolygon fill='%23000' points='209.377 363.306 232.004 385.933 366.627 251.31 232.004 116.687 209.377 139.313 305.374 235.311 16 235.311 16 267.311 305.372 267.311 209.377 363.306'/%3E%3C/g%3E%3C/svg%3E") !default;
+$sidebar-toggler-focus-shadow: $focus-ring-box-shadow !default;
+$sidebar-toggler-hover-color: var(--#{$prefix}secondary-color) !default;
+$sidebar-toggler-focus-color: var(--#{$prefix}secondary-color) !default;
+$sidebar-toggler-transition: transform .15s !default;
+// scss-docs-end sidebar-toggler
+
+// Footer
+// scss-docs-start footer-variables
+$footer-min-height: 3rem !default;
+$footer-padding-y: $spacer * .5 !default;
+$footer-padding-x: $spacer !default;
+$footer-bg: var(--#{$prefix}tertiary-bg) !default;
+$footer-color: var(--#{$prefix}body-color) !default;
+$footer-border-width: var(--#{$prefix}border-width) !default;
+$footer-border-color: var(--#{$prefix}border-color) !default;
+// scss-docs-end footer-variables
+
+// Spinners
+
+// scss-docs-start spinner-variables
+$spinner-width: 2rem !default;
+$spinner-height: $spinner-width !default;
+$spinner-vertical-align: -.125em !default;
+$spinner-border-width: .25em !default;
+$spinner-animation-speed: .75s !default;
+
+$spinner-width-sm: 1rem !default;
+$spinner-height-sm: $spinner-width-sm !default;
+$spinner-border-width-sm: .2em !default;
+// scss-docs-end spinner-variables
+
+
+// Close
+
+// scss-docs-start close-variables
+$btn-close-width: 1em !default;
+$btn-close-height: $btn-close-width !default;
+$btn-close-padding-x: .25em !default;
+$btn-close-padding-y: $btn-close-padding-x !default;
+$btn-close-color: $black !default;
+$btn-close-bg: url("data:image/svg+xml,
") !default;
+$btn-close-focus-shadow: $focus-ring-box-shadow !default;
+$btn-close-opacity: .5 !default;
+$btn-close-hover-opacity: .75 !default;
+$btn-close-focus-opacity: 1 !default;
+$btn-close-disabled-opacity: .25 !default;
+$btn-close-filter: null !default;
+$btn-close-white-filter: invert(1) grayscale(100%) brightness(200%) !default; // Deprecated in v5.3.2
+// scss-docs-end close-variables
+
+
+// Offcanvas
+
+// scss-docs-start offcanvas-variables
+$offcanvas-padding-y: $modal-inner-padding !default;
+$offcanvas-padding-x: $modal-inner-padding !default;
+$offcanvas-horizontal-width: 400px !default;
+$offcanvas-vertical-height: 30vh !default;
+$offcanvas-transition-duration: .3s !default;
+$offcanvas-border-color: $modal-content-border-color !default;
+$offcanvas-border-width: $modal-content-border-width !default;
+$offcanvas-title-line-height: $modal-title-line-height !default;
+$offcanvas-bg-color: var(--#{$prefix}body-bg) !default;
+$offcanvas-color: var(--#{$prefix}body-color) !default;
+$offcanvas-box-shadow: $modal-content-box-shadow-xs !default;
+$offcanvas-backdrop-bg: $modal-backdrop-bg !default;
+$offcanvas-backdrop-opacity: $modal-backdrop-opacity !default;
+// scss-docs-end offcanvas-variables
+
+// Code
+
+$code-font-size: $small-font-size !default;
+$code-color: $pink !default;
+
+$kbd-padding-y: .1875rem !default;
+$kbd-padding-x: .375rem !default;
+$kbd-font-size: $code-font-size !default;
+$kbd-color: var(--#{$prefix}body-bg) !default;
+$kbd-bg: var(--#{$prefix}body-color) !default;
+$nested-kbd-font-weight: null !default; // Deprecated in v4.2.6, removing in v6
+
+$pre-color: null !default;
diff --git a/src/scss/scss/coreui-grid.rtl.scss b/src/scss/scss/coreui-grid.rtl.scss
new file mode 100644
index 000000000..b665e3199
--- /dev/null
+++ b/src/scss/scss/coreui-grid.rtl.scss
@@ -0,0 +1,4 @@
+@use "coreui-grid" with (
+ $enable-ltr: false,
+ $enable-rtl: true
+);
diff --git a/src/scss/scss/coreui-grid.scss b/src/scss/scss/coreui-grid.scss
new file mode 100644
index 000000000..40d3d7568
--- /dev/null
+++ b/src/scss/scss/coreui-grid.scss
@@ -0,0 +1,54 @@
+@forward "banner" with (
+ $file: "Grid"
+);
+
+@forward "variables";
+@forward "variables-dark";
+@forward "containers";
+@forward "grid" with (
+ $include-column-box-sizing: true !default,
+);
+@use "utilities" as *;
+@use "functions/maps" as *;
+
+// Only use the utilities we need
+// stylelint-disable-next-line scss/dollar-variable-default
+$utilities: map-get-multiple(
+ $utilities,
+ (
+ "display",
+ "order",
+ "flex",
+ "flex-direction",
+ "flex-grow",
+ "flex-shrink",
+ "flex-wrap",
+ "justify-content",
+ "align-items",
+ "align-content",
+ "align-self",
+ "margin",
+ "margin-x",
+ "margin-y",
+ "margin-top",
+ "margin-end",
+ "margin-bottom",
+ "margin-start",
+ "negative-margin",
+ "negative-margin-x",
+ "negative-margin-y",
+ "negative-margin-top",
+ "negative-margin-end",
+ "negative-margin-bottom",
+ "negative-margin-start",
+ "padding",
+ "padding-x",
+ "padding-y",
+ "padding-top",
+ "padding-end",
+ "padding-bottom",
+ "padding-start",
+ )
+);
+
+@use "utilities/api";
diff --git a/src/scss/scss/coreui-reboot.rtl.scss b/src/scss/scss/coreui-reboot.rtl.scss
new file mode 100644
index 000000000..fc65bed51
--- /dev/null
+++ b/src/scss/scss/coreui-reboot.rtl.scss
@@ -0,0 +1,4 @@
+@use "coreui-reboot" with (
+ $enable-ltr: false,
+ $enable-rtl: true
+);
diff --git a/src/scss/scss/coreui-reboot.scss b/src/scss/scss/coreui-reboot.scss
new file mode 100644
index 000000000..8d79f13fa
--- /dev/null
+++ b/src/scss/scss/coreui-reboot.scss
@@ -0,0 +1,9 @@
+@use "banner" with (
+ $file: "Reboot"
+);
+
+@forward "variables";
+@forward "variables-dark";
+
+@forward "root";
+@forward "reboot";
diff --git a/src/scss/scss/coreui-utilities.rtl.scss b/src/scss/scss/coreui-utilities.rtl.scss
new file mode 100644
index 000000000..9b5f86bbd
--- /dev/null
+++ b/src/scss/scss/coreui-utilities.rtl.scss
@@ -0,0 +1,4 @@
+@use "coreui-utilities" with (
+ $enable-ltr: false,
+ $enable-rtl: true
+);
diff --git a/src/scss/scss/coreui-utilities.scss b/src/scss/scss/coreui-utilities.scss
new file mode 100644
index 000000000..4f856864f
--- /dev/null
+++ b/src/scss/scss/coreui-utilities.scss
@@ -0,0 +1,16 @@
+@use "banner" with (
+ $file: "Utilities"
+);
+
+// Configuration
+@forward "variables";
+@forward "variables-dark";
+
+// Layout & components
+@forward "root";
+
+// Helpers
+@forward "helpers";
+
+// Utilities
+@forward "utilities/api";
diff --git a/src/scss/scss/coreui.rtl.scss b/src/scss/scss/coreui.rtl.scss
new file mode 100644
index 000000000..9b247df14
--- /dev/null
+++ b/src/scss/scss/coreui.rtl.scss
@@ -0,0 +1,4 @@
+@use "coreui" with (
+ $enable-ltr: false,
+ $enable-rtl: true
+);
diff --git a/src/scss/scss/coreui.scss b/src/scss/scss/coreui.scss
new file mode 100644
index 000000000..e4ff42f43
--- /dev/null
+++ b/src/scss/scss/coreui.scss
@@ -0,0 +1,55 @@
+@use "banner";
+
+// scss-docs-start import-stack
+// Configuration
+@forward "variables";
+@forward "variables-dark";
+@forward "functions";
+@forward "mixins";
+
+// Layout & components
+@forward "root";
+@forward "reboot";
+@forward "type";
+@forward "images";
+@forward "containers";
+@forward "grid";
+@forward "tables";
+@forward "forms";
+@forward "buttons";
+@forward "transitions";
+@forward "dropdown";
+@forward "button-group";
+@forward "nav";
+@forward "navbar";
+@forward "card";
+@forward "accordion";
+@forward "breadcrumb";
+@forward "pagination";
+@forward "badge";
+@forward "alert";
+@forward "progress";
+@forward "list-group";
+@forward "close";
+@forward "toasts";
+@forward "modal";
+@forward "tooltip";
+@forward "popover";
+@forward "carousel";
+@forward "spinners";
+@forward "offcanvas";
+@forward "placeholders";
+
+@forward "avatar";
+@forward "callout";
+@forward "footer";
+@forward "header";
+@forward "icon";
+@forward "sidebar";
+
+// Helpers
+@forward "helpers";
+
+// Utilities
+@forward "utilities/api";
+// scss-docs-end import-stack
diff --git a/src/scss/scss/forms/_floating-labels.import.scss b/src/scss/scss/forms/_floating-labels.import.scss
new file mode 100644
index 000000000..fe4b5f53e
--- /dev/null
+++ b/src/scss/scss/forms/_floating-labels.import.scss
@@ -0,0 +1 @@
+@forward "floating-labels";
diff --git a/src/scss/scss/forms/_floating-labels.scss b/src/scss/scss/forms/_floating-labels.scss
new file mode 100644
index 000000000..767dcee16
--- /dev/null
+++ b/src/scss/scss/forms/_floating-labels.scss
@@ -0,0 +1,101 @@
+@use "../mixins/border-radius" as *;
+@use "../mixins/transition" as *;
+@use "../variables" as *;
+
+.form-floating {
+ position: relative;
+
+ > .form-control,
+ > .form-control-plaintext,
+ > .form-select {
+ height: $form-floating-height;
+ min-height: $form-floating-height;
+ line-height: $form-floating-line-height;
+ }
+
+ > label {
+ position: absolute;
+ inset-inline-start: 0;
+ top: 0;
+ z-index: 2;
+ max-width: 100%;
+ height: 100%; // allow textareas
+ padding: $form-floating-padding-y $form-floating-padding-x;
+ overflow: hidden;
+ color: rgba(var(--#{$prefix}body-color-rgb), #{$form-floating-label-opacity});
+ text-align: start;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ pointer-events: none;
+ border: $input-border-width solid transparent; // Required for aligning label's text with the input as it affects inner box model
+ transform-origin: 0 0;
+ @include transition($form-floating-transition);
+ }
+
+ > .form-control,
+ > .form-control-plaintext {
+ padding: $form-floating-padding-y $form-floating-padding-x;
+
+ &::placeholder {
+ color: transparent;
+ }
+
+ &:focus,
+ &:not(:placeholder-shown) {
+ padding-top: $form-floating-input-padding-t;
+ padding-bottom: $form-floating-input-padding-b;
+ }
+ // Duplicated because `:-webkit-autofill` invalidates other selectors when grouped
+ &:-webkit-autofill {
+ padding-top: $form-floating-input-padding-t;
+ padding-bottom: $form-floating-input-padding-b;
+ }
+ }
+
+ > .form-select {
+ padding-inline-start: $form-floating-padding-x;
+ padding-top: $form-floating-input-padding-t;
+ padding-bottom: $form-floating-input-padding-b;
+ }
+
+ > .form-control:focus,
+ > .form-control:not(:placeholder-shown),
+ > .form-control-plaintext,
+ > .form-select {
+ ~ label {
+ transform: $form-floating-label-transform;
+ }
+ }
+ // Duplicated because `:-webkit-autofill` invalidates other selectors when grouped
+ > .form-control:-webkit-autofill {
+ ~ label {
+ transform: $form-floating-label-transform;
+ }
+ }
+ > textarea:focus,
+ > textarea:not(:placeholder-shown) {
+ ~ label::after {
+ position: absolute;
+ inset: $form-floating-padding-y ($form-floating-padding-x * .5);
+ z-index: -1;
+ height: $form-floating-label-height;
+ content: "";
+ background-color: $input-bg;
+ @include border-radius($input-border-radius);
+ }
+ }
+ > textarea:disabled ~ label::after {
+ background-color: $input-disabled-bg;
+ }
+
+ > .form-control-plaintext {
+ ~ label {
+ border-width: $input-border-width 0; // Required to properly position label text - as explained above
+ }
+ }
+
+ > :disabled ~ label,
+ > .form-control:disabled ~ label { // Required for `.form-control`s because of specificity
+ color: $form-floating-label-disabled-color;
+ }
+}
diff --git a/src/scss/scss/forms/_form-check.import.scss b/src/scss/scss/forms/_form-check.import.scss
new file mode 100644
index 000000000..bec28f4c2
--- /dev/null
+++ b/src/scss/scss/forms/_form-check.import.scss
@@ -0,0 +1 @@
+@forward "form-check";
diff --git a/src/scss/scss/forms/_form-check.scss b/src/scss/scss/forms/_form-check.scss
new file mode 100644
index 000000000..2771e0933
--- /dev/null
+++ b/src/scss/scss/forms/_form-check.scss
@@ -0,0 +1,214 @@
+@use "sass:map";
+@use "../functions/escape-svg" as *;
+@use "../mixins/border-radius" as *;
+@use "../mixins/color-mode" as *;
+@use "../mixins/transition" as *;
+@use "../variables" as *;
+@use "../variables-dark" as *;
+
+//
+// Check/radio
+//
+
+.form-check {
+ display: block;
+ min-height: $form-check-min-height;
+ padding-inline-start: $form-check-padding-start;
+ margin-bottom: $form-check-margin-bottom;
+
+ .form-check-input {
+ float: inline-start;
+ margin-inline-start: $form-check-padding-start * -1;
+ }
+}
+
+.form-check-reverse {
+ padding-inline: 0 $form-check-padding-start;
+ text-align: end;
+
+ .form-check-input {
+ float: inline-end;
+ margin-inline: 0 $form-check-padding-start * -1;
+ }
+}
+
+.form-check-input {
+ --#{$prefix}form-check-bg: #{$form-check-input-bg};
+
+ flex-shrink: 0;
+ width: $form-check-input-width;
+ height: $form-check-input-width;
+ margin-top: ($line-height-base - $form-check-input-width) * .5; // line-height minus check height
+ vertical-align: top;
+ appearance: none;
+ background-color: var(--#{$prefix}form-check-bg);
+ background-image: var(--#{$prefix}form-check-bg-image);
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: contain;
+ border: $form-check-input-border;
+ print-color-adjust: exact; // Keep themed appearance for print
+ @include transition($form-check-transition);
+
+ &[type="checkbox"] {
+ @include border-radius($form-check-input-border-radius);
+ }
+
+ &[type="radio"] {
+ // stylelint-disable-next-line property-disallowed-list
+ border-radius: $form-check-radio-border-radius;
+ }
+
+ &:active {
+ filter: $form-check-input-active-filter;
+ }
+
+ &:focus {
+ border-color: $form-check-input-focus-border;
+ outline: 0;
+ box-shadow: $form-check-input-focus-box-shadow;
+ }
+
+ &:checked {
+ background-color: var(--#{$prefix}form-check-input-checked-bg-color, $form-check-input-checked-bg-color);
+ border-color: var(--#{$prefix}form-check-input-checked-border-color, $form-check-input-checked-border-color);
+
+ &[type="checkbox"] {
+ @if $enable-gradients {
+ --#{$prefix}form-check-bg-image: #{escape-svg($form-check-input-checked-bg-image)}, var(--#{$prefix}gradient);
+ } @else {
+ --#{$prefix}form-check-bg-image: #{escape-svg($form-check-input-checked-bg-image)};
+ }
+ }
+
+ &[type="radio"] {
+ @if $enable-gradients {
+ --#{$prefix}form-check-bg-image: #{escape-svg($form-check-radio-checked-bg-image)}, var(--#{$prefix}gradient);
+ } @else {
+ --#{$prefix}form-check-bg-image: #{escape-svg($form-check-radio-checked-bg-image)};
+ }
+ }
+ }
+
+ &[type="checkbox"]:indeterminate {
+ background-color: $form-check-input-indeterminate-bg-color;
+ border-color: $form-check-input-indeterminate-border-color;
+
+ @if $enable-gradients {
+ --#{$prefix}form-check-bg-image: #{escape-svg($form-check-input-indeterminate-bg-image)}, var(--#{$prefix}gradient);
+ } @else {
+ --#{$prefix}form-check-bg-image: #{escape-svg($form-check-input-indeterminate-bg-image)};
+ }
+ }
+
+ &:disabled {
+ pointer-events: none;
+ filter: none;
+ opacity: $form-check-input-disabled-opacity;
+ }
+
+ // Use disabled attribute in addition of :disabled pseudo-class
+ // See: https://github.com/twbs/bootstrap/issues/28247
+ &[disabled],
+ &:disabled {
+ ~ .form-check-label {
+ cursor: default;
+ opacity: $form-check-label-disabled-opacity;
+ }
+ }
+}
+
+.form-check-label {
+ color: var(--#{$prefix}form-check-label-color, $form-check-label-color);
+ cursor: $form-check-label-cursor;
+}
+
+//
+// Switch
+//
+
+.form-switch {
+ padding-inline-start: $form-switch-padding-start;
+
+ .form-check-input {
+ --#{$prefix}form-switch-bg: #{escape-svg($form-switch-bg-image)};
+
+ width: $form-switch-width;
+ margin-inline-start: $form-switch-padding-start * -1;
+ background-image: var(--#{$prefix}form-switch-bg);
+ background-position: left center;
+ @include border-radius($form-switch-border-radius, 0);
+ @include transition($form-switch-transition);
+
+ &:focus {
+ --#{$prefix}form-switch-bg: #{escape-svg($form-switch-focus-bg-image)};
+ }
+
+ &:checked {
+ background-position: $form-switch-checked-bg-position;
+
+ @if $enable-gradients {
+ --#{$prefix}form-switch-bg: #{escape-svg($form-switch-checked-bg-image)}, var(--#{$prefix}gradient);
+ } @else {
+ --#{$prefix}form-switch-bg: #{escape-svg($form-switch-checked-bg-image)};
+ }
+ }
+ }
+
+ &.form-check-reverse {
+ padding-inline: 0 $form-switch-padding-start;
+
+ .form-check-input {
+ margin-inline: 0 $form-switch-padding-start * -1;
+ }
+ }
+}
+
+@each $size, $map in $form-switch-widths {
+ $width: map.get($map, "width");
+ $height: map.get($map, "height");
+
+ .form-switch-#{$size} {
+ min-height: $height;
+ padding-inline-start: $width + .5em;
+
+ .form-check-input {
+ width: $width;
+ height: $height;
+ margin-inline-start: ($width + .5em) * -1;
+ }
+
+ .form-check-label {
+ // stylelint-disable-next-line function-disallowed-list
+ padding-top: calc((#{$height} - #{$font-size-base}) / 2);
+ }
+ }
+}
+
+.form-check-inline {
+ display: inline-block;
+ margin-inline-end: $form-check-inline-margin-end;
+}
+
+.btn-check {
+ position: absolute;
+ clip: rect(0, 0, 0, 0);
+ pointer-events: none;
+
+ &[disabled],
+ &:disabled {
+ + .btn {
+ pointer-events: none;
+ filter: none;
+ opacity: $form-check-btn-check-disabled-opacity;
+ }
+ }
+}
+
+@if $enable-dark-mode {
+ @include color-mode(dark) {
+ .form-switch .form-check-input:not(:checked):not(:focus) {
+ --#{$prefix}form-switch-bg: #{escape-svg($form-switch-bg-image-dark)};
+ }
+ }
+}
diff --git a/src/scss/scss/forms/_form-control.import.scss b/src/scss/scss/forms/_form-control.import.scss
new file mode 100644
index 000000000..814ff4d69
--- /dev/null
+++ b/src/scss/scss/forms/_form-control.import.scss
@@ -0,0 +1 @@
+@forward "form-control";
diff --git a/src/scss/scss/forms/_form-control.scss b/src/scss/scss/forms/_form-control.scss
new file mode 100644
index 000000000..731f05fc4
--- /dev/null
+++ b/src/scss/scss/forms/_form-control.scss
@@ -0,0 +1,222 @@
+@use "sass:math";
+@use "../mixins/border-radius" as *;
+@use "../mixins/box-shadow" as *;
+@use "../mixins/gradients" as *;
+@use "../mixins/transition" as *;
+@use "../vendor/rfs" as *;
+@use "../variables" as *;
+
+//
+// General form controls (plus a few specific high-level interventions)
+//
+
+.form-control {
+ display: block;
+ width: 100%;
+ padding: $input-padding-y $input-padding-x;
+ font-family: $input-font-family;
+ @include font-size($input-font-size);
+ font-weight: $input-font-weight;
+ line-height: $input-line-height;
+ color: $input-color;
+ appearance: none; // Fix appearance for date inputs in Safari
+ background-color: $input-bg;
+ background-clip: padding-box;
+ border: $input-border-width solid $input-border-color;
+
+ // Note: This has no effect on
s in some browsers, due to the limited stylability of ``s in CSS.
+ @include border-radius($input-border-radius, 0);
+
+ @include box-shadow($input-box-shadow);
+ @include transition($input-transition);
+
+ &[type="file"] {
+ overflow: hidden; // prevent pseudo element button overlap
+
+ &:not(:disabled):not([readonly]) {
+ cursor: pointer;
+ }
+ }
+
+ // Customize the `:focus` state to imitate native WebKit styles.
+ &:focus {
+ color: $input-focus-color;
+ background-color: $input-focus-bg;
+ border-color: $input-focus-border-color;
+ outline: 0;
+ @if $enable-shadows {
+ @include box-shadow($input-box-shadow, $input-focus-box-shadow);
+ } @else {
+ // Avoid using mixin so we can pass custom focus shadow properly
+ box-shadow: $input-focus-box-shadow;
+ }
+ }
+
+ &::-webkit-date-and-time-value {
+ // On Android Chrome, form-control's "width: 100%" makes the input width too small
+ // Tested under Android 11 / Chrome 89, Android 12 / Chrome 100, Android 13 / Chrome 109
+ //
+ // On iOS Safari, form-control's "appearance: none" + "width: 100%" makes the input width too small
+ // Tested under iOS 16.2 / Safari 16.2
+ min-width: 85px; // Seems to be a good minimum safe width
+
+ // Add some height to date inputs on iOS
+ // https://github.com/twbs/bootstrap/issues/23307
+ // TODO: we can remove this workaround once https://bugs.webkit.org/show_bug.cgi?id=198959 is resolved
+ // Multiply line-height by 1em if it has no unit
+ height: if(math.unit($input-line-height) == "", $input-line-height * 1em, $input-line-height);
+
+ // Android Chrome type="date" is taller than the other inputs
+ // because of "margin: 1px 24px 1px 4px" inside the shadow DOM
+ // Tested under Android 11 / Chrome 89, Android 12 / Chrome 100, Android 13 / Chrome 109
+ margin: 0;
+ }
+
+ // Prevent excessive date input height in Webkit
+ // https://github.com/twbs/bootstrap/issues/34433
+ &::-webkit-datetime-edit {
+ display: block;
+ padding: 0;
+ }
+
+ // Placeholder
+ &::placeholder {
+ color: var(--#{$prefix}input-placeholder-color, $input-placeholder-color);
+ // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526.
+ opacity: 1;
+ }
+
+ // Disabled inputs
+ //
+ // HTML5 says that controls under a fieldset > legend:first-child won't be
+ // disabled if the fieldset is disabled. Due to implementation difficulty, we
+ // don't honor that edge case; we style them as disabled anyway.
+ &:disabled {
+ color: $input-disabled-color;
+ background-color: $input-disabled-bg;
+ border-color: $input-disabled-border-color;
+ // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655.
+ opacity: 1;
+ }
+
+ // File input buttons theming
+ &::file-selector-button {
+ padding: $input-padding-y $input-padding-x;
+ margin: (-$input-padding-y) (-$input-padding-x);
+ margin-inline-end: $input-padding-x;
+ color: $form-file-button-color;
+ @include gradient-bg($form-file-button-bg);
+ pointer-events: none;
+ border-color: inherit;
+ border-style: solid;
+ border-width: 0;
+ border-inline-end-width: $input-border-width;
+ border-radius: 0; // stylelint-disable-line property-disallowed-list
+ @include transition($btn-transition);
+ }
+
+ &:hover:not(:disabled):not([readonly])::file-selector-button {
+ background-color: $form-file-button-hover-bg;
+ }
+}
+
+// Readonly controls as plain text
+//
+// Apply class to a readonly input to make it appear like regular plain
+// text (without any border, background color, focus indicator)
+
+.form-control-plaintext {
+ display: block;
+ width: 100%;
+ padding: $input-padding-y 0;
+ margin-bottom: 0; // match inputs if this class comes on inputs with default margins
+ line-height: $input-line-height;
+ color: $input-plaintext-color;
+ background-color: transparent;
+ border: solid transparent;
+ border-width: $input-border-width 0;
+
+ &:focus {
+ outline: 0;
+ }
+
+ &.form-control-sm,
+ &.form-control-lg {
+ padding-right: 0;
+ padding-left: 0;
+ }
+}
+
+// Form control sizing
+//
+// Build on `.form-control` with modifier classes to decrease or increase the
+// height and font-size of form controls.
+//
+// Repeated in `_input_group.scss` to avoid Sass extend issues.
+
+.form-control-sm {
+ min-height: $input-height-sm;
+ padding: $input-padding-y-sm $input-padding-x-sm;
+ @include font-size($input-font-size-sm);
+ @include border-radius($input-border-radius-sm);
+
+ &::file-selector-button {
+ padding: $input-padding-y-sm $input-padding-x-sm;
+ margin: (-$input-padding-y-sm) (-$input-padding-x-sm);
+ margin-inline-end: $input-padding-x-sm;
+ }
+}
+
+.form-control-lg {
+ min-height: $input-height-lg;
+ padding: $input-padding-y-lg $input-padding-x-lg;
+ @include font-size($input-font-size-lg);
+ @include border-radius($input-border-radius-lg);
+
+ &::file-selector-button {
+ padding: $input-padding-y-lg $input-padding-x-lg;
+ margin: (-$input-padding-y-lg) (-$input-padding-x-lg);
+ margin-inline-end: $input-padding-x-lg;
+ }
+}
+
+// Make sure textareas don't shrink too much when resized
+// https://github.com/twbs/bootstrap/pull/29124
+// stylelint-disable selector-no-qualifying-type
+textarea {
+ &.form-control {
+ min-height: $input-height;
+ }
+
+ &.form-control-sm {
+ min-height: $input-height-sm;
+ }
+
+ &.form-control-lg {
+ min-height: $input-height-lg;
+ }
+}
+// stylelint-enable selector-no-qualifying-type
+
+.form-control-color {
+ width: $form-color-width;
+ height: $input-height;
+ padding: $input-padding-y;
+
+ &:not(:disabled):not([readonly]) {
+ cursor: pointer;
+ }
+
+ &::-moz-color-swatch {
+ border: 0 !important; // stylelint-disable-line declaration-no-important
+ @include border-radius($input-border-radius);
+ }
+
+ &::-webkit-color-swatch {
+ border: 0 !important; // stylelint-disable-line declaration-no-important
+ @include border-radius($input-border-radius);
+ }
+
+ &.form-control-sm { height: $input-height-sm; }
+ &.form-control-lg { height: $input-height-lg; }
+}
diff --git a/src/scss/scss/forms/_form-range.import.scss b/src/scss/scss/forms/_form-range.import.scss
new file mode 100644
index 000000000..d89fa67a4
--- /dev/null
+++ b/src/scss/scss/forms/_form-range.import.scss
@@ -0,0 +1 @@
+@forward "form-range";
diff --git a/src/scss/scss/forms/_form-range.scss b/src/scss/scss/forms/_form-range.scss
new file mode 100644
index 000000000..d26776823
--- /dev/null
+++ b/src/scss/scss/forms/_form-range.scss
@@ -0,0 +1,98 @@
+@use "../functions/math" as *;
+@use "../mixins/border-radius" as *;
+@use "../mixins/box-shadow" as *;
+@use "../mixins/gradients" as *;
+@use "../mixins/transition" as *;
+@use "../variables" as *;
+
+// Range
+//
+// Style range inputs the same across browsers. Vendor-specific rules for pseudo
+// elements cannot be mixed. As such, there are no shared styles for focus or
+// active states on prefixed selectors.
+
+.form-range {
+ width: 100%;
+ height: add($form-range-thumb-height, $form-range-thumb-focus-box-shadow-width * 2);
+ padding: 0; // Need to reset padding
+ appearance: none;
+ background-color: transparent;
+
+ &:focus {
+ outline: 0;
+
+ // Pseudo-elements must be split across multiple rulesets to have an effect.
+ // No box-shadow() mixin for focus accessibility.
+ &::-webkit-slider-thumb { box-shadow: $form-range-thumb-focus-box-shadow; }
+ &::-moz-range-thumb { box-shadow: $form-range-thumb-focus-box-shadow; }
+ }
+
+ &::-moz-focus-outer {
+ border: 0;
+ }
+
+ &::-webkit-slider-thumb {
+ width: $form-range-thumb-width;
+ height: $form-range-thumb-height;
+ margin-top: ($form-range-track-height - $form-range-thumb-height) * .5; // Webkit specific
+ appearance: none;
+ @include gradient-bg($form-range-thumb-bg);
+ border: $form-range-thumb-border;
+ @include border-radius($form-range-thumb-border-radius);
+ @include box-shadow($form-range-thumb-box-shadow);
+ @include transition($form-range-thumb-transition);
+
+ &:active {
+ @include gradient-bg($form-range-thumb-active-bg);
+ }
+ }
+
+ &::-webkit-slider-runnable-track {
+ width: $form-range-track-width;
+ height: $form-range-track-height;
+ color: transparent; // Why?
+ cursor: $form-range-track-cursor;
+ background-color: $form-range-track-bg;
+ border-color: transparent;
+ @include border-radius($form-range-track-border-radius);
+ @include box-shadow($form-range-track-box-shadow);
+ }
+
+ &::-moz-range-thumb {
+ width: $form-range-thumb-width;
+ height: $form-range-thumb-height;
+ appearance: none;
+ @include gradient-bg($form-range-thumb-bg);
+ border: $form-range-thumb-border;
+ @include border-radius($form-range-thumb-border-radius);
+ @include box-shadow($form-range-thumb-box-shadow);
+ @include transition($form-range-thumb-transition);
+
+ &:active {
+ @include gradient-bg($form-range-thumb-active-bg);
+ }
+ }
+
+ &::-moz-range-track {
+ width: $form-range-track-width;
+ height: $form-range-track-height;
+ color: transparent;
+ cursor: $form-range-track-cursor;
+ background-color: $form-range-track-bg;
+ border-color: transparent; // Firefox specific?
+ @include border-radius($form-range-track-border-radius);
+ @include box-shadow($form-range-track-box-shadow);
+ }
+
+ &:disabled {
+ pointer-events: none;
+
+ &::-webkit-slider-thumb {
+ background-color: $form-range-thumb-disabled-bg;
+ }
+
+ &::-moz-range-thumb {
+ background-color: $form-range-thumb-disabled-bg;
+ }
+ }
+}
diff --git a/src/scss/scss/forms/_form-select.import.scss b/src/scss/scss/forms/_form-select.import.scss
new file mode 100644
index 000000000..f713dc8cf
--- /dev/null
+++ b/src/scss/scss/forms/_form-select.import.scss
@@ -0,0 +1 @@
+@forward "form-select";
diff --git a/src/scss/scss/forms/_form-select.scss b/src/scss/scss/forms/_form-select.scss
new file mode 100644
index 000000000..8870aed5b
--- /dev/null
+++ b/src/scss/scss/forms/_form-select.scss
@@ -0,0 +1,93 @@
+@use "../functions/escape-svg" as *;
+@use "../mixins/border-radius" as *;
+@use "../mixins/box-shadow" as *;
+@use "../mixins/color-mode" as *;
+@use "../mixins/ltr-rtl" as *;
+@use "../mixins/transition" as *;
+@use "../vendor/rfs" as *;
+@use "../variables" as *;
+@use "../variables-dark" as *;
+
+// Select
+//
+// Replaces the browser default select with a custom one, mostly pulled from
+// https://primer.github.io/.
+
+.form-select {
+ --#{$prefix}form-select-bg-img: #{escape-svg($form-select-indicator)};
+
+ display: block;
+ width: 100%;
+ padding-inline-start: $form-select-padding-x;
+ padding-inline-end: $form-select-indicator-padding;
+ padding-top: $form-select-padding-y;
+ padding-bottom: $form-select-padding-y;
+ font-family: $form-select-font-family;
+ @include font-size($form-select-font-size);
+ font-weight: $form-select-font-weight;
+ line-height: $form-select-line-height;
+ color: $form-select-color;
+ appearance: none;
+ background-color: $form-select-bg;
+ background-image: var(--#{$prefix}form-select-bg-img), var(--#{$prefix}form-select-bg-icon, none);
+ background-repeat: no-repeat;
+ @include ltr-rtl-value-only("background-position", $form-select-bg-position);
+ background-size: $form-select-bg-size;
+ border: $form-select-border-width solid $form-select-border-color;
+ @include border-radius($form-select-border-radius, 0);
+ @include box-shadow($form-select-box-shadow);
+ @include transition($form-select-transition);
+
+ &:focus {
+ border-color: $form-select-focus-border-color;
+ outline: 0;
+ @if $enable-shadows {
+ @include box-shadow($form-select-box-shadow, $form-select-focus-box-shadow);
+ } @else {
+ // Avoid using mixin so we can pass custom focus shadow properly
+ box-shadow: $form-select-focus-box-shadow;
+ }
+ }
+
+ &[multiple],
+ &[size]:not([size="1"]) {
+ padding-inline-end: $form-select-padding-x;
+ background-image: none;
+ }
+
+ &:disabled {
+ color: $form-select-disabled-color;
+ background-color: $form-select-disabled-bg;
+ border-color: $form-select-disabled-border-color;
+ }
+
+ // Remove outline from select box in FF
+ &:-moz-focusring {
+ color: transparent;
+ text-shadow: 0 0 0 $form-select-color;
+ }
+}
+
+.form-select-sm {
+ padding-inline-start: $form-select-padding-x-sm;
+ padding-top: $form-select-padding-y-sm;
+ padding-bottom: $form-select-padding-y-sm;
+ @include font-size($form-select-font-size-sm);
+ @include border-radius($form-select-border-radius-sm);
+}
+
+.form-select-lg {
+ padding-inline-start: $form-select-padding-x-lg;
+ padding-top: $form-select-padding-y-lg;
+ padding-bottom: $form-select-padding-y-lg;
+ @include font-size($form-select-font-size-lg);
+ @include border-radius($form-select-border-radius-lg);
+}
+
+@if $enable-dark-mode {
+ @include color-mode(dark) {
+ .form-select {
+ --#{$prefix}form-select-bg-img: #{escape-svg($form-select-indicator-dark)};
+ }
+ }
+}
diff --git a/src/scss/scss/forms/_form-text.import.scss b/src/scss/scss/forms/_form-text.import.scss
new file mode 100644
index 000000000..689cb7bdd
--- /dev/null
+++ b/src/scss/scss/forms/_form-text.import.scss
@@ -0,0 +1 @@
+@forward "form-text";
diff --git a/src/scss/scss/forms/_form-text.scss b/src/scss/scss/forms/_form-text.scss
new file mode 100644
index 000000000..2d0423b60
--- /dev/null
+++ b/src/scss/scss/forms/_form-text.scss
@@ -0,0 +1,14 @@
+@use "../vendor/rfs" as *;
+@use "../variables" as *;
+
+//
+// Form text
+//
+
+.form-text {
+ margin-top: $form-text-margin-top;
+ @include font-size($form-text-font-size);
+ font-style: $form-text-font-style;
+ font-weight: $form-text-font-weight;
+ color: $form-text-color;
+}
diff --git a/src/scss/scss/forms/_input-group.import.scss b/src/scss/scss/forms/_input-group.import.scss
new file mode 100644
index 000000000..a74012c5e
--- /dev/null
+++ b/src/scss/scss/forms/_input-group.import.scss
@@ -0,0 +1 @@
+@forward "input-group";
diff --git a/src/scss/scss/forms/_input-group.scss b/src/scss/scss/forms/_input-group.scss
new file mode 100644
index 000000000..f33c3e19c
--- /dev/null
+++ b/src/scss/scss/forms/_input-group.scss
@@ -0,0 +1,138 @@
+@use "sass:map";
+@use "sass:string";
+@use "../mixins/border-radius" as *;
+@use "../vendor/rfs" as *;
+@use "../variables" as *;
+
+//
+// Base styles
+//
+
+.input-group {
+ position: relative;
+ display: flex;
+ flex-wrap: wrap; // For form validation feedback
+ align-items: stretch;
+ width: 100%;
+
+ > .form-control,
+ > .form-select,
+ > .form-floating {
+ position: relative; // For focus state's z-index
+ flex: 1 1 auto;
+ width: 1%;
+ min-width: 0; // https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size
+ }
+
+ // Bring the "active" form control to the top of surrounding elements
+ > .form-control:focus,
+ > .form-select:focus,
+ > .form-floating:focus-within {
+ z-index: 5;
+ }
+
+ // Ensure buttons are always above inputs for more visually pleasing borders.
+ // This isn't needed for `.input-group-text` since it shares the same border-color
+ // as our inputs.
+ .btn {
+ position: relative;
+ z-index: 2;
+
+ &:focus {
+ z-index: 5;
+ }
+ }
+}
+
+
+// Textual addons
+//
+// Serves as a catch-all element for any text or radio/checkbox input you wish
+// to prepend or append to an input.
+
+.input-group-text {
+ display: flex;
+ align-items: center;
+ padding: $input-group-addon-padding-y $input-group-addon-padding-x;
+ @include font-size($input-font-size); // Match inputs
+ font-weight: $input-group-addon-font-weight;
+ line-height: $input-line-height;
+ color: var(--#{$prefix}input-group-addon-color, $input-group-addon-color);
+ text-align: center;
+ white-space: nowrap;
+ background-color: var(--#{$prefix}input-group-addon-bg, $input-group-addon-bg);
+ border: $input-border-width solid var(--#{$prefix}input-group-addon-border-color, $input-group-addon-border-color);
+ @include border-radius($input-border-radius);
+}
+
+
+// Sizing
+//
+// Remix the default form control sizing classes into new ones for easier
+// manipulation.
+
+.input-group-lg > .form-control,
+.input-group-lg > .form-select,
+.input-group-lg > .input-group-text,
+.input-group-lg > .btn {
+ padding: $input-padding-y-lg $input-padding-x-lg;
+ @include font-size($input-font-size-lg);
+ @include border-radius($input-border-radius-lg);
+}
+
+.input-group-sm > .form-control,
+.input-group-sm > .form-select,
+.input-group-sm > .input-group-text,
+.input-group-sm > .btn {
+ padding: $input-padding-y-sm $input-padding-x-sm;
+ @include font-size($input-font-size-sm);
+ @include border-radius($input-border-radius-sm);
+}
+
+.input-group-lg > .form-select,
+.input-group-sm > .form-select {
+ padding-inline-end: $form-select-padding-x + $form-select-indicator-padding;
+}
+
+
+// Rounded corners
+//
+// These rulesets must come after the sizing ones to properly override sm and lg
+// border-radius values when extending. They're more specific than we'd like
+// with the `.input-group >` part, but without it, we cannot override the sizing.
+
+// stylelint-disable-next-line no-duplicate-selectors
+.input-group {
+ &:not(.has-validation) {
+ > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),
+ > .dropdown-toggle:nth-last-child(n + 3),
+ > .form-floating:not(:last-child) > .form-control,
+ > .form-floating:not(:last-child) > .form-select {
+ @include border-end-radius(0);
+ }
+ }
+
+ &.has-validation {
+ > :nth-last-child(n + 3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),
+ > .dropdown-toggle:nth-last-child(n + 4),
+ > .form-floating:nth-last-child(n + 3) > .form-control,
+ > .form-floating:nth-last-child(n + 3) > .form-select {
+ @include border-end-radius(0);
+ }
+ }
+
+ $validation-messages: "";
+ @each $state in map.keys($form-validation-states) {
+ $validation-messages: $validation-messages + ":not(." + string.unquote($state) + "-tooltip)" + ":not(." + string.unquote($state) + "-feedback)";
+ }
+
+ > :not(:first-child):not(.dropdown-menu)#{$validation-messages} {
+ margin-inline-start: calc(-1 * #{$input-border-width}); // stylelint-disable-line function-disallowed-list
+ @include border-start-radius(0);
+ }
+
+ > .form-floating:not(:first-child) > .form-control,
+ > .form-floating:not(:first-child) > .form-select {
+ @include border-start-radius(0);
+ }
+}
diff --git a/src/scss/scss/forms/_labels.import.scss b/src/scss/scss/forms/_labels.import.scss
new file mode 100644
index 000000000..5f543d274
--- /dev/null
+++ b/src/scss/scss/forms/_labels.import.scss
@@ -0,0 +1 @@
+@forward "labels";
diff --git a/src/scss/scss/forms/_labels.scss b/src/scss/scss/forms/_labels.scss
new file mode 100644
index 000000000..314717b7c
--- /dev/null
+++ b/src/scss/scss/forms/_labels.scss
@@ -0,0 +1,40 @@
+@use "../functions/math" as *;
+@use "../vendor/rfs" as *;
+@use "../variables" as *;
+
+//
+// Labels
+//
+
+.form-label {
+ margin-bottom: $form-label-margin-bottom;
+ @include font-size($form-label-font-size);
+ font-style: $form-label-font-style;
+ font-weight: $form-label-font-weight;
+ color: $form-label-color;
+}
+
+// For use with horizontal and inline forms, when you need the label (or legend)
+// text to align with the form controls.
+.col-form-label {
+ padding-top: add($input-padding-y, $input-border-width);
+ padding-bottom: add($input-padding-y, $input-border-width);
+ margin-bottom: 0; // Override the `` default
+ @include font-size(inherit); // Override the `` default
+ font-style: $form-label-font-style;
+ font-weight: $form-label-font-weight;
+ line-height: $input-line-height;
+ color: $form-label-color;
+}
+
+.col-form-label-lg {
+ padding-top: add($input-padding-y-lg, $input-border-width);
+ padding-bottom: add($input-padding-y-lg, $input-border-width);
+ @include font-size($input-font-size-lg);
+}
+
+.col-form-label-sm {
+ padding-top: add($input-padding-y-sm, $input-border-width);
+ padding-bottom: add($input-padding-y-sm, $input-border-width);
+ @include font-size($input-font-size-sm);
+}
diff --git a/src/scss/scss/forms/_validation.import.scss b/src/scss/scss/forms/_validation.import.scss
new file mode 100644
index 000000000..916c78803
--- /dev/null
+++ b/src/scss/scss/forms/_validation.import.scss
@@ -0,0 +1 @@
+@forward "validation";
diff --git a/src/scss/scss/forms/_validation.scss b/src/scss/scss/forms/_validation.scss
new file mode 100644
index 000000000..47a3edb68
--- /dev/null
+++ b/src/scss/scss/forms/_validation.scss
@@ -0,0 +1,15 @@
+@use "../mixins/forms" as *;
+@use "../variables" as *;
+
+// Form validation
+//
+// Provide feedback to users when form field values are valid or invalid. Works
+// primarily for client-side validation via scoped `:invalid` and `:valid`
+// pseudo-classes but also includes `.is-invalid` and `.is-valid` classes for
+// server-side validation.
+
+// scss-docs-start form-validation-states-loop
+@each $state, $data in $form-validation-states {
+ @include form-validation-state($state, $data...);
+}
+// scss-docs-end form-validation-states-loop
diff --git a/src/scss/scss/functions/_assert-ascending.scss b/src/scss/scss/functions/_assert-ascending.scss
new file mode 100644
index 000000000..ff6cf38ca
--- /dev/null
+++ b/src/scss/scss/functions/_assert-ascending.scss
@@ -0,0 +1,19 @@
+@use "sass:math";
+
+// Ascending
+// Used to evaluate Sass maps like our grid breakpoints.
+@mixin assert-ascending($map, $map-name) {
+ $prev-key: null;
+ $prev-num: null;
+ @each $key, $num in $map {
+ @if $prev-num == null or math.unit($num) == "%" or math.unit($prev-num) == "%" {
+ // Do nothing
+ } @else if not math.compatible($prev-num, $num) {
+ @warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !";
+ } @else if $prev-num >= $num {
+ @warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !";
+ }
+ $prev-key: $key;
+ $prev-num: $num;
+ }
+}
diff --git a/src/scss/scss/functions/_assert-starts-at-zero.scss b/src/scss/scss/functions/_assert-starts-at-zero.scss
new file mode 100644
index 000000000..6585d888e
--- /dev/null
+++ b/src/scss/scss/functions/_assert-starts-at-zero.scss
@@ -0,0 +1,14 @@
+@use "sass:list";
+@use "sass:map";
+
+// Starts at zero
+// Used to ensure the min-width of the lowest breakpoint starts at 0.
+@mixin assert-starts-at-zero($map, $map-name: "$grid-breakpoints") {
+ @if list.length($map) > 0 {
+ $values: map.values($map);
+ $first-value: list.nth($values, 1);
+ @if $first-value != 0 {
+ @warn "First breakpoint in #{$map-name} must start at 0, but starts at #{$first-value}.";
+ }
+ }
+}
diff --git a/src/scss/scss/functions/_color-contrast-variables.scss b/src/scss/scss/functions/_color-contrast-variables.scss
new file mode 100644
index 000000000..3270627d6
--- /dev/null
+++ b/src/scss/scss/functions/_color-contrast-variables.scss
@@ -0,0 +1,23 @@
+@use "contrast-ratio" as *;
+
+// We use this function only in variables.scss because recursive imports are not allowed in Sass anymore.
+
+@function color-contrast-variables($background, $color-contrast-dark, $color-contrast-light, $white, $black, $min-contrast-ratio) {
+ $foregrounds: $color-contrast-light, $color-contrast-dark, $white, $black;
+ $max-ratio: 0;
+ $max-ratio-color: null;
+
+ @each $color in $foregrounds {
+ $contrast-ratio: contrast-ratio($background, $color);
+ @if $contrast-ratio > $min-contrast-ratio {
+ @return $color;
+ } @else if $contrast-ratio > $max-ratio {
+ $max-ratio: $contrast-ratio;
+ $max-ratio-color: $color;
+ }
+ }
+
+ @warn "Found no color leading to #{$min-contrast-ratio}:1 contrast ratio against #{$background}...";
+
+ @return $max-ratio-color;
+}
diff --git a/src/scss/scss/functions/_color-contrast.scss b/src/scss/scss/functions/_color-contrast.scss
new file mode 100644
index 000000000..80f13c66b
--- /dev/null
+++ b/src/scss/scss/functions/_color-contrast.scss
@@ -0,0 +1,27 @@
+@use "contrast-ratio" as *;
+@use "../variables" as *;
+
+// Color contrast
+// See https://github.com/twbs/bootstrap/pull/30168
+
+// scss-docs-start color-contrast-function
+@function color-contrast($background, $color-contrast-dark: $color-contrast-dark, $color-contrast-light: $color-contrast-light, $min-contrast-ratio: $min-contrast-ratio) {
+ $foregrounds: $color-contrast-light, $color-contrast-dark, $white, $black;
+ $max-ratio: 0;
+ $max-ratio-color: null;
+
+ @each $color in $foregrounds {
+ $contrast-ratio: contrast-ratio($background, $color);
+ @if $contrast-ratio > $min-contrast-ratio {
+ @return $color;
+ } @else if $contrast-ratio > $max-ratio {
+ $max-ratio: $contrast-ratio;
+ $max-ratio-color: $color;
+ }
+ }
+
+ @warn "Found no color leading to #{$min-contrast-ratio}:1 contrast ratio against #{$background}...";
+
+ @return $max-ratio-color;
+}
+// scss-docs-end color-contrast-function
diff --git a/src/scss/scss/functions/_color.scss b/src/scss/scss/functions/_color.scss
new file mode 100644
index 000000000..98939419d
--- /dev/null
+++ b/src/scss/scss/functions/_color.scss
@@ -0,0 +1,18 @@
+@use "sass:color";
+
+// scss-docs-start color-functions
+// Tint a color: mix a color with white
+@function tint-color($color, $weight) {
+ @return color.mix(white, $color, $weight);
+}
+
+// Shade a color: mix a color with black
+@function shade-color($color, $weight) {
+ @return color.mix(black, $color, $weight);
+}
+
+// Shade the color if the weight is positive, else tint it
+@function shift-color($color, $weight) {
+ @return if($weight > 0, shade-color($color, $weight), tint-color($color, -$weight));
+}
+// scss-docs-end color-functions
diff --git a/src/scss/scss/functions/_contrast-ratio.scss b/src/scss/scss/functions/_contrast-ratio.scss
new file mode 100644
index 000000000..227d414cf
--- /dev/null
+++ b/src/scss/scss/functions/_contrast-ratio.scss
@@ -0,0 +1,35 @@
+@use "sass:color";
+@use "sass:map";
+@use "sass:math";
+@use "math" as *;
+
+@function contrast-ratio($background, $foreground) {
+ $l1: luminance($background);
+ $l2: luminance(opaque($background, $foreground));
+
+ @return if($l1 > $l2, divide($l1 + .05, $l2 + .05), divide($l2 + .05, $l1 + .05));
+}
+
+// Return WCAG2.2 relative luminance
+// See https://www.w3.org/TR/WCAG/#dfn-relative-luminance
+// See https://www.w3.org/TR/WCAG/#dfn-contrast-ratio
+@function luminance($color) {
+ $rgb: (
+ "r": color.channel($color, "red", $space: rgb), // stylelint-disable-line scss/at-function-named-arguments
+ "g": color.channel($color, "green", $space: rgb), // stylelint-disable-line scss/at-function-named-arguments
+ "b": color.channel($color, "blue", $space: rgb) // stylelint-disable-line scss/at-function-named-arguments
+ );
+
+ @each $name, $value in $rgb {
+ $value: if(divide($value, 255) < .04045, divide(divide($value, 255), 12.92), math.pow(divide((divide(math.round($value) + 1, 255) + .055), 1.055), 2.4));
+ $rgb: map.merge($rgb, ($name: $value));
+ }
+
+ @return (map.get($rgb, "r") * .2126) + (map.get($rgb, "g") * .7152) + (map.get($rgb, "b") * .0722);
+}
+
+// Return opaque color
+// opaque(#fff, rgba(0, 0, 0, .5)) => #808080
+@function opaque($background, $foreground) {
+ @return color.mix(rgba($foreground, 1), $background, color.opacity($foreground) * 100%);
+}
diff --git a/src/scss/scss/functions/_escape-svg.scss b/src/scss/scss/functions/_escape-svg.scss
new file mode 100644
index 000000000..020e389ac
--- /dev/null
+++ b/src/scss/scss/functions/_escape-svg.scss
@@ -0,0 +1,22 @@
+@use "sass:string";
+@use "str-replace" as *;
+@use "../variables" as *;
+
+// See https://codepen.io/kevinweber/pen/dXWoRw
+//
+// Requires the use of quotes around data URIs.
+
+@function escape-svg($string) {
+ @if string.index($string, "data:image/svg+xml") {
+ @each $char, $encoded in $escaped-characters {
+ // Do not escape the url brackets
+ @if string.index($string, "url(") == 1 {
+ $string: url("#{str-replace(string.slice($string, 6, -3), $char, $encoded)}");
+ } @else {
+ $string: str-replace($string, $char, $encoded);
+ }
+ }
+ }
+
+ @return $string;
+}
diff --git a/src/scss/scss/functions/_maps.scss b/src/scss/scss/functions/_maps.scss
new file mode 100644
index 000000000..99a17a5c3
--- /dev/null
+++ b/src/scss/scss/functions/_maps.scss
@@ -0,0 +1,57 @@
+@use "sass:list";
+@use "sass:map";
+@use "sass:meta";
+@use "color" as *;
+@use "rgba-css-var" as *;
+@use "to-rgb" as *;
+@use "../variables" as *;
+
+// stylelint-disable scss/dollar-variable-pattern
+@function map-loop($map, $func, $args...) {
+ $_map: ();
+
+ @each $key, $value in $map {
+ // allow to pass the $key and $value of the map as an function argument
+ $_args: ();
+ @each $arg in $args {
+ $_args: list.append($_args, if($arg == "$prefix", $prefix, if($arg == "$key", $key, if($arg == "$value", $value, $arg))));
+ }
+
+ $_map: map.merge($_map, ($key: meta.call(meta.get-function($func), $_args...)));
+ }
+ @return $_map;
+}
+// stylelint-enable scss/dollar-variable-pattern
+
+// Internal Bootstrap function to turn maps into its negative variant.
+// It prefixes the keys with `n` and makes the value negative.
+@function negativify-map($map) {
+ $result: ();
+ @each $key, $value in $map {
+ @if $key != 0 {
+ $result: map.merge($result, ("n" + $key: (-$value)));
+ }
+ }
+ @return $result;
+}
+
+// Get multiple keys from a sass map
+@function map-get-multiple($map, $values) {
+ $result: ();
+ @each $key, $value in $map {
+ @if (list.index($values, $key) != null) {
+ $result: map.merge($result, ($key: $value));
+ }
+ }
+ @return $result;
+}
+
+// Merge multiple maps
+@function map-merge-multiple($maps...) {
+ $merged-maps: ();
+
+ @each $map in $maps {
+ $merged-maps: map.merge($merged-maps, $map);
+ }
+ @return $merged-maps;
+}
diff --git a/src/scss/scss/functions/_math.scss b/src/scss/scss/functions/_math.scss
new file mode 100644
index 000000000..3dab817f9
--- /dev/null
+++ b/src/scss/scss/functions/_math.scss
@@ -0,0 +1,87 @@
+@use "sass:map";
+@use "sass:math";
+@use "sass:meta";
+@use "sass:string";
+
+// Return valid calc
+@function add($value1, $value2, $return-calc: true) {
+ @if $value1 == null {
+ @return $value2;
+ }
+
+ @if $value2 == null {
+ @return $value1;
+ }
+
+ @if meta.type-of($value1) == number and meta.type-of($value2) == number and math.compatible($value1, $value2) {
+ @return $value1 + $value2;
+ }
+
+ @return if($return-calc == true, calc(#{$value1} + #{$value2}), $value1 + string.unquote(" + ") + $value2);
+}
+
+@function subtract($value1, $value2, $return-calc: true) {
+ @if $value1 == null and $value2 == null {
+ @return null;
+ }
+
+ @if $value1 == null {
+ @return -$value2;
+ }
+
+ @if $value2 == null {
+ @return $value1;
+ }
+
+ @if meta.type-of($value1) == number and meta.type-of($value2) == number and math.compatible($value1, $value2) {
+ @return $value1 - $value2;
+ }
+
+ @if meta.type-of($value2) != number {
+ $value2: string.unquote("(") + $value2 + string.unquote(")");
+ }
+
+ @return if($return-calc == true, calc(#{$value1} - #{$value2}), $value1 + string.unquote(" - ") + $value2);
+}
+
+@function divide($dividend, $divisor, $precision: 10) {
+ $sign: if($dividend > 0 and $divisor > 0 or $dividend < 0 and $divisor < 0, 1, -1);
+ $dividend: abs($dividend);
+ $divisor: abs($divisor);
+ @if $dividend == 0 {
+ @return 0;
+ }
+ @if $divisor == 0 {
+ @error "Cannot divide by 0";
+ }
+ $remainder: $dividend;
+ $result: 0;
+ $factor: 10;
+ @while ($remainder > 0 and $precision >= 0) {
+ $quotient: 0;
+ @while ($remainder >= $divisor) {
+ $remainder: $remainder - $divisor;
+ $quotient: $quotient + 1;
+ }
+ $result: $result * 10 + $quotient;
+ $factor: $factor * .1;
+ $remainder: $remainder * 10;
+ $precision: $precision - 1;
+ @if ($precision < 0 and $remainder >= $divisor * 5) {
+ $result: $result + 1;
+ }
+ }
+ $result: $result * $factor * $sign;
+ $dividend-unit: math.unit($dividend);
+ $divisor-unit: math.unit($divisor);
+ $unit-map: (
+ "px": 1px,
+ "rem": 1rem,
+ "em": 1em,
+ "%": 1%
+ );
+ @if ($dividend-unit != $divisor-unit and map.has-key($unit-map, $dividend-unit)) {
+ $result: $result * map.get($unit-map, $dividend-unit);
+ }
+ @return $result;
+}
diff --git a/src/scss/scss/functions/_rgba-css-var.scss b/src/scss/scss/functions/_rgba-css-var.scss
new file mode 100644
index 000000000..da8de063d
--- /dev/null
+++ b/src/scss/scss/functions/_rgba-css-var.scss
@@ -0,0 +1,9 @@
+@function rgba-css-var($prefix, $identifier, $target) {
+ @if $identifier == "body" and $target == "bg" {
+ @return rgba(var(--#{$prefix}#{$identifier}-bg-rgb), var(--#{$prefix}#{$target}-opacity));
+ } @if $identifier == "body" and $target == "text" {
+ @return rgba(var(--#{$prefix}#{$identifier}-color-rgb), var(--#{$prefix}#{$target}-opacity));
+ } @else {
+ @return rgba(var(--#{$prefix}#{$identifier}-rgb), var(--#{$prefix}#{$target}-opacity));
+ }
+}
diff --git a/src/scss/scss/functions/_str-replace.scss b/src/scss/scss/functions/_str-replace.scss
new file mode 100644
index 000000000..d12863137
--- /dev/null
+++ b/src/scss/scss/functions/_str-replace.scss
@@ -0,0 +1,19 @@
+@use "sass:string";
+
+// Replace `$search` with `$replace` in `$string`
+// Used on our SVG icon backgrounds for custom forms.
+//
+// @author Kitty Giraudel
+// @param {String} $string - Initial string
+// @param {String} $search - Substring to replace
+// @param {String} $replace ('') - New value
+// @return {String} - Updated string
+@function str-replace($string, $search, $replace: "") {
+ $index: string.index($string, $search);
+
+ @if $index {
+ @return string.slice($string, 1, $index - 1) + $replace + str-replace(string.slice($string, $index + string.length($search)), $search, $replace);
+ }
+
+ @return $string;
+}
diff --git a/src/scss/scss/functions/_to-rgb.scss b/src/scss/scss/functions/_to-rgb.scss
new file mode 100644
index 000000000..363b732cf
--- /dev/null
+++ b/src/scss/scss/functions/_to-rgb.scss
@@ -0,0 +1,5 @@
+@use "sass:color";
+
+@function to-rgb($value) {
+ @return color.channel($value, "red", $space: rgb), color.channel($value, "green", $space: rgb), color.channel($value, "blue", $space: rgb);
+}
diff --git a/src/scss/scss/helpers/_clearfix.scss b/src/scss/scss/helpers/_clearfix.scss
new file mode 100644
index 000000000..e2d9a81f3
--- /dev/null
+++ b/src/scss/scss/helpers/_clearfix.scss
@@ -0,0 +1,5 @@
+@use "../mixins/clearfix" as *;
+
+.clearfix {
+ @include clearfix();
+}
diff --git a/src/scss/scss/helpers/_color-bg.scss b/src/scss/scss/helpers/_color-bg.scss
new file mode 100644
index 000000000..b215d7da1
--- /dev/null
+++ b/src/scss/scss/helpers/_color-bg.scss
@@ -0,0 +1,25 @@
+@use "../functions/color" as *;
+@use "../functions/color-contrast" as *;
+@use "../functions/to-rgb" as *;
+@use "../mixins/color-mode" as *;
+@use "../variables" as *;
+@use "../variables-dark" as *;
+
+@each $color, $value in $theme-colors {
+ .text-bg-#{$color} {
+ color: color-contrast($value) if($enable-important-utilities, !important, null);
+ background-color: rgba(var(--#{$prefix}#{$color}-rgb), var(--#{$prefix}bg-opacity, 1)) if($enable-important-utilities, !important, null);
+ }
+}
+
+@if $enable-dark-mode {
+ @include color-mode(dark) {
+ @each $color, $value in $theme-colors-dark {
+ $color-rgb: to-rgb($value);
+ .text-bg-#{$color} {
+ color: color-contrast($value) if($enable-important-utilities, !important, null);
+ background-color: rgba($color-rgb, var(--#{$prefix}bg-opacity, 1)) if($enable-important-utilities, !important, null);
+ }
+ }
+ }
+}
diff --git a/src/scss/scss/helpers/_colored-links.scss b/src/scss/scss/helpers/_colored-links.scss
new file mode 100644
index 000000000..8d4c11644
--- /dev/null
+++ b/src/scss/scss/helpers/_colored-links.scss
@@ -0,0 +1,34 @@
+@use "../functions/color" as *;
+@use "../functions/color-contrast" as *;
+@use "../functions/to-rgb" as *;
+@use "../variables" as *;
+
+@each $color, $value in $theme-colors {
+ .link-#{$color} {
+ color: rgba(var(--#{$prefix}#{$color}-rgb), var(--#{$prefix}link-opacity, 1)) if($enable-important-utilities, !important, null);
+ text-decoration-color: rgba(var(--#{$prefix}#{$color}-rgb), var(--#{$prefix}link-underline-opacity, 1)) if($enable-important-utilities, !important, null);
+
+ @if $link-shade-percentage != 0 {
+ &:hover,
+ &:focus {
+ $hover-color: if(color-contrast($value) == $color-contrast-light, shade-color($value, $link-shade-percentage), tint-color($value, $link-shade-percentage));
+ color: rgba(#{to-rgb($hover-color)}, var(--#{$prefix}link-opacity, 1)) if($enable-important-utilities, !important, null);
+ text-decoration-color: rgba(to-rgb($hover-color), var(--#{$prefix}link-underline-opacity, 1)) if($enable-important-utilities, !important, null);
+ }
+ }
+ }
+}
+
+// One-off special link helper as a bridge until v6
+.link-body-emphasis {
+ color: rgba(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-opacity, 1)) if($enable-important-utilities, !important, null);
+ text-decoration-color: rgba(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-underline-opacity, 1)) if($enable-important-utilities, !important, null);
+
+ @if $link-shade-percentage != 0 {
+ &:hover,
+ &:focus {
+ color: rgba(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-opacity, .75)) if($enable-important-utilities, !important, null);
+ text-decoration-color: rgba(var(--#{$prefix}emphasis-color-rgb), var(--#{$prefix}link-underline-opacity, .75)) if($enable-important-utilities, !important, null);
+ }
+ }
+}
diff --git a/src/scss/scss/helpers/_focus-ring.scss b/src/scss/scss/helpers/_focus-ring.scss
new file mode 100644
index 000000000..7bdee2dbc
--- /dev/null
+++ b/src/scss/scss/helpers/_focus-ring.scss
@@ -0,0 +1,7 @@
+@use "../variables" as *;
+
+.focus-ring:focus {
+ outline: 0;
+ // By default, there is no `--cui-focus-ring-x`, `--cui-focus-ring-y`, or `--cui-focus-ring-blur`, but we provide CSS variables with fallbacks to initial `0` values
+ box-shadow: var(--#{$prefix}focus-ring-x, 0) var(--#{$prefix}focus-ring-y, 0) var(--#{$prefix}focus-ring-blur, 0) var(--#{$prefix}focus-ring-width) var(--#{$prefix}focus-ring-color);
+}
diff --git a/src/scss/scss/helpers/_icon-link.scss b/src/scss/scss/helpers/_icon-link.scss
new file mode 100644
index 000000000..53cb8e9a1
--- /dev/null
+++ b/src/scss/scss/helpers/_icon-link.scss
@@ -0,0 +1,28 @@
+@use "../mixins/transition" as *;
+@use "../variables" as *;
+
+.icon-link {
+ display: inline-flex;
+ gap: $icon-link-gap;
+ align-items: center;
+ text-decoration-color: rgba(var(--#{$prefix}link-color-rgb), var(--#{$prefix}link-opacity, .5));
+ text-underline-offset: $icon-link-underline-offset;
+ backface-visibility: hidden;
+
+ > .bi {
+ flex-shrink: 0;
+ width: $icon-link-icon-size;
+ height: $icon-link-icon-size;
+ fill: currentcolor;
+ @include transition($icon-link-icon-transition);
+ }
+}
+
+.icon-link-hover {
+ &:hover,
+ &:focus-visible {
+ > .bi {
+ transform: var(--#{$prefix}icon-link-transform, $icon-link-icon-transform);
+ }
+ }
+}
diff --git a/src/scss/scss/helpers/_position.scss b/src/scss/scss/helpers/_position.scss
new file mode 100644
index 000000000..5bb068935
--- /dev/null
+++ b/src/scss/scss/helpers/_position.scss
@@ -0,0 +1,40 @@
+@use "sass:map";
+@use "../mixins/breakpoints" as *;
+@use "../variables" as *;
+
+// Shorthand
+
+.fixed-top {
+ position: fixed;
+ top: 0;
+ right: 0;
+ left: 0;
+ z-index: $zindex-fixed;
+}
+
+.fixed-bottom {
+ position: fixed;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: $zindex-fixed;
+}
+
+// Responsive sticky top and bottom
+@each $breakpoint in map.keys($grid-breakpoints) {
+ @include media-breakpoint-up($breakpoint) {
+ $infix: breakpoint-infix($breakpoint, $grid-breakpoints);
+
+ .sticky#{$infix}-top {
+ position: sticky;
+ top: 0;
+ z-index: $zindex-sticky;
+ }
+
+ .sticky#{$infix}-bottom {
+ position: sticky;
+ bottom: 0;
+ z-index: $zindex-sticky;
+ }
+ }
+}
diff --git a/src/scss/scss/helpers/_ratio.scss b/src/scss/scss/helpers/_ratio.scss
new file mode 100644
index 000000000..c08481179
--- /dev/null
+++ b/src/scss/scss/helpers/_ratio.scss
@@ -0,0 +1,28 @@
+@use "../variables" as *;
+
+// Credit: Nicolas Gallagher and SUIT CSS.
+
+.ratio {
+ position: relative;
+ width: 100%;
+
+ &::before {
+ display: block;
+ padding-top: var(--#{$prefix}aspect-ratio);
+ content: "";
+ }
+
+ > * {
+ position: absolute;
+ inset-inline-start: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ }
+}
+
+@each $key, $ratio in $aspect-ratios {
+ .ratio-#{$key} {
+ --#{$prefix}aspect-ratio: #{$ratio};
+ }
+}
diff --git a/src/scss/scss/helpers/_stacks.scss b/src/scss/scss/helpers/_stacks.scss
new file mode 100644
index 000000000..6cd237ae6
--- /dev/null
+++ b/src/scss/scss/helpers/_stacks.scss
@@ -0,0 +1,15 @@
+// scss-docs-start stacks
+.hstack {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ align-self: stretch;
+}
+
+.vstack {
+ display: flex;
+ flex: 1 1 auto;
+ flex-direction: column;
+ align-self: stretch;
+}
+// scss-docs-end stacks
diff --git a/src/scss/scss/helpers/_stretched-link.scss b/src/scss/scss/helpers/_stretched-link.scss
new file mode 100644
index 000000000..ec283094d
--- /dev/null
+++ b/src/scss/scss/helpers/_stretched-link.scss
@@ -0,0 +1,17 @@
+@use "../variables" as *;
+
+//
+// Stretched link
+//
+
+.stretched-link {
+ &::#{$stretched-link-pseudo-element} {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: $stretched-link-z-index;
+ content: "";
+ }
+}
diff --git a/src/scss/scss/helpers/_text-truncation.scss b/src/scss/scss/helpers/_text-truncation.scss
new file mode 100644
index 000000000..191dc4d22
--- /dev/null
+++ b/src/scss/scss/helpers/_text-truncation.scss
@@ -0,0 +1,9 @@
+@use "../mixins/text-truncate" as *;
+
+//
+// Text truncation
+//
+
+.text-truncate {
+ @include text-truncate();
+}
diff --git a/src/scss/scss/helpers/_visually-hidden.scss b/src/scss/scss/helpers/_visually-hidden.scss
new file mode 100644
index 000000000..ad6bf0884
--- /dev/null
+++ b/src/scss/scss/helpers/_visually-hidden.scss
@@ -0,0 +1,10 @@
+@use "../mixins/visually-hidden" as *;
+
+//
+// Visually hidden
+//
+
+.visually-hidden,
+.visually-hidden-focusable:not(:focus):not(:focus-within) {
+ @include visually-hidden();
+}
diff --git a/src/scss/scss/helpers/_vr.scss b/src/scss/scss/helpers/_vr.scss
new file mode 100644
index 000000000..0b764dde6
--- /dev/null
+++ b/src/scss/scss/helpers/_vr.scss
@@ -0,0 +1,11 @@
+@use "../variables" as *;
+
+.vr {
+ display: inline-block;
+ align-self: stretch;
+ width: $vr-border-width;
+ min-height: 1em;
+ padding: 0;
+ background-color: currentcolor;
+ opacity: $hr-opacity;
+}
diff --git a/src/scss/scss/mixins/_alert.scss b/src/scss/scss/mixins/_alert.scss
new file mode 100644
index 000000000..a801fea2d
--- /dev/null
+++ b/src/scss/scss/mixins/_alert.scss
@@ -0,0 +1,21 @@
+@use "../variables" as *;
+@use "../mixins/deprecate" as *;
+
+@include deprecate("`alert-variant()`", "v4.3.0", "v6.0.0");
+
+// scss-docs-start alert-variant-mixin
+@mixin alert-variant($background, $border, $color) {
+ --#{$prefix}alert-color: #{$color};
+ --#{$prefix}alert-bg: #{$background};
+ --#{$prefix}alert-border-color: #{$border};
+ --#{$prefix}alert-link-color: #{shade-color($color, 20%)};
+
+ @if $enable-gradients {
+ background-image: var(--#{$prefix}gradient);
+ }
+
+ .alert-link {
+ color: var(--#{$prefix}alert-link-color);
+ }
+}
+// scss-docs-end alert-variant-mixin
diff --git a/src/scss/scss/mixins/_avatar.scss b/src/scss/scss/mixins/_avatar.scss
new file mode 100644
index 000000000..1daa889bf
--- /dev/null
+++ b/src/scss/scss/mixins/_avatar.scss
@@ -0,0 +1,12 @@
+@use "../variables" as *;
+@use "../mixins/deprecate" as *;
+
+@include deprecate("`avatar()`", "v5.1.0", "v6.0.0");
+
+@mixin avatar($width) {
+ --#{$prefix}avatar-width: #{$width};
+ --#{$prefix}avatar-height: #{$width};
+ --#{$prefix}avatar-font-size: #{$width * .4};
+ --#{$prefix}avatar-status-width: #{divide($width, 3.75)};
+ --#{$prefix}avatar-status-height: #{divide($width, 3.75)};
+}
diff --git a/src/scss/scss/mixins/_backdrop.scss b/src/scss/scss/mixins/_backdrop.scss
new file mode 100644
index 000000000..07bd7b97c
--- /dev/null
+++ b/src/scss/scss/mixins/_backdrop.scss
@@ -0,0 +1,16 @@
+@use "../variables" as *;
+
+// Shared between modals and offcanvases
+@mixin overlay-backdrop($zindex, $backdrop-bg, $backdrop-opacity) {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: $zindex;
+ width: 100vw;
+ height: 100vh;
+ background-color: $backdrop-bg;
+
+ // Fade for backdrop
+ &.fade { opacity: 0; }
+ &.show { opacity: $backdrop-opacity; }
+}
diff --git a/src/scss/scss/mixins/_border-radius.scss b/src/scss/scss/mixins/_border-radius.scss
new file mode 100644
index 000000000..5581b3008
--- /dev/null
+++ b/src/scss/scss/mixins/_border-radius.scss
@@ -0,0 +1,82 @@
+// stylelint-disable property-disallowed-list
+@use "sass:list";
+@use "sass:meta";
+@use "../variables" as *;
+
+// Single side border-radius
+
+// Helper function to replace negative values with 0
+@function valid-radius($radius) {
+ $return: ();
+ @each $value in $radius {
+ @if meta.type-of($value) == number {
+ $return: list.append($return, max($value, 0));
+ } @else {
+ $return: list.append($return, $value);
+ }
+ }
+ @return $return;
+}
+
+// scss-docs-start border-radius-mixins
+@mixin border-radius($radius: $border-radius, $fallback-border-radius: false) {
+ @if $enable-rounded {
+ border-radius: valid-radius($radius);
+ }
+ @else if $fallback-border-radius != false {
+ border-radius: $fallback-border-radius;
+ }
+}
+
+@mixin border-top-radius($radius: $border-radius) {
+ @if $enable-rounded {
+ border-top-left-radius: valid-radius($radius);
+ border-top-right-radius: valid-radius($radius);
+ }
+}
+
+@mixin border-end-radius($radius: $border-radius) {
+ @if $enable-rounded {
+ border-start-end-radius: valid-radius($radius);
+ border-end-end-radius: valid-radius($radius);
+ }
+}
+
+@mixin border-bottom-radius($radius: $border-radius) {
+ @if $enable-rounded {
+ border-bottom-right-radius: valid-radius($radius);
+ border-bottom-left-radius: valid-radius($radius);
+ }
+}
+
+@mixin border-start-radius($radius: $border-radius) {
+ @if $enable-rounded {
+ border-start-start-radius: valid-radius($radius);
+ border-end-start-radius: valid-radius($radius);
+ }
+}
+
+@mixin border-top-start-radius($radius: $border-radius) {
+ @if $enable-rounded {
+ border-start-start-radius: valid-radius($radius);
+ }
+}
+
+@mixin border-top-end-radius($radius: $border-radius) {
+ @if $enable-rounded {
+ border-start-end-radius: valid-radius($radius);
+ }
+}
+
+@mixin border-bottom-end-radius($radius: $border-radius) {
+ @if $enable-rounded {
+ border-end-end-radius: valid-radius($radius);
+ }
+}
+
+@mixin border-bottom-start-radius($radius: $border-radius) {
+ @if $enable-rounded {
+ border-end-start-radius: valid-radius($radius);
+ }
+}
+// scss-docs-end border-radius-mixins
diff --git a/src/scss/scss/mixins/_box-shadow.scss b/src/scss/scss/mixins/_box-shadow.scss
new file mode 100644
index 000000000..842c7b136
--- /dev/null
+++ b/src/scss/scss/mixins/_box-shadow.scss
@@ -0,0 +1,21 @@
+@use "sass:list";
+@use "../variables" as *;
+
+@mixin box-shadow($shadow...) {
+ @if $enable-shadows {
+ $result: ();
+
+ @each $value in $shadow {
+ @if $value != null {
+ $result: list.append($result, $value, "comma");
+ }
+ @if $value == none and list.length($shadow) > 1 {
+ @warn "The keyword 'none' must be used as a single argument.";
+ }
+ }
+
+ @if (length($result) > 0) {
+ box-shadow: $result;
+ }
+ }
+}
diff --git a/src/scss/scss/mixins/_breakpoints.scss b/src/scss/scss/mixins/_breakpoints.scss
new file mode 100644
index 000000000..4c24cde21
--- /dev/null
+++ b/src/scss/scss/mixins/_breakpoints.scss
@@ -0,0 +1,204 @@
+@use "sass:list";
+@use "sass:map";
+@use "../variables" as *;
+
+// Breakpoint viewport sizes and media queries.
+//
+// Breakpoints are defined as a map of (name: minimum width), order from small to large:
+//
+// (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)
+//
+// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.
+
+// Name of the next breakpoint, or null for the last breakpoint.
+//
+// >> breakpoint-next(sm)
+// md
+// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))
+// md
+// >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl xxl))
+// md
+@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map.keys($breakpoints)) {
+ $n: list.index($breakpoint-names, $name);
+ @if not $n {
+ @error "breakpoint `#{$name}` not found in `#{$breakpoints}`";
+ }
+ @return if($n < list.length($breakpoint-names), list.nth($breakpoint-names, $n + 1), null);
+}
+
+// Minimum breakpoint width. Null for the smallest (first) breakpoint.
+//
+// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))
+// 576px
+@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {
+ $min: map.get($breakpoints, $name);
+ @return if($min != 0, $min, null);
+}
+
+// Maximum breakpoint width.
+// The maximum value is reduced by 0.02px to work around the limitations of
+// `min-` and `max-` prefixes and viewports with fractional widths.
+// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max
+// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.
+// See https://bugs.webkit.org/show_bug.cgi?id=178261
+//
+// >> breakpoint-max(md, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))
+// 767.98px
+@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {
+ $max: map.get($breakpoints, $name);
+ @return if($max and $max > 0, $max - .02, null);
+}
+
+// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front.
+// Useful for making responsive utilities.
+//
+// >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))
+// "" (Returns a blank string)
+// >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))
+// "-sm"
+@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) {
+ @return if(breakpoint-min($name, $breakpoints) == null, "", "-#{$name}");
+}
+
+// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.
+// Makes the @content apply to the given breakpoint and wider.
+@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {
+ $min: breakpoint-min($name, $breakpoints);
+ @if $min {
+ @media (min-width: $min) {
+ @content;
+ }
+ } @else {
+ @content;
+ }
+}
+
+// Media of at most the maximum breakpoint width. No query for the largest breakpoint.
+// Makes the @content apply to the given breakpoint and narrower.
+@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) {
+ $max: breakpoint-max($name, $breakpoints);
+ @if $max {
+ @media (max-width: $max) {
+ @content;
+ }
+ } @else {
+ @content;
+ }
+}
+
+// Media that spans multiple breakpoint widths.
+// Makes the @content apply between the min and max breakpoints
+@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) {
+ $min: breakpoint-min($lower, $breakpoints);
+ $max: breakpoint-max($upper, $breakpoints);
+
+ @if $min != null and $max != null {
+ @media (min-width: $min) and (max-width: $max) {
+ @content;
+ }
+ } @else if $max == null {
+ @include media-breakpoint-up($lower, $breakpoints) {
+ @content;
+ }
+ } @else if $min == null {
+ @include media-breakpoint-down($upper, $breakpoints) {
+ @content;
+ }
+ }
+}
+
+// Media between the breakpoint's minimum and maximum widths.
+// No minimum for the smallest breakpoint, and no maximum for the largest one.
+// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.
+@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {
+ $min: breakpoint-min($name, $breakpoints);
+ $next: breakpoint-next($name, $breakpoints);
+ $max: breakpoint-max($next, $breakpoints);
+
+ @if $min != null and $max != null {
+ @media (min-width: $min) and (max-width: $max) {
+ @content;
+ }
+ } @else if $max == null {
+ @include media-breakpoint-up($name, $breakpoints) {
+ @content;
+ }
+ } @else if $min == null {
+ @include media-breakpoint-down($next, $breakpoints) {
+ @content;
+ }
+ }
+}
+
+//
+// Container query mixins
+//
+
+// Container query for applying styles when the container’s inline size is at least the breakpoint’s minimum width.
+@mixin container-breakpoint-up($name, $breakpoints: $cq-grid-breakpoints) {
+ $min: breakpoint-min($name, $breakpoints);
+ @if $min {
+ @container (min-width: #{$min}) {
+ @content;
+ }
+ } @else {
+ @content;
+ }
+}
+
+// Container query for applying styles when the container’s inline size is at most the breakpoint’s maximum width.
+@mixin container-breakpoint-down($name, $breakpoints: $cq-grid-breakpoints) {
+ $max: breakpoint-max($name, $breakpoints);
+ @if $max {
+ @container (max-width: #{$max}) {
+ @content;
+ }
+ } @else {
+ @content;
+ }
+}
+
+// Container query for applying styles between two breakpoints.
+@mixin container-breakpoint-between($lower, $upper, $breakpoints: $cq-grid-breakpoints) {
+ $min: breakpoint-min($lower, $breakpoints);
+ $max: breakpoint-max($upper, $breakpoints);
+
+ @if $min != null and $max != null {
+ @container (min-width: #{$min}) and (max-width: #{$max}) {
+ @content;
+ }
+ } @else if $max == null {
+ // When the upper breakpoint is the last one.
+ @include container-breakpoint-up($lower, $breakpoints) {
+ @content;
+ }
+ } @else if $min == null {
+ // When the lower breakpoint is the smallest.
+ @include container-breakpoint-down($upper, $breakpoints) {
+ @content;
+ }
+ }
+}
+
+// Container query for applying styles only within a single breakpoint range.
+@mixin container-breakpoint-only($name, $breakpoints: $cq-grid-breakpoints) {
+ $min: breakpoint-min($name, $breakpoints);
+ $next: breakpoint-next($name, $breakpoints);
+ $max: breakpoint-max($next, $breakpoints);
+
+ @if $min != null and $max != null {
+ @container (min-width: #{$min}) and (max-width: #{$max}) {
+ @content;
+ }
+ } @else if $max == null {
+ // Last breakpoint – no maximum query.
+ @include container-breakpoint-up($name, $breakpoints) {
+ @content;
+ }
+ } @else if $min == null {
+ // First breakpoint – no minimum query.
+ @include container-breakpoint-down($next, $breakpoints) {
+ @content;
+ }
+ }
+}
diff --git a/src/scss/scss/mixins/_buttons.scss b/src/scss/scss/mixins/_buttons.scss
new file mode 100644
index 000000000..a7091af49
--- /dev/null
+++ b/src/scss/scss/mixins/_buttons.scss
@@ -0,0 +1,103 @@
+@use "sass:color";
+@use "../functions/color" as *;
+@use "../functions/color-contrast" as *;
+@use "../functions/to-rgb" as *;
+@use "../vendor/rfs" as *;
+@use "../variables" as *;
+
+// Button variants
+//
+// Easily pump out default styles, as well as :hover, :focus, :active,
+// and disabled options for all buttons
+
+// scss-docs-start btn-variant-mixin
+@mixin button-variant(
+ $background,
+ $border,
+ $color: color-contrast($background),
+ $hover-background: if($color == $color-contrast-light, shade-color($background, $btn-hover-bg-shade-amount), tint-color($background, $btn-hover-bg-tint-amount)),
+ $hover-border: if($color == $color-contrast-light, shade-color($border, $btn-hover-border-shade-amount), tint-color($border, $btn-hover-border-tint-amount)),
+ $hover-color: color-contrast($hover-background),
+ $active-background: if($color == $color-contrast-light, shade-color($background, $btn-active-bg-shade-amount), tint-color($background, $btn-active-bg-tint-amount)),
+ $active-border: if($color == $color-contrast-light, shade-color($border, $btn-active-border-shade-amount), tint-color($border, $btn-active-border-tint-amount)),
+ $active-color: color-contrast($active-background),
+ $disabled-background: $background,
+ $disabled-border: $border,
+ $disabled-color: color-contrast($disabled-background)
+) {
+ --#{$prefix}btn-color: #{$color};
+ --#{$prefix}btn-bg: #{$background};
+ --#{$prefix}btn-border-color: #{$border};
+ --#{$prefix}btn-hover-color: #{$hover-color};
+ --#{$prefix}btn-hover-bg: #{$hover-background};
+ --#{$prefix}btn-hover-border-color: #{$hover-border};
+ --#{$prefix}btn-focus-shadow-rgb: #{to-rgb(color.mix($color, $border, 15%))};
+ --#{$prefix}btn-active-color: #{$active-color};
+ --#{$prefix}btn-active-bg: #{$active-background};
+ --#{$prefix}btn-active-border-color: #{$active-border};
+ --#{$prefix}btn-active-shadow: #{$btn-active-box-shadow};
+ --#{$prefix}btn-disabled-color: #{$disabled-color};
+ --#{$prefix}btn-disabled-bg: #{$disabled-background};
+ --#{$prefix}btn-disabled-border-color: #{$disabled-border};
+}
+// scss-docs-end btn-variant-mixin
+
+// scss-docs-start btn-outline-variant-mixin
+@mixin button-outline-variant(
+ $color,
+ $color-hover: color-contrast($color),
+ $active-background: $color,
+ $active-border: $color,
+ $active-color: color-contrast($active-background)
+) {
+ --#{$prefix}btn-color: #{$color};
+ --#{$prefix}btn-border-color: #{$color};
+ --#{$prefix}btn-hover-color: #{$color-hover};
+ --#{$prefix}btn-hover-bg: #{$active-background};
+ --#{$prefix}btn-hover-border-color: #{$active-border};
+ --#{$prefix}btn-focus-shadow-rgb: #{to-rgb($color)};
+ --#{$prefix}btn-active-color: #{$active-color};
+ --#{$prefix}btn-active-bg: #{$active-background};
+ --#{$prefix}btn-active-border-color: #{$active-border};
+ --#{$prefix}btn-active-shadow: #{$btn-active-box-shadow};
+ --#{$prefix}btn-disabled-color: #{$color};
+ --#{$prefix}btn-disabled-bg: transparent;
+ --#{$prefix}btn-disabled-border-color: #{$color};
+ --#{$prefix}gradient: none;
+}
+// scss-docs-end btn-outline-variant-mixin
+
+// scss-docs-start btn-ghost-variant-mixin
+@mixin button-ghost-variant(
+ $color,
+ $color-hover: color-contrast($color),
+ $hover-background: $color,
+ $hover-border: $color,
+ $hover-color: color-contrast($color),
+ $active-background: $color,
+ $active-border: $color,
+ $active-color: color-contrast($color)
+) {
+ --#{$prefix}btn-color: #{$color};
+ --#{$prefix}btn-border-color: transparent;
+ --#{$prefix}btn-hover-bg: #{$hover-background};
+ --#{$prefix}btn-hover-border-color: #{$hover-border};
+ --#{$prefix}btn-hover-color: #{$color-hover};
+ --#{$prefix}btn-active-bg: #{$active-background};
+ --#{$prefix}btn-active-border-color: #{$active-border};
+ --#{$prefix}btn-active-color: #{$active-color};
+ --#{$prefix}btn-disabled-color: #{$color};
+ --#{$prefix}btn-disabled-bg: transparent;
+ --#{$prefix}btn-disabled-border-color: transparent;
+}
+// scss-docs-end btn-ghost-variant-mixin
+
+// Button sizes
+// scss-docs-start btn-size-mixin
+@mixin button-size($padding-y, $padding-x, $font-size, $border-radius) {
+ --#{$prefix}btn-padding-y: #{$padding-y};
+ --#{$prefix}btn-padding-x: #{$padding-x};
+ @include rfs($font-size, --#{$prefix}btn-font-size);
+ --#{$prefix}btn-border-radius: #{$border-radius};
+}
+// scss-docs-end btn-size-mixin
diff --git a/src/scss/scss/mixins/_caret.scss b/src/scss/scss/mixins/_caret.scss
new file mode 100644
index 000000000..816e308fb
--- /dev/null
+++ b/src/scss/scss/mixins/_caret.scss
@@ -0,0 +1,71 @@
+@use "../variables" as *;
+
+// scss-docs-start caret-mixins
+@mixin caret-down($width: $caret-width) {
+ border-top: $width solid;
+ border-right: $width solid transparent;
+ border-bottom: 0;
+ border-left: $width solid transparent;
+}
+
+@mixin caret-up($width: $caret-width) {
+ border-top: 0;
+ border-right: $width solid transparent;
+ border-bottom: $width solid;
+ border-left: $width solid transparent;
+}
+
+@mixin caret-end($width: $caret-width) {
+ border-top: $width solid transparent;
+ border-right: 0;
+ border-bottom: $width solid transparent;
+ border-left: $width solid;
+}
+
+@mixin caret-start($width: $caret-width) {
+ border-top: $width solid transparent;
+ border-right: $width solid;
+ border-bottom: $width solid transparent;
+}
+
+@mixin caret(
+ $direction: down,
+ $width: $caret-width,
+ $spacing: $caret-spacing,
+ $vertical-align: $caret-vertical-align
+) {
+ @if $enable-caret {
+ &::after {
+ display: inline-block;
+ margin-inline-start: $spacing;
+ vertical-align: $vertical-align;
+ content: "";
+ @if $direction == down {
+ @include caret-down($width);
+ } @else if $direction == up {
+ @include caret-up($width);
+ } @else if $direction == end {
+ @include caret-end($width);
+ }
+ }
+
+ @if $direction == start {
+ &::after {
+ display: none;
+ }
+
+ &::before {
+ display: inline-block;
+ margin-inline-end: $spacing;
+ vertical-align: $vertical-align;
+ content: "";
+ @include caret-start($width);
+ }
+ }
+
+ &:empty::after {
+ margin-inline-start: 0;
+ }
+ }
+}
+// scss-docs-end caret-mixins
diff --git a/src/scss/scss/mixins/_clearfix.scss b/src/scss/scss/mixins/_clearfix.scss
new file mode 100644
index 000000000..ffc62bb28
--- /dev/null
+++ b/src/scss/scss/mixins/_clearfix.scss
@@ -0,0 +1,9 @@
+// scss-docs-start clearfix
+@mixin clearfix() {
+ &::after {
+ display: block;
+ clear: both;
+ content: "";
+ }
+}
+// scss-docs-end clearfix
diff --git a/src/scss/scss/mixins/_color-mode.scss b/src/scss/scss/mixins/_color-mode.scss
new file mode 100644
index 000000000..2b5d4b539
--- /dev/null
+++ b/src/scss/scss/mixins/_color-mode.scss
@@ -0,0 +1,23 @@
+@use "../variables" as *;
+
+// scss-docs-start color-mode-mixin
+@mixin color-mode($mode: light, $root: false) {
+ @if $color-mode-type == "media-query" {
+ @if $root == true {
+ @media (prefers-color-scheme: $mode) {
+ :root {
+ @content;
+ }
+ }
+ } @else {
+ @media (prefers-color-scheme: $mode) {
+ @content;
+ }
+ }
+ } @else {
+ [data#{$data-infix}theme="#{$mode}"] {
+ @content;
+ }
+ }
+}
+// scss-docs-end color-mode-mixin
diff --git a/src/scss/scss/mixins/_color-scheme.scss b/src/scss/scss/mixins/_color-scheme.scss
new file mode 100644
index 000000000..90497aa0a
--- /dev/null
+++ b/src/scss/scss/mixins/_color-scheme.scss
@@ -0,0 +1,7 @@
+// scss-docs-start mixin-color-scheme
+@mixin color-scheme($name) {
+ @media (prefers-color-scheme: #{$name}) {
+ @content;
+ }
+}
+// scss-docs-end mixin-color-scheme
diff --git a/src/scss/scss/mixins/_container.scss b/src/scss/scss/mixins/_container.scss
new file mode 100644
index 000000000..5986f2d98
--- /dev/null
+++ b/src/scss/scss/mixins/_container.scss
@@ -0,0 +1,13 @@
+@use "../variables" as *;
+
+// Container mixins
+
+@mixin make-container($gutter: $container-padding-x) {
+ --#{$prefix}gutter-x: #{$gutter};
+ --#{$prefix}gutter-y: 0;
+ width: 100%;
+ padding-right: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list
+ padding-left: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list
+ margin-right: auto;
+ margin-left: auto;
+}
diff --git a/src/scss/scss/mixins/_deprecate.scss b/src/scss/scss/mixins/_deprecate.scss
new file mode 100644
index 000000000..9807ffffa
--- /dev/null
+++ b/src/scss/scss/mixins/_deprecate.scss
@@ -0,0 +1,12 @@
+@use "../variables" as *;
+
+// Deprecate mixin
+//
+// This mixin can be used to deprecate mixins or functions.
+// `$enable-deprecation-messages` is a global variable, `$ignore-warning` is a variable that can be passed to
+// some deprecated mixins to suppress the warning (for example if the mixin is still be used in the current version of Bootstrap)
+@mixin deprecate($name, $deprecate-version, $remove-version, $ignore-warning: false) {
+ @if ($enable-deprecation-messages != false and $ignore-warning != true) {
+ @warn "#{$name} has been deprecated as of #{$deprecate-version}. It will be removed entirely in #{$remove-version}.";
+ }
+}
diff --git a/src/scss/scss/mixins/_forms.scss b/src/scss/scss/mixins/_forms.scss
new file mode 100644
index 000000000..c374d615c
--- /dev/null
+++ b/src/scss/scss/mixins/_forms.scss
@@ -0,0 +1,170 @@
+@use "../functions/escape-svg" as *;
+@use "../functions/math" as *;
+@use "../mixins/border-radius" as *;
+@use "../mixins/ltr-rtl" as *;
+@use "../vendor/rfs" as *;
+@use "../variables" as *;
+
+// This mixin uses an `if()` technique to be compatible with Dart Sass
+// See https://github.com/sass/sass/issues/1873#issuecomment-152293725 for more details
+
+// scss-docs-start form-validation-mixins
+@mixin form-validation-state-selector($state) {
+ @if ($state == "valid" or $state == "invalid") {
+ .was-validated #{if(&, "&", "")}:#{$state},
+ #{if(&, "&", "")}.is-#{$state} {
+ @content;
+ }
+ } @else {
+ #{if(&, "&", "")}.is-#{$state} {
+ @content;
+ }
+ }
+}
+
+@mixin form-validation-state(
+ $state,
+ $color,
+ $icon,
+ $tooltip-color: color-contrast($color, $color-contrast-dark, $color-contrast-light, $white, $black, $min-contrast-ratio),
+ $tooltip-bg-color: rgba($color, $form-feedback-tooltip-opacity),
+ $focus-box-shadow: 0 0 $input-btn-focus-blur $input-focus-width rgba($color, $input-btn-focus-color-opacity),
+ $border-color: $color
+) {
+ .#{$state}-feedback {
+ display: none;
+ width: 100%;
+ margin-top: $form-feedback-margin-top;
+ @include font-size($form-feedback-font-size);
+ font-style: $form-feedback-font-style;
+ color: $color;
+ }
+
+ .#{$state}-tooltip {
+ position: absolute;
+ top: 100%;
+ z-index: 5;
+ display: none;
+ max-width: 100%; // Contain to parent when possible
+ padding: $form-feedback-tooltip-padding-y $form-feedback-tooltip-padding-x;
+ margin-top: .1rem;
+ @include font-size($form-feedback-tooltip-font-size);
+ line-height: $form-feedback-tooltip-line-height;
+ color: $tooltip-color;
+ background-color: $tooltip-bg-color;
+ @include border-radius($form-feedback-tooltip-border-radius);
+ }
+
+ @include form-validation-state-selector($state) {
+ ~ .#{$state}-feedback,
+ ~ .#{$state}-tooltip {
+ display: block;
+ }
+ }
+
+ .form-control {
+ @include form-validation-state-selector($state) {
+ border-color: $border-color;
+
+ @if $enable-validation-icons {
+ padding-inline-end: $input-height-inner;
+ background-image: escape-svg($icon);
+ background-repeat: no-repeat;
+ @include ltr-rtl-value-only("background-position", right $input-height-inner-quarter center, left $input-height-inner-quarter center);
+ background-size: $input-height-inner-half $input-height-inner-half;
+ }
+
+ &:focus {
+ border-color: $border-color;
+ @if $enable-shadows {
+ @include box-shadow($input-box-shadow, $focus-box-shadow);
+ } @else {
+ // Avoid using mixin so we can pass custom focus shadow properly
+ box-shadow: $focus-box-shadow;
+ }
+ }
+ }
+ }
+
+ // stylelint-disable-next-line selector-no-qualifying-type
+ textarea.form-control {
+ @include form-validation-state-selector($state) {
+ @if $enable-validation-icons {
+ padding-inline-end: $input-height-inner;
+ @include ltr-rtl-value-only("background-position", top $input-height-inner-quarter right $input-height-inner-quarter, top $input-height-inner-quarter left $input-height-inner-quarter);
+ }
+ }
+ }
+
+ .form-select {
+ @include form-validation-state-selector($state) {
+ border-color: $border-color;
+
+ @if $enable-validation-icons {
+ &:not([multiple]):not([size]),
+ &:not([multiple])[size="1"] {
+ --#{$prefix}form-select-bg-icon: #{escape-svg($icon)};
+ padding-inline-end: $form-select-feedback-icon-padding-end;
+ @include ltr-rtl-value-only("background-position", #{$form-select-bg-position, $form-select-feedback-icon-position});
+ background-size: $form-select-bg-size, $form-select-feedback-icon-size;
+ }
+ }
+
+ &:focus {
+ border-color: $border-color;
+ @if $enable-shadows {
+ @include box-shadow($form-select-box-shadow, $focus-box-shadow);
+ } @else {
+ // Avoid using mixin so we can pass custom focus shadow properly
+ box-shadow: $focus-box-shadow;
+ }
+ }
+ }
+ }
+
+ .form-control-color {
+ @include form-validation-state-selector($state) {
+ @if $enable-validation-icons {
+ width: add($form-color-width, $input-height-inner);
+ }
+ }
+ }
+
+ .form-check-input {
+ @include form-validation-state-selector($state) {
+ border-color: $border-color;
+
+ &:checked {
+ background-color: $color;
+ }
+
+ &:focus {
+ box-shadow: $focus-box-shadow;
+ }
+
+ ~ .form-check-label {
+ color: $color;
+ }
+ }
+ }
+ .form-check-inline .form-check-input {
+ ~ .#{$state}-feedback {
+ margin-inline-start: .5em;
+ }
+ }
+
+ .input-group {
+ > .form-control:not(:focus),
+ > .form-select:not(:focus),
+ > .form-floating:not(:focus-within) {
+ @include form-validation-state-selector($state) {
+ @if $state == "valid" {
+ z-index: 3;
+ } @else if $state == "invalid" {
+ z-index: 4;
+ }
+ }
+ }
+ }
+}
+// scss-docs-end form-validation-mixins
diff --git a/src/scss/scss/mixins/_gradients.scss b/src/scss/scss/mixins/_gradients.scss
new file mode 100644
index 000000000..8ac4e7afa
--- /dev/null
+++ b/src/scss/scss/mixins/_gradients.scss
@@ -0,0 +1,49 @@
+@use "../variables" as *;
+
+// Gradients
+
+// scss-docs-start gradient-bg-mixin
+@mixin gradient-bg($color: null) {
+ background-color: $color;
+
+ @if $enable-gradients {
+ background-image: var(--#{$prefix}gradient);
+ }
+}
+// scss-docs-end gradient-bg-mixin
+
+// scss-docs-start gradient-mixins
+// Horizontal gradient, from left to right
+//
+// Creates two color stops, start and end, by specifying a color and position for each color stop.
+@mixin gradient-x($start-color: $gray-700, $end-color: $gray-800, $start-percent: 0%, $end-percent: 100%) {
+ background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent);
+}
+
+// Vertical gradient, from top to bottom
+//
+// Creates two color stops, start and end, by specifying a color and position for each color stop.
+@mixin gradient-y($start-color: $gray-700, $end-color: $gray-800, $start-percent: null, $end-percent: null) {
+ background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent);
+}
+
+@mixin gradient-directional($start-color: $gray-700, $end-color: $gray-800, $deg: 45deg) {
+ background-image: linear-gradient($deg, $start-color, $end-color);
+}
+
+@mixin gradient-x-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) {
+ background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color);
+}
+
+@mixin gradient-y-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) {
+ background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color);
+}
+
+@mixin gradient-radial($inner-color: $gray-700, $outer-color: $gray-800) {
+ background-image: radial-gradient(circle, $inner-color, $outer-color);
+}
+
+@mixin gradient-striped($color: rgba($white, .15), $angle: 45deg) {
+ background-image: linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent);
+}
+// scss-docs-end gradient-mixins
diff --git a/src/scss/scss/mixins/_grid.scss b/src/scss/scss/mixins/_grid.scss
new file mode 100644
index 000000000..ae42859ac
--- /dev/null
+++ b/src/scss/scss/mixins/_grid.scss
@@ -0,0 +1,220 @@
+@use "sass:map";
+@use "sass:math";
+@use "sass:meta";
+@use "breakpoints" as *;
+@use "../functions/math" as *;
+@use "../maps" as *;
+@use "../variables" as *;
+
+// Grid system
+//
+// Generate semantic grid columns with these mixins.
+
+@mixin make-row($gutter: $grid-gutter-width) {
+ --#{$prefix}gutter-x: #{$gutter};
+ --#{$prefix}gutter-y: 0;
+ display: flex;
+ flex-wrap: wrap;
+ // TODO: Revisit calc order after https://github.com/react-bootstrap/react-bootstrap/issues/6039 is fixed
+ margin-top: calc(-1 * var(--#{$prefix}gutter-y)); // stylelint-disable-line function-disallowed-list
+ margin-right: calc(-.5 * var(--#{$prefix}gutter-x)); // stylelint-disable-line function-disallowed-list
+ margin-left: calc(-.5 * var(--#{$prefix}gutter-x)); // stylelint-disable-line function-disallowed-list
+}
+
+@mixin make-col-ready($include-column-box-sizing: false) {
+ // Add box sizing if only the grid is loaded
+ box-sizing: if(meta.variable-exists(include-column-box-sizing) and $include-column-box-sizing, border-box, null);
+ // Prevent columns from becoming too narrow when at smaller grid tiers by
+ // always setting `width: 100%;`. This works because we set the width
+ // later on to override this initial width.
+ flex-shrink: 0;
+ width: 100%;
+ max-width: 100%; // Prevent `.col-auto`, `.col` (& responsive variants) from breaking out the grid
+ padding-right: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list
+ padding-left: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list
+ margin-top: var(--#{$prefix}gutter-y);
+}
+
+@mixin make-col($size: false, $columns: $grid-columns) {
+ @if $size {
+ flex: 0 0 auto;
+ width: math.percentage(divide($size, $columns));
+
+ } @else {
+ flex: 1 1 0;
+ max-width: 100%;
+ }
+}
+
+@mixin make-col-auto() {
+ flex: 0 0 auto;
+ width: auto;
+}
+
+@mixin make-col-offset($size, $columns: $grid-columns) {
+ $num: divide($size, $columns);
+ margin-inline-start: if($num == 0, 0, math.percentage($num));
+}
+
+// Row columns
+//
+// Specify on a parent element(e.g., .row) to force immediate children into NN
+// number of columns. Supports wrapping to new lines, but does not do a Masonry
+// style grid.
+@mixin row-cols($count) {
+ > * {
+ flex: 0 0 auto;
+ width: math.percentage(divide(1, $count));
+ }
+}
+
+// Framework grid generation
+//
+// Used only by Bootstrap to generate the correct number of grid classes given
+// any value of `$grid-columns`.
+
+@mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) {
+ @each $breakpoint in map.keys($breakpoints) {
+ $infix: breakpoint-infix($breakpoint, $breakpoints);
+
+ @include media-breakpoint-up($breakpoint, $breakpoints) {
+ // Provide basic `.col-{bp}` classes for equal-width flexbox columns
+ .col#{$infix} {
+ flex: 1 0 0;
+ }
+
+ .row-cols#{$infix}-auto > * {
+ @include make-col-auto();
+ }
+
+ @if $grid-row-columns > 0 {
+ @for $i from 1 through $grid-row-columns {
+ .row-cols#{$infix}-#{$i} {
+ @include row-cols($i);
+ }
+ }
+ }
+
+ .col#{$infix}-auto {
+ @include make-col-auto();
+ }
+
+ @if $columns > 0 {
+ @for $i from 1 through $columns {
+ .col#{$infix}-#{$i} {
+ @include make-col($i, $columns);
+ }
+ }
+
+ // `$columns - 1` because offsetting by the width of an entire row isn't possible
+ @for $i from 0 through ($columns - 1) {
+ @if not ($infix == "" and $i == 0) { // Avoid emitting useless .offset-0
+ .offset#{$infix}-#{$i} {
+ @include make-col-offset($i, $columns);
+ }
+ }
+ }
+ }
+
+ // Gutters
+ //
+ // Make use of `.g-*`, `.gx-*` or `.gy-*` utilities to change spacing between the columns.
+ @each $key, $value in $gutters {
+ .g#{$infix}-#{$key},
+ .gx#{$infix}-#{$key} {
+ --#{$prefix}gutter-x: #{$value};
+ }
+
+ .g#{$infix}-#{$key},
+ .gy#{$infix}-#{$key} {
+ --#{$prefix}gutter-y: #{$value};
+ }
+ }
+ }
+ }
+}
+
+@mixin make-cssgrid($columns: $grid-columns, $breakpoints: $grid-breakpoints) {
+ @each $breakpoint in map.keys($breakpoints) {
+ $infix: breakpoint-infix($breakpoint, $breakpoints);
+
+ @include media-breakpoint-up($breakpoint, $breakpoints) {
+ @if $columns > 0 {
+ @for $i from 1 through $columns {
+ .g-col#{$infix}-#{$i} {
+ grid-column: auto / span $i;
+ }
+ }
+
+ // Start with `1` because `0` is an invalid value.
+ // Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible.
+ @for $i from 1 through ($columns - 1) {
+ .g-start#{$infix}-#{$i} {
+ grid-column-start: $i;
+ }
+ }
+ }
+ }
+ }
+}
+
+@mixin make-c-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) {
+ @each $breakpoint in map.keys($breakpoints) {
+ $infix: breakpoint-infix($breakpoint, $breakpoints);
+
+ @include container-breakpoint-up($breakpoint, $breakpoints) {
+ // Provide basic `.cq-col-{bp}` classes for equal-width flexbox columns
+ .cq-col#{$infix} {
+ flex: 1 0 0;
+ }
+
+ .cq-row-cols#{$infix}-auto > * {
+ @include make-col-auto();
+ }
+
+ @if $grid-row-columns > 0 {
+ @for $i from 1 through $grid-row-columns {
+ .cq-row-cols#{$infix}-#{$i} {
+ @include row-cols($i);
+ }
+ }
+ }
+
+ .cq-col#{$infix}-auto {
+ @include make-col-auto();
+ }
+
+ @if $columns > 0 {
+ @for $i from 1 through $columns {
+ .cq-col#{$infix}-#{$i} {
+ @include make-col($i, $columns);
+ }
+ }
+
+ // `$columns - 1` because offsetting by the width of an entire row isn't possible
+ @for $i from 0 through ($columns - 1) {
+ @if not ($infix == "" and $i == 0) { // Avoid emitting useless .offset-0
+ .cq-offset#{$infix}-#{$i} {
+ @include make-col-offset($i, $columns);
+ }
+ }
+ }
+ }
+
+ // Gutters
+ //
+ // Make use of `.cq-g-*`, `.cq-gx-*` or `.cq-gy-*` utilities to change spacing between the columns.
+ @each $key, $value in $gutters {
+ .cq-g#{$infix}-#{$key},
+ .cq-gx#{$infix}-#{$key} {
+ --#{$prefix}gutter-x: #{$value};
+ }
+
+ .cq-g#{$infix}-#{$key},
+ .cq-gy#{$infix}-#{$key} {
+ --#{$prefix}gutter-y: #{$value};
+ }
+ }
+ }
+ }
+}
diff --git a/src/scss/scss/mixins/_icon.scss b/src/scss/scss/mixins/_icon.scss
new file mode 100644
index 000000000..870dc25cf
--- /dev/null
+++ b/src/scss/scss/mixins/_icon.scss
@@ -0,0 +1,6 @@
+// Icon sizes
+@mixin icon-size($icon-size) {
+ width: $icon-size;
+ height: $icon-size;
+ font-size: $icon-size;
+}
diff --git a/src/scss/scss/mixins/_image.scss b/src/scss/scss/mixins/_image.scss
new file mode 100644
index 000000000..e1df779f8
--- /dev/null
+++ b/src/scss/scss/mixins/_image.scss
@@ -0,0 +1,16 @@
+// Image Mixins
+// - Responsive image
+// - Retina image
+
+
+// Responsive image
+//
+// Keep images from scaling beyond the width of their parents.
+
+@mixin img-fluid {
+ // Part 1: Set a maximum relative to the parent
+ max-width: 100%;
+ // Part 2: Override the height to auto, otherwise images will be stretched
+ // when setting a width and height attribute on the img element.
+ height: auto;
+}
diff --git a/src/scss/scss/mixins/_list-group.scss b/src/scss/scss/mixins/_list-group.scss
new file mode 100644
index 000000000..9859ab556
--- /dev/null
+++ b/src/scss/scss/mixins/_list-group.scss
@@ -0,0 +1,22 @@
+@use "sass:map";
+@use "../mixins/deprecate" as *;
+
+@include deprecate("`list-group-item-variant()`", "v4.3.0", "v6.0.0");
+
+// List Groups
+
+// scss-docs-start list-group-mixin
+@mixin list-group-item-variant($state, $variant) {
+ $background: map.get($variant, "bg");
+ $background-hover: map.get($variant, "bg-hover");
+ $color: contrast-ratio-correction(map.get($variant, "color"), map.get($variant, "bg"), $alert-color-scale, $state);
+
+ --#{$prefix}list-group-color: #{$color};
+ --#{$prefix}list-group-bg: #{$background};
+ --#{$prefix}list-group-hover-bg: #{$background-hover};
+ --#{$prefix}list-group-action-hover-color: #{$color};
+ --#{$prefix}list-group-action-active-color: #{$white};
+ --#{$prefix}list-group-action-active-bg: #{$color};
+ --#{$prefix}list-group-action-active-border-color: #{$color};
+}
+// scss-docs-end list-group-mixin
diff --git a/src/scss/scss/mixins/_lists.scss b/src/scss/scss/mixins/_lists.scss
new file mode 100644
index 000000000..27556e6f9
--- /dev/null
+++ b/src/scss/scss/mixins/_lists.scss
@@ -0,0 +1,7 @@
+// Lists
+
+// Unstyled keeps list items block level, just removes default browser padding and list-style
+@mixin list-unstyled {
+ padding-inline-start: 0;
+ list-style: none;
+}
diff --git a/src/scss/scss/mixins/_ltr-rtl.scss b/src/scss/scss/mixins/_ltr-rtl.scss
new file mode 100644
index 000000000..dcb91fc47
--- /dev/null
+++ b/src/scss/scss/mixins/_ltr-rtl.scss
@@ -0,0 +1,118 @@
+@use "sass:string";
+@use "../functions/str-replace" as *;
+@use "../variables" as *;
+
+// Applies styles only when LTR is enabled and the HTML does NOT have the "rtl"
+// attribute (i.e., left-to-right contexts).
+
+@mixin ltr {
+ @if $enable-ltr {
+ html:not([dir="rtl"]) & {
+ @content;
+ }
+ }
+}
+
+// Applies styles only when RTL is enabled and the element is within an RTL
+// context (i.e., the element is a descendant of an element with dir="rtl").
+
+@mixin rtl {
+ @if $enable-rtl {
+ *[dir="rtl"] & {
+ @content;
+ }
+ }
+}
+
+// This function takes an element (or string) and returns its "mirrored" version.
+// For example, it will replace "left" with "right" and vice versa.
+
+@function reflect($element) {
+ $string: #{$element};
+ @if string.index($string, "left") {
+ @return str-replace($string, "left", "right");
+ }
+ @if string.index($string, "right") {
+ @return str-replace($string, "right", "left");
+ }
+
+ @return string.unquote($string);
+}
+
+// Generates CSS for a given property with different values for LTR and RTL
+// contexts. Allows an optional custom property name and value for RTL.
+//
+// @param {String} $property - The CSS property for LTR context (e.g., margin-left).
+// @param {String} $value - The value for the property in LTR context.
+// @param {String} $property-rtl - Optional custom property name for RTL context.
+// @param {String} $value-rtl - Optional custom value for RTL context.
+// @param {Boolean} $important - Optional "!important" flag.
+
+@mixin ltr-rtl($property, $value, $property-rtl: null, $value-rtl: null, $important: false) {
+ $property-reflected: reflect($property);
+ $value-reflected: reflect($value);
+
+ @if $enable-ltr and $enable-rtl {
+ @include ltr() {
+ #{$property}: $value if($important, !important, null);
+ }
+ @include rtl() {
+ @if $value-rtl {
+ #{$property-reflected}: $value-rtl if($important, !important, null);
+ }
+ @else {
+ #{$property-reflected}: $value-reflected if($important, !important, null);
+ }
+ }
+ }
+ @else {
+ @if $enable-rtl {
+ @if $value-rtl {
+ #{$property-reflected}: $value-rtl if($important, !important, null);
+ }
+ @else {
+ #{$property-reflected}: $value-reflected if($important, !important, null);
+ }
+ }
+ @else {
+ #{$property}: $value if($important, !important, null);
+ }
+ }
+}
+
+// Similar to ltr-rtl but assumes the property name is the same for both
+// contexts. It only mirrors the value for RTL if a custom RTL value is not
+// provided.
+//
+// @param {String} $property - The CSS property to apply.
+// @param {String} $value - The value for the property in LTR context.
+// @param {String} $value-rtl - Optional custom value for RTL context.
+// @param {Boolean} $important - Optional "!important" flag.
+
+@mixin ltr-rtl-value-only($property, $value, $value-rtl: null, $important: false) {
+ $value-reflected: reflect($value);
+
+ @if $enable-ltr and $enable-rtl {
+ @include ltr() {
+ #{$property}: $value if($important, !important, null);
+ }
+ @include rtl() {
+ @if $value-rtl {
+ #{$property}: $value-rtl if($important, !important, null);
+ } @else {
+ #{$property}: $value-reflected if($important, !important, null);
+ }
+ }
+ }
+ @else {
+ @if $enable-rtl {
+ @if $value-rtl {
+ #{$property}: $value-rtl if($important, !important, null);
+ } @else {
+ #{$property}: $value-reflected if($important, !important, null);
+ }
+ } @else {
+ #{$property}: $value if($important, !important, null);
+ }
+ }
+}
diff --git a/src/scss/scss/mixins/_pagination.scss b/src/scss/scss/mixins/_pagination.scss
new file mode 100644
index 000000000..31a67f913
--- /dev/null
+++ b/src/scss/scss/mixins/_pagination.scss
@@ -0,0 +1,13 @@
+@use "../vendor/rfs" as *;
+@use "../variables" as *;
+
+// Pagination
+
+// scss-docs-start pagination-mixin
+@mixin pagination-size($padding-y, $padding-x, $font-size, $border-radius) {
+ --#{$prefix}pagination-padding-x: #{$padding-x};
+ --#{$prefix}pagination-padding-y: #{$padding-y};
+ @include rfs($font-size, --#{$prefix}pagination-font-size);
+ --#{$prefix}pagination-border-radius: #{$border-radius};
+}
+// scss-docs-end pagination-mixin
diff --git a/src/scss/scss/mixins/_reset-text.scss b/src/scss/scss/mixins/_reset-text.scss
new file mode 100644
index 000000000..769368a17
--- /dev/null
+++ b/src/scss/scss/mixins/_reset-text.scss
@@ -0,0 +1,18 @@
+@use "../variables" as *;
+
+@mixin reset-text {
+ font-family: $font-family-base;
+ // We deliberately do NOT reset font-size or overflow-wrap / word-wrap.
+ font-style: normal;
+ font-weight: $font-weight-normal;
+ line-height: $line-height-base;
+ text-align: start;
+ text-decoration: none;
+ text-shadow: none;
+ text-transform: none;
+ letter-spacing: normal;
+ word-break: normal;
+ white-space: normal;
+ word-spacing: normal;
+ line-break: auto;
+}
diff --git a/src/scss/scss/mixins/_resize.scss b/src/scss/scss/mixins/_resize.scss
new file mode 100644
index 000000000..66f233a63
--- /dev/null
+++ b/src/scss/scss/mixins/_resize.scss
@@ -0,0 +1,6 @@
+// Resize anything
+
+@mixin resizable($direction) {
+ overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible`
+ resize: $direction; // Options: horizontal, vertical, both
+}
diff --git a/src/scss/scss/mixins/_table-variants.scss b/src/scss/scss/mixins/_table-variants.scss
new file mode 100644
index 000000000..77b47da85
--- /dev/null
+++ b/src/scss/scss/mixins/_table-variants.scss
@@ -0,0 +1,30 @@
+@use "sass:color";
+@use "sass:math";
+@use "../functions/color-contrast" as *;
+@use "../functions/contrast-ratio" as *;
+@use "../variables" as *;
+
+// scss-docs-start table-variant
+@mixin table-variant($state, $background) {
+ .table-#{$state} {
+ $color: color-contrast(opaque($body-bg, $background));
+ $hover-bg: color.mix($color, $background, math.percentage($table-hover-bg-factor));
+ $striped-bg: color.mix($color, $background, math.percentage($table-striped-bg-factor));
+ $active-bg: color.mix($color, $background, math.percentage($table-active-bg-factor));
+ $table-border-color: color.mix($color, $background, math.percentage($table-border-factor));
+
+ --#{$prefix}table-color: #{$color};
+ --#{$prefix}table-bg: #{$background};
+ --#{$prefix}table-border-color: #{$table-border-color};
+ --#{$prefix}table-striped-bg: #{$striped-bg};
+ --#{$prefix}table-striped-color: #{color-contrast($striped-bg)};
+ --#{$prefix}table-active-bg: #{$active-bg};
+ --#{$prefix}table-active-color: #{color-contrast($active-bg)};
+ --#{$prefix}table-hover-bg: #{$hover-bg};
+ --#{$prefix}table-hover-color: #{color-contrast($hover-bg)};
+
+ color: var(--#{$prefix}table-color);
+ border-color: var(--#{$prefix}table-border-color);
+ }
+}
+// scss-docs-end table-variant
diff --git a/src/scss/scss/mixins/_text-truncate.scss b/src/scss/scss/mixins/_text-truncate.scss
new file mode 100644
index 000000000..3504bb1aa
--- /dev/null
+++ b/src/scss/scss/mixins/_text-truncate.scss
@@ -0,0 +1,8 @@
+// Text truncate
+// Requires inline-block or block for proper styling
+
+@mixin text-truncate() {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
diff --git a/src/scss/scss/mixins/_transition.scss b/src/scss/scss/mixins/_transition.scss
new file mode 100644
index 000000000..e4d6bfd28
--- /dev/null
+++ b/src/scss/scss/mixins/_transition.scss
@@ -0,0 +1,29 @@
+// stylelint-disable property-disallowed-list
+@use "sass:list";
+@use "../variables" as *;
+
+@mixin transition($transition...) {
+ @if list.length($transition) == 0 {
+ $transition: $transition-base;
+ }
+
+ @if list.length($transition) > 1 {
+ @each $value in $transition {
+ @if $value == null or $value == none {
+ @warn "The keyword 'none' or 'null' must be used as a single argument.";
+ }
+ }
+ }
+
+ @if $enable-transitions {
+ @if list.nth($transition, 1) != null {
+ transition: $transition;
+ }
+
+ @if $enable-reduced-motion and list.nth($transition, 1) != null and list.nth($transition, 1) != none {
+ @media (prefers-reduced-motion: reduce) {
+ transition: none;
+ }
+ }
+ }
+}
diff --git a/src/scss/scss/mixins/_utilities.import.scss b/src/scss/scss/mixins/_utilities.import.scss
new file mode 100644
index 000000000..d94f1f77b
--- /dev/null
+++ b/src/scss/scss/mixins/_utilities.import.scss
@@ -0,0 +1 @@
+@forward "utilities";
diff --git a/src/scss/scss/mixins/_utilities.scss b/src/scss/scss/mixins/_utilities.scss
new file mode 100644
index 000000000..c4d6b2124
--- /dev/null
+++ b/src/scss/scss/mixins/_utilities.scss
@@ -0,0 +1,140 @@
+@use "sass:list";
+@use "sass:map";
+@use "sass:meta";
+@use "sass:string";
+@use "../variables" as *;
+@use "../vendor/rfs" as *;
+@use "ltr-rtl" as *;
+
+// Generate selectors for theme variants
+@function theme-prefix($theme, $selector) {
+ @return #{"[data" + $data-infix + "theme="$theme + "] ." + $theme + "\\:" + $selector + ", [data" + $data-infix + "theme="$theme + "] ." + $theme + "\\:" + $selector + ":not([class*='#{$theme}:'])"};
+}
+
+// Utility generator
+// Used to generate utilities & print utilities
+@mixin generate-utility($utility, $infix: "", $is-rfs-media-query: false) {
+ $values: map.get($utility, values);
+
+ // If the values are a list or string, convert it into a map
+ @if meta.type-of($values) == "string" or meta.type-of(list.nth($values, 1)) != "list" {
+ $values: list.zip($values, $values);
+ }
+
+ @each $key, $value in $values {
+ $properties: map.get($utility, property);
+
+ // Multiple properties are possible, for example with vertical or horizontal margins or paddings
+ @if meta.type-of($properties) == "string" {
+ $properties: list.append((), $properties);
+ }
+
+ // Use custom class if present
+ $property-class: if(map.has-key($utility, class), map.get($utility, class), list.nth($properties, 1));
+ $property-class: if($property-class == null, "", $property-class);
+
+ // Use custom CSS variable name if present, otherwise default to `class`
+ $css-variable-name: if(map.has-key($utility, css-variable-name), map.get($utility, css-variable-name), map.get($utility, class));
+
+ // State params to generate pseudo-classes
+ $state: if(map.has-key($utility, state), map.get($utility, state), ());
+
+ $infix: if($property-class == "" and string.slice($infix, 1, 1) == "-", string.slice($infix, 2), $infix);
+
+ // Don't prefix if value key is null (e.g. with shadow class)
+ $property-class-modifier: if($key, if($property-class == "" and $infix == "", "", "-") + $key, "");
+
+ @if map.get($utility, rfs) {
+ // Inside the media query
+ @if $is-rfs-media-query {
+ $val: rfs-value($value);
+
+ // Do not render anything if fluid and non fluid values are the same
+ $value: if($val == rfs-fluid-value($value), null, $val);
+ }
+ @else {
+ $value: rfs-fluid-value($value);
+ }
+ }
+
+ $is-css-var: map.get($utility, css-var);
+ $is-dark-mode: map.get($utility, dark-mode);
+ $is-local-vars: map.get($utility, local-vars);
+ $is-rtl: map.get($utility, rtl);
+
+ @if $value != null {
+ @if $is-rtl == false {
+ /* rtl:begin:remove */
+ }
+
+ @if $is-css-var {
+ @if $enable-dark-mode and $is-dark-mode {
+ #{theme-prefix("dark", "#{$property-class + $infix + $property-class-modifier}")},
+ .#{$property-class + $infix + $property-class-modifier} {
+ --#{$prefix}#{$css-variable-name}: #{$value};
+ }
+ } @else {
+ .#{$property-class + $infix + $property-class-modifier} {
+ --#{$prefix}#{$css-variable-name}: #{$value};
+ }
+ }
+
+ @each $pseudo in $state {
+ .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} {
+ --#{$prefix}#{$css-variable-name}: #{$value};
+ }
+ }
+ } @else {
+ @if $enable-dark-mode and $is-dark-mode {
+ #{theme-prefix("dark", "#{$property-class + $infix + $property-class-modifier}")},
+ .#{$property-class + $infix + $property-class-modifier} {
+ @each $property in $properties {
+ @if $is-local-vars {
+ @each $local-var, $variable in $is-local-vars {
+ --#{$prefix}#{$local-var}: #{$variable};
+ }
+ }
+ #{$property}: $value if($enable-important-utilities, !important, null);
+ }
+ }
+ } @else {
+ .#{$property-class + $infix + $property-class-modifier} {
+ @each $property in $properties {
+ @if $is-local-vars {
+ @each $local-var, $variable in $is-local-vars {
+ --#{$prefix}#{$local-var}: #{$variable};
+ }
+ }
+ @if $is-rtl == true {
+ @if (meta.type-of($value) == "map") {
+ @include ltr-rtl($property, map.get($value, "ltr"), null, map.get($value, "rtl"), if($enable-important-utilities, !important, null));
+ } @else {
+ @include ltr-rtl($property, $value, null, null, if($enable-important-utilities, !important, null));
+ }
+ } @else {
+ #{$property}: $value if($enable-important-utilities, !important, null);
+ }
+ }
+ }
+ }
+
+ @each $pseudo in $state {
+ .#{$property-class + $infix + $property-class-modifier}-#{$pseudo}:#{$pseudo} {
+ @each $property in $properties {
+ @if $is-local-vars {
+ @each $local-var, $variable in $is-local-vars {
+ --#{$prefix}#{$local-var}: #{$variable};
+ }
+ }
+ #{$property}: $value if($enable-important-utilities, !important, null);
+ }
+ }
+ }
+ }
+
+ @if $is-rtl == false {
+ /* rtl:end:remove */
+ }
+ }
+ }
+}
diff --git a/src/scss/scss/mixins/_visually-hidden.scss b/src/scss/scss/mixins/_visually-hidden.scss
new file mode 100644
index 000000000..388916ccf
--- /dev/null
+++ b/src/scss/scss/mixins/_visually-hidden.scss
@@ -0,0 +1,33 @@
+// stylelint-disable declaration-no-important
+
+// Hide content visually while keeping it accessible to assistive technologies
+//
+// See: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/
+// See: https://kittygiraudel.com/2016/10/13/css-hide-and-seek/
+
+@mixin visually-hidden() {
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ margin: -1px !important; // Fix for https://github.com/twbs/bootstrap/issues/25686
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ white-space: nowrap !important;
+ border: 0 !important;
+
+ // Fix for positioned table caption that could become anonymous cells
+ &:not(caption) {
+ position: absolute !important;
+ }
+}
+
+// Use to only display content when it's focused, or one of its child elements is focused
+// (i.e. when focus is within the element/container that the class was applied to)
+//
+// Useful for "Skip to main content" links; see https://www.w3.org/WAI/WCAG22/Techniques/general/G1.html
+
+@mixin visually-hidden-focusable() {
+ &:not(:focus):not(:focus-within) {
+ @include visually-hidden();
+ }
+}
diff --git a/src/scss/scss/sidebar/_sidebar-narrow.scss b/src/scss/scss/sidebar/_sidebar-narrow.scss
new file mode 100644
index 000000000..65aa436fb
--- /dev/null
+++ b/src/scss/scss/sidebar/_sidebar-narrow.scss
@@ -0,0 +1,102 @@
+@use "../mixins/breakpoints" as *;
+@use "../mixins/ltr-rtl" as *;
+@use "../variables" as *;
+@use "sidebar" as *;
+
+.sidebar-narrow {
+ // scss-docs-start sidebar-narrow-css-vars
+ --#{$prefix}sidebar-narrow-width: #{$sidebar-narrow-width};
+ // scss-docs-end sidebar-narrow-css-vars
+
+ @include media-breakpoint-up($mobile-breakpoint) {
+ flex: 0 0 var(--#{$prefix}sidebar-narrow-width);
+ width: var(--#{$prefix}sidebar-narrow-width);
+ padding-bottom: var(--#{$prefix}sidebar-toggler-height);
+ overflow: visible;
+
+ .sidebar-brand-full {
+ display: none;
+ }
+
+ .sidebar-brand-narrow {
+ display: block;
+ }
+
+ .sidebar-header {
+ justify-content: center;
+ padding-right: 0;
+ padding-left: 0;
+ }
+
+ .sidebar-nav {
+ --#{$prefix}sidebar-nav-link-padding-x: #{$sidebar-narrow-nav-link-padding-x};
+ --#{$prefix}sidebar-nav-link-padding-y: #{$sidebar-narrow-nav-link-padding-y};
+ }
+
+ .nav-link {
+ overflow: hidden;
+ }
+
+ .nav-icon {
+ flex: 0 0 calc(var(--#{$prefix}sidebar-narrow-width) - (var(--#{$prefix}sidebar-nav-padding-x) * 2) - (var(--#{$prefix}sidebar-nav-link-padding-x) * 2)); // stylelint-disable-line function-disallowed-list
+ }
+
+ .d-narrow-none,
+ .nav-label,
+ .nav-title,
+ .nav-group-items,
+ .nav-group.show .nav-group-items,
+ .sidebar-form {
+ height: 0 !important; // stylelint-disable-line declaration-no-important
+ padding: 0 !important; // stylelint-disable-line declaration-no-important
+ margin: 0 !important; // stylelint-disable-line declaration-no-important
+ visibility: hidden;
+ opacity: 0;
+ }
+
+ .sidebar-toggler::before {
+ @include ltr-rtl("transform", rotate(-180deg), null, rotate(0deg));
+ }
+
+ &.sidebar-end .sidebar-toggler::before {
+ transform: rotate(0deg);
+ }
+ }
+}
+
+.sidebar-narrow-unfoldable {
+ // scss-docs-start sidebar-narrow-unfoldable-css-vars
+ --#{$prefix}sidebar-narrow-unfoldable-box-shadow: #{$sidebar-narrow-unfoldable-box-shadow};
+ // scss-docs-end sidebar-narrow-unfoldable-css-vars
+
+ @extend .sidebar-fixed;
+
+ &:not(:hover) {
+ @extend .sidebar-narrow;
+ }
+
+ &:hover {
+ box-shadow: var(--#{$prefix}sidebar-narrow-unfoldable-box-shadow);
+ box-shadow: $box-shadow;
+
+ .sidebar-toggler::before {
+ @include ltr-rtl("transform", rotate(-180deg), null, rotate(0deg));
+ }
+
+ &.sidebar-end .sidebar-toggler::before {
+ transform: rotate(0deg);
+ }
+ }
+}
+
+.sidebar-narrow,
+.sidebar-narrow-unfoldable {
+ @include media-breakpoint-up($mobile-breakpoint) {
+ &:not(.sidebar-end):not(.hide) ~ * {
+ --#{$prefix}sidebar-occupy-start: #{$sidebar-narrow-width};
+ }
+ &.sidebar-end:not(.hide) ~ * {
+ --#{$prefix}sidebar-occupy-end: #{$sidebar-narrow-width};
+ }
+ }
+}
diff --git a/src/scss/scss/sidebar/_sidebar-nav.scss b/src/scss/scss/sidebar/_sidebar-nav.scss
new file mode 100644
index 000000000..ecb2e55c0
--- /dev/null
+++ b/src/scss/scss/sidebar/_sidebar-nav.scss
@@ -0,0 +1,272 @@
+// Sidebar navigation
+@use "../functions/escape-svg" as *;
+@use "../mixins/border-radius" as *;
+@use "../mixins/transition" as *;
+@use "../variables" as *;
+
+.sidebar-nav {
+ // scss-docs-start sidebar-nav-css-vars
+ --#{$prefix}sidebar-nav-padding-x: #{$sidebar-nav-padding-x};
+ --#{$prefix}sidebar-nav-padding-y: #{$sidebar-nav-padding-y};
+ --#{$prefix}sidebar-nav-gap: #{$sidebar-nav-gap};
+
+ --#{$prefix}sidebar-nav-title-padding-x: #{$sidebar-nav-title-padding-x};
+ --#{$prefix}sidebar-nav-title-padding-y: #{$sidebar-nav-title-padding-y};
+ --#{$prefix}sidebar-nav-title-margin-top: #{$sidebar-nav-title-margin-top};
+ --#{$prefix}sidebar-nav-title-color: #{$sidebar-nav-title-color};
+
+ --#{$prefix}sidebar-nav-link-padding-x: #{$sidebar-nav-link-padding-x};
+ --#{$prefix}sidebar-nav-link-padding-y: #{$sidebar-nav-link-padding-y};
+ --#{$prefix}sidebar-nav-link-color: #{$sidebar-nav-link-color};
+ --#{$prefix}sidebar-nav-link-bg: #{$sidebar-nav-link-bg};
+ --#{$prefix}sidebar-nav-link-border-color: #{$sidebar-nav-link-border-color};
+ --#{$prefix}sidebar-nav-link-border-radius: #{$sidebar-nav-link-border-radius};
+ --#{$prefix}sidebar-nav-link-border-width: #{$sidebar-nav-link-border-width};
+
+ --#{$prefix}sidebar-nav-link-active-color: #{$sidebar-nav-link-active-color};
+ --#{$prefix}sidebar-nav-link-active-bg: #{$sidebar-nav-link-active-bg};
+ --#{$prefix}sidebar-nav-link-disabled-color: #{$sidebar-nav-link-disabled-color};
+ --#{$prefix}sidebar-nav-link-hover-color: #{$sidebar-nav-link-hover-color};
+ --#{$prefix}sidebar-nav-link-hover-bg: #{$sidebar-nav-link-hover-bg};
+
+ --#{$prefix}sidebar-nav-link-icon-margin: #{$sidebar-nav-link-icon-margin};
+ --#{$prefix}sidebar-nav-link-icon-color: #{$sidebar-nav-link-icon-color};
+ --#{$prefix}sidebar-nav-link-icon-width: #{$sidebar-nav-link-icon-width};
+ --#{$prefix}sidebar-nav-link-icon-height: #{$sidebar-nav-link-icon-height};
+ --#{$prefix}sidebar-nav-link-icon-font-size: #{$sidebar-nav-link-icon-font-size};
+ --#{$prefix}sidebar-nav-link-active-icon-color: #{$sidebar-nav-link-active-icon-color};
+ --#{$prefix}sidebar-nav-link-disabled-icon-color: #{$sidebar-nav-link-disabled-icon-color};
+ --#{$prefix}sidebar-nav-link-hover-icon-color: #{$sidebar-nav-link-hover-icon-color};
+
+ --#{$prefix}sidebar-nav-link-icon-bullet-size: #{$sidebar-nav-link-icon-bullet-size};
+ --#{$prefix}sidebar-nav-link-icon-bullet-bg: #{$sidebar-nav-link-icon-bullet-bg};
+ --#{$prefix}sidebar-nav-link-icon-bullet-border-width: #{$sidebar-nav-link-icon-bullet-border-width};
+ --#{$prefix}sidebar-nav-link-icon-bullet-border-radius: #{$sidebar-nav-link-icon-bullet-border-radius};
+ --#{$prefix}sidebar-nav-link-icon-bullet-border-color: #{$sidebar-nav-link-icon-bullet-border-color};
+ --#{$prefix}sidebar-nav-link-active-icon-bullet-bg: #{$sidebar-nav-link-active-icon-bullet-bg};
+ --#{$prefix}sidebar-nav-link-active-icon-bullet-border-color: #{$sidebar-nav-link-active-icon-bullet-border-color};
+ --#{$prefix}sidebar-nav-link-disabled-icon-bullet-bg: #{$sidebar-nav-link-disabled-icon-bullet-bg};
+ --#{$prefix}sidebar-nav-link-disabled-icon-bullet-border-color: #{$sidebar-nav-link-disabled-icon-bullet-border-color};
+ --#{$prefix}sidebar-nav-link-hover-icon-bullet-bg: #{$sidebar-nav-link-hover-icon-bullet-bg};
+ --#{$prefix}sidebar-nav-link-hover-icon-bullet-border-color: #{$sidebar-nav-link-hover-icon-bullet-border-color};
+
+ --#{$prefix}sidebar-nav-group-bg: #{$sidebar-nav-group-bg};
+ --#{$prefix}sidebar-nav-group-border-width: #{$sidebar-nav-group-border-width};
+ --#{$prefix}sidebar-nav-group-border-radius: #{$sidebar-nav-group-border-radius};
+ --#{$prefix}sidebar-nav-group-border-color: #{$sidebar-nav-group-border-color};
+ --#{$prefix}sidebar-nav-group-items-padding-y: #{$sidebar-nav-group-items-padding-y};
+ --#{$prefix}sidebar-nav-group-items-padding-x: #{$sidebar-nav-group-items-padding-x};
+ --#{$prefix}sidebar-nav-group-indicator-color: #{$sidebar-nav-group-indicator-color};
+ --#{$prefix}sidebar-nav-group-indicator-icon: #{escape-svg($sidebar-nav-group-indicator-icon)};
+ --#{$prefix}sidebar-nav-group-indicator-hover-color: #{$sidebar-nav-group-indicator-hover-color};
+ --#{$prefix}sidebar-nav-group-indicator-hover-icon: #{escape-svg($sidebar-nav-group-indicator-hover-icon)};
+ --#{$prefix}sidebar-nav-group-toggle-show-color: #{$sidebar-nav-group-toggle-show-color};
+ // scss-docs-end sidebar-nav-css-vars
+
+ position: relative;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ padding: var(--#{$prefix}sidebar-nav-padding-y) var(--#{$prefix}sidebar-nav-padding-x);
+ margin-bottom: 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+ list-style: none;
+
+ .nav-item + .nav-item,
+ .nav-item + .nav-group,
+ .nav-group + .nav-item {
+ margin-top: var(--#{$prefix}sidebar-nav-gap);
+ }
+
+ .nav-title {
+ padding: var(--#{$prefix}sidebar-nav-title-padding-y) var(--#{$prefix}sidebar-nav-title-padding-x);
+ margin-top: var(--#{$prefix}sidebar-nav-title-margin-top);
+ font-size: 80%;
+ font-weight: 700;
+ color: var(--#{$prefix}sidebar-nav-title-color);
+ text-transform: uppercase;
+ @include transition($sidebar-nav-title-transition);
+ }
+
+ .nav-link {
+ display: flex;
+ flex: 1;
+ align-items: center;
+ padding: var(--#{$prefix}sidebar-nav-link-padding-y) var(--#{$prefix}sidebar-nav-link-padding-x);
+ color: var(--#{$prefix}sidebar-nav-link-color);
+ text-decoration: none;
+ white-space: nowrap;
+ background: var(--#{$prefix}sidebar-nav-link-bg);
+ border: var(--#{$prefix}sidebar-nav-link-border-width) solid var(--#{$prefix}sidebar-nav-link-border-color);
+ @include border-radius(var(--#{$prefix}sidebar-nav-link-border-radius));
+ @include transition($sidebar-nav-link-transition);
+
+ &.active {
+ color: var(--#{$prefix}sidebar-nav-link-active-color);
+ background: var(--#{$prefix}sidebar-nav-link-active-bg);
+
+ .nav-icon {
+ color: var(--#{$prefix}sidebar-nav-link-active-icon-color);
+ }
+
+ .nav-icon-bullet {
+ background: var(--#{$prefix}sidebar-link-active-icon-bullet-bg);
+ border-color: var(--#{$prefix}sidebar-link-active-icon-bullet-border-color);
+ }
+ }
+
+ &.disabled {
+ color: var(--#{$prefix}sidebar-nav-link-disabled-color);
+ pointer-events: none;
+ cursor: not-allowed;
+ background: transparent;
+
+ .nav-icon {
+ color: var(--#{$prefix}sidebar-nav-link-disabled-icon-color);
+ }
+
+ .nav-icon-bullet {
+ background: var(--#{$prefix}sidebar-link-disabled-icon-bullet-bg);
+ border-color: var(--#{$prefix}sidebar-link-disabled-icon-bullet-border-color);
+ }
+
+ &:hover {
+ color: var(--#{$prefix}sidebar-nav-link-disabled-color);
+
+ .nav-icon {
+ color: var(--#{$prefix}sidebar-nav-link-disabled-icon-color);
+ }
+
+ .nav-icon-bullet {
+ background: var(--#{$prefix}sidebar-link-disabled-icon-bullet-bg);
+ border-color: var(--#{$prefix}sidebar-link-disabled-icon-bullet-border-color);
+ }
+
+ &.nav-dropdown-toggle::after {
+ background-color: var(--#{$prefix}sidebar-nav-group-indicator-hover-color);
+ mask-image: var(--#{$prefix}sidebar-nav-group-indicator-hover-icon);
+ }
+ }
+ }
+
+ @media (hover: hover), (-ms-high-contrast: none) {
+ &:hover {
+ color: var(--#{$prefix}sidebar-nav-link-hover-color);
+ text-decoration: none;
+ background: var(--#{$prefix}sidebar-nav-link-hover-bg);
+
+ .nav-icon {
+ color: var(--#{$prefix}sidebar-nav-link-hover-icon-color);
+ }
+
+ .nav-icon-bullet {
+ background: var(--#{$prefix}sidebar-link-hover-icon-bullet-bg);
+ border-color: var(--#{$prefix}sidebar-link-hover-icon-bullet-border-color);
+ }
+
+ &.nav-group-toggle::after {
+ background-color: var(--#{$prefix}sidebar-nav-group-indicator-hover-color);
+ mask-image: var(--#{$prefix}sidebar-nav-group-indicator-hover-icon);
+ }
+ }
+ }
+ }
+
+ .nav-icon {
+ display: flex;
+ flex: 0 0 var(--#{$prefix}sidebar-nav-link-icon-width);
+ align-items: center;
+ justify-content: center;
+ height: var(--#{$prefix}sidebar-nav-link-icon-height);
+ margin-inline-end: var(--#{$prefix}sidebar-nav-link-icon-margin);
+ font-size: var(--#{$prefix}sidebar-nav-link-icon-font-size);
+ color: var(--#{$prefix}sidebar-nav-link-icon-color);
+ text-align: center;
+ pointer-events: none;
+ fill: currentcolor;
+ @include transition(inherit);
+ }
+
+ .nav-icon-bullet {
+ display: inline-block;
+ width: var(--#{$prefix}sidebar-nav-link-icon-bullet-size);
+ height: var(--#{$prefix}sidebar-nav-link-icon-bullet-size);
+ background: var(--#{$prefix}sidebar-nav-link-icon-bullet-bg);
+ border: var(--#{$prefix}sidebar-nav-link-icon-bullet-border-width) solid var(--#{$prefix}sidebar-nav-link-icon-bullet-border-color);
+ border-radius: var(--#{$prefix}sidebar-nav-link-icon-bullet-border-radius); // stylelint-disable-line property-disallowed-list
+ }
+
+ // stylelint-disable-next-line selector-no-qualifying-type
+ svg.nav-icon {
+ overflow: hidden; // fix chrome 105+ width issue
+ }
+
+ .nav-group {
+ position: relative;
+ border: var(--#{$prefix}sidebar-nav-group-border-width) solid var(--#{$prefix}sidebar-nav-group-border-color);
+ @include border-radius(var(--#{$prefix}sidebar-nav-group-border-radius));
+ @include transition($sidebar-nav-group-transition);
+
+ .nav-group-items {
+ padding: var(--#{$prefix}sidebar-nav-group-items-padding-y) var(--#{$prefix}sidebar-nav-group-items-padding-x);
+ overflow: hidden;
+ @include transition($sidebar-nav-group-items-transition);
+ }
+
+ &:not(.show) .nav-group-items {
+ display: none;
+ }
+
+ &.show {
+ background: var(--#{$prefix}sidebar-nav-group-bg);
+
+ .nav-group-toggle {
+ color: var(--#{$prefix}sidebar-nav-group-toggle-show-color);
+ }
+
+ > .nav-group-toggle::after {
+ transform: rotate(180deg);
+ }
+
+ + .show {
+ margin-top: var(--#{$prefix}sidebar-nav-gap);
+ }
+ }
+ }
+
+ .nav-group-toggle {
+ cursor: pointer;
+
+ &::after {
+ display: block;
+ flex: 0 12px;
+ height: 12px;
+ margin-inline-start: auto;
+ content: "";
+ background-color: var(--#{$prefix}sidebar-nav-group-indicator-color);
+ mask-image: var(--#{$prefix}sidebar-nav-group-indicator-icon);
+ @include transition($sidebar-nav-group-indicator-transition);
+ }
+ }
+
+ .nav-group-items {
+ padding: 0;
+ list-style: none;
+
+ .nav-link {
+ padding-inline-start: calc(var(--#{$prefix}sidebar-nav-link-padding-x) + var(--#{$prefix}sidebar-nav-link-icon-width) + var(--#{$prefix}sidebar-nav-link-icon-margin)); // stylelint-disable-line function-disallowed-list
+ }
+
+ .nav-icon {
+ margin-inline-start: calc(-1 * (var(--#{$prefix}sidebar-nav-link-icon-width) + var(--#{$prefix}sidebar-nav-link-icon-margin))); // stylelint-disable-line function-disallowed-list
+ }
+ }
+
+ &.compact,
+ .compact {
+ .nav-link {
+ --#{$prefix}sidebar-nav-link-padding-y: #{$sidebar-compact-nav-link-padding-y};
+ }
+ }
+}
diff --git a/src/scss/scss/sidebar/_sidebar.scss b/src/scss/scss/sidebar/_sidebar.scss
new file mode 100644
index 000000000..b35e6aab8
--- /dev/null
+++ b/src/scss/scss/sidebar/_sidebar.scss
@@ -0,0 +1,278 @@
+// stylelint-disable function-disallowed-list
+@use "../functions/escape-svg" as *;
+@use "../mixins/backdrop" as *;
+@use "../mixins/border-radius" as *;
+@use "../mixins/breakpoints" as *;
+@use "../mixins/ltr-rtl" as *;
+@use "../mixins/transition" as *;
+@use "../variables" as *;
+@use "../variables-dark" as *;
+
+.sidebar {
+ // scss-docs-start sidebar-css-vars
+ --#{$prefix}sidebar-zindex: #{$zindex-sidebar};
+ --#{$prefix}sidebar-width: #{$sidebar-width};
+ --#{$prefix}sidebar-bg: #{$sidebar-bg};
+ --#{$prefix}sidebar-padding-x: #{$sidebar-padding-x};
+ --#{$prefix}sidebar-padding-y: #{$sidebar-padding-y};
+ --#{$prefix}sidebar-color: #{$sidebar-color};
+ --#{$prefix}sidebar-brand-color: #{$sidebar-brand-color};
+ --#{$prefix}sidebar-brand-bg: #{$sidebar-brand-bg};
+ // scss-docs-end sidebar-css-vars
+
+ position: relative;
+ display: flex;
+ flex: 0 0 var(--#{$prefix}sidebar-width);
+ flex-direction: column;
+ // put the nav on the left
+ order: -1;
+ width: var(--#{$prefix}sidebar-width);
+ color: var(--#{$prefix}sidebar-color);
+ background: var(--#{$prefix}sidebar-bg);
+ box-shadow: none;
+ @include transition($sidebar-transition);
+
+ &:not(.sidebar-end){
+ margin-inline-start: 0;
+ }
+
+ &.sidebar-end {
+ order: 99;
+ margin-inline-end: 0;
+ }
+
+ @include media-breakpoint-up($mobile-breakpoint) {
+ &:not(.hide):not(.sidebar-narrow):not(.sidebar-narrow-unfoldable):not(.sidebar-overlaid) {
+ &:not(.sidebar-end) ~ * {
+ --#{$prefix}sidebar-occupy-start: #{$sidebar-width};
+ }
+ &.sidebar-end ~ * {
+ --#{$prefix}sidebar-occupy-end: #{$sidebar-width};
+ }
+ }
+
+ &.hide {
+ &:not(.sidebar-end) {
+ margin-inline-start: calc(-1 * var(--#{$prefix}sidebar-width));
+ }
+ &.sidebar-end {
+ margin-inline-end: calc(-1 * var(--#{$prefix}sidebar-width));
+
+ }
+ }
+ }
+
+ @include media-breakpoint-down($mobile-breakpoint) {
+ // Some of our components use this property to detect if the sidebar has mobile behavior.
+ --#{$prefix}is-mobile: true;
+
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ z-index: var(--#{$prefix}sidebar-zindex);
+
+ &:not(.sidebar-end) {
+ inset-inline-start: 0;
+
+ &:not(.show) {
+ margin-inline-start: calc(-1 * var(--#{$prefix}sidebar-width));
+ }
+ }
+
+ &.sidebar-end {
+ inset-inline-end: 0;
+
+ &:not(.show) {
+ margin-inline-end: calc(-1 * var(--#{$prefix}sidebar-width));
+ }
+ }
+ }
+}
+
+.sidebar-fixed {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ z-index: var(--#{$prefix}sidebar-zindex);
+
+ &:not(.sidebar-end) {
+ inset-inline-start: 0;
+ }
+
+ &.sidebar-end {
+ inset-inline-end: 0;
+ }
+}
+
+.sidebar-overlaid {
+ // scss-docs-start sidebar-overlaid-css-vars
+ --#{$prefix}sidebar-overlaid-box-shadow: #{$sidebar-overlaid-box-shadow};
+ // scss-docs-end sidebar-overlaid-css-vars
+
+ @extend .sidebar-fixed;
+
+ &:not(.sidebar-end){
+ margin-inline-start: calc(-1 * var(--#{$prefix}sidebar-width));
+ }
+
+ &.sidebar-end {
+ margin-inline-end: calc(-1 * var(--#{$prefix}sidebar-width));
+ }
+
+ &.show {
+ box-shadow: var(--#{$prefix}sidebar-overlaid-box-shadow);
+
+ &:not(.sidebar-end) {
+ margin-inline-start: 0;
+ }
+
+ &.sidebar-end {
+ margin-inline-end: 0;
+ }
+ }
+}
+
+@each $width, $value in $sidebar-widths {
+ .sidebar-#{$width} {
+ --#{$prefix}sidebar-width: #{$value};
+
+ @include media-breakpoint-up($mobile-breakpoint) {
+ &:not(.hide):not(.sidebar-narrow):not(.sidebar-narrow-unfoldable):not(.sidebar-overlaid) {
+ &:not(.sidebar-end) ~ * {
+ --#{$prefix}sidebar-occupy-start: #{$value};
+ }
+ &.sidebar-end ~ * {
+ --#{$prefix}sidebar-occupy-end: #{$value};
+ }
+ }
+ }
+ }
+}
+
+.sidebar-brand {
+ color: var(--#{$prefix}sidebar-brand-color);
+ white-space: nowrap;
+
+ .sidebar-brand-narrow {
+ display: none;
+ }
+}
+
+.sidebar-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--#{$prefix}sidebar-padding-y) var(--#{$prefix}sidebar-padding-x);
+
+ .btn-close {
+ padding: calc(var(--#{$prefix}sidebar-padding-y) * .5) calc(var(--#{$prefix}sidebar-padding-x) * .5);
+ margin-inline-end: calc(-.5 * var(--#{$prefix}sidebar-padding-x));
+ margin-top: calc(-.5 * var(--#{$prefix}sidebar-padding-y));
+ margin-bottom: calc(-.5 * var(--#{$prefix}sidebar-padding-y));
+ }
+}
+
+.sidebar-body {
+ padding: var(--#{$prefix}sidebar-padding-y) var(--#{$prefix}sidebar-padding-x);
+}
+
+.sidebar-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--#{$prefix}sidebar-padding-y) var(--#{$prefix}sidebar-padding-x);
+}
+
+.sidebar-toggler {
+ // scss-docs-start sidebar-toggler-css-vars
+ --#{$prefix}sidebar-toggler-width: #{$sidebar-toggler-width};
+ --#{$prefix}sidebar-toggler-height: #{$sidebar-toggler-height};
+ --#{$prefix}sidebar-toggler-bg: #{$sidebar-toggler-bg};
+ --#{$prefix}sidebar-toggler-color: #{$sidebar-toggler-color};
+ --#{$prefix}sidebar-toggler-icon: #{escape-svg($sidebar-toggler-icon)};
+ --#{$prefix}sidebar-toggler-hover-color: #{$sidebar-toggler-hover-color};
+ --#{$prefix}sidebar-toggler-focus-shadow: #{$sidebar-toggler-focus-shadow};
+ --#{$prefix}sidebar-toggler-focus-color: #{$sidebar-toggler-focus-color};
+ --#{$prefix}sidebar-toggler-transition: #{$sidebar-toggler-transition};
+ // scss-docs-end sidebar-toggler-css-vars
+
+ position: relative;
+ box-sizing: content-box;
+ width: var(--#{$prefix}sidebar-toggler-width);
+ height: var(--#{$prefix}sidebar-toggler-height);
+ padding: $sidebar-toggler-padding-y $sidebar-toggler-padding-x;
+ background-color: var(--#{$prefix}sidebar-toggler-bg);
+ border: 0;
+ @include border-radius();
+
+ &::before {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ content: "";
+ background-color: var(--#{$prefix}sidebar-toggler-color);
+ mask: var(--#{$prefix}sidebar-toggler-icon) no-repeat center;
+ @include transition(var(--#{$prefix}sidebar-toggler-transition));
+ @include rtl() {
+ transform: rotate(-180deg);
+ }
+ }
+
+ // Override 's hover style
+ &:hover {
+ text-decoration: none;
+ &::before {
+ background-color: var(--#{$prefix}sidebar-toggler-hover-color);
+ }
+ }
+
+ &:focus {
+ position: relative;
+ outline: 0;
+ box-shadow: var(--#{$prefix}sidebar-toggler-focus-shadow);
+
+ &::before {
+ background-color: var(--#{$prefix}sidebar-toggler-focus-color);
+ }
+ }
+
+ @include media-breakpoint-down($mobile-breakpoint) {
+ display: none;
+ }
+}
+
+
+// Backdrop background
+
+.sidebar-backdrop {
+ // scss-docs-start sidebar-backdrop-css-vars
+ --#{$prefix}backdrop-zindex: #{$zindex-sidebar-backdrop};
+ --#{$prefix}backdrop-bg: #{$sidebar-backdrop-bg};
+ --#{$prefix}backdrop-opacity: #{$sidebar-backdrop-opacity};
+ // scss-docs-end sidebar-backdrop-css-vars
+
+ @include media-breakpoint-down($mobile-breakpoint) {
+ @include overlay-backdrop(var(--#{$prefix}backdrop-zindex), var(--#{$prefix}backdrop-bg), var(--#{$prefix}backdrop-opacity));
+ }
+}
+
+.sidebar-dark {
+ --#{$prefix}body-color: #{$body-color-dark};
+ --#{$prefix}body-bg: #{$body-bg-dark};
+
+ --#{$prefix}emphasis-color: #{$body-emphasis-color-dark};
+
+ --#{$prefix}secondary-color: #{$body-secondary-color-dark};
+ --#{$prefix}secondary-bg: #{$body-secondary-bg-dark};
+
+ --#{$prefix}tertiary-color: #{$body-tertiary-color-dark};
+ --#{$prefix}tertiary-bg: #{$body-tertiary-bg-dark};
+
+ --#{$prefix}border-color: #{$border-color-dark};
+
+ .sidebar-toggler {
+ filter: var(--#{$prefix}sidebar-toggler-white-filter);
+ }
+}
diff --git a/src/scss/scss/themes/bootstrap/bootstrap.rtl.scss b/src/scss/scss/themes/bootstrap/bootstrap.rtl.scss
new file mode 100644
index 000000000..142cacaef
--- /dev/null
+++ b/src/scss/scss/themes/bootstrap/bootstrap.rtl.scss
@@ -0,0 +1,4 @@
+@use "bootstrap" with (
+ $enable-ltr: false,
+ $enable-rtl: true
+);
diff --git a/src/scss/scss/themes/bootstrap/bootstrap.scss b/src/scss/scss/themes/bootstrap/bootstrap.scss
new file mode 100644
index 000000000..60cf15329
--- /dev/null
+++ b/src/scss/scss/themes/bootstrap/bootstrap.scss
@@ -0,0 +1,125 @@
+@use "sass:color";
+@use "../../banner" with (
+ $file: "Bootstrap Theme"
+);
+@use "../../functions/color" as *;
+
+// scss-docs-start gray-color-variables
+$white: #fff !default;
+$gray-100: #f8f9fa !default;
+$gray-200: #e9ecef !default;
+$gray-300: #dee2e6 !default;
+$gray-400: #ced4da !default;
+$gray-500: #adb5bd !default;
+$gray-600: #6c757d !default;
+$gray-700: #495057 !default;
+$gray-800: #343a40 !default;
+$gray-900: #212529 !default;
+$black: #000 !default;
+// scss-docs-end gray-color-variables
+
+// scss-docs-start color-variables
+$blue: #0d6efd !default;
+$indigo: #6610f2 !default;
+$purple: #6f42c1 !default;
+$pink: #d63384 !default;
+$red: #dc3545 !default;
+$orange: #fd7e14 !default;
+$yellow: #ffc107 !default;
+$green: #198754 !default;
+$teal: #20c997 !default;
+$cyan: #0dcaf0 !default;
+// scss-docs-end color-variables
+
+// scss-docs-start theme-color-variables
+$primary: $blue !default;
+$secondary: $gray-600 !default;
+$success: $green !default;
+$info: $cyan !default;
+$warning: $yellow !default;
+$danger: $red !default;
+$light: $gray-100 !default;
+$dark: $gray-900 !default;
+// scss-docs-end theme-color-variables
+
+// scss-docs-start theme-text-dark-variables
+$primary-text-emphasis-dark: tint-color($primary, 40%) !default;
+$secondary-text-emphasis-dark: tint-color($secondary, 40%) !default;
+$success-text-emphasis-dark: tint-color($success, 40%) !default;
+$info-text-emphasis-dark: tint-color($info, 40%) !default;
+$warning-text-emphasis-dark: tint-color($warning, 40%) !default;
+$danger-text-emphasis-dark: tint-color($danger, 40%) !default;
+// scss-docs-end theme-text-dark-variables
+
+// scss-docs-start theme-bg-subtle-dark-variables
+$primary-bg-subtle-dark: shade-color($primary, 80%) !default;
+$secondary-bg-subtle-dark: shade-color($secondary, 80%) !default;
+$success-bg-subtle-dark: shade-color($success, 80%) !default;
+$info-bg-subtle-dark: shade-color($info, 80%) !default;
+$warning-bg-subtle-dark: shade-color($warning, 80%) !default;
+$danger-bg-subtle-dark: shade-color($danger, 80%) !default;
+// scss-docs-end theme-bg-subtle-dark-variables
+
+// scss-docs-start theme-border-subtle-dark-variables
+$primary-border-subtle-dark: shade-color($primary, 40%) !default;
+$secondary-border-subtle-dark: shade-color($secondary, 40%) !default;
+$success-border-subtle-dark: shade-color($success, 40%) !default;
+$info-border-subtle-dark: shade-color($info, 40%) !default;
+$warning-border-subtle-dark: shade-color($warning, 40%) !default;
+$danger-border-subtle-dark: shade-color($danger, 40%) !default;
+// scss-docs-end theme-border-subtle-dark-variables
+
+@forward "../../coreui" with (
+ $prefix: bs- !default,
+ $data-infix: -bs- !default,
+ $white: $white !default,
+ $gray-100: $gray-100 !default,
+ $gray-200: $gray-200 !default,
+ $gray-300: $gray-300 !default,
+ $gray-400: $gray-400 !default,
+ $gray-500: $gray-500 !default,
+ $gray-600: $gray-600 !default,
+ $gray-700: $gray-700 !default,
+ $gray-800: $gray-800 !default,
+ $gray-900: $gray-900 !default,
+ $black: $black !default,
+ $primary: $primary !default,
+ $secondary: $secondary !default,
+ $success: $success !default,
+ $info: $info !default,
+ $warning: $warning !default,
+ $danger: $danger !default,
+ $light: $light !default,
+ $dark: $dark !default,
+ $body-color: $gray-900 !default,
+ $body-secondary-color: rgba($gray-900, .75) !default,
+ $body-tertiary-bg: rgba($gray-900, .75) !default,
+ $component-active-color: $white !default,
+ $component-active-bg: $primary !default,
+ $body-color-dark: $gray-300 !default,
+ $body-bg-dark: $gray-900 !default,
+ $body-secondary-color-dark: rgba($gray-300, .75) !default,
+ $body-secondary-bg-dark: $gray-800 !default,
+ $body-tertiary-color-dark: rgba($gray-300, .5) !default,
+ $body-tertiary-bg-dark: color.mix($gray-800, $gray-900, 50%) !default,
+ $primary-text-emphasis-dark: $primary-text-emphasis-dark !default,
+ $secondary-text-emphasis-dark: $secondary-text-emphasis-dark !default,
+ $success-text-emphasis-dark: $success-text-emphasis-dark !default,
+ $info-text-emphasis-dark: $info-text-emphasis-dark !default,
+ $warning-text-emphasis-dark: $warning-text-emphasis-dark !default,
+ $danger-text-emphasis-dark: $danger-text-emphasis-dark !default,
+ $primary-bg-subtle-dark: $primary-bg-subtle-dark !default,
+ $secondary-bg-subtle-dark: $secondary-bg-subtle-dark !default,
+ $success-bg-subtle-dark: $success-bg-subtle-dark !default,
+ $info-bg-subtle-dark: $info-bg-subtle-dark !default,
+ $warning-bg-subtle-dark: $warning-bg-subtle-dark !default,
+ $danger-bg-subtle-dark: $danger-bg-subtle-dark !default,
+ $primary-border-subtle-dark: $primary-border-subtle-dark !default,
+ $secondary-border-subtle-dark: $secondary-border-subtle-dark !default,
+ $success-border-subtle-dark: $success-border-subtle-dark !default,
+ $info-border-subtle-dark: $info-border-subtle-dark !default,
+ $warning-border-subtle-dark: $warning-border-subtle-dark !default,
+ $danger-border-subtle-dark: $danger-border-subtle-dark !default,
+ $theme-colors-dark: () !default,
+ $grays-dark: () !default,
+);
diff --git a/src/scss/scss/utilities/_api.import.scss b/src/scss/scss/utilities/_api.import.scss
new file mode 100644
index 000000000..a003da2df
--- /dev/null
+++ b/src/scss/scss/utilities/_api.import.scss
@@ -0,0 +1 @@
+@forward "api";
diff --git a/src/scss/scss/utilities/_api.scss b/src/scss/scss/utilities/_api.scss
new file mode 100644
index 000000000..ad8136a24
--- /dev/null
+++ b/src/scss/scss/utilities/_api.scss
@@ -0,0 +1,55 @@
+@use "sass:map";
+@use "sass:meta";
+@use "../utilities" as *;
+@use "../mixins/breakpoints" as *;
+@use "../mixins/utilities" as *;
+@use "../vendor/rfs" as *;
+@use "../variables" as *;
+
+// Loop over each breakpoint
+@each $breakpoint in map.keys($grid-breakpoints) {
+
+ // Generate media query if needed
+ @include media-breakpoint-up($breakpoint) {
+ $infix: breakpoint-infix($breakpoint, $grid-breakpoints);
+
+ // Loop over each utility property
+ @each $key, $utility in $utilities {
+ // The utility can be disabled with `false`, thus check if the utility is a map first
+ // Only proceed if responsive media queries are enabled or if it's the base media query
+ @if meta.type-of($utility) == "map" and (map.get($utility, responsive) or $infix == "") {
+ @include generate-utility($utility, $infix);
+ }
+ }
+ }
+}
+
+// RFS rescaling
+@media (min-width: $rfs-mq-value) {
+ @each $breakpoint in map.keys($grid-breakpoints) {
+ $infix: breakpoint-infix($breakpoint, $grid-breakpoints);
+
+ @if (map.get($grid-breakpoints, $breakpoint) < $rfs-breakpoint) {
+ // Loop over each utility property
+ @each $key, $utility in $utilities {
+ // The utility can be disabled with `false`, thus check if the utility is a map first
+ // Only proceed if responsive media queries are enabled or if it's the base media query
+ @if meta.type-of($utility) == "map" and map.get($utility, rfs) and (map.get($utility, responsive) or $infix == "") {
+ @include generate-utility($utility, $infix, true);
+ }
+ }
+ }
+ }
+}
+
+
+// Print utilities
+@media print {
+ @each $key, $utility in $utilities {
+ // The utility can be disabled with `false`, thus check if the utility is a map first
+ // Then check if the utility needs print styles
+ @if meta.type-of($utility) == "map" and map.get($utility, print) == true {
+ @include generate-utility($utility, "-print");
+ }
+ }
+}
diff --git a/src/scss/scss/vendor/_rfs.scss b/src/scss/scss/vendor/_rfs.scss
new file mode 100644
index 000000000..737abc923
--- /dev/null
+++ b/src/scss/scss/vendor/_rfs.scss
@@ -0,0 +1,354 @@
+// stylelint-disable scss/dimension-no-non-numeric-values
+@use "sass:map";
+@use "sass:math";
+@use "sass:meta";
+@use "sass:string";
+
+@use "../variables" as *;
+
+// SCSS RFS mixin
+//
+// Automated responsive values for font sizes, paddings, margins and much more
+//
+// Licensed under MIT (https://github.com/twbs/rfs/blob/main/LICENSE)
+
+// Configuration
+
+// Base value
+$rfs-base-value: 1.25rem !default;
+$rfs-unit: rem !default;
+
+@if $rfs-unit != rem and $rfs-unit != px {
+ @error "`#{$rfs-unit}` is not a valid unit for $rfs-unit. Use `px` or `rem`.";
+}
+
+// Breakpoint at where values start decreasing if screen width is smaller
+$rfs-breakpoint: 1200px !default;
+$rfs-breakpoint-unit: px !default;
+
+@if $rfs-breakpoint-unit != px and $rfs-breakpoint-unit != em and $rfs-breakpoint-unit != rem {
+ @error "`#{$rfs-breakpoint-unit}` is not a valid unit for $rfs-breakpoint-unit. Use `px`, `em` or `rem`.";
+}
+
+// Resize values based on screen height and width
+$rfs-two-dimensional: false !default;
+
+// Factor of decrease
+$rfs-factor: 10 !default;
+
+@if meta.type-of($rfs-factor) != number or $rfs-factor <= 1 {
+ @error "`#{$rfs-factor}` is not a valid $rfs-factor, it must be greater than 1.";
+}
+
+// Mode. Possibilities: "min-media-query", "max-media-query"
+$rfs-mode: min-media-query !default;
+
+// Generate enable or disable classes. Possibilities: false, "enable" or "disable"
+$rfs-class: false !default;
+
+// 1 rem = $rfs-rem-value px
+$rfs-rem-value: 16 !default;
+
+// Safari iframe resize bug: https://github.com/twbs/rfs/issues/14
+$rfs-safari-iframe-resize-bug-fix: false !default;
+
+// Disable RFS by setting $enable-rfs to false
+$enable-rfs: $enable-rfs !default;
+
+// Cache $rfs-base-value unit
+$rfs-base-value-unit: math.unit($rfs-base-value);
+
+@function rfs-divide($dividend, $divisor, $precision: 10) {
+ $sign: if($dividend > 0 and $divisor > 0 or $dividend < 0 and $divisor < 0, 1, -1);
+ $dividend: abs($dividend);
+ $divisor: abs($divisor);
+ @if $dividend == 0 {
+ @return 0;
+ }
+ @if $divisor == 0 {
+ @error "Cannot divide by 0";
+ }
+ $remainder: $dividend;
+ $result: 0;
+ $factor: 10;
+ @while ($remainder > 0 and $precision >= 0) {
+ $quotient: 0;
+ @while ($remainder >= $divisor) {
+ $remainder: $remainder - $divisor;
+ $quotient: $quotient + 1;
+ }
+ $result: $result * 10 + $quotient;
+ $factor: $factor * .1;
+ $remainder: $remainder * 10;
+ $precision: $precision - 1;
+ @if ($precision < 0 and $remainder >= $divisor * 5) {
+ $result: $result + 1;
+ }
+ }
+ $result: $result * $factor * $sign;
+ $dividend-unit: math.unit($dividend);
+ $divisor-unit: math.unit($divisor);
+ $unit-map: (
+ "px": 1px,
+ "rem": 1rem,
+ "em": 1em,
+ "%": 1%
+ );
+ @if ($dividend-unit != $divisor-unit and map.has-key($unit-map, $dividend-unit)) {
+ $result: $result * map.get($unit-map, $dividend-unit);
+ }
+ @return $result;
+}
+
+// Remove px-unit from $rfs-base-value for calculations
+@if $rfs-base-value-unit == px {
+ $rfs-base-value: rfs-divide($rfs-base-value, $rfs-base-value * 0 + 1);
+}
+@else if $rfs-base-value-unit == rem {
+ $rfs-base-value: rfs-divide($rfs-base-value, rfs-divide($rfs-base-value * 0 + 1, $rfs-rem-value));
+}
+
+// Cache $rfs-breakpoint unit to prevent multiple calls
+$rfs-breakpoint-unit-cache: math.unit($rfs-breakpoint);
+
+// Remove unit from $rfs-breakpoint for calculations
+@if $rfs-breakpoint-unit-cache == px {
+ $rfs-breakpoint: rfs-divide($rfs-breakpoint, $rfs-breakpoint * 0 + 1);
+}
+@else if $rfs-breakpoint-unit-cache == rem or $rfs-breakpoint-unit-cache == "em" {
+ $rfs-breakpoint: rfs-divide($rfs-breakpoint, rfs-divide($rfs-breakpoint * 0 + 1, $rfs-rem-value));
+}
+
+// Calculate the media query value
+$rfs-mq-value: if($rfs-breakpoint-unit == px, #{$rfs-breakpoint}px, #{rfs-divide($rfs-breakpoint, $rfs-rem-value)}#{$rfs-breakpoint-unit});
+$rfs-mq-property-width: if($rfs-mode == max-media-query, max-width, min-width);
+$rfs-mq-property-height: if($rfs-mode == max-media-query, max-height, min-height);
+
+// Internal mixin used to determine which media query needs to be used
+@mixin _rfs-media-query {
+ @if $rfs-two-dimensional {
+ @if $rfs-mode == max-media-query {
+ @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}), (#{$rfs-mq-property-height}: #{$rfs-mq-value}) {
+ @content;
+ }
+ }
+ @else {
+ @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}) and (#{$rfs-mq-property-height}: #{$rfs-mq-value}) {
+ @content;
+ }
+ }
+ }
+ @else {
+ @media (#{$rfs-mq-property-width}: #{$rfs-mq-value}) {
+ @content;
+ }
+ }
+}
+
+// Internal mixin that adds disable classes to the selector if needed.
+@mixin _rfs-rule {
+ @if $rfs-class == disable and $rfs-mode == max-media-query {
+ // Adding an extra class increases specificity, which prevents the media query to override the property
+ &,
+ .disable-rfs &,
+ &.disable-rfs {
+ @content;
+ }
+ }
+ @else if $rfs-class == enable and $rfs-mode == min-media-query {
+ .enable-rfs &,
+ &.enable-rfs {
+ @content;
+ }
+ } @else {
+ @content;
+ }
+}
+
+// Internal mixin that adds enable classes to the selector if needed.
+@mixin _rfs-media-query-rule {
+
+ @if $rfs-class == enable {
+ @if $rfs-mode == min-media-query {
+ @content;
+ }
+
+ @include _rfs-media-query () {
+ .enable-rfs &,
+ &.enable-rfs {
+ @content;
+ }
+ }
+ }
+ @else {
+ @if $rfs-class == disable and $rfs-mode == min-media-query {
+ .disable-rfs &,
+ &.disable-rfs {
+ @content;
+ }
+ }
+ @include _rfs-media-query () {
+ @content;
+ }
+ }
+}
+
+// Helper function to get the formatted non-responsive value
+@function rfs-value($values) {
+ // Convert to list
+ $values: if(meta.type-of($values) != list, ($values,), $values);
+
+ $val: "";
+
+ // Loop over each value and calculate value
+ @each $value in $values {
+ @if $value == 0 {
+ $val: $val + " 0";
+ }
+ @else {
+ // Cache $value unit
+ $unit: if(meta.type-of($value) == "number", math.unit($value), false);
+
+ @if $unit == px {
+ // Convert to rem if needed
+ $val: $val + " " + if($rfs-unit == rem, #{rfs-divide($value, $value * 0 + $rfs-rem-value)}rem, $value);
+ }
+ @else if $unit == rem {
+ // Convert to px if needed
+ $val: $val + " " + if($rfs-unit == px, #{rfs-divide($value, $value * 0 + 1) * $rfs-rem-value}px, $value);
+ } @else {
+ // If $value isn't a number (like inherit) or $value has a unit (not px or rem, like 1.5em) or $ is 0, just print the value
+ $val: $val + " " + $value;
+ }
+ }
+ }
+
+ // Remove first space
+ @return string.unquote(string.slice($val, 2));
+}
+
+// Helper function to get the responsive value calculated by RFS
+@function rfs-fluid-value($values) {
+ // Convert to list
+ $values: if(meta.type-of($values) != list, ($values,), $values);
+
+ $val: "";
+
+ // Loop over each value and calculate value
+ @each $value in $values {
+ @if $value == 0 {
+ $val: $val + " 0";
+ } @else {
+ // Cache $value unit
+ $unit: if(meta.type-of($value) == "number", math.unit($value), false);
+
+ // If $value isn't a number (like inherit) or $value has a unit (not px or rem, like 1.5em) or $ is 0, just print the value
+ @if not $unit or $unit != px and $unit != rem {
+ $val: $val + " " + $value;
+ } @else {
+ // Remove unit from $value for calculations
+ $value: rfs-divide($value, $value * 0 + if($unit == px, 1, rfs-divide(1, $rfs-rem-value)));
+
+ // Only add the media query if the value is greater than the minimum value
+ @if abs($value) <= $rfs-base-value or not $enable-rfs {
+ $val: $val + " " + if($rfs-unit == rem, #{rfs-divide($value, $rfs-rem-value)}rem, #{$value}px);
+ }
+ @else {
+ // Calculate the minimum value
+ $value-min: $rfs-base-value + rfs-divide(abs($value) - $rfs-base-value, $rfs-factor);
+
+ // Calculate difference between $value and the minimum value
+ $value-diff: abs($value) - $value-min;
+
+ // Base value formatting
+ $min-width: if($rfs-unit == rem, #{rfs-divide($value-min, $rfs-rem-value)}rem, #{$value-min}px);
+
+ // Use negative value if needed
+ $min-width: if($value < 0, -$min-width, $min-width);
+
+ // Use `vmin` if two-dimensional is enabled
+ $variable-unit: if($rfs-two-dimensional, vmin, vw);
+
+ // Calculate the variable width between 0 and $rfs-breakpoint
+ $variable-width: #{rfs-divide($value-diff * 100, $rfs-breakpoint)}#{$variable-unit};
+
+ // Return the calculated value
+ $val: $val + " calc(" + $min-width + if($value < 0, " - ", " + ") + $variable-width + ")";
+ }
+ }
+ }
+ }
+
+ // Remove first space
+ @return string.unquote(string.slice($val, 2));
+}
+
+// RFS mixin
+@mixin rfs($values, $property: font-size) {
+ @if $values != null {
+ $val: rfs-value($values);
+ $fluid-val: rfs-fluid-value($values);
+
+ // Do not print the media query if responsive & non-responsive values are the same
+ @if $val == $fluid-val {
+ #{$property}: $val;
+ }
+ @else {
+ @include _rfs-rule () {
+ #{$property}: if($rfs-mode == max-media-query, $val, $fluid-val);
+
+ // Include safari iframe resize fix if needed
+ min-width: if($rfs-safari-iframe-resize-bug-fix, (0 * 1vw), null);
+ }
+
+ @include _rfs-media-query-rule () {
+ #{$property}: if($rfs-mode == max-media-query, $fluid-val, $val);
+ }
+ }
+ }
+}
+
+// Shorthand helper mixins
+@mixin font-size($value) {
+ @include rfs($value);
+}
+
+@mixin padding($value) {
+ @include rfs($value, padding);
+}
+
+@mixin padding-top($value) {
+ @include rfs($value, padding-top);
+}
+
+@mixin padding-right($value) {
+ @include rfs($value, padding-right);
+}
+
+@mixin padding-bottom($value) {
+ @include rfs($value, padding-bottom);
+}
+
+@mixin padding-left($value) {
+ @include rfs($value, padding-left);
+}
+
+@mixin margin($value) {
+ @include rfs($value, margin);
+}
+
+@mixin margin-top($value) {
+ @include rfs($value, margin-top);
+}
+
+@mixin margin-right($value) {
+ @include rfs($value, margin-right);
+}
+
+@mixin margin-bottom($value) {
+ @include rfs($value, margin-bottom);
+}
+
+@mixin margin-left($value) {
+ @include rfs($value, margin-left);
+}
\ No newline at end of file
diff --git a/src/scss/style.scss b/src/scss/style.scss
index 4fbc82356..7d1f760b6 100644
--- a/src/scss/style.scss
+++ b/src/scss/style.scss
@@ -1,15 +1,21 @@
// If you want to override variables do it here
-@import "variables";
+@use "variables" as *;
// Import styles
-@import "@coreui/coreui/scss/coreui";
-@import "@coreui/chartjs/scss/coreui-chartjs";
+@use "scss/coreui" as coreui;
+// @use "coreui-chartjs/coreui-chartjs" as chartjs;
+// @use "@coreui/coreui/scss/coreui" as coreui;
+// @use "@coreui/chartjs/scss/coreui-chartjs" as chartjs;
// Vendors
-@import "vendors/simplebar";
+@use "vendors/simplebar" as simplebar;
// Custom styles for this theme
-@import "theme";
+@use "theme" as theme;
// If you want to add custom CSS you can put it here
-@import "custom";
+@use "custom" as custom;
+
+// Import Bootstrap CSS and Bootstrap Icons CSS
+@import 'bootstrap/dist/css/bootstrap.min.css';
+@import 'bootstrap-icons/font/bootstrap-icons.css';
diff --git a/src/services/authService.js b/src/services/authService.js
new file mode 100644
index 000000000..a9be6f39a
--- /dev/null
+++ b/src/services/authService.js
@@ -0,0 +1,72 @@
+import axios from 'axios'
+
+const API_URL = 'http://localhost:8081/auth/'
+
+const login = async (email, password) => {
+ try {
+ const response = await axios.post(`${API_URL}signin`, { email, password })
+ if (response.data.token) {
+ localStorage.setItem('token', response.data.token)
+ }
+ return response.data
+ } catch (error) {
+ console.error('Error logging in:', error)
+ throw error
+ }
+}
+
+const logout = async () => {
+ try {
+ const response = await axios.post(
+ `${API_URL}logout`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ },
+ )
+ if (response.data.clearToken) {
+ localStorage.removeItem('user')
+ localStorage.removeItem('token')
+ // Clear Axios default headers
+ delete axios.defaults.headers.common['Authorization']
+ // Redirect to login page or update UI state
+ }
+ return response.data
+ } catch (error) {
+ if (error.response?.status === 401) {
+ // Token already invalid/expired, clean up anyway
+ localStorage.removeItem('token')
+ delete axios.defaults.headers.common['Authorization']
+ }
+ throw error
+ }
+}
+
+const checkAuth = () => {
+ return axios
+ .get(`${API_URL}check-auth`, {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ })
+ .then((response) => {
+ return response
+ })
+ .catch((error) => {
+ console.error('Error checking authentication:')
+ return error
+ })
+}
+
+const getCurrentUser = () => {
+ return JSON.parse(localStorage.getItem('user'))
+}
+
+export default {
+ login,
+ logout,
+ checkAuth,
+ getCurrentUser,
+}
diff --git a/src/services/jiraService.js b/src/services/jiraService.js
new file mode 100644
index 000000000..e87f0e2b7
--- /dev/null
+++ b/src/services/jiraService.js
@@ -0,0 +1,116 @@
+import axios from 'axios'
+
+const API_URL = 'http://localhost:8081/jira_config/'
+
+const getAllConfigJira = () => {
+ return axios
+ .get(`${API_URL}getAllConfig`, {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ })
+ .then((response) => {
+ return response
+ })
+ .catch((error) => {
+ console.error('Error fetching all config Jira:', error)
+ return error
+ })
+}
+
+const checkConnectionJiraApi = (protocol, host, username, password, apiVersion, strictSSL) => {
+ return axios
+ .post(
+ `${API_URL}checkConnection`,
+ { protocol, host, username, password, apiVersion, strictSSL },
+ {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ },
+ )
+ .then((response) => {
+ return response
+ })
+ .catch((error) => {
+ console.log('Error checking connection Api Jira:', error)
+ return error
+ })
+}
+
+const addNewConfigJiraAPI = (protocol, host, username, password, apiVersion, strictSSL) => {
+ return axios
+ .post(
+ `${API_URL}addConfig`,
+ { protocol, host, username, password, apiVersion, strictSSL },
+ {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ },
+ )
+ .then((response) => {
+ return response
+ })
+ .catch((error) => {
+ console.log('Error adding new config Api Jira:', error)
+ return error
+ })
+}
+
+const deleteConfigJiraAPI = (idList) => {
+ return axios
+ .post(
+ `${API_URL}deleteConfigByID`,
+ { ids: idList },
+ {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ },
+ )
+ .then((response) => {
+ return response
+ })
+ .catch((error) => {
+ console.log('Error deleting config Api Jira:', error)
+ return error
+ })
+}
+
+const editConfigJiraAPI = (
+ id,
+ protocol,
+ host,
+ username,
+ password,
+ apiVersion,
+ strictSSL,
+ enableConfig,
+) => {
+ return axios
+ .post(
+ `${API_URL}updateConfigByID`,
+ { id, protocol, host, username, password, apiVersion, strictSSL, enableConfig },
+ {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ },
+ )
+ .then((response) => {
+ return response
+ })
+ .catch((error) => {
+ console.log('Error editing config Api Jira:', error)
+ return error
+ })
+}
+
+export default {
+ getAllConfigJira,
+ checkConnectionJiraApi,
+ addNewConfigJiraAPI,
+ deleteConfigJiraAPI,
+ editConfigJiraAPI,
+}
diff --git a/src/services/projectService.js b/src/services/projectService.js
new file mode 100644
index 000000000..9fe8bc938
--- /dev/null
+++ b/src/services/projectService.js
@@ -0,0 +1,102 @@
+import axios from 'axios'
+import { toast } from 'react-toastify'
+import { getAllProjectAPI } from '../actions/projectActions'
+
+const API_URL = 'http://localhost:8081/project/'
+
+const getAllProjects = async () => {
+ try {
+ const response = await axios.get(`${API_URL}getAllProject`, {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ })
+ return response
+ } catch (error) {
+ console.error('Error fetching all config Jira:', error)
+ return error
+ }
+}
+
+const addNewProject = async (projectData, dispatch) => {
+ try {
+ const response = await axios.post(`${API_URL}addNewProject`, projectData, {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ })
+ if (response.status === 201 && !response.data.error) {
+ toast.success(response.data.message || 'Project added successfully')
+ if (dispatch) {
+ setTimeout(() => {
+ dispatch(getAllProjectAPI())
+ }, 1000)
+ }
+ }
+ return response
+ } catch (error) {
+ console.error('Error adding new project:', error)
+ toast.error(error.response?.data?.message || 'Failed to add new project')
+ return error
+ }
+}
+
+const deleteProject = async (projectId, dispatch) => {
+ try {
+ const response = await axios.post(
+ `${API_URL}deleteProjectByID`,
+ { ids: projectId },
+ {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ },
+ )
+ if (response.status === 200 && !response.data.error) {
+ toast.success(response.message || 'Project deleted successfully')
+ if (dispatch) {
+ setTimeout(() => {
+ dispatch(getAllProjectAPI())
+ }, 1000)
+ }
+ }
+ return response
+ } catch (error) {
+ console.error('Error deleting project:', error)
+ toast.error(error.response?.data?.message || 'Failed to delete project')
+ return error
+ }
+}
+
+const editProject = async (projectId, projectData, dispatch) => {
+ try {
+ const response = await axios.post(
+ `${API_URL}updateProjectByID`,
+ { projectId, projectData },
+ {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ },
+ )
+ if (response.status === 200 && !response.data.error) {
+ toast.success(response.data.message || 'Project edited successfully')
+ if (dispatch) {
+ setTimeout(() => {
+ dispatch(getAllProjectAPI())
+ }, 1000)
+ }
+ }
+ return response
+ } catch (error) {
+ console.error('Error editing project:', error)
+ toast.error(error.response?.data?.message || 'Failed to edit project')
+ return error
+ }
+}
+export default {
+ getAllProjects,
+ addNewProject,
+ deleteProject,
+ editProject,
+}
diff --git a/src/services/ticketService.js b/src/services/ticketService.js
new file mode 100644
index 000000000..8ab8e009a
--- /dev/null
+++ b/src/services/ticketService.js
@@ -0,0 +1,44 @@
+import axios from 'axios'
+
+const API_URL = 'http://localhost:8081/ticket/'
+
+const getAllTickets = () => {
+ return axios
+ .get(`${API_URL}getAllTicket`, {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ })
+ .then((response) => {
+ return response
+ })
+ .catch((error) => {
+ console.error('Error fetching all config Jira:', error)
+ return error
+ })
+}
+
+const addNewTicket = (ticketData) => {
+ return axios
+ .post(
+ `${API_URL}addNewTicket`,
+ { ticket: ticketData },
+ {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ },
+ )
+ .then((response) => {
+ return response
+ })
+ .catch((error) => {
+ console.error('Error fetching all config Jira:', error)
+ return error
+ })
+}
+
+export default {
+ getAllTickets,
+ addNewTicket,
+}
diff --git a/src/services/userService.js b/src/services/userService.js
new file mode 100644
index 000000000..4f1bd8342
--- /dev/null
+++ b/src/services/userService.js
@@ -0,0 +1,23 @@
+import axios from 'axios'
+
+const API_URL = 'http://localhost:8081/user/'
+
+const getAllUsers = () => {
+ return axios
+ .get(`${API_URL}getAllUsers`, {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ })
+ .then((response) => {
+ return response
+ })
+ .catch((error) => {
+ console.error('Error fetching all users:', error)
+ return error
+ })
+}
+
+export default {
+ getAllUsers,
+}
diff --git a/src/store.js b/src/store.js
index 8ad30dad6..035aee198 100644
--- a/src/store.js
+++ b/src/store.js
@@ -1,18 +1,30 @@
-import { legacy_createStore as createStore } from 'redux'
+import React from 'react'
+import { createStore, applyMiddleware, combineReducers } from 'redux'
+import { thunk } from 'redux-thunk'
+import { composeWithDevTools } from 'redux-devtools-extension'
+import { Provider } from 'react-redux'
+import PropTypes from 'prop-types'
+import authReducer from './reducers/authReducer'
+import dataReducer from './reducers/appReducer'
+import jiraReducer from './reducers/jiraReducer'
+import ticketReducer from './reducers/ticketReducer'
+import userReducer from './reducers/userReducer'
+import projectReducer from './reducers/projectReducer'
-const initialState = {
- sidebarShow: true,
- theme: 'light',
-}
+const rootReducer = combineReducers({
+ auth: authReducer,
+ data: dataReducer,
+ jira: jiraReducer,
+ ticket: ticketReducer,
+ user: userReducer,
+ project: projectReducer,
+})
-const changeState = (state = initialState, { type, ...rest }) => {
- switch (type) {
- case 'set':
- return { ...state, ...rest }
- default:
- return state
- }
-}
+const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)))
-const store = createStore(changeState)
-export default store
+const StoreProvider = ({ children }) => {children}
+
+StoreProvider.propTypes = {
+ children: PropTypes.node,
+}
+export default StoreProvider
diff --git a/src/utils/TicketsConsts.js b/src/utils/TicketsConsts.js
new file mode 100644
index 000000000..2f5551b3f
--- /dev/null
+++ b/src/utils/TicketsConsts.js
@@ -0,0 +1,75 @@
+export const projects = [
+ {
+ value: 'interne',
+ label: 'Interne',
+ },
+]
+export const issueTypes = [
+ {
+ value: 'Bug',
+ label: 'Bug',
+ },
+ {
+ value: 'Task',
+ label: 'Task',
+ },
+ {
+ value: 'Story',
+ label: 'Story',
+ },
+ {
+ value: 'Epic',
+ label: 'Epic',
+ },
+ // {
+ // value: 'Sub-task',
+ // label: 'Sub-task',
+ // },
+ // {
+ // value: 'Improvement',
+ // label: 'Improvement',
+ // },
+ // {
+ // value: 'New Feature',
+ // label: 'New Feature',
+ // },
+ // {
+ // value: 'Test',
+ // label: 'Test',
+ // },
+ // {
+ // value: 'Documentation',
+ // label: 'Documentation',
+ // },
+ // {
+ // value: 'Test Execution',
+ // label: 'Test Execution',
+ // },
+ // {
+ // value: 'Pre-Condition',
+ // label: 'Pre-Condition',
+ // },
+ // {
+ // value: 'Test Plan',
+ // label: 'Test Plan',
+ // },
+ // {
+ // value: 'Incident',
+ // label: 'Incident',
+ // },
+]
+
+export const Prioritys = [
+ {
+ value: 'P1',
+ label: 'High',
+ },
+ {
+ value: 'P2',
+ label: 'Medium',
+ },
+ {
+ value: 'P3',
+ label: 'Low',
+ },
+]
diff --git a/src/utils/authProviders.js b/src/utils/authProviders.js
new file mode 100644
index 000000000..99dd4f1fd
--- /dev/null
+++ b/src/utils/authProviders.js
@@ -0,0 +1,4 @@
+export const providers = [
+ { id: 'SSO', name: 'SSO' },
+ { id: 'credentials', name: 'Email and Password' },
+]
diff --git a/src/utils/emptyIssue.js b/src/utils/emptyIssue.js
new file mode 100644
index 000000000..2be3bd2ed
--- /dev/null
+++ b/src/utils/emptyIssue.js
@@ -0,0 +1,165 @@
+export const emptyIssue = {
+ fields: {
+ components: [],
+ statuscategorychangedate: '',
+ workratio: -1,
+ assignee: null,
+ aggregatetimeoriginalestimate: null,
+ status: {
+ name: 'En cours',
+ description: 'Ce ticket est en cours de traitement par la personne assignée.',
+ statusCategory: {
+ id: 4,
+ name: 'En cours',
+ colorName: 'yellow',
+ self: 'https://sesame-team-pfe.atlassian.net/rest/api/2/statuscategory/4',
+ key: 'indeterminate',
+ },
+ id: '10001',
+ self: 'https://sesame-team-pfe.atlassian.net/rest/api/2/status/10001',
+ iconUrl: 'https://sesame-team-pfe.atlassian.net/',
+ },
+ aggregatetimespent: null,
+ watches: {
+ isWatching: false,
+ watchCount: 0,
+ self: 'https://sesame-team-pfe.atlassian.net/rest/api/2/issue/SCRUM-2/watchers',
+ },
+ reporter: {
+ self: 'https://sesame-team-pfe.atlassian.net/rest/api/2/user?accountId=62c81fef7273faf658f2068d',
+ emailAddress: 'mohamedamine.derouich@sesame.com.tn',
+ accountType: 'atlassian',
+ accountId: '62c81fef7273faf658f2068d',
+ avatarUrls: {
+ '32x32':
+ 'https://secure.gravatar.com/avatar/fba9f0dcfc7a2b63c55bfb9d22cde300?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMD-3.png',
+ '16x16':
+ 'https://secure.gravatar.com/avatar/fba9f0dcfc7a2b63c55bfb9d22cde300?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMD-3.png',
+ '24x24':
+ 'https://secure.gravatar.com/avatar/fba9f0dcfc7a2b63c55bfb9d22cde300?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMD-3.png',
+ '48x48':
+ 'https://secure.gravatar.com/avatar/fba9f0dcfc7a2b63c55bfb9d22cde300?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMD-3.png',
+ },
+ timeZone: 'Etc/GMT-1',
+ active: true,
+ displayName: 'Mohamed Amine DEROUICH',
+ },
+ timespent: null,
+ customfield_10001: null,
+ resolution: null,
+ labels: [],
+ progress: {
+ total: 0,
+ progress: 0,
+ },
+ aggregateprogress: {
+ total: 0,
+ progress: 0,
+ },
+ resolutiondate: null,
+ environment: null,
+ duedate: null,
+ votes: {
+ self: 'https://sesame-team-pfe.atlassian.net/rest/api/2/issue/SCRUM-2/votes',
+ hasVoted: false,
+ votes: 0,
+ },
+ customfield_10020: null,
+ security: null,
+ description: null,
+ customfield_10021: null,
+ issuelinks: [],
+ subtasks: [],
+ created: '',
+ lastViewed: '',
+ creator: {
+ displayName: 'Mohamed Amine DEROUICH',
+ timeZone: 'Etc/GMT-1',
+ accountType: 'atlassian',
+ active: true,
+ emailAddress: 'mohamedamine.derouich@sesame.com.tn',
+ avatarUrls: {
+ '24x24':
+ 'https://secure.gravatar.com/avatar/fba9f0dcfc7a2b63c55bfb9d22cde300?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMD-3.png',
+ '16x16':
+ 'https://secure.gravatar.com/avatar/fba9f0dcfc7a2b63c55bfb9d22cde300?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMD-3.png',
+ '32x32':
+ 'https://secure.gravatar.com/avatar/fba9f0dcfc7a2b63c55bfb9d22cde300?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMD-3.png',
+ '48x48':
+ 'https://secure.gravatar.com/avatar/fba9f0dcfc7a2b63c55bfb9d22cde300?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMD-3.png',
+ },
+ self: 'https://sesame-team-pfe.atlassian.net/rest/api/2/user?accountId=62c81fef7273faf658f2068d',
+ accountId: '62c81fef7273faf658f2068d',
+ },
+ timeoriginalestimate: null,
+ fixVersions: [],
+ summary: '',
+ updated: '',
+ issuetype: {
+ id: '10002',
+ hierarchyLevel: 0,
+ iconUrl:
+ 'https://sesame-team-pfe.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium',
+ avatarId: 10303,
+ subtask: false,
+ description: 'Un problème ou une erreur.',
+ entityId: 'b6942a7a-0278-49e3-89d3-85295176d3e8',
+ name: 'Bug',
+ self: 'https://sesame-team-pfe.atlassian.net/rest/api/2/issuetype/10002',
+ },
+ aggregatetimeestimate: null,
+ customfield_10016: null,
+ customfield_10032: null,
+ customfield_10019: '0|i00007:',
+ project: {
+ name: 'backendTakeIT',
+ simplified: true,
+ id: '10000',
+ avatarUrls: {
+ '16x16':
+ 'https://sesame-team-pfe.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10412?size=xsmall',
+ '48x48':
+ 'https://sesame-team-pfe.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10412',
+ '24x24':
+ 'https://sesame-team-pfe.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10412?size=small',
+ '32x32':
+ 'https://sesame-team-pfe.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10412?size=medium',
+ },
+ key: 'SCRUM',
+ projectTypeKey: 'software',
+ self: 'https://sesame-team-pfe.atlassian.net/rest/api/2/project/10000',
+ },
+ statusCategory: {
+ colorName: 'yellow',
+ id: 4,
+ key: 'indeterminate',
+ self: 'https://sesame-team-pfe.atlassian.net/rest/api/2/statuscategory/4',
+ name: 'En cours',
+ },
+ versions: [],
+ timeestimate: null,
+ priority: {
+ id: '3',
+ name: 'Medium',
+ iconUrl: 'https://sesame-team-pfe.atlassian.net/images/icons/priorities/medium.svg',
+ self: 'https://sesame-team-pfe.atlassian.net/rest/api/2/priority/3',
+ },
+ },
+ id: '',
+ key: 'interne',
+ self: '',
+ // createdAt: {
+ // seconds: 1746709762,
+ // nanoseconds: 95000000,
+ // },
+ configId: '',
+ // lastSync: {
+ // seconds: 1746801743,
+ // nanoseconds: 830000000,
+ // },
+ // updatedAt: {
+ // seconds: 1746801743,
+ // nanoseconds: 830000000,
+ // },
+ expand: 'operations,versionedRepresentations,editmeta,changelog,renderedFields',
+}
diff --git a/src/views/forms/addNewConfigJira.js b/src/views/forms/addNewConfigJira.js
new file mode 100644
index 000000000..69bdc0fe9
--- /dev/null
+++ b/src/views/forms/addNewConfigJira.js
@@ -0,0 +1,173 @@
+import React, { useState } from 'react'
+import { CButton, CCallout, CForm, CFormCheck, CFormInput } from '@coreui/react'
+import {
+ addNewConfigJiraAPI,
+ checkConnectionJiraAPI,
+ getAllConfigJiraAPI,
+} from '../../actions/jiraActions'
+import { useDispatch, useSelector } from 'react-redux'
+import { toast } from 'react-toastify'
+
+const AddNewConfigJira = () => {
+ const dispatch = useDispatch()
+ const { configCanbeAdded } = useSelector((state) => state.jira)
+
+ const [FormControlInputHostURL, setFormControlInputHostURL] = useState('')
+ const [RadioOptionProtocol, setRadioOptionProtocol] = useState('https')
+ const [FormControlInputUsername, setFormControlInputUsername] = useState('')
+ const [FormControlInputPassword, setFormControlInputPassword] = useState('')
+ const [FormControlInputAPIVersion, setFormControlInputAPIVersion] = useState(2)
+ const [CheckStrictSSL, setCheckStrictSSL] = useState(true)
+
+ const checkConnection = () => {
+ dispatch(
+ checkConnectionJiraAPI(
+ RadioOptionProtocol,
+ FormControlInputHostURL,
+ FormControlInputUsername,
+ FormControlInputPassword,
+ FormControlInputAPIVersion,
+ CheckStrictSSL,
+ ),
+ )
+ .then((response) => {
+ if (response) {
+ if (response.data.error) {
+ toast.error('Connection failed')
+ } else {
+ toast.success('Connection successful')
+ }
+ }
+ })
+ .catch((error) => {
+ console.error('Error checking connection:', error)
+ toast.error('Connection failed')
+ })
+ }
+
+ const handleFormSubmit = (e) => {
+ e.preventDefault()
+ if (configCanbeAdded) {
+ dispatch(
+ addNewConfigJiraAPI(
+ RadioOptionProtocol,
+ FormControlInputHostURL,
+ FormControlInputUsername,
+ FormControlInputPassword,
+ FormControlInputAPIVersion,
+ CheckStrictSSL,
+ ),
+ )
+ .then((response) => {
+ if (response) {
+ console.log(response)
+ if (response.data.error) {
+ toast.error('adding failed')
+ } else {
+ toast.success('successful adding')
+ }
+ }
+ })
+ .then(() => {
+ dispatch(getAllConfigJiraAPI())
+ })
+ .catch((error) => {
+ console.error('Error checking connection:', error)
+ toast.error('Connection failed')
+ })
+ } else {
+ toast.error('please check the connection before adding a configuration')
+ }
+ }
+ return (
+
+ setFormControlInputHostURL(e.target.value)}
+ value={FormControlInputHostURL}
+ />
+ setRadioOptionProtocol(e.target.value)}
+ />
+ setRadioOptionProtocol(e.target.value)}
+ />
+
+ setFormControlInputUsername(e.target.value)}
+ value={FormControlInputUsername}
+ />
+ setFormControlInputPassword(e.target.value)}
+ value={FormControlInputPassword}
+ />
+ setFormControlInputAPIVersion(e.target.value)}
+ value={FormControlInputAPIVersion}
+ />
+ setCheckStrictSSL(e.target.checked)}
+ checked={CheckStrictSSL}
+ />
+
+ before adding a configuration, please make sure that the host url is reachable and the
+ username and password are correct.
+ Note: please check the Connection before adding a configuration.
+
+
+ handleFormSubmit(e)}>
+ Add Configuration
+
+ checkConnection()}>
+ Test Connection
+
+
+
+ )
+}
+
+export default AddNewConfigJira
diff --git a/src/views/forms/addNewProject.js b/src/views/forms/addNewProject.js
new file mode 100644
index 000000000..7358a109c
--- /dev/null
+++ b/src/views/forms/addNewProject.js
@@ -0,0 +1,111 @@
+import React, { useEffect, useState } from 'react'
+import { CButton, CCallout, CCol, CForm, CFormInput, CFormSelect, CRow } from '@coreui/react'
+import { useDispatch, useSelector } from 'react-redux'
+import { toast } from 'react-toastify'
+import { addNewProjectAPI } from '../../actions/projectActions'
+
+const AddNewProject = () => {
+ const { user } = useSelector((state) => state.auth.user)
+ const [projectName, setProjectName] = useState('')
+ const [key, setKey] = useState('')
+ const [projectType, setProjectType] = useState('')
+ const dispatch = useDispatch()
+ const handleFormSubmit = (e) => {
+ e.preventDefault()
+ if (!projectName || !key || !projectType) {
+ toast.error('Please fill in all required fields')
+ return
+ }
+ if (key.length < 3) {
+ toast.error('Key must be at least 3 characters long')
+ return
+ }
+ if (!/^[A-Z]+$/.test(key)) {
+ toast.error('Key must contain only uppercase letters')
+ return
+ }
+ dispatch(
+ addNewProjectAPI({
+ projectName,
+ key,
+ projectType,
+ projectCategory: 'No category',
+ projectLead: user.uid,
+ }),
+ )
+ }
+
+ return (
+
+
+
+ setProjectName(e.target.value)}
+ value={projectName}
+ />
+
+
+ setKey(e.target.value.toUpperCase())}
+ />
+
+
+
+
+ e.target.value && setProjectType(e.target.value)}
+ value={projectType}
+ />
+
+
+
+
+
+
+
+
+ handleFormSubmit(e)}>
+ Add new Project
+
+
+
+ )
+}
+export default AddNewProject
diff --git a/src/views/pages/Tickets/TicketView.js b/src/views/pages/Tickets/TicketView.js
new file mode 100644
index 000000000..fa52968f8
--- /dev/null
+++ b/src/views/pages/Tickets/TicketView.js
@@ -0,0 +1,603 @@
+import React, { useEffect } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import { useParams, useNavigate } from 'react-router-dom'
+import {
+ CBadge,
+ CButton,
+ CCard,
+ CCardBody,
+ CCardHeader,
+ CCol,
+ CContainer,
+ CRow,
+ CSpinner,
+ CTable,
+ CTableBody,
+ CTableDataCell,
+ CTableRow,
+} from '@coreui/react'
+import CIcon from '@coreui/icons-react'
+import { cilArrowLeft, cilPencil } from '@coreui/icons'
+
+const TicketView = () => {
+ const { code } = useParams()
+ const navigate = useNavigate()
+ const dispatch = useDispatch()
+
+ // Récupérer le ticket depuis le store
+ const { ticketList, loading } = useSelector((state) => state.ticket)
+ const ticket = ticketList.find((t) => t.key === code)
+
+ useEffect(() => {
+ // Si le ticket n'est pas dans la liste, vous pourriez faire un appel API
+ if (!ticket && !loading) {
+ console.log('Récupération du ticket:', code)
+ }
+ }, [code, ticket, loading, dispatch])
+
+ const handleGoBack = () => {
+ navigate('/tickets')
+ }
+
+ const handleEditTicket = () => {
+ console.log('Éditer le ticket:', ticket)
+ }
+
+ if (loading) {
+ return (
+
+
+
+ )
+ }
+
+ if (!ticket) {
+ return (
+
+
+
+
+
+ Ticket non trouvé
+ Le ticket avec la clé "{code}" n'a pas été trouvé.
+
+
+ Retour à la liste
+
+
+
+
+
+
+ )
+ }
+
+ const formatDate = (dateString) => {
+ if (!dateString) return 'N/A'
+ return new Date(dateString).toLocaleDateString('fr-FR', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ })
+ }
+
+ const getStatusColor = (status) => {
+ switch (status?.toLowerCase()) {
+ case 'done':
+ case 'terminé':
+ return 'success'
+ case 'in progress':
+ case 'en cours':
+ return 'warning'
+ case 'to do':
+ case 'à faire':
+ return 'secondary'
+ default:
+ return 'primary'
+ }
+ }
+
+ const getPriorityColor = (priority) => {
+ switch (priority?.toLowerCase()) {
+ case 'highest':
+ case 'très haute':
+ return 'danger'
+ case 'high':
+ case 'haute':
+ return 'warning'
+ case 'medium':
+ case 'moyenne':
+ return 'info'
+ case 'low':
+ case 'basse':
+ return 'secondary'
+ default:
+ return 'primary'
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+ Retour
+
+ / Tickets / {ticket.key}
+
+
+
+ Éditer
+
+
+
+
+
+
+
+
+
+
+
{ticket.key}
+
+ {ticket.configId ? 'Externe' : 'Interne'}
+
+
+
+
+ {ticket.fields?.summary || 'Pas de résumé'}
+
+ {ticket.fields?.description && (
+
+
Description
+
{ticket.fields.description}
+
+ )}
+
+ {ticket.fields?.issuetype && (
+
+
Type d'issue
+
+ {ticket.fields.issuetype.iconUrl && (
+
+ )}
+
{ticket.fields.issuetype.name}
+ {ticket.fields.issuetype.description && (
+
+ - {ticket.fields.issuetype.description}
+
+ )}
+
+
+ )}
+
+ {/* Informations supplémentaires */}
+ {ticket.fields?.environment && (
+
+
Environnement
+
{ticket.fields.environment}
+
+ )}
+
+ {ticket.fields?.components && ticket.fields.components.length > 0 && (
+
+
Composants
+
+ {ticket.fields.components.map((component, index) => (
+
+ {component.name}
+
+ ))}
+
+
+ )}
+
+ {ticket.fields?.labels && ticket.fields.labels.length > 0 && (
+
+
Labels
+
+ {ticket.fields.labels.map((label, index) => (
+
+ {label}
+
+ ))}
+
+
+ )}
+
+ {ticket.fields?.fixVersions && ticket.fields.fixVersions.length > 0 && (
+
+
Versions de correction
+
+ {ticket.fields.fixVersions.map((version, index) => (
+
+ {version.name}
+
+ ))}
+
+
+ )}
+
+ {ticket.fields?.affectedVersions && ticket.fields.affectedVersions.length > 0 && (
+
+
Versions affectées
+
+ {ticket.fields.affectedVersions.map((version, index) => (
+
+ {version.name}
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+
+ Détails
+
+
+
+
+
+ Statut
+
+
+ {ticket.fields?.status?.name || 'N/A'}
+
+
+
+
+ {ticket.fields?.priority && (
+
+ Priorité
+
+
+ {ticket.fields.priority.name}
+
+
+
+ )}
+
+ {ticket.fields?.assignee && (
+
+ Assigné à
+
+
+ {ticket.fields.assignee.avatarUrls && (
+
+ )}
+
+
{ticket.fields.assignee.displayName}
+ {ticket.fields.assignee.emailAddress && (
+
+ {ticket.fields.assignee.emailAddress}
+
+ )}
+
+
+
+
+ )}
+
+ {ticket.fields?.reporter && (
+
+ Rapporteur
+
+
+ {ticket.fields.reporter.avatarUrls && (
+
+ )}
+
+
{ticket.fields.reporter.displayName}
+ {ticket.fields.reporter.emailAddress && (
+
+ {ticket.fields.reporter.emailAddress}
+
+ )}
+
+
+
+
+ )}
+
+ {ticket.fields?.project && (
+
+ Projet
+
+
+ {ticket.fields.project.avatarUrls && (
+
+ )}
+
+
{ticket.fields.project.name}
+
{ticket.fields.project.key}
+
+
+
+
+ )}
+
+ {ticket.fields?.resolution && (
+
+ Résolution
+
+ {ticket.fields.resolution.name}
+ {ticket.fields.resolution.description && (
+
+
+ {ticket.fields.resolution.description}
+
+
+ )}
+
+
+ )}
+
+ {ticket.fields?.timeestimate && (
+
+ Estimation
+
+ {Math.round(ticket.fields.timeestimate / 3600)} heures
+
+
+ )}
+
+ {ticket.fields?.timespent && (
+
+ Temps passé
+
+ {Math.round(ticket.fields.timespent / 3600)} heures
+
+
+ )}
+
+ {ticket.fields?.duedate && (
+
+ Date d'échéance
+
+
+ {formatDate(ticket.fields.duedate)}
+
+
+
+ )}
+
+ {ticket.fields?.resolutiondate && (
+
+ Date de résolution
+ {formatDate(ticket.fields.resolutiondate)}
+
+ )}
+
+
+ Créé
+ {formatDate(ticket.fields?.created)}
+
+
+
+ Mis à jour
+ {formatDate(ticket.fields?.updated)}
+
+
+
+
+
+
+
+
+ {/* Section Commentaires */}
+ {ticket.fields?.comment &&
+ ticket.fields.comment.comments &&
+ ticket.fields.comment.comments.length > 0 && (
+
+
+
+
+ Commentaires ({ticket.fields.comment.comments.length})
+
+
+ {ticket.fields.comment.comments.map((comment, index) => (
+
+
+ {comment.author?.avatarUrls && (
+
+ )}
+
+ {comment.author?.displayName || 'Anonyme'}
+ {formatDate(comment.created)}
+
+
+
+
{comment.body}
+ {comment.updated && comment.updated !== comment.created && (
+
+ Modifié le {formatDate(comment.updated)}
+
+ )}
+
+
+ ))}
+
+
+
+
+ )}
+
+ {/* Section Historique */}
+ {ticket.changelog && ticket.changelog.histories && ticket.changelog.histories.length > 0 && (
+
+
+
+
+ Historique des modifications
+
+
+ {ticket.changelog.histories.slice(0, 10).map((history, index) => (
+
+
+ {history.author?.avatarUrls && (
+
+ )}
+
+ {history.author?.displayName || 'Système'}
+ {formatDate(history.created)}
+
+
+
+ {history.items?.map((item, itemIndex) => (
+
+ {item.field} modifié
+ {item.fromString && (
+
+ {' '}
+ de {item.fromString}
+
+ )}
+ {item.toString && (
+
+ {' '}
+ vers {item.toString}
+
+ )}
+
+ ))}
+
+
+ ))}
+
+
+
+
+ )}
+
+ {/* Section Liens et Relations */}
+ {ticket.fields?.issuelinks && ticket.fields.issuelinks.length > 0 && (
+
+
+
+
+ Tickets liés
+
+
+ {ticket.fields.issuelinks.map((link, index) => (
+
+
+ {link.type?.name || 'Lié'}
+
+ {link.outwardIssue && (
+
+ {link.outwardIssue.key} -{' '}
+ {link.outwardIssue.fields?.summary}
+
+ {link.outwardIssue.fields?.status?.name}
+
+
+ )}
+ {link.inwardIssue && (
+
+ {link.inwardIssue.key} - {link.inwardIssue.fields?.summary}
+
+ {link.inwardIssue.fields?.status?.name}
+
+
+ )}
+
+ ))}
+
+
+
+
+ )}
+
+ {/* Section Sous-tâches */}
+ {ticket.fields?.subtasks && ticket.fields.subtasks.length > 0 && (
+
+
+
+
+ Sous-tâches ({ticket.fields.subtasks.length})
+
+
+
+
+ {ticket.fields.subtasks.map((subtask, index) => (
+
+
+
+ {subtask.key}
+
+ {subtask.fields?.summary}
+
+
+ {subtask.fields?.status?.name}
+
+
+
+ {subtask.fields?.assignee?.displayName || 'Non assigné'}
+
+
+ ))}
+
+
+
+
+
+
+ )}
+
+ )
+}
+
+export default TicketView
diff --git a/src/views/pages/Tickets/TicketsHome.js b/src/views/pages/Tickets/TicketsHome.js
new file mode 100644
index 000000000..94f434fb6
--- /dev/null
+++ b/src/views/pages/Tickets/TicketsHome.js
@@ -0,0 +1,102 @@
+import React, { useEffect, useRef, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import { useNavigate } from 'react-router-dom'
+
+import {
+ CBadge,
+ CButton,
+ CCol,
+ CContainer,
+ CRow,
+ CSpinner,
+ CTable,
+ CTableBody,
+ CTableDataCell,
+ CTableHead,
+ CTableHeaderCell,
+ CTableRow,
+} from '@coreui/react'
+
+import { getAllTicketAPI, toggleCreateTicketModalOpen } from '../../../actions/ticketActions'
+
+const Tickets = () => {
+ const dispatch = useDispatch()
+ const navigate = useNavigate()
+ const isFirstRender = useRef(true)
+ const { ticketList, loading } = useSelector((state) => state.ticket)
+
+ useEffect(() => {
+ if (isFirstRender.current) {
+ dispatch(getAllTicketAPI())
+ isFirstRender.current = false
+ }
+ }, [dispatch])
+
+ const handleClickAjouterTicket = (event) => {
+ event.preventDefault()
+ dispatch(toggleCreateTicketModalOpen())
+ }
+
+ const handleRowClick = (ticket) => {
+ console.log('Ticket cliqué :', ticket)
+ // Rediriger vers la vue détaillée du ticket
+ navigate(`/ticket/${ticket.key}`)
+ }
+
+ if (loading) {
+ return (
+
+
+
+ )
+ }
+ return (
+
+
+
+ All Ticket View
+ {/* Current Jira API configuration settings
*/}
+
+
+ handleClickAjouterTicket(event)}
+ >
+ Ajouter Ticket
+
+
+
+
+
+
+ From
+ Key
+ Summary
+ Status
+
+
+
+ {ticketList.map((item, index) => (
+ handleRowClick(item)}
+ style={{ cursor: 'pointer' }}
+ >
+
+
+ {item.configId ? 'externe' : 'interne'}
+
+
+ {item.key}
+ {item.fields.summary}
+ {item.fields.status.name}
+
+ ))}
+
+
+
+ )
+}
+
+export default Tickets
diff --git a/src/views/pages/jira/ConfigJiraApi.js b/src/views/pages/jira/ConfigJiraApi.js
new file mode 100644
index 000000000..2ca951642
--- /dev/null
+++ b/src/views/pages/jira/ConfigJiraApi.js
@@ -0,0 +1,245 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import {
+ deleteConfigJiraAPI,
+ editConfigJiraAPI,
+ getAllConfigJiraAPI,
+} from '../../../actions/jiraActions'
+import { useDispatch, useSelector } from 'react-redux'
+import {
+ CTable,
+ CButton,
+ CCol,
+ CRow,
+ CContainer,
+ CCollapse,
+ CCard,
+ CCardBody,
+ CButtonGroup,
+ CBadge,
+} from '@coreui/react'
+import AddNewConfigJira from '../../forms/addNewConfigJira'
+import { toast } from 'react-toastify'
+import { toggleEditConfigJiraModalOpen } from '../../../actions/jiraActions'
+const columns = [
+ {
+ key: 'status',
+ label: 'Status',
+ _props: { scope: 'col' },
+ },
+ {
+ key: 'Host',
+ label: 'Host',
+ _props: { scope: 'col' },
+ },
+ {
+ key: 'Username',
+ label: 'Username',
+ _props: { scope: 'col' },
+ },
+ {
+ key: 'Protocol',
+ label: 'Protocol',
+ _props: { scope: 'col' },
+ },
+ {
+ key: 'API Version',
+ label: 'API Version',
+ _props: { scope: 'col' },
+ },
+ {
+ key: 'Actions',
+ label: 'actions',
+ _props: { scope: 'col' },
+ },
+ // {
+ // key: 'Strict SSL',
+ // label: 'Strict SSL',
+ // _props: { scope: 'col' },
+ // },
+]
+
+const ConfigJiraApi = () => {
+ const dispatch = useDispatch()
+
+ const isFirstRender = useRef(true)
+
+ const { jiraConfigList } = useSelector((state) => state.jira)
+
+ const [visible, setVisible] = useState(false)
+ const [configItems, setConfigItems] = useState([])
+
+ const handleClickAjouterConfiguration = (event) => {
+ event.preventDefault()
+ setVisible(!visible)
+ }
+
+ const handleClickDeleteConfiguration = useCallback(
+ (event) => {
+ event.preventDefault()
+ const configId = event.target.id.split('-')[1]
+ // Call the delete action here
+ const deleteList = []
+ deleteList.push(configId)
+ dispatch(deleteConfigJiraAPI(deleteList))
+ .then((response) => {
+ if (response) {
+ if (response.data.error) {
+ toast.error('delete failed')
+ } else {
+ toast.success('successful deleted')
+ }
+ }
+ })
+ .then(() => {
+ dispatch(getAllConfigJiraAPI())
+ })
+ .catch((error) => {
+ console.error('Error checking connection:', error)
+ toast.error('Connection failed')
+ })
+ },
+ [dispatch],
+ )
+
+ const handleChangeStatusConfiguration = useCallback(
+ (event) => {
+ event.preventDefault()
+ const configId = event.target.id.split('-')[1]
+ // Call the edit action here
+ const configToEdit = jiraConfigList.find((config) => config.id === configId)
+ dispatch(
+ editConfigJiraAPI(
+ configId,
+ configToEdit.protocol,
+ configToEdit.host,
+ configToEdit.username,
+ configToEdit.password,
+ configToEdit.apiVersion,
+ configToEdit.strictSSL,
+ !configToEdit.enableConfig,
+ ),
+ )
+ .then((response) => {
+ if (response) {
+ if (response.data.error) {
+ toast.error('update failed')
+ } else {
+ toast.success('successful updated')
+ }
+ }
+ })
+ .then(() => {
+ dispatch(getAllConfigJiraAPI())
+ })
+ .catch((error) => {
+ toast.error('Connection failed')
+ })
+ },
+ [dispatch, jiraConfigList],
+ )
+
+ const handleClickEditConfiguration = useCallback(
+ (event) => {
+ event.preventDefault()
+ const configId = event.target.id.split('-')[1]
+ // Call the edit action here
+ dispatch(toggleEditConfigJiraModalOpen(configId))
+ },
+ [dispatch],
+ )
+
+ useEffect(() => {
+ if (isFirstRender.current) {
+ dispatch(getAllConfigJiraAPI())
+ isFirstRender.current = false
+ }
+ }, [dispatch])
+
+ useEffect(() => {
+ if (jiraConfigList && jiraConfigList.length > 0) {
+ const transformedItems = jiraConfigList.map((item) => ({
+ id: item.id,
+ status: item.enableConfig ? (
+ Enabled
+ ) : (
+ Disabled
+ ),
+ Host: item.host,
+ Username: item.username,
+ Protocol: item.protocol,
+ 'API Version': item.apiVersion,
+ 'Strict SSL': item.strictSSL,
+ Actions: (
+
+ handleClickDeleteConfiguration(e)}
+ id={`delete-${item.id}`}
+ >
+ delete
+
+ handleClickEditConfiguration(e)}
+ id={`edit-${item.id}`}
+ >
+ Edit
+
+ handleChangeStatusConfiguration(e)}
+ id={`status-${item.id}`}
+ >
+ {item.enableConfig ? 'Disable' : 'Enable'}
+
+
+ ),
+ }))
+ setConfigItems(transformedItems)
+ }
+ }, [
+ jiraConfigList,
+ handleClickDeleteConfiguration,
+ handleClickEditConfiguration,
+ handleChangeStatusConfiguration,
+ ])
+
+ return (
+
+
+
+ Configuration Jira API
+ Current Jira API configuration settings
+
+
+ handleClickAjouterConfiguration(event)}
+ >
+ Ajouter Configuration
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*
+ Edit Configuration
+ Test Connection
+
*/}
+
+ )
+}
+
+export default React.memo(ConfigJiraApi)
diff --git a/src/views/pages/login/Login.js b/src/views/pages/login/Login.js
index 1b2ee0baa..b6882bb82 100644
--- a/src/views/pages/login/Login.js
+++ b/src/views/pages/login/Login.js
@@ -1,85 +1,70 @@
import React from 'react'
-import { Link } from 'react-router-dom'
-import {
- CButton,
- CCard,
- CCardBody,
- CCardGroup,
- CCol,
- CContainer,
- CForm,
- CFormInput,
- CInputGroup,
- CInputGroupText,
- CRow,
-} from '@coreui/react'
-import CIcon from '@coreui/icons-react'
-import { cilLockLocked, cilUser } from '@coreui/icons'
+import { AppProvider } from '@toolpad/core/AppProvider'
+import { SignInPage } from '@toolpad/core/SignInPage'
+import { useTheme } from '@mui/material/styles'
+import { useNavigate } from 'react-router-dom'
+import { useDispatch } from 'react-redux'
+
+import { login, checkAuthentication } from '../../../actions/authActions'
+import { providers } from '../../../utils/authProviders'
+import logo from '../../../assets/images/logo.png'
+
+const BRANDING = {
+ logo: ,
+ title: 'Takeit',
+}
const Login = () => {
+ const theme = useTheme()
+ const dispatch = useDispatch()
+ const navigate = useNavigate()
+
+ const redirect = (user) => {
+ if (user.IsEmployee || user.IsManager) {
+ navigate('/')
+ }
+ }
+
return (
-
-
-
-
-
-
-
-
- Login
- Sign In to your account
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Login
-
-
-
-
- Forgot password?
-
-
-
-
-
-
-
-
-
-
Sign up
-
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
- tempor incididunt ut labore et dolore magna aliqua.
-
-
-
- Register Now!
-
-
-
-
-
-
-
-
-
-
+
+ {
+ if (provider.id === 'credentials') {
+ try {
+ const email = formData.get('email')
+ const password = formData.get('password')
+ const loginResponse = await dispatch(login(email, password))
+
+ if (loginResponse.error) {
+ return { error: 'Invalid username or password' }
+ }
+
+ await dispatch(checkAuthentication())
+
+ if (loginResponse && loginResponse.user) {
+ redirect(loginResponse.user)
+ return { success: true, user: loginResponse.user }
+ }
+
+ return { error: 'Unexpected response format' }
+ } catch (error) {
+ console.error('Login error:', error)
+ return { error: 'Authentication failed' }
+ }
+ }
+
+ return { error: "Cette fonctionnalité n'est pas disponible pour le moment." }
+ }}
+ slotProps={{
+ form: { noValidate: false },
+ emailField: { variant: 'standard', autoFocus: false },
+ passwordField: { variant: 'standard' },
+ submitButton: { variant: 'outlined' },
+ oAuthButton: { variant: 'contained' },
+ }}
+ providers={providers}
+ />
+
)
}
diff --git a/src/views/pages/page404/Page404.js b/src/views/pages/page404/Page404.js
deleted file mode 100644
index d7fe9a0a2..000000000
--- a/src/views/pages/page404/Page404.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import React from 'react'
-import {
- CButton,
- CCol,
- CContainer,
- CFormInput,
- CInputGroup,
- CInputGroupText,
- CRow,
-} from '@coreui/react'
-import CIcon from '@coreui/icons-react'
-import { cilMagnifyingGlass } from '@coreui/icons'
-
-const Page404 = () => {
- return (
-
-
-
-
-
-
404
-
Oops! You{"'"}re lost.
-
- The page you are looking for was not found.
-
-
-
-
-
-
-
- Search
-
-
-
-
-
- )
-}
-
-export default Page404
diff --git a/src/views/pages/page500/Page500.js b/src/views/pages/page500/Page500.js
deleted file mode 100644
index ea11a0cb2..000000000
--- a/src/views/pages/page500/Page500.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import React from 'react'
-import {
- CButton,
- CCol,
- CContainer,
- CFormInput,
- CInputGroup,
- CInputGroupText,
- CRow,
-} from '@coreui/react'
-import CIcon from '@coreui/icons-react'
-import { cilMagnifyingGlass } from '@coreui/icons'
-
-const Page500 = () => {
- return (
-
-
-
-
-
- 500
- Houston, we have a problem!
-
- The page you are looking for is temporarily unavailable.
-
-
-
-
-
-
-
- Search
-
-
-
-
-
- )
-}
-
-export default Page500
diff --git a/src/views/pages/projet/Projet.js b/src/views/pages/projet/Projet.js
new file mode 100644
index 000000000..28c5fe579
--- /dev/null
+++ b/src/views/pages/projet/Projet.js
@@ -0,0 +1,167 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import {
+ CButton,
+ CButtonGroup,
+ CCard,
+ CCardBody,
+ CCol,
+ CCollapse,
+ CContainer,
+ CRow,
+ CTable,
+ CSpinner,
+} from '@coreui/react'
+
+import {
+ getAllProjectAPI,
+ deleteProjectAPI,
+ toggleEditProjectModalOpen,
+} from '../../../actions/projectActions'
+import AddNewProject from '../../forms/addNewProject'
+
+const columns = [
+ {
+ key: 'projectName',
+ label: 'Project Name',
+ _props: { scope: 'col' },
+ },
+ {
+ key: 'key',
+ label: 'Key',
+ _props: { scope: 'col' },
+ },
+ {
+ key: 'projectType',
+ label: 'Project Type',
+ _props: { scope: 'col' },
+ },
+ {
+ key: 'projectLead',
+ label: 'Project Lead',
+ _props: { scope: 'col' },
+ },
+ {
+ key: 'projectCategory',
+ label: 'Project Category',
+ _props: { scope: 'col' },
+ },
+ {
+ key: 'actions',
+ label: 'Actions',
+ _props: { scope: 'col' },
+ },
+]
+
+const Projet = () => {
+ const { projectList, loading } = useSelector((state) => state.project)
+ const isFirstRender = useRef(true)
+ const [visible, setVisible] = useState(false)
+ const [projectItems, setProjectItems] = useState([])
+ const dispatch = useDispatch()
+
+ useEffect(() => {
+ if (isFirstRender.current) {
+ dispatch(getAllProjectAPI())
+ isFirstRender.current = false
+ }
+ }, [dispatch])
+
+ const handleClickDelete = useCallback(
+ (event) => {
+ event.preventDefault()
+ const projectId = event.target.id.split('-')[1]
+ const deleteList = []
+ deleteList.push(projectId)
+ dispatch(deleteProjectAPI(deleteList))
+ },
+ [dispatch],
+ )
+
+ const handleClickEdit = useCallback(
+ (event) => {
+ event.preventDefault()
+ const projectId = event.target.id.split('-')[1]
+ dispatch(toggleEditProjectModalOpen(projectId))
+ },
+ [dispatch],
+ )
+
+ useEffect(() => {
+ if (projectList && projectList.length > 0) {
+ const transformedItems = projectList.map((item) => ({
+ id: item.id,
+ projectName: item.projectName,
+ key: item.key,
+ projectType: item.projectType,
+ projectLead: item.projectLead,
+ projectCategory: item.projectCategory,
+ actions: (
+
+ handleClickDelete(e)}
+ id={`delete-${item.id}`}
+ >
+ delete
+
+ handleClickEdit(e)}
+ id={`edit-${item.id}`}
+ >
+ Edit
+
+
+ ),
+ }))
+ setProjectItems(transformedItems)
+ setVisible(false)
+ }
+ }, [projectList, setProjectItems, handleClickDelete, handleClickEdit])
+
+ const handleClickAjouterProject = (event) => {
+ event.preventDefault()
+ setVisible(!visible)
+ }
+
+ if (loading) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ All project types
+ {/* Current Jira API configuration settings
*/}
+
+
+ handleClickAjouterProject(event)}
+ >
+ Ajouter Project
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default Projet
diff --git a/src/views/pages/register/Register.js b/src/views/pages/register/Register.js
deleted file mode 100644
index d78b24c8f..000000000
--- a/src/views/pages/register/Register.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import React from 'react'
-import {
- CButton,
- CCard,
- CCardBody,
- CCol,
- CContainer,
- CForm,
- CFormInput,
- CInputGroup,
- CInputGroupText,
- CRow,
-} from '@coreui/react'
-import CIcon from '@coreui/icons-react'
-import { cilLockLocked, cilUser } from '@coreui/icons'
-
-const Register = () => {
- return (
-
-
-
-
-
-
-
- Register
- Create your account
-
-
-
-
-
-
-
- @
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Create Account
-
-
-
-
-
-
-
-
- )
-}
-
-export default Register