Files
WRNexusJS/.publish/ui/components/Carousel.wrn
T
Clintchiz 2c960fc1dc
Quality / quality (ubuntu-latest) (push) Failing after 9m49s
Quality / quality (windows-latest) (push) Canceled after 0s
refactor: migrate legacy wire namespace to wrn
2026-08-12 18:51:15 +05:30

899 lines
27 KiB
Plaintext

// Interactive, hydration-safe content carousel.
component Carousel {
outputs {
initialize(payload: { index: number; count: number })
change(payload: { index: number; previousIndex: number; item: string | number | boolean | null | object; reason: string | boolean })
previous(payload: { index: number; previousIndex: number; item: string | number | boolean | null | object })
next(payload: { index: number; previousIndex: number; item: string | number | boolean | null | object })
play(payload: { index: number; interval: number })
pause(payload: { index: number })
reachStart(payload: { index: number })
reachEnd(payload: { index: number })
dragStart(payload: { index: number; x: null })
dragEnd(payload: { index: number; distance: number })
}
props {
size: string = "default"
color: string = "primary"
title: string = ""
description: string = ""
items: unknown[] = []
activeIndex: number = 0
slidesPerView: number = 1
gap: string = "0.75rem"
showPagination: boolean = false
isAutoPlay: boolean = false
autoplayInterval: number = 4000
isInfiniteLoop: boolean = false
isRTL: boolean = false
isCentered: boolean = false
isDraggable: boolean = false
isAutoHeight: boolean = false
isSnap: boolean = false
showCounter: boolean = false
thumbnails: string = "none"
ariaLabel: string = "Content carousel"
variant: string = "default"
class: string = ""
}
state currentIndex = activeIndex
state playing: boolean = false
state dragOrigin = null
state dragOffset: number = 0
state autoplayTimer = null
state carouselRoot = null
functions {
shared function slideCount() {
return items.length
}
shared function maximumIndex(count, visible) {
count = slideCount()
visible = Math.max(1, Number(slidesPerView) || 1)
if (isCentered || isSnap) {
return Math.max(0, count - 1)
}
return Math.max(0, count - visible)
}
shared function normalizedIndex(index, maximum) {
maximum = maximumIndex()
if (slideCount() === 0) {
return 0
}
if (isInfiniteLoop) {
if (index < 0) {
return maximum
}
if (index > maximum) {
return 0
}
}
return Math.max(0, Math.min(index, maximum))
}
client function rememberRoot(sourceEvent, root) {
if (!sourceEvent || !sourceEvent.currentTarget) {
return
}
root = sourceEvent.currentTarget.closest(".wrn-next--carousel")
if (root) {
carouselRoot = root
}
}
client function syncNavigation(sourceEvent, index, root, viewport, slides, slide, thumbnails, thumbnail, rail) {
rememberRoot(sourceEvent)
root = carouselRoot
if (!root) {
return
}
if (isSnap) {
viewport = root.querySelector(".wrn-next__carousel-viewport")
slides = root.querySelectorAll(".wrn-next__carousel-slide")
slide = slides[index]
if (viewport && slide) {
viewport.scrollTo({
left: slide.offsetLeft - (viewport.clientWidth - slide.clientWidth) / 2,
behavior: "smooth"
})
}
}
thumbnails = root.querySelectorAll(".wrn-next__carousel-thumbnails button")
if (thumbnails[index]) {
thumbnail = thumbnails[index]
rail = thumbnail.parentElement
rail.scrollTo({
left: thumbnail.offsetLeft - (rail.clientWidth - thumbnail.clientWidth) / 2,
top: thumbnail.offsetTop - (rail.clientHeight - thumbnail.clientHeight) / 2,
behavior: "smooth"
})
}
}
client function handleSnapScroll(sourceEvent, slides, viewport, viewportCenter, closestIndex, closestDistance, slide, slideCenter, distance, previousIndex) {
if (!isSnap) {
return
}
rememberRoot(sourceEvent)
viewport = sourceEvent.currentTarget
slides = viewport.querySelectorAll(".wrn-next__carousel-slide")
viewportCenter = viewport.scrollLeft + viewport.clientWidth / 2
closestIndex = currentIndex
closestDistance = 1000000000
slides.forEach(function (candidate, index) {
slideCenter = candidate.offsetLeft + candidate.clientWidth / 2
distance = Math.abs(slideCenter - viewportCenter)
if (distance < closestDistance) {
closestDistance = distance
closestIndex = index
}
})
closestIndex = normalizedIndex(closestIndex)
if (closestIndex !== currentIndex) {
previousIndex = currentIndex
currentIndex = closestIndex
output.change({
index: currentIndex,
previousIndex: previousIndex,
item: items[currentIndex],
reason: "snap"
})
slides = carouselRoot.querySelectorAll(".wrn-next__carousel-thumbnails button")
slide = slides[currentIndex]
if (slide) {
viewport = slide.parentElement
viewport.scrollTo({
left: slide.offsetLeft - (viewport.clientWidth - slide.clientWidth) / 2,
top: slide.offsetTop - (viewport.clientHeight - slide.clientHeight) / 2,
behavior: "smooth"
})
}
}
}
client function selectSlide(index, reason, sourceEvent, previousIndex) {
if (slideCount() === 0) {
return
}
previousIndex = currentIndex
currentIndex = normalizedIndex(Number(index))
syncNavigation(sourceEvent, currentIndex)
if (previousIndex === currentIndex && reason !== "initialize") {
return
}
output.change({
index: currentIndex,
previousIndex: previousIndex,
item: items[currentIndex],
reason: reason || "select"
})
if (currentIndex === 0) {
output.reachStart({ index: currentIndex })
}
if (currentIndex === maximumIndex()) {
output.reachEnd({ index: currentIndex })
}
}
client function previousSlide(sourceEvent, previousIndex) {
previousIndex = currentIndex
selectSlide(currentIndex - 1, "previous", sourceEvent)
output.previous({
index: currentIndex,
previousIndex: previousIndex,
item: items[currentIndex]
})
}
client function nextSlide(sourceEvent, previousIndex) {
previousIndex = currentIndex
selectSlide(currentIndex + 1, "next", sourceEvent)
output.next({
index: currentIndex,
previousIndex: previousIndex,
item: items[currentIndex]
})
}
client function handleKeydown(sourceEvent) {
if (sourceEvent.key === "ArrowLeft") {
sourceEvent.preventDefault()
if (isRTL) {
nextSlide(sourceEvent)
} else {
previousSlide(sourceEvent)
}
}
if (sourceEvent.key === "ArrowRight") {
sourceEvent.preventDefault()
if (isRTL) {
previousSlide(sourceEvent)
} else {
nextSlide(sourceEvent)
}
}
if (sourceEvent.key === "Home") {
sourceEvent.preventDefault()
selectSlide(0, "keyboard", sourceEvent)
}
if (sourceEvent.key === "End") {
sourceEvent.preventDefault()
selectSlide(maximumIndex(), "keyboard", sourceEvent)
}
}
client function beginDrag(sourceEvent) {
if (!isDraggable || isSnap) {
return
}
sourceEvent.preventDefault()
dragOrigin = sourceEvent.clientX
dragOffset = 0
sourceEvent.currentTarget.setPointerCapture(sourceEvent.pointerId)
output.dragStart({ index: currentIndex, x: dragOrigin })
}
client function moveDrag(sourceEvent) {
if (!isDraggable || isSnap || dragOrigin === null) {
return
}
sourceEvent.preventDefault()
dragOffset = sourceEvent.clientX - dragOrigin
}
shared function cancelDrag() {
dragOrigin = null
dragOffset = 0
}
client function endDrag(sourceEvent, distance) {
if (!isDraggable || isSnap || dragOrigin === null) {
return
}
sourceEvent.preventDefault()
distance = dragOffset || sourceEvent.clientX - dragOrigin
dragOrigin = null
dragOffset = 0
if (Math.abs(distance) > 20) {
if ((distance < 0 && !isRTL) || (distance > 0 && isRTL)) {
nextSlide(sourceEvent)
} else {
previousSlide(sourceEvent)
}
}
output.dragEnd({
index: currentIndex,
distance: distance
})
}
shared function advanceAutoplay() {
if (currentIndex >= maximumIndex()) {
selectSlide(0, "autoplay")
} else {
selectSlide(currentIndex + 1, "autoplay")
}
}
client function startAutoplay() {
if (!isAutoPlay || playing || slideCount() < 2) {
return
}
playing = true
autoplayTimer = setInterval(advanceAutoplay, Math.max(1000, Number(autoplayInterval)))
output.play({ index: currentIndex, interval: autoplayInterval })
}
client function pauseAutoplay() {
if (autoplayTimer) {
clearInterval(autoplayTimer)
}
autoplayTimer = null
if (playing) {
output.pause({ index: currentIndex })
}
playing = false
}
}
lifecycle {
mount {
output.initialize({ index: currentIndex, count: slideCount() })
startAutoplay()
}
unmount {
if (autoplayTimer) {
clearInterval(autoplayTimer)
}
}
}
view {
<section
{...attrs}
class="wrn-component wrn-next--carousel wrn-component--color-{color} wrn-component--size-{size} wrn-next--variant-{variant} {class}"
data-rtl="{isRTL}"
data-centered="{isCentered}"
data-draggable="{isDraggable && !isSnap}"
data-dragging="{dragOrigin !== null}"
data-auto-height="{isAutoHeight}"
data-snap="{isSnap}"
data-thumbnails="{thumbnails}"
dir="{isRTL ? 'rtl' : 'ltr'}"
role="region"
aria-roledescription="carousel"
aria-label="{ariaLabel}"
@keydown="handleKeydown(event)"
@mouseenter="rememberRoot(event); pauseAutoplay()"
@mouseleave="startAutoplay()"
@focusin="rememberRoot(event); pauseAutoplay()"
@focusout="startAutoplay()"
>
{#if title || description}
<header class="wrn-next__carousel-header">
{#if title}<h3>{title}</h3>{/if}
{#if description}<p>{description}</p>{/if}
</header>
{/if}
<div class="wrn-next__carousel-layout">
{#if thumbnails === "vertical"}
<div class="wrn-next__carousel-thumbnails" aria-label="Choose a slide">
{#each items as item, index}
<button
type="button"
data-active="{index === currentIndex}"
aria-label="Show slide {index + 1}: {item.label || item.title}"
aria-current="{index === currentIndex ? 'true' : 'false'}"
@click="selectSlide(index, 'thumbnail', event)"
>
{#if item.thumbnail}<img src="{item.thumbnail}" alt="" />{/if}
<span>{item.label || item.title || "Slide " + (index + 1)}</span>
</button>
{/each}
</div>
{/if}
<div class="wrn-next__carousel-main">
<div class="wrn-next__carousel-stage">
<div
class="wrn-next__carousel-viewport"
tabindex="0"
@scroll="handleSnapScroll(event)"
@pointerdown="beginDrag(event)"
@pointermove="moveDrag(event)"
@pointerup="endDrag(event)"
@pointercancel="cancelDrag()"
@lostpointercapture="cancelDrag()"
>
<div
class="wrn-next__carousel-track"
style="--wrn-carousel-index: {currentIndex}; --wrn-carousel-per-view: {slidesPerView}; --wrn-carousel-gap: {gap}; --wrn-carousel-drag-offset: {dragOffset}px"
>
{#each items as item, index}
<article
class="wrn-next__carousel-slide"
data-active="{index === currentIndex}"
role="group"
aria-roledescription="slide"
aria-label="{index + 1} of {items.length}"
aria-hidden="{index === currentIndex ? 'false' : 'true'}"
>
{#if item.imageSrc}
<img src="{item.imageSrc}" alt="{item.imageAlt || item.title || ''}" />
{/if}
<div class="wrn-next__carousel-slide-content">
{#if item.eyebrow}<span>{item.eyebrow}</span>{/if}
{#if item.title || item.label}<h4>{item.title || item.label}</h4>{/if}
{#if item.description}<p>{item.description}</p>{/if}
{#if item.actionLabel}
<a href="{item.actionHref || '#'}">{item.actionLabel}</a>
{/if}
</div>
</article>
{/each}
<slot />
</div>
</div>
<button
class="wrn-next__carousel-control wrn-next__carousel-control--previous"
type="button"
aria-label="Previous slide"
disabled="{!isInfiniteLoop && currentIndex === 0}"
@click="previousSlide(event)"
>
<span class="icon-[lucide--chevron-left]" aria-hidden="true"></span>
</button>
<button
class="wrn-next__carousel-control wrn-next__carousel-control--next"
type="button"
aria-label="Next slide"
disabled="{!isInfiniteLoop && currentIndex === maximumIndex()}"
@click="nextSlide(event)"
>
<span class="icon-[lucide--chevron-right]" aria-hidden="true"></span>
</button>
{#if showCounter}
<output class="wrn-next__carousel-counter" aria-live="polite">
{currentIndex + 1} / {items.length}
</output>
{/if}
</div>
{#if showPagination}
<div class="wrn-next__carousel-pagination" aria-label="Choose a slide">
{#each items as item, index}
<button
type="button"
data-active="{index === currentIndex}"
aria-label="Show slide {index + 1}"
aria-current="{index === currentIndex ? 'true' : 'false'}"
@click="selectSlide(index, 'pagination', event)"
></button>
{/each}
</div>
{/if}
{#if thumbnails === "horizontal"}
<div class="wrn-next__carousel-thumbnails" aria-label="Choose a slide">
{#each items as item, index}
<button
type="button"
data-active="{index === currentIndex}"
aria-label="Show slide {index + 1}: {item.label || item.title}"
aria-current="{index === currentIndex ? 'true' : 'false'}"
@click="selectSlide(index, 'thumbnail', event)"
>
{#if item.thumbnail}<img src="{item.thumbnail}" alt="" />{/if}
<span>{item.label || item.title || "Slide " + (index + 1)}</span>
</button>
{/each}
</div>
{/if}
</div>
</div>
</section>
}
style {
.wrn-next--carousel {
--wrn-carousel-color: var(--wrn-component-color, var(--wrn-color-primary));
display: grid;
width: 100%;
min-width: 0;
gap: 1rem;
padding: 1rem;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-md);
color: var(--wrn-color-text);
background: var(--wrn-color-surface);
box-shadow: var(--wrn-shadow-1);
}
.wrn-next__carousel-header h3,
.wrn-next__carousel-header p {
margin: 0;
}
.wrn-next__carousel-header p {
margin-top: 0.35rem;
color: var(--wrn-color-text-muted);
}
.wrn-next__carousel-layout {
display: flex;
min-width: 0;
gap: 0.75rem;
}
.wrn-next__carousel-main {
display: grid;
flex: 1;
min-width: 0;
gap: 0.75rem;
}
.wrn-next__carousel-stage {
position: relative;
min-width: 0;
}
.wrn-next__carousel-viewport {
position: relative;
overflow: hidden;
min-width: 0;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-md);
background: var(--wrn-color-surface-subtle, var(--wrn-color-surface));
outline: none;
}
.wrn-next__carousel-viewport:focus-visible {
box-shadow: 0 0 0 3px color-mix(in srgb, var(--wrn-carousel-color) 30%, transparent);
}
.wrn-next__carousel-track {
display: flex;
align-items: stretch;
gap: var(--wrn-carousel-gap);
transform: translateX(
calc(
var(--wrn-carousel-index) * -1 *
(
(100% - (var(--wrn-carousel-per-view) - 1) * var(--wrn-carousel-gap)) /
var(--wrn-carousel-per-view) + var(--wrn-carousel-gap)
)
)
);
transition: transform var(--wrn-motion-base) var(--wrn-ease-emphasized);
translate: var(--wrn-carousel-drag-offset, 0) 0;
will-change: transform;
}
.wrn-next--carousel[data-dragging="true"] .wrn-next__carousel-track {
transition: none;
}
.wrn-next--carousel[data-rtl="true"] .wrn-next__carousel-track {
transform: translateX(
calc(
var(--wrn-carousel-index) *
(
(100% - (var(--wrn-carousel-per-view) - 1) * var(--wrn-carousel-gap)) /
var(--wrn-carousel-per-view) + var(--wrn-carousel-gap)
)
)
);
}
.wrn-next__carousel-slide {
position: relative;
display: grid;
flex: 0 0
calc(
(100% - (var(--wrn-carousel-per-view) - 1) * var(--wrn-carousel-gap)) /
var(--wrn-carousel-per-view)
);
overflow: hidden;
min-width: 0;
min-height: 20rem;
place-items: center;
background: var(--wrn-color-surface-raised, var(--wrn-color-surface));
}
.wrn-next--carousel[data-auto-height="true"] .wrn-next__carousel-slide {
min-height: 0;
}
.wrn-next--carousel[data-centered="true"] .wrn-next__carousel-slide {
flex-basis: min(78%, 32rem);
}
.wrn-next--carousel[data-centered="true"] .wrn-next__carousel-track {
box-sizing: border-box;
transform: translateX(
calc(var(--wrn-carousel-index) * -1 * (min(78%, 32rem) + var(--wrn-carousel-gap)))
);
}
.wrn-next--carousel[data-centered="true"] .wrn-next__carousel-slide:first-child {
margin-inline-start: calc((100% - min(78%, 32rem)) / 2);
}
.wrn-next--carousel[data-centered="true"] .wrn-next__carousel-slide:last-child {
margin-inline-end: calc((100% - min(78%, 32rem)) / 2);
}
.wrn-next--carousel[data-centered="true"][data-rtl="true"] .wrn-next__carousel-track {
transform: translateX(
calc(var(--wrn-carousel-index) * (min(78%, 32rem) + var(--wrn-carousel-gap)))
);
}
.wrn-next__carousel-slide > img {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
inset: 0;
}
.wrn-next__carousel-slide-content {
position: relative;
z-index: 1;
width: min(100%, 38rem);
padding: clamp(2rem, 8vw, 5rem);
text-align: center;
}
.wrn-next__carousel-slide-content h4,
.wrn-next__carousel-slide-content p {
margin: 0;
}
.wrn-next__carousel-slide-content h4 {
font-size: clamp(1.5rem, 4vw, 2.4rem);
}
.wrn-next__carousel-slide-content p {
margin-top: 0.65rem;
color: var(--wrn-color-text-muted);
}
.wrn-next__carousel-control {
position: absolute;
z-index: 3;
top: 50%;
display: grid;
width: 2.75rem;
min-height: 2.75rem;
padding: 0;
border: 1px solid color-mix(in srgb, var(--wrn-color-border) 70%, transparent);
border-radius: 999px;
background: color-mix(in srgb, var(--wrn-color-text) 76%, transparent);
color: var(--wrn-color-surface);
cursor: pointer;
place-items: center;
transform: translateY(-50%);
}
.wrn-next__carousel-control:focus-visible,
.wrn-next__carousel-pagination button:focus-visible,
.wrn-next__carousel-thumbnails button:focus-visible {
outline: 2px solid var(--wrn-carousel-color);
outline-offset: 2px;
}
.wrn-next__carousel-control:disabled {
cursor: not-allowed;
opacity: 0.35;
}
.wrn-next__carousel-control--previous {
left: 1rem;
}
.wrn-next__carousel-control--next {
right: 1rem;
}
.wrn-next--carousel[data-rtl="true"] .wrn-next__carousel-control--previous {
right: 1rem;
left: auto;
}
.wrn-next--carousel[data-rtl="true"] .wrn-next__carousel-control--next {
right: auto;
left: 1rem;
}
.wrn-next__carousel-pagination {
display: flex;
min-height: 1.5rem;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.wrn-next__carousel-pagination button {
width: 0.65rem;
min-height: 0.65rem;
padding: 0;
border: 1px solid var(--wrn-color-text-muted);
border-radius: 999px;
background: transparent;
cursor: pointer;
}
.wrn-next__carousel-pagination button[data-active="true"] {
border-color: var(--wrn-carousel-color);
background: var(--wrn-carousel-color);
}
.wrn-next__carousel-counter {
position: absolute;
z-index: 3;
bottom: 0.8rem;
left: 50%;
padding: 0.25rem 0.65rem;
border-radius: 999px;
background: color-mix(in srgb, var(--wrn-color-text) 82%, transparent);
color: var(--wrn-color-surface);
font-weight: 700;
transform: translateX(-50%);
}
.wrn-next__carousel-thumbnails {
display: flex;
overflow: auto;
gap: 0.55rem;
scrollbar-width: thin;
}
.wrn-next__carousel-thumbnails button {
display: grid;
flex: 0 0 8.5rem;
min-height: 3.75rem;
overflow: hidden;
padding: 0;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
background: var(--wrn-color-surface);
color: var(--wrn-color-text);
cursor: pointer;
place-items: center;
}
.wrn-next__carousel-thumbnails button[data-active="true"] {
border-color: var(--wrn-carousel-color);
box-shadow: inset 0 0 0 1px var(--wrn-carousel-color);
}
.wrn-next__carousel-thumbnails img {
width: 100%;
height: 4rem;
object-fit: cover;
}
.wrn-next__carousel-thumbnails span {
padding: 0.5rem;
}
.wrn-next--carousel[data-thumbnails="vertical"] .wrn-next__carousel-thumbnails {
flex: 0 0 9rem;
flex-direction: column;
max-height: 28rem;
}
.wrn-next--carousel[data-thumbnails="vertical"] .wrn-next__carousel-thumbnails button {
flex-basis: 7.5rem;
width: 100%;
}
.wrn-next--carousel[data-draggable="true"] .wrn-next__carousel-viewport {
cursor: grab;
touch-action: pan-y;
user-select: none;
-webkit-user-select: none;
}
.wrn-next--carousel[data-draggable="true"] .wrn-next__carousel-viewport:active {
cursor: grabbing;
}
.wrn-next--carousel[data-draggable="true"] img,
.wrn-next--carousel[data-draggable="true"] a {
-webkit-user-drag: none;
}
.wrn-next--carousel[data-snap="true"] .wrn-next__carousel-viewport {
overflow-x: auto;
scroll-behavior: smooth;
scroll-snap-type: x mandatory;
scrollbar-width: thin;
}
.wrn-next--carousel[data-snap="true"] .wrn-next__carousel-track {
--wrn-carousel-snap-slide: calc(
(100% - (var(--wrn-carousel-per-view) - 1) * var(--wrn-carousel-gap)) /
var(--wrn-carousel-per-view)
);
display: grid;
width: 100%;
min-width: 100%;
box-sizing: content-box;
grid-auto-columns: var(--wrn-carousel-snap-slide);
grid-auto-flow: column;
padding-inline: calc((100% - var(--wrn-carousel-snap-slide)) / 2);
transform: none;
}
.wrn-next--carousel[data-snap="true"] .wrn-next__carousel-slide {
width: auto;
min-width: 0;
scroll-snap-align: center;
}
@media (max-width: 640px) {
.wrn-next__carousel-slide {
flex-basis: 100%;
min-height: 16rem;
}
.wrn-next--carousel[data-thumbnails="vertical"] .wrn-next__carousel-layout {
flex-direction: column-reverse;
}
.wrn-next--carousel[data-thumbnails="vertical"] .wrn-next__carousel-thumbnails {
flex: auto;
flex-direction: row;
max-height: none;
}
.wrn-next--carousel[data-thumbnails="vertical"] .wrn-next__carousel-thumbnails button {
flex-basis: 8.5rem;
width: auto;
}
}
@media (prefers-reduced-motion: reduce) {
.wrn-next__carousel-track {
transition-duration: 0.01ms;
}
}
/* Shared component foundation. */
.wrn-component {
--wrn-component-color: var(--wrn-color-primary);
--wrn-component-scale: 1;
margin: 0;
color: var(--wrn-color-text);
font-family: var(--wrn-font-sans, inherit);
}
.wrn-component--color-primary {
--wrn-component-color: var(--wrn-color-primary);
}
.wrn-component--color-secondary {
--wrn-component-color: var(--wrn-color-secondary);
}
.wrn-component--color-success {
--wrn-component-color: var(--wrn-color-success);
}
.wrn-component--color-warning {
--wrn-component-color: var(--wrn-color-warning);
}
.wrn-component--color-danger {
--wrn-component-color: var(--wrn-color-danger);
}
.wrn-component--color-info {
--wrn-component-color: var(--wrn-color-info);
}
.wrn-component--size-xs {
--wrn-component-scale: 0.78;
}
.wrn-component--size-sm {
--wrn-component-scale: 0.88;
}
.wrn-component--size-default,
.wrn-component--size-md {
--wrn-component-scale: 1;
}
.wrn-component--size-lg {
--wrn-component-scale: 1.14;
}
.wrn-component--size-xl {
--wrn-component-scale: 1.28;
}
.wrn-component:not(.wrn-btn) {
font-size: calc(1em * var(--wrn-component-scale));
}
.wrn-component :is(a, button, input, select, textarea):focus-visible {
outline-color: var(--wrn-component-color);
}
.wrn-component p {
margin: 0;
color: var(--wrn-color-muted);
line-height: 1.6;
}
}
}