Refactor blog carousel to new data attribute structure

This commit is contained in:
thePR0M3TH3AN committed 2025-10-14 07:31:49 -04:00
1 parent a45b4934c6
commit 44431ce944
8 files changed
+287 -535

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
:root{--color-page:#fff;--color-page-alt:#f9fafb;--color-surface:#0f172a;--color-surface-alt:#111827;--color-bg:var(--color-surface);--color-card:#1e293b;--color-card-private:#3b1d74;--color-card-critical:#3f0b13;--color-panel:#1f2937;--color-panel-hover:#334155;--color-border:#374151;--color-border-subtle:#d1d5db;--color-primary:#fe0032;--color-secondary:#ff93a5;--color-logo-accent:var(--color-primary);--color-logo-wordmark:var(--color-neutral-700);--color-logo-wordmark-inverse:var(--color-white);--color-logo-background:var(--color-neutral-700);--color-logo-background-inverse:var(--color-white);--color-info:#60a5fa;--color-info-strong:#2563eb;--color-info-pressed:#1d4ed8;--color-info-glass:rgba(37,99,235,.18);--color-muted-strong:#6b7280;--color-text:#f8fafc;--color-text-strong:#0f172a;--color-text-muted:var(--color-muted);--color-muted:#94a3b8;--color-muted-alt:#9ca3af;--color-subtle-text:#e2e8f0;--color-subtle:#e5e7eb;--color-critical:#7f1d1d;--color-critical-strong:#991b1b;--color-warning:#facc15;--color-warning-strong:#d97706;--color-warning-surface:rgba(146,64,14,.24);--color-warning-glass:rgba(250,204,21,.18);--color-neutral-700:#3f3f46;--color-neutral-600:#52525b;--color-white:#fff;--color-black:#000;--color-overlay:#1a2234;--color-overlay-black:rgba(0,0,0,.9);--color-overlay-muted:rgba(15,23,42,.55);--color-overlay-strong:rgba(15,23,42,.75);--color-overlay-darker:rgba(15,23,42,.88);--color-overlay-panel:rgba(26,34,52,.82);--color-overlay-panel-soft:rgba(26,34,52,.65);--color-overlay-panel-tint:rgba(31,41,55,.5);--color-overlay-panel-glass:rgba(28,37,58,.72);--color-overlay-panel-glass-soft:rgba(31,41,55,.55);--color-modal-backdrop:rgba(8,12,24,.58);--color-modal-surface:rgba(20,28,46,.78);--color-overlay-veil:rgba(11,17,32,.96);--color-overlay-banner:rgba(51,65,85,.95);--color-sidebar-surface:rgba(17,24,39,.94);--color-sidebar-surface-alt:rgba(11,22,48,.78);--color-sidebar-border:rgba(148,163,184,.28);--color-sidebar-border-strong:rgba(148,163,184,.42);--color-sidebar-hover:rgba(56,189,248,.18);--color-sidebar-hover-strong:rgba(129,140,248,.28);--color-sidebar-glow:rgba(56,189,248,.35);--color-sidebar-accent-start:rgba(56,189,248,.9);--color-sidebar-accent-end:rgba(129,140,248,.85);--color-sidebar-text-muted:rgba(203,213,225,.75);--color-overlay-panel-gradient-start:rgba(30,41,59,.92);--color-overlay-panel-gradient-end:rgba(15,23,42,.92);--color-surface-tint-subtle:rgba(148,163,184,.08);--color-border-translucent:rgba(148,163,184,.18);--color-border-translucent-strong:rgba(148,163,184,.25);--color-border-translucent-bright:rgba(148,163,184,.32);--color-border-translucent-stronger:rgba(148,163,184,.35);--color-border-translucent-strongest:rgba(148,163,184,.4);--color-border-translucent-glow:rgba(148,163,184,.55);--color-border-translucent-medium:rgba(148,163,184,.2);--color-border-neutral-soft:rgba(229,231,235,.35);--color-border-info-soft:rgba(96,165,250,.35);--color-border-sidebar-strong:rgba(55,65,81,.9);--color-ring-private:rgba(168,85,247,.55);--color-ring-private-hover:rgba(192,132,252,.65);--color-ring-critical:rgba(239,68,68,.4);--color-ring-critical-soft:rgba(239,68,68,.35);--color-ring-critical-hover:rgba(239,68,68,.45);--color-ring-critical-strong:rgba(239,68,68,.55);--color-ring-alert:rgba(220,38,38,.6);--color-gradient-visibility-start:rgba(129,140,248,.4);--color-gradient-visibility-end:rgba(192,132,252,.32);--color-white-soft:hsla(0,0%,100%,.1);--color-white-softer:hsla(0,0%,100%,.3);--color-white-softest:hsla(0,0%,100%,.4);--color-shadow-floating:rgba(8,11,30,.55);--color-shadow-elevated:rgba(8,11,30,.45);--color-shadow-panel:rgba(15,23,42,.35);--color-shadow-notification:rgba(15,23,42,.3);--color-shadow-sidebar:rgba(0,0,0,.45);--color-divider-strong:rgba(15,23,42,.35);--color-text-frosted-strong:rgba(226,232,240,.94);--color-text-frosted:rgba(226,232,240,.9);--color-text-frosted-soft:rgba(226,232,240,.82);--scrollbar-width:0.5rem;--scrollbar-track:var(--color-white-soft);--scrollbar-thumb:var(--color-white-softer);--scrollbar-thumb-hover:var(--color-white-softest);--z-sidebar-overlay:10;--z-sidebar:20;--z-modal-root:100;--z-modal-overlay:110;--z-modal-content:120;--z-modal-player:130;--sidebar-width-expanded:16rem;--sidebar-width-collapsed:4rem;--sidebar-width:var(--sidebar-width-expanded);--sidebar-control-size:2.75rem;--sidebar-content-gap:1rem;--space-sidebar-shell-inline:var(--space-lg);--space-sidebar-shell-block-start:var(--space-lg);--space-sidebar-shell-block-end:1.75rem;--space-sidebar-scroll-gap:1.75rem;--space-sidebar-panel-gap:1.35rem;--space-sidebar-panel-padding-block:1.35rem;--space-sidebar-panel-padding-inline:1.1rem;--space-sidebar-footer-gap:0.85rem;--space-sidebar-trigger-padding-block:0.78rem;--space-sidebar-popover-offset:0.85rem;--space-sidebar-popover-padding-block:0.85rem;--space-sidebar-popover-padding-inline:0.65rem;--space-sidebar-popover-gap:0.45rem;--space-sidebar-nav-padding-block:0.7rem;--space-sidebar-subnav-padding-block:0.55rem;--space-sidebar-nav-Line truncated
/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family-sans);font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--font-family-mono);font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{font-size:100%}@media (min-width:1024px){html{font-size:75%}}*,:after,:before{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent}::-webkit-scrollbar{width:var(--scrollbar-width);height:var(--scrollbar-width)}::-webkit-scrollbar-track{background:var(--scrollbar-track)}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:var(--radius-sm)}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}body{font-family:var(--font-family-sans);background-color:var(--color-page);color:var(--color-text-strong);line-height:1.5;margin:0;padding:0;overflow-x:hidden}#editNip71TagsFieldset>legend,#nip71TagsFieldset>legend{margin-left:0;padding:0}#app{background-color:var(--color-page);color:inherit}body.modal-open,html.modal-open{overflow:hidden;height:100%}body.modal-open #app{overflow:hidden}body.modal-open #sidebar,body.modal-open #sidebarOverlay{pointer-events:none;opacity:0;visibility:hidden}body.modal-open #sidebar{z-index:var(--z-sidebar-overlay)}body.modal-open #sidebarOverlay{z-index:calc(var(--z-sidebar-overlay) - 1)}.ng-cloak,.ng-hide:not(.ng-hide-animate),.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}ng\:form{display:block}.ng-animate-shim{visibility:hidden}.ng-anchor{position:absolute}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}#playerModal.ds-header{display:flex}.ds-header{display:flex;align-items:center;justify-content:flex-start;padding-top:var(--space-md);padding-bottom:var(--space-md)}.focus-ring{outline:2px solid transparent;outline-offset:2px}.focus-ring:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:var(--color-info);--tw-ring-opacity:0.7;--tw-ring-offset-width:2px;--tw-ring-offset-color:var(--color-surface);transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transitionLine truncated
/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family-sans);font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--font-family-mono);font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}html{font-size:100%}@media (min-width:1024px){html{font-size:75%}}*,:after,:before{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent}::-webkit-scrollbar{width:var(--scrollbar-width);height:var(--scrollbar-width)}::-webkit-scrollbar-track{background:var(--scrollbar-track)}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:var(--radius-sm)}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}body{font-family:var(--font-family-sans);background-color:var(--color-page);color:var(--color-text-strong);line-height:1.5;margin:0;padding:0;overflow-x:hidden}#editNip71TagsFieldset>legend,#nip71TagsFieldset>legend{margin-left:0;padding:0}#app{background-color:var(--color-page);color:inherit}body.modal-open,html.modal-open{overflow:hidden;height:100%}body.modal-open #app{overflow:hidden}body.modal-open #sidebar,body.modal-open #sidebarOverlay{pointer-events:none;opacity:0;visibility:hidden}body.modal-open #sidebar{z-index:var(--z-sidebar-overlay)}body.modal-open #sidebarOverlay{z-index:calc(var(--z-sidebar-overlay) - 1)}.ng-cloak,.ng-hide:not(.ng-hide-animate),.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}ng\:form{display:block}.ng-animate-shim{visibility:hidden}.ng-anchor{position:absolute}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}#playerModal.ds-header{display:flex}.ds-header{display:flex;align-items:center;justify-content:flex-start;padding-top:var(--space-md);padding-bottom:var(--space-md)}.focus-ring{outline:2px solid transparent;outline-offset:2px}.focus-ring:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:var(--color-info);--tw-ring-opacity:0.7;--tw-ring-offset-width:2px;--tw-ring-offset-color:var(--color-surface);transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transitionLine truncated
+36 -444
View File
@@ -336,6 +336,7 @@
--blog-carousel-track-ease: var(--motion-ease-out, ease-out);
--blog-carousel-slide-duration: var(--motion-duration-base, 160ms);
--blog-carousel-progress-duration: var(--motion-duration-slow, 320ms);
--blog-carousel-per-page: 1;
}
.blog-carousel[data-transition="instant"] {
@@ -344,6 +345,7 @@
}
.blog-carousel__viewport {
position: relative;
overflow: hidden;
border-radius: var(--blog-carousel-slide-radius);
}
@@ -351,7 +353,7 @@
.blog-carousel__track {
display: grid;
grid-auto-flow: column;
grid-auto-columns: minmax(0, 100%);
grid-auto-columns: minmax(0, calc(100% / var(--blog-carousel-per-page)));
gap: var(--blog-carousel-gap);
width: 100%;
margin: 0;
@@ -439,54 +441,37 @@
pointer-events: none;
}
.blog-carousel[data-per-page="1"] {
--blog-carousel-per-page: 1;
}
.blog-carousel[data-per-page="2"] {
--blog-carousel-per-page: 2;
}
.blog-carousel[data-per-page="3"] {
--blog-carousel-per-page: 3;
}
.blog-carousel[data-per-page="4"] {
--blog-carousel-per-page: 4;
}
.blog-carousel__controls {
display: flex;
justify-content: flex-end;
gap: var(--space-sm);
}
.blog-progress {
position: relative;
display: flex;
gap: var(--space-xs);
align-items: center;
width: 100%;
--blog-carousel-progress-duration: var(--motion-duration-slow, 320ms);
}
.blog-progress[data-transition="instant"] {
--blog-carousel-progress-duration: 0s;
}
.blog-progress__item {
position: relative;
flex: 1 1 0%;
height: var(--blog-carousel-progress-height);
border-radius: var(--blog-carousel-progress-radius);
background: var(--blog-carousel-progress-track);
overflow: hidden;
}
.blog-progress__item::after {
content: "";
position: absolute;
inset: 0;
background: var(--blog-carousel-progress-active);
transform: scaleX(0);
transform-origin: left center;
transition: transform var(--blog-carousel-progress-duration)
var(--motion-ease-out, ease-out);
}
.blog-progress[data-state="paused"] .blog-progress__item::after {
transition-duration: 0s;
}
.blog-progress__item[data-active="true"]::after,
.blog-progress__item[data-state="complete"]::after {
transform: scaleX(1);
}
.blog-progress__item[data-active="true"] {
background: var(--blog-carousel-progress-track, var(--color-border));
}
.blog-progress__item[data-state="upcoming"]::after {
transform: scaleX(0);
.blog-progress [data-progress-meter] {
flex: 1 1 auto;
}
@media (prefers-reduced-motion: reduce) {
@@ -504,9 +489,6 @@
transition-duration: 0s;
}
.blog-progress__item::after {
transition-duration: 0s;
}
}
.blog-emoji-showdown {
@@ -2935,44 +2917,21 @@
margin-bottom: var(--space-xl);
}
.blog-app-root .short-notes .splide__slide {
.blog-app-root .short-notes .blog-carousel__slide {
font-size: var(--blog-font-size-body);
overflow-wrap: break-word;
}
.blog-app-root .short-notes .splide__slide a {
.blog-app-root .short-notes .blog-carousel__slide a {
text-decoration: none;
}
.blog-app-root .short-notes .splide__slide .date {
.blog-app-root .short-notes .blog-carousel__slide .date {
font-size: var(--blog-font-size-meta);
color: var(--color-blog-gray-275);
margin-bottom: var(--blog-space-compact);
}
.blog-app-root .short-notes .splide__arrow--prev {
left: -3em;
}
.blog-app-root .short-notes .splide__arrow--next {
right: -3em;
}
@media (max-width: 768px) {
.blog-app-root .short-notes .splide__arrow--prev,
.blog-app-root .short-notes .splide__arrow--next {
display: none;
}
}
.blog-app-root .short-notes .splide__arrow {
background: none;
}
.blog-app-root .short-notes .splide__arrow svg {
fill: var(--color-blog-gray-275);
}
.dark .blog-app-root .short-notes {
border-bottom: var(--space-xs) solid var(--color-card);
}
@@ -2981,6 +2940,12 @@
color: var(--color-blog-gray-220);
}
@media (max-width: 768px) {
.blog-app-root .short-notes [data-carousel-control] {
display: none;
}
}
.blog-app-root .listing-notes {
margin-top: var(--space-md);
color: var(--color-blog-gray-440);
@@ -3544,379 +3509,6 @@
}
}
.blog-app-root .splide__container {
box-sizing: border-box;
position: relative;
}
.blog-app-root .splide__list {
backface-visibility: hidden;
display: -ms-flexbox;
display: flex;
height: 100%;
margin: 0 !important;
padding: 0 !important;
}
.blog-app-root .splide.is-initialized:not(.is-active) .splide__list {
display: block;
}
.blog-app-root .splide__pagination {
-ms-flex-align: center;
align-items: center;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-ms-flex-pack: center;
justify-content: center;
margin: 0;
pointer-events: none;
}
.blog-app-root .splide__pagination li {
display: inline-block;
line-height: 1;
list-style-type: none;
margin: 0;
pointer-events: auto;
}
.blog-app-root .splide:not(.is-overflow) .splide__pagination {
display: none;
}
.blog-app-root .splide__progress {
border-radius: var(--blog-carousel-progress-radius);
}
.blog-app-root .splide__progress__bar {
width: 100%;
}
.blog-app-root .splide {
position: relative;
visibility: hidden;
}
.blog-app-root .splide.is-initialized,
.blog-app-root .splide.is-rendered {
visibility: visible;
}
.blog-app-root .splide__slide {
backface-visibility: hidden;
box-sizing: border-box;
-ms-flex-negative: 0;
flex-shrink: 0;
list-style-type: none !important;
margin: 0;
position: relative;
}
.blog-app-root .splide__slide img {
vertical-align: bottom;
}
.blog-app-root .splide__spinner {
animation: splide-loading 1s linear infinite;
border: 2px solid var(--color-blog-gray-300);
border-left-color: transparent;
border-radius: 50%;
bottom: 0;
contain: strict;
display: inline-block;
height: 20px;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0;
width: 20px;
}
.blog-app-root .splide__sr {
clip: rect(0 0 0 0);
border: 0;
height: var(--space-4xs);
margin: calc(var(--space-4xs) * -1);
overflow: hidden;
padding: 0;
position: absolute;
width: var(--space-4xs);
}
.blog-app-root .splide__toggle.is-active .splide__toggle__play,
.blog-app-root .splide__toggle__pause {
display: none;
}
.blog-app-root .splide__toggle.is-active .splide__toggle__pause {
display: inline;
}
.blog-app-root .splide__track {
overflow: hidden;
position: relative;
z-index: 0;
}
@keyframes splide-loading {
0% {
transform: rotate(0);
}
to {
transform: rotate(1turn);
}
}
.blog-app-root .splide__track--draggable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.blog-app-root .splide__track--fade > .splide__list > .splide__slide {
margin: 0 !important;
opacity: 0;
z-index: 0;
}
.blog-app-root .splide__track--fade > .splide__list > .splide__slide.is-active {
opacity: 1;
z-index: 1;
}
.blog-app-root .splide--rtl {
direction: rtl;
}
.blog-app-root .splide__track--ttb > .splide__list {
display: block;
}
.blog-app-root .splide__arrow {
-ms-flex-align: center;
align-items: center;
background: var(--color-blog-gray-210);
border: 0;
border-radius: 50%;
cursor: pointer;
display: -ms-flexbox;
display: flex;
height: 2em;
-ms-flex-pack: center;
justify-content: center;
opacity: 0.7;
padding: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 2em;
z-index: 1;
}
.blog-app-root .splide__arrow svg {
fill: var(--color-black);
height: 1.2em;
width: 1.2em;
}
.blog-app-root .splide__arrow:hover:not(:disabled) {
opacity: 0.9;
}
.blog-app-root .splide__arrow:disabled {
opacity: 0.3;
}
.blog-app-root .splide__arrow:focus-visible {
outline: var(--outline-thick-width) solid var(--color-blog-sky-500);
outline-offset: var(--outline-thick-width);
}
.blog-app-root .splide__arrow--prev {
left: 1em;
}
.blog-app-root .splide__arrow--prev svg {
transform: scaleX(-1);
}
.blog-app-root .splide__arrow--next {
right: 1em;
}
.blog-app-root .splide.is-focus-in .splide__arrow:focus {
outline: var(--outline-thick-width) solid var(--color-blog-sky-500);
outline-offset: var(--outline-thick-width);
}
.blog-app-root .splide__pagination {
bottom: 0.5em;
left: 0;
padding: 0 1em;
position: absolute;
right: 0;
z-index: 1;
}
.blog-app-root .splide__pagination__page {
background: var(--color-blog-gray-210);
border: 0;
border-radius: 50%;
display: inline-block;
height: 8px;
margin: var(--outline-thick-width);
opacity: 0.7;
padding: 0;
position: relative;
transition: transform 0.2s linear;
width: 8px;
}
.blog-app-root .splide__pagination__page.is-active {
background: var(--color-white);
transform: scale(1.4);
z-index: 1;
}
.blog-app-root .splide__pagination__page:hover {
cursor: pointer;
opacity: 0.9;
}
.blog-app-root .splide__pagination__page:focus-visible {
outline: var(--outline-thick-width) solid var(--color-blog-sky-500);
outline-offset: var(--outline-thick-width);
}
.blog-app-root .splide.is-focus-in .splide__pagination__page:focus {
outline: var(--outline-thick-width) solid var(--color-blog-sky-500);
outline-offset: var(--outline-thick-width);
}
.blog-app-root .splide__progress__bar {
height: var(--blog-carousel-progress-height);
background: transparent;
}
.blog-app-root .splide__slide {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.blog-app-root .splide__slide:focus {
outline: 0;
}
@supports (outline-offset: calc(var(--outline-thick-width) * -1)) {
.blog-app-root .splide__slide:focus-visible {
outline: var(--outline-thick-width) solid var(--color-blog-sky-500);
outline-offset: calc(var(--outline-thick-width) * -1);
}
}
@media screen and (-ms-high-contrast: none) {
.blog-app-root .splide__slide:focus-visible {
border: var(--outline-thick-width) solid var(--color-blog-sky-500);
}
}
@supports (outline-offset: calc(var(--outline-thick-width) * -1)) {
.blog-app-root .splide.is-focus-in .splide__slide:focus {
outline: var(--outline-thick-width) solid var(--color-blog-sky-500);
outline-offset: calc(var(--outline-thick-width) * -1);
}
}
@media screen and (-ms-high-contrast: none) {
.blog-app-root .splide.is-focus-in .splide__slide:focus {
border: var(--outline-thick-width) solid var(--color-blog-sky-500);
}
.blog-app-root .splide.is-focus-in .splide__track > .splide__list > .splide__slide:focus {
border-color: var(--color-blog-sky-500);
}
}
.blog-app-root .splide__toggle {
cursor: pointer;
}
.blog-app-root .splide__toggle:focus-visible {
outline: var(--outline-thick-width) solid var(--color-blog-sky-500);
outline-offset: var(--outline-thick-width);
}
.blog-app-root .splide.is-focus-in .splide__toggle:focus {
outline: var(--outline-thick-width) solid var(--color-blog-sky-500);
outline-offset: var(--outline-thick-width);
}
.blog-app-root .splide__track--nav > .splide__list > .splide__slide {
border: var(--outline-thick-width) solid transparent;
cursor: pointer;
}
.blog-app-root .splide__track--nav > .splide__list > .splide__slide.is-active {
border: var(--outline-thick-width) solid var(--color-black);
}
.blog-app-root .splide__arrows--rtl .splide__arrow--prev {
left: auto;
right: 1em;
}
.blog-app-root .splide__arrows--rtl .splide__arrow--prev svg {
transform: scaleX(1);
}
.blog-app-root .splide__arrows--rtl .splide__arrow--next {
left: 1em;
right: auto;
}
.blog-app-root .splide__arrows--rtl .splide__arrow--next svg {
transform: scaleX(-1);
}
.blog-app-root .splide__arrows--ttb .splide__arrow {
left: 50%;
transform: translate(-50%);
}
.blog-app-root .splide__arrows--ttb .splide__arrow--prev {
top: 1em;
}
.blog-app-root .splide__arrows--ttb .splide__arrow--prev svg {
transform: rotate(-90deg);
}
.blog-app-root .splide__arrows--ttb .splide__arrow--next {
bottom: 1em;
top: auto;
}
.blog-app-root .splide__arrows--ttb .splide__arrow--next svg {
transform: rotate(90deg);
}
.blog-app-root .splide__pagination--ttb {
bottom: 0;
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
left: auto;
padding: 1em 0;
right: 0.5em;
top: 0;
}
.blog-app-root .loader-container.svelte-181fgen {
display: flex;
align-items: center;
+34
View File
@@ -43,6 +43,40 @@ The blog embed now consumes dedicated typography and spacing tokens alongside Ta
Templates should reference these tokens (directly or via Tailwind arbitrary values) instead of hard-coded measurements so the embed stays in sync with future blog refreshes.
### Blog carousel markup
The short-notes carousel ships as a semantic wrapper around the `blog-carousel` primitive defined in `css/tailwind.source.css`. The runtime no longer injects inline styles or Splide classes—layout is driven entirely by data attributes that map onto tokenised CSS rules.
**Structure**
```html
<section class="short-notes blog-carousel" data-carousel data-per-page="3" data-index="0">
<div class="blog-carousel__viewport" data-carousel-viewport>
<ul class="blog-carousel__track" data-carousel-track>
<li class="blog-carousel__slide" data-carousel-slide data-index="0" data-active="true">
...
</li>
<!-- additional slides -->
</ul>
<button type="button" data-carousel-prev data-carousel-control="prev">‹</button>
<button type="button" data-carousel-next data-carousel-control="next">›</button>
</div>
<div class="blog-carousel__progress blog-progress" data-carousel-progress>
<progress class="progress progress--blog" data-progress-meter aria-label="Carousel progress"></progress>
</div>
</section>
```
**State contract**
- `data-per-page` controls how many columns render inside the viewport. CSS exposes variants for 1–4 slides per page; the custom property `--blog-carousel-per-page` updates automatically.
- `data-index` represents the active *page* (zero-indexed). The track translates by `100%` for each page, so the attribute should advance in whole-number increments rather than slide indices.
- Slides expose `data-index`, `data-active`, `data-inert`, and `data-state`. The carousel controller toggles `data-state="active"`, `"idle"`, and `"leaving"` to drive scale/opacity transitions without touching inline styles.
- The root `section` advertises `data-state="active" | "idle"` plus `data-transition="auto" | "instant"` when short-circuiting animations (for example, during first render or programmatic jumps).
- Progress wrappers set `data-state="idle" | "active" | "paused" | "complete"` while the inner `<progress>` leverages the shared `progress` primitive (`data-progress-meter`, `data-variant="blog"`) so theme tokens continue to drive track/fill colours.
Controllers should rely on these data hooks when orchestrating autoplay, pausing on pointer/visibility changes, or syncing the segmented progress indicator. Avoid reintroducing inline `style` mutations—tests enforce the contract via the inline-style guard.
### Layout & spacing extensions
The core spacing scale gained intermediate stops for tighter micro-layouts and larger hero treatments. `css/tokens.css` now exports `--space-4xs`, `--space-3xs`, `--space-xs-snug`, `--space-md-plus`, `--space-xl-compact`, `--space-xl-plus`, and `--space-2xl-plus`; Tailwind surfaces matching utilities such as `p-4xs`, `p-3xs`, `px-xs-snug`, `py-md-plus`, `gap-xl-compact`, `px-xl-plus`, and `gap-2xl-plus`. Pair them with the new layout primitives—`--menu-min-width` (`min-w-menu`), `--modal-max-width` (`max-w-modal`), `--layout-player-max-width` (`max-w-player`), and `--layout-docs-max-width` (`max-w-docs`)—to anchor consistent breakpoints across popovers, modals, and documentation shells without reintroducing raw `rem`/`px` literals. Blog-specific chrome reuses the same additions: the theme toggle knob now references `--radius-toggle-thumb`, and focus affordances draw from `--outline-thick-width` instead of hard-coded pixel outlines.
+150 -43
View File
@@ -1,5 +1,3 @@
import { applyDynamicStyles } from '../ui/styleSystem.js';
function toArray(value) {
if (!value) {
return [];
@@ -7,6 +5,85 @@ function toArray(value) {
return Array.isArray(value) ? value : Array.from(value);
}
const SLIDE_STATE_IDLE = 'idle';
const SLIDE_STATE_ACTIVE = 'active';
const SLIDE_STATE_LEAVING = 'leaving';
const SLIDE_TRANSITION_TIMEOUT = 420;
function getRange(pageIndex, perPage) {
const start = pageIndex * perPage;
return { start, end: start + perPage };
}
function clearTimer(state, slide) {
const handle = state.timers.get(slide);
if (typeof handle === 'number') {
clearTimeout(handle);
}
state.timers.delete(slide);
}
function scheduleSlideIdle(state, slide) {
clearTimer(state, slide);
const handle = setTimeout(() => {
state.timers.delete(slide);
if (slide.dataset.active !== 'true') {
slide.dataset.state = SLIDE_STATE_IDLE;
}
}, SLIDE_TRANSITION_TIMEOUT);
state.timers.set(slide, handle);
}
function applySlideState(state, slide, { isActive, wasActive, immediate }) {
if (isActive) {
clearTimer(state, slide);
slide.dataset.active = 'true';
slide.dataset.state = SLIDE_STATE_ACTIVE;
slide.dataset.inert = 'false';
slide.setAttribute('aria-hidden', 'false');
return;
}
slide.dataset.active = 'false';
slide.dataset.inert = 'true';
slide.setAttribute('aria-hidden', 'true');
if (!immediate && wasActive) {
slide.dataset.state = SLIDE_STATE_LEAVING;
scheduleSlideIdle(state, slide);
return;
}
clearTimer(state, slide);
slide.dataset.state = SLIDE_STATE_IDLE;
}
function updateSlides(state, { previousIndex, immediate = false } = {}) {
const currentRange = getRange(state.index, state.perPage);
const previousRange =
typeof previousIndex === 'number' ? getRange(previousIndex, state.perPage) : currentRange;
state.slides.forEach((slide, slideIndex) => {
const isActive = slideIndex >= currentRange.start && slideIndex < currentRange.end;
const wasActive = slideIndex >= previousRange.start && slideIndex < previousRange.end;
applySlideState(state, slide, { isActive, wasActive, immediate });
});
}
function syncTransitionMode(container, track, mode) {
if (!container || !track) {
return;
}
if (mode) {
container.dataset.transition = mode;
track.dataset.transition = mode;
} else {
delete container.dataset.transition;
delete track.dataset.transition;
}
}
export function createCarousel(container, options = {}) {
const track = options.track || container?.querySelector('[data-carousel-track]');
const slides = options.slides || toArray(track?.children);
@@ -15,45 +92,63 @@ export function createCarousel(container, options = {}) {
}
const perPage = Math.max(1, Math.min(options.perPage || 1, slides.length));
const pageCount = Math.max(1, Math.ceil(slides.length / perPage));
const state = {
container,
track,
slides,
perPage,
pageCount,
loop: Boolean(options.loop),
autoPlayInterval: Math.max(0, options.autoPlayInterval || 0),
index: 0,
progress: null,
cleanup: [],
timers: new Map(),
setProgress(progressInstance) {
this.progress = progressInstance || null;
},
};
container.classList.add('splide', 'is-initialized');
container.classList.add('is-active');
applyDynamicStyles(track, { transition: 'transform 420ms ease' }, { slot: 'transition' });
container.dataset.count = String(pageCount);
container.dataset.perPage = String(perPage);
container.dataset.index = '0';
container.dataset.state = pageCount > 1 ? 'active' : 'idle';
const basis = 100 / perPage;
slides.forEach((slide) => {
slide.classList.add('splide__slide');
applyDynamicStyles(slide, { flex: `0 0 ${basis}%`, maxWidth: `${basis}%` }, { slot: 'size' });
syncTransitionMode(container, track, 'auto');
slides.forEach((slide, slideIndex) => {
slide.dataset.index = String(slideIndex);
slide.dataset.active = 'false';
slide.dataset.state = SLIDE_STATE_IDLE;
slide.dataset.inert = 'true';
slide.setAttribute('aria-hidden', 'true');
});
function updateActiveSlides() {
const offset = (state.index * 100) / state.perPage;
applyDynamicStyles(track, { transform: `translateX(-${offset}%)` }, { slot: 'position' });
state.slides.forEach((slide, idx) => {
const inRange = idx >= state.index && idx < state.index + state.perPage;
slide.classList.toggle('is-active', inRange);
slide.setAttribute('aria-hidden', inRange ? 'false' : 'true');
});
function handlePrev(event) {
event?.preventDefault?.();
advanceSlide(state, -1);
}
state.goTo = function goTo(targetIndex, { restartProgress = true } = {}) {
const maxIndex = Math.max(0, state.slides.length - state.perPage);
let nextIndex = targetIndex;
function handleNext(event) {
event?.preventDefault?.();
advanceSlide(state, 1);
}
if (options.controls?.prev) {
options.controls.prev.addEventListener('click', handlePrev);
state.cleanup.push(() => options.controls.prev.removeEventListener('click', handlePrev));
}
if (options.controls?.next) {
options.controls.next.addEventListener('click', handleNext);
state.cleanup.push(() => options.controls.next.removeEventListener('click', handleNext));
}
state.goTo = function goTo(targetPage, { restartProgress = true, instant = false } = {}) {
const maxIndex = state.pageCount - 1;
let nextIndex = targetPage;
if (nextIndex < 0) {
nextIndex = state.loop ? maxIndex : 0;
@@ -61,8 +156,18 @@ export function createCarousel(container, options = {}) {
nextIndex = state.loop ? 0 : maxIndex;
}
const previousIndex = state.index;
state.index = nextIndex;
updateActiveSlides();
container.dataset.index = String(nextIndex);
syncTransitionMode(container, track, instant ? 'instant' : 'auto');
updateSlides(state, { previousIndex, immediate: instant });
if (instant) {
syncTransitionMode(container, track, 'auto');
}
if (restartProgress && state.autoPlayInterval > 0 && state.progress) {
state.progress.reset();
@@ -70,26 +175,8 @@ export function createCarousel(container, options = {}) {
}
};
const prevHandler = (event) => {
event?.preventDefault();
advanceSlide(state, -1);
};
const nextHandler = (event) => {
event?.preventDefault();
advanceSlide(state, 1);
};
state.goTo(0, { restartProgress: false, instant: true });
if (options.controls?.prev) {
options.controls.prev.addEventListener('click', prevHandler);
state.cleanup.push(() => options.controls.prev.removeEventListener('click', prevHandler));
}
if (options.controls?.next) {
options.controls.next.addEventListener('click', nextHandler);
state.cleanup.push(() => options.controls.next.removeEventListener('click', nextHandler));
}
state.goTo(state.index, { restartProgress: false });
return state;
}
@@ -104,6 +191,7 @@ export function destroyCarousel(state) {
if (!state) {
return;
}
state.cleanup?.forEach((dispose) => {
try {
dispose();
@@ -113,12 +201,31 @@ export function destroyCarousel(state) {
});
state.cleanup = [];
for (const handle of state.timers.values()) {
clearTimeout(handle);
}
state.timers.clear();
if (state.progress && typeof state.progress.destroy === 'function') {
state.progress.destroy();
}
if (state.container) {
state.container.classList.remove('is-initialized');
state.container.classList.remove('is-active');
delete state.container.dataset.index;
delete state.container.dataset.count;
delete state.container.dataset.perPage;
delete state.container.dataset.state;
delete state.container.dataset.transition;
}
if (state.track) {
delete state.track.dataset.transition;
}
state.slides?.forEach((slide) => {
delete slide.dataset.active;
delete slide.dataset.state;
delete slide.dataset.inert;
slide.setAttribute('aria-hidden', 'false');
});
}
+26 -15
View File
@@ -1,7 +1,6 @@
import { createCarousel, advanceSlide, destroyCarousel } from './carousel.js';
import { mountProgressBar } from './progressBar.js';
import { initZapthreadsEmbed as defaultInitZapthreadsEmbed } from './zapthreadsEmbed.js';
import { applyDynamicStyles } from '../ui/styleSystem.js';
const CAROUSEL_AUTOPLAY_INTERVAL = 8000;
@@ -235,8 +234,18 @@ function renderTopNotes(notes) {
function createArrowButton(direction) {
const button = document.createElement('button');
button.type = 'button';
button.className = `splide__arrow splide__arrow--${direction} flex h-10 w-10 items-center justify-center rounded-full border border-blog-neutral-200 text-xl text-blog-gray-500 transition hover:text-blog-purple dark:border-blog-neutral-700 dark:text-blog-gray-200`;
const sideClass =
direction === 'next'
? 'right-0 translate-x-1/2'
: 'left-0 -translate-x-1/2';
button.className = `blog-carousel__control pointer-events-auto absolute top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border border-blog-neutral-200 bg-white/80 text-xl text-blog-gray-500 shadow-sm transition hover:text-blog-purple dark:border-blog-neutral-700 dark:bg-blog-neutral-900/80 dark:text-blog-gray-200 ${sideClass}`;
button.setAttribute('aria-label', direction === 'next' ? 'Next slide' : 'Previous slide');
button.setAttribute('data-carousel-control', direction);
if (direction === 'next') {
button.setAttribute('data-carousel-next', 'true');
} else {
button.setAttribute('data-carousel-prev', 'true');
}
button.textContent = direction === 'next' ? '›' : '‹';
return button;
}
@@ -247,28 +256,31 @@ function renderShortNotesCarousel(notes, config) {
}
const section = document.createElement('section');
section.className = 'short-notes splide flex flex-col gap-4';
section.className = 'short-notes blog-carousel relative flex flex-col gap-4';
section.setAttribute('data-carousel', 'true');
const heading = document.createElement('h2');
heading.className = 'text-2xl font-semibold text-blog-gray-800 dark:text-blog-gray-100';
heading.textContent = 'Short notes';
section.appendChild(heading);
const trackWrapper = document.createElement('div');
trackWrapper.className = 'splide__track overflow-hidden';
section.appendChild(trackWrapper);
const viewport = document.createElement('div');
viewport.className = 'blog-carousel__viewport relative overflow-hidden';
viewport.setAttribute('data-carousel-viewport', 'true');
section.appendChild(viewport);
const list = document.createElement('ul');
list.className = 'splide__list flex gap-4';
list.className = 'blog-carousel__track';
list.setAttribute('data-carousel-track', 'true');
trackWrapper.appendChild(list);
viewport.appendChild(list);
const perPage = Math.min(notes.length, 3);
const summariesLimit = config.shortFeedSummaryMaxChars || 300;
notes.forEach((note) => {
const item = document.createElement('li');
item.className = 'splide__slide flex flex-col gap-2 rounded-lg bg-blog-neutral-50 p-4 dark:bg-blog-neutral-900';
item.className =
'blog-carousel__slide flex flex-col gap-2 rounded-lg bg-blog-neutral-50 p-4 transition-colors dark:bg-blog-neutral-900';
item.setAttribute('data-carousel-slide', 'true');
const link = document.createElement('a');
@@ -291,8 +303,8 @@ function renderShortNotesCarousel(notes, config) {
const prev = createArrowButton('prev');
const next = createArrowButton('next');
section.appendChild(prev);
section.appendChild(next);
viewport.appendChild(prev);
viewport.appendChild(next);
const carousel = createCarousel(section, {
track: list,
@@ -381,8 +393,8 @@ function renderMainFeed(notes) {
notes.forEach((note) => {
const article = document.createElement('article');
article.id = note.id;
article.className = 'flex flex-col gap-4 border-b border-blog-neutral-200 pb-8 last:border-b-0 dark:border-blog-neutral-800';
applyDynamicStyles(article, { scrollMarginTop: '6rem' }, { slot: 'anchor-offset' });
article.className =
'flex scroll-mt-24 flex-col gap-4 border-b border-blog-neutral-200 pb-8 last:border-b-0 dark:border-blog-neutral-800';
const header = document.createElement('header');
header.className = 'flex flex-col gap-2';
@@ -596,8 +608,7 @@ function mountBlogApp({ initZapthreadsEmbed = defaultInitZapthreadsEmbed } = {})
}
const main = document.createElement('main');
main.className = 'mx-auto flex w-full max-w-5xl flex-col gap-12 px-4 py-12';
applyDynamicStyles(main, { scrollPaddingTop: '6rem' }, { slot: 'scroll-padding' });
main.className = 'mx-auto flex w-full max-w-5xl flex-col gap-12 px-4 py-12 scroll-pt-24';
main.appendChild(hero);
const topNotes = config.topNotes > 0 ? notes.slice(0, config.topNotes) : [];
+6 -3
View File
@@ -7,15 +7,17 @@ export function mountProgressBar(
}
const wrapper = document.createElement('div');
wrapper.className =
'splide__progress mt-2 h-1 w-full overflow-hidden rounded-full bg-blog-neutral-100 dark:bg-blog-neutral-800';
wrapper.className = 'blog-carousel__progress blog-progress mt-2';
wrapper.dataset.state = 'idle';
wrapper.setAttribute('data-carousel-progress', 'true');
const meter = document.createElement('progress');
meter.className = 'splide__progress__bar progress progress--blog transition-none';
meter.className = 'progress progress--blog';
meter.max = 100;
meter.value = 0;
meter.dataset.progress = '0';
meter.dataset.state = 'idle';
meter.setAttribute('data-progress-meter', 'true');
meter.setAttribute('data-variant', 'blog');
meter.setAttribute('aria-label', 'Carousel progress');
meter.setAttribute('aria-valuetext', 'Carousel progress 0% complete');
@@ -35,6 +37,7 @@ export function mountProgressBar(
}
function setState(state) {
wrapper.dataset.state = state;
meter.dataset.state = state;
}
+30 -25
View File
@@ -21,15 +21,17 @@ function assertNoInlineStyleAttributes(elements, messagePrefix = 'element') {
}
const CAROUSEL_HTML = `
<div data-carousel>
<div data-carousel-track>
<article data-slide="0">Slide 1</article>
<article data-slide="1">Slide 2</article>
<article data-slide="2">Slide 3</article>
<section class="blog-carousel" data-carousel>
<div data-carousel-viewport>
<ul data-carousel-track>
<li data-carousel-slide>Slide 1</li>
<li data-carousel-slide>Slide 2</li>
<li data-carousel-slide>Slide 3</li>
</ul>
</div>
<button type="button" data-carousel-prev>Prev</button>
<button type="button" data-carousel-next>Next</button>
</div>
</section>
`;
const PROGRESS_HTML = '<div id="progress-root"></div>';
@@ -62,7 +64,7 @@ describe('blog carousel', () => {
env.cleanup();
});
test('initializes slides with dynamic styling and accessibility state', () => {
test('initializes slides with data attributes and accessibility state', () => {
const state = createCarousel(container, {
perPage: 1,
loop: true,
@@ -72,27 +74,28 @@ describe('blog carousel', () => {
assert.ok(state, 'state should be created when container markup is valid');
assert.strictEqual(state.perPage, 1);
assert.strictEqual(state.slides.length, 3);
assert.ok(container.classList.contains('splide'));
assert.ok(container.classList.contains('is-initialized'));
assert.ok(container.classList.contains('is-active'));
assert.strictEqual(state.pageCount, 3);
const trackDynamicClasses = Array.from(track.classList).filter((className) => className.startsWith('bvds-'));
assert.ok(trackDynamicClasses.length >= 1, 'track should receive dynamic style classes');
assert.strictEqual(container.dataset.index, '0');
assert.strictEqual(container.dataset.perPage, '1');
assert.strictEqual(container.dataset.count, '3');
assert.strictEqual(container.dataset.state, 'active');
assert.strictEqual(track.dataset.transition, 'auto');
assertNoInlineStyleAttributes([container, track, ...slides], 'carousel element');
for (const slide of slides) {
assert.ok(slide.classList.contains('splide__slide'));
const dynamicClasses = Array.from(slide.classList).filter((className) => className.startsWith('bvds-'));
assert.ok(dynamicClasses.length >= 1, 'slide should receive dynamic sizing styles');
}
assert.strictEqual(slides[0].getAttribute('aria-hidden'), 'false');
assert.strictEqual(slides[1].getAttribute('aria-hidden'), 'true');
assert.strictEqual(slides[2].getAttribute('aria-hidden'), 'true');
slides.forEach((slide, index) => {
assert.strictEqual(slide.dataset.index, String(index));
assert.strictEqual(slide.dataset.active, index === 0 ? 'true' : 'false');
assert.strictEqual(slide.dataset.inert, index === 0 ? 'false' : 'true');
assert.strictEqual(slide.getAttribute('aria-hidden'), index === 0 ? 'false' : 'true');
});
state.goTo(2);
assert.strictEqual(state.index, 2);
assert.strictEqual(container.dataset.index, '2');
assert.strictEqual(slides[2].dataset.active, 'true');
assert.strictEqual(slides[0].dataset.active, 'false');
assert.strictEqual(slides[2].getAttribute('aria-hidden'), 'false');
assert.strictEqual(slides[0].getAttribute('aria-hidden'), 'true');
@@ -169,6 +172,7 @@ describe('blog carousel', () => {
const previousIndex = state.index;
nextButton.dispatchEvent(new env.window.MouseEvent('click', { bubbles: true }));
assert.strictEqual(state.index, previousIndex, 'clicks after destroy should not change the index');
assert.strictEqual(container.dataset.index, undefined);
});
test('advanceSlide delegates to goTo with relative offsets', () => {
@@ -202,13 +206,14 @@ describe('progress bar', () => {
const progress = mountProgressBar(container, { initialDuration: 500, autoStart: false });
assert.ok(progress, 'progress instance should be returned');
const wrapper = container.querySelector('.splide__progress');
const wrapper = container.querySelector('.blog-carousel__progress');
assert.ok(wrapper, 'wrapper element should be appended');
const meter = wrapper.querySelector('progress.splide__progress__bar');
const meter = wrapper.querySelector('progress.progress--blog');
assert.ok(meter, 'progress meter should be created');
assert.strictEqual(meter.max, 100, 'progress meter should expose a 0-100 range');
assert.strictEqual(meter.value, 0, 'progress meter should start at zero');
assert.strictEqual(meter.dataset.state, 'idle');
assert.strictEqual(wrapper.dataset.state, 'idle');
assert.strictEqual(meter.dataset.progress, '0');
assert.strictEqual(
meter.getAttribute('aria-valuetext'),
@@ -228,7 +233,7 @@ describe('progress bar', () => {
const onComplete = createSpy();
const progress = mountProgressBar(container, { initialDuration: 600, autoStart: true, onComplete });
const wrapper = progress.element;
const meter = wrapper.querySelector('progress.splide__progress__bar');
const meter = wrapper.querySelector('progress.progress--blog');
assert.ok(meter, 'progress meter should exist when autoStart is enabled');
assertNoInlineStyleAttributes([container, wrapper, meter], 'progress element');
@@ -252,7 +257,7 @@ describe('progress bar', () => {
test('manual controls support stop, restart, and cleanup', () => {
const onComplete = createSpy();
const progress = mountProgressBar(container, { initialDuration: 800, onComplete });
const meter = progress.element.querySelector('progress.splide__progress__bar');
const meter = progress.element.querySelector('progress.progress--blog');
progress.start(800);
env.raf.step(0);
+4 -4
View File
@@ -78,7 +78,7 @@ export function installInlineStyleGuard(document) {
prototype.setAttribute = function inlineStyleSafeSetAttribute(name, value) {
if (typeof name === 'string' && name.toLowerCase() === 'style') {
throw new Error(
'Inline style attributes are disabled in blog widget tests. Use applyDynamicStyles or CSS class toggles instead.',
'Inline style attributes are disabled in blog widget tests. Use CSS classes or data attributes instead.',
);
}
return original.call(this, name, value);
@@ -93,7 +93,7 @@ export function installInlineStyleGuard(document) {
prototype.setAttributeNS = function inlineStyleSafeSetAttributeNS(namespace, name, value) {
if (typeof name === 'string' && name.toLowerCase() === 'style') {
throw new Error(
'Inline style attributes are disabled in blog widget tests. Use applyDynamicStyles or CSS class toggles instead.',
'Inline style attributes are disabled in blog widget tests. Use CSS classes or data attributes instead.',
);
}
return original.call(this, namespace, name, value);
@@ -116,12 +116,12 @@ export function installInlineStyleGuard(document) {
Object.defineProperty(element, 'style', {
get() {
throw new Error(
'Inline style access is disabled in blog widget tests. Use applyDynamicStyles or CSS class toggles instead.',
'Inline style access is disabled in blog widget tests. Use CSS classes or data attributes instead.',
);
},
set() {
throw new Error(
'Inline style assignment is disabled in blog widget tests. Use applyDynamicStyles or CSS class toggles instead.',
'Inline style assignment is disabled in blog widget tests. Use CSS classes or data attributes instead.',
);
},
configurable: true,