diff --git a/creativecloud/blocks/firefly-carousel/firefly-carousel.css b/creativecloud/blocks/firefly-carousel/firefly-carousel.css
new file mode 100644
index 000000000..60245e759
--- /dev/null
+++ b/creativecloud/blocks/firefly-carousel/firefly-carousel.css
@@ -0,0 +1,223 @@
+.firefly-carousel {
+ --ff-gap: 8px;
+ --ff-card-width: 80vw;
+ --ff-card-height: calc(var(--ff-card-width) / 1.4);
+ --ff-nav-size: 40px;
+ --ff-control-gap: 8px;
+ --ff-card-border-radius: 16px;
+ padding: 0;
+ position: relative;
+}
+
+.firefly-carousel {
+ width: 100%;
+}
+
+.firefly-carousel .firefly-carousel-viewport {
+ width: 100%;
+ overflow: hidden;
+ position: relative;
+ padding-bottom: 160px;
+}
+
+.firefly-carousel .firefly-carousel-track {
+ display: flex;
+ gap: var(--ff-gap);
+ will-change: transform;
+ transition: transform 320ms ease;
+ margin-left: calc((100% - var(--ff-card-width)) / 2);
+}
+
+.firefly-carousel .firefly-carousel-card {
+ position: relative;
+ flex: 0 0 var(--ff-card-width);
+ width: var(--ff-card-width);
+ height: var(--ff-card-height);
+ overflow: hidden;
+ border-radius: var(--ff-card-border-radius);
+ background: #111;
+ outline: none;
+}
+
+.firefly-carousel .firefly-carousel-card:focus-visible::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ border: 2px solid #1473E6;
+ border-radius: var(--ff-card-border-radius);
+ pointer-events: none;
+ z-index: 2;
+}
+
+.firefly-carousel .firefly-carousel-card.active {
+ /* Slightly reduced shadow extent (5px) to avoid clipping by content below */
+ box-shadow: 0 95px 75px 0 rgba(0, 0, 0, 0.19), 0 64.815px 46.852px 0 rgba(0, 0, 0, 0.14), 0 38.519px 25.481px 0 rgba(0, 0, 0, 0.12), 0 20px 13px 0 rgba(0, 0, 0, 0.09), 0 8.148px 6.519px 0 rgba(0, 0, 0, 0.07), 0 1.852px 3.148px 0 rgba(0, 0, 0, 0.05);
+}
+
+.firefly-carousel .firefly-carousel-media {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.firefly-carousel .firefly-carousel-media picture,
+.firefly-carousel .firefly-carousel-media img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+}
+
+.firefly-carousel .firefly-carousel-prompt {
+ position: absolute;
+ bottom: 16px;
+ left: 50%;
+ transform: translateX(-50%);
+ display: none;
+ flex-direction: row;
+ align-items: center;
+ padding: 8px 9px 8px 8px;
+ border-radius: 8px;
+ border: 1px solid var(--Color-Middle-Grey, #707070);
+ background: var(--container-fill, #FFF);
+ box-shadow: 0 0 4px 1px rgba(0, 0, 0, 0.25);
+ width: fit-content;
+ max-width: calc(var(--ff-card-width) - 80px);
+ color: inherit;
+ text-decoration: none;
+ cursor: pointer;
+ font-family: inherit;
+ text-align: start;
+}
+
+.firefly-carousel .firefly-carousel-card.active .firefly-carousel-prompt {
+ display: flex;
+}
+
+[dir="rtl"] .firefly-carousel .firefly-carousel-prompt {
+ left: 56px;
+ right: 16px;
+}
+
+.firefly-carousel .firefly-carousel-prompt-text {
+ flex: 0 1 auto;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+ letter-spacing: -0.14px;
+ color: inherit;
+ text-decoration: none;
+}
+
+.firefly-carousel .firefly-carousel-prompt-icon {
+ flex: 0 0 auto;
+ display: inline-flex;
+ align-items: center;
+ justify-content: flex-end;
+ width: 16px;
+ margin-left: 24px;
+ height: 16px;
+}
+
+.firefly-carousel .firefly-carousel-nav-btn {
+ width: var(--ff-nav-size);
+ height: var(--ff-nav-size);
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ position: absolute;
+ top: calc(var(--ff-card-height) / 2);
+ z-index: 1;
+ border-radius: 90.909px;
+ background: #010101;
+ backdrop-filter: blur(13.63636302947998px);
+ border: none;
+ cursor: pointer;
+}
+
+.firefly-carousel .firefly-carousel-nav-btn:focus-visible::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ border: 2px solid #1473E6;
+ border-radius: 90.909px;
+ pointer-events: none;
+ z-index: 2;
+}
+
+.firefly-carousel .firefly-carousel-nav-btn.prev {
+ left: calc((100% - var(--ff-card-width)) / 2);
+ transform: translate(-50%, -50%);
+}
+
+.firefly-carousel .firefly-carousel-nav-btn.next {
+ right: calc((100% - var(--ff-card-width)) / 2);
+ transform: translate(50%, -50%);
+}
+
+.firefly-carousel .firefly-carousel-nav-btn:disabled {
+ opacity: 0.5;
+ background: rgba(80, 80, 80, 0.40);
+ backdrop-filter: blur(13.63636302947998px);
+}
+
+.firefly-carousel .firefly-carousel-nav-btn svg {
+ width: 10.909px;
+ height: 10.909px;
+ flex-shrink: 0;
+ color: #FFFFFF;
+}
+
+@media screen and (min-width: 600px) {
+ .firefly-carousel {
+ --ff-card-width: 70vw;
+ --ff-card-height: calc(var(--ff-card-width) / 1.6);
+ --ff-card-border-radius: 12px;
+ }
+
+ .firefly-carousel .firefly-carousel-prompt {
+ left: 30px;
+ bottom: 20px;
+ transform: none;
+ max-width: calc(var(--ff-card-width) / 2.2);
+ }
+}
+
+@media screen and (min-width: 1200px) {
+ .firefly-carousel {
+ --ff-card-width: 60vw;
+ --ff-card-height: calc(var(--ff-card-width) / 1.6);
+ --ff-card-border-radius: 24px;
+ --ff-nav-size: 65px;
+ }
+
+ .firefly-carousel .firefly-carousel-card.active .firefly-carousel-prompt {
+ padding: 16px 18px 16px 16px;
+ border-radius: 11px;
+ left: 55px;
+ bottom: 55px;
+ max-width: calc(var(--ff-card-width) / 1.6);
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity 180ms ease;
+ }
+
+ .firefly-carousel .firefly-carousel-card.active:hover .firefly-carousel-prompt,
+ .firefly-carousel .firefly-carousel-card.active:focus-within .firefly-carousel-prompt {
+ opacity: 1;
+ visibility: visible;
+ pointer-events: auto;
+ }
+
+ .firefly-carousel .firefly-carousel-prompt-text {
+ font-size: 22px;
+ letter-spacing: -0.22px;
+ }
+}
diff --git a/creativecloud/blocks/firefly-carousel/firefly-carousel.js b/creativecloud/blocks/firefly-carousel/firefly-carousel.js
new file mode 100644
index 000000000..3e25cc0c1
--- /dev/null
+++ b/creativecloud/blocks/firefly-carousel/firefly-carousel.js
@@ -0,0 +1,335 @@
+import { createTag } from '../../scripts/utils.js';
+
+const BLOCK = 'firefly-carousel';
+const GAP = 8;
+const TRANSITION_FALLBACK_MS = 420;
+const AUTOSCROLL_INTERVAL_MS = 4000;
+const NAV_DIRECTIONS = {
+ PREV: 'prev',
+ NEXT: 'next',
+};
+const NAV_INDEX_DELTAS = {
+ [NAV_DIRECTIONS.PREV]: -1,
+ [NAV_DIRECTIONS.NEXT]: 1,
+};
+const NAV_FRAMES = {
+ BASE: { beforeActive: 1, offsetMultiplier: -1 },
+ NEXT: { beforeActive: 1, offsetMultiplier: -2 },
+ PREV_STAGE: { beforeActive: 2, offsetMultiplier: -2 },
+ PREV: { beforeActive: 2, offsetMultiplier: -1 },
+};
+const NAV_SEQUENCES = {
+ [NAV_DIRECTIONS.NEXT]: [
+ { frame: NAV_FRAMES.NEXT, animate: true },
+ ],
+ [NAV_DIRECTIONS.PREV]: [
+ { frame: NAV_FRAMES.PREV_STAGE, animate: false },
+ { frame: NAV_FRAMES.PREV, animate: true },
+ ],
+};
+
+const ICONS = {
+ prompt: ``,
+ navPrev: ``,
+ navNext: ``,
+};
+
+// Parses carousel items directly from the block DOM. Each item has picture, caption, and deeplink
+function parseItemsFromDOM(el) {
+ const itemDivs = el.querySelectorAll(':scope > div');
+ const items = [];
+
+ itemDivs.forEach((itemDiv) => {
+ const [mediaDiv, captionDiv, deeplinkDiv] = itemDiv.children;
+ const pictureEl = mediaDiv?.querySelector('picture');
+ const deeplinkAnchor = deeplinkDiv?.querySelector('a[href]');
+ if (!pictureEl) return;
+
+ items.push({
+ pictureEl,
+ promptText: captionDiv?.textContent?.trim() || '',
+ deeplinkUrl: deeplinkAnchor?.getAttribute('href') || '',
+ });
+ });
+ return items;
+}
+
+function wrapIndex(index, length) {
+ return (index + length) % length;
+}
+
+function createPromptPill(promptText, deeplinkUrl) {
+ const attrs = {
+ class: `${BLOCK}-prompt`,
+ title: promptText || '',
+ 'aria-label': promptText || 'Open in Firefly',
+ };
+ const pill = createTag('button', { ...attrs, type: 'button', tabIndex: -1 });
+ if (deeplinkUrl) {
+ pill.addEventListener('click', () => {
+ window.open(deeplinkUrl, '_blank', 'noopener');
+ });
+ }
+
+ const text = createTag('span', { class: `${BLOCK}-prompt-text` }, promptText || '');
+ const icon = createTag('span', { class: `${BLOCK}-prompt-icon`, 'aria-hidden': 'true' });
+ icon.innerHTML = ICONS.prompt;
+
+ pill.append(text, icon);
+ return pill;
+}
+
+function createCard(item, index) {
+ const card = createTag('article', { class: `${BLOCK}-card` });
+ card.dataset.slideIndex = String(index);
+ const mediaWrapper = createTag('div', { class: `${BLOCK}-media` });
+ const clonedPicture = item.pictureEl.cloneNode(true);
+ const img = clonedPicture.querySelector('img');
+ if (img) img.setAttribute('loading', 'eager');
+ mediaWrapper.appendChild(clonedPicture);
+ card.append(mediaWrapper, createPromptPill(item.promptText || '', item.deeplinkUrl));
+ return card;
+}
+
+function createNavButton(direction, onClick) {
+ const isPrev = direction === NAV_DIRECTIONS.PREV;
+ const button = createTag('button', {
+ class: `${BLOCK}-nav-btn ${isPrev ? 'prev' : 'next'}`,
+ type: 'button',
+ 'aria-label': isPrev ? 'Previous slide' : 'Next slide',
+ });
+ button.innerHTML = isPrev ? ICONS.navPrev : ICONS.navNext;
+ button.addEventListener('click', onClick);
+ return button;
+}
+
+/** Populates the track with only authored carousel cards. */
+function buildTrack(track, items) {
+ const cards = items.map((item, index) => createCard(item, index));
+ track.append(...cards);
+ return cards;
+}
+
+function getTrackStep(cards) {
+ const firstCard = cards[0];
+ const cardWidth = firstCard?.getBoundingClientRect().width;
+ if (!cardWidth) return null;
+ return cardWidth + GAP;
+}
+
+function updateActiveCard(cards, currentIndex) {
+ cards.forEach((card) => {
+ const isActive = Number(card.dataset.slideIndex) === currentIndex;
+ card.classList.toggle('active', isActive);
+ card.tabIndex = isActive ? 0 : -1;
+ const prompt = card.querySelector(`.${BLOCK}-prompt`);
+ if (prompt) prompt.tabIndex = isActive ? 0 : -1;
+ });
+}
+
+function setCircularOrder(cards, currentIndex, itemCount, beforeActive) {
+ const startIndex = wrapIndex(currentIndex - beforeActive, itemCount);
+ for (let pos = 0; pos < itemCount; pos += 1) {
+ const realIndex = wrapIndex(startIndex + pos, itemCount);
+ cards[realIndex].style.order = String(pos + 1);
+ }
+}
+
+function applyNavFrame(track, cards, state, itemCount, frame, animate = false) {
+ const step = getTrackStep(cards);
+ if (step === null) return;
+ setCircularOrder(cards, state.currentIndex, itemCount, frame.beforeActive);
+ updateActiveCard(cards, state.currentIndex);
+ track.style.transition = animate ? '' : 'none';
+ track.style.transform = `translate3d(${frame.offsetMultiplier * step}px, 0, 0)`;
+ if (!animate) {
+ track.getBoundingClientRect();
+ track.style.transition = '';
+ }
+}
+
+function waitForTrackTransition(track, onDone) {
+ let settled = false;
+ const settle = () => {
+ if (settled) return;
+ settled = true;
+ // eslint-disable-next-line no-use-before-define
+ clearTimeout(fallbackTimer);
+ onDone();
+ };
+ const fallbackTimer = setTimeout(settle, TRANSITION_FALLBACK_MS);
+ track.addEventListener('transitionend', settle, { once: true });
+}
+
+function updateCurrentIndex(state, itemCount, direction) {
+ state.currentIndex = wrapIndex(
+ state.currentIndex + NAV_INDEX_DELTAS[direction],
+ itemCount,
+ );
+}
+
+function createMoveHandler(track, itemCount, state, applyFrame) {
+ return (direction) => {
+ if (itemCount <= 1 || state.isAnimating) return false;
+ state.isAnimating = true;
+ NAV_SEQUENCES[direction].forEach(({ frame, animate }) => applyFrame(frame, animate));
+ waitForTrackTransition(track, () => {
+ updateCurrentIndex(state, itemCount, direction);
+ applyFrame(NAV_FRAMES.BASE, false);
+ state.isAnimating = false;
+ });
+ return true;
+ };
+}
+
+/** Creates circular prev/next navigation controls. */
+function createNavControls(track, navContainer, itemCount, state, cards) {
+ const applyFrame = (frame, animate = false) => applyNavFrame(
+ track,
+ cards,
+ state,
+ itemCount,
+ frame,
+ animate,
+ );
+ const move = createMoveHandler(track, itemCount, state, applyFrame);
+ let prevButton;
+ let nextButton;
+
+ const moveByDirection = (direction, focusButton = false) => {
+ const moved = move(direction);
+ if (!moved || !focusButton) return moved;
+ if (direction === NAV_DIRECTIONS.PREV) prevButton.focus();
+ else nextButton.focus();
+ return moved;
+ };
+
+ prevButton = createNavButton(NAV_DIRECTIONS.PREV, () => moveByDirection(NAV_DIRECTIONS.PREV));
+ nextButton = createNavButton(NAV_DIRECTIONS.NEXT, () => moveByDirection(NAV_DIRECTIONS.NEXT));
+ navContainer.prepend(prevButton);
+ navContainer.append(nextButton);
+ applyFrame(NAV_FRAMES.BASE, false);
+ return {
+ reposition: () => applyFrame(NAV_FRAMES.BASE, false),
+ moveNext: (focusButton = false) => moveByDirection(NAV_DIRECTIONS.NEXT, focusButton),
+ movePrev: (focusButton = false) => moveByDirection(NAV_DIRECTIONS.PREV, focusButton),
+ };
+}
+
+/** Creates the top-level structural containers for the carousel. */
+function createCarouselStructure() {
+ const viewport = createTag('div', { class: `${BLOCK}-viewport` });
+ const track = createTag('div', { class: `${BLOCK}-track` });
+ viewport.appendChild(track);
+ return { viewport, track };
+}
+
+// Keeps the track position up-to-date when the viewport
+function observeResize(viewport, updateCarousel) {
+ const observer = new ResizeObserver(() => updateCarousel());
+ observer.observe(viewport);
+}
+
+const SWIPE_THRESHOLD_PX = 50;
+
+/** Enables touch swipe: left = next, right = prev. Ignores mostly-vertical drags. */
+function setupSwipe(viewport, moveNext, movePrev) {
+ let startX = 0;
+ let startY = 0;
+
+ viewport.addEventListener('touchstart', (e) => {
+ if (e.changedTouches.length !== 1) return;
+ startX = e.changedTouches[0].clientX;
+ startY = e.changedTouches[0].clientY;
+ }, { passive: true });
+
+ viewport.addEventListener('touchend', (e) => {
+ if (e.changedTouches.length !== 1) return;
+ const endX = e.changedTouches[0].clientX;
+ const endY = e.changedTouches[0].clientY;
+ const deltaX = endX - startX;
+ const deltaY = endY - startY;
+
+ if (Math.abs(deltaX) < SWIPE_THRESHOLD_PX) return;
+ if (Math.abs(deltaY) >= Math.abs(deltaX)) return;
+
+ if (deltaX < 0) moveNext();
+ else movePrev();
+ }, { passive: true });
+}
+
+function setupArrowKeyNavigation(el, controls) {
+ el.addEventListener('keydown', (e) => {
+ if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
+ if (!el.contains(document.activeElement)) return;
+ e.preventDefault();
+ if (e.key === 'ArrowLeft') controls.movePrev(true);
+ else controls.moveNext(true);
+ });
+}
+
+function setupAutoScroll(viewport, moveNext) {
+ let autoScrollTimer = null;
+ let autoScrollStopped = false;
+
+ const clearAutoScroll = () => {
+ if (!autoScrollTimer) return;
+ clearInterval(autoScrollTimer);
+ autoScrollTimer = null;
+ };
+
+ const stopAutoScroll = () => {
+ if (autoScrollStopped) return;
+ autoScrollStopped = true;
+ clearAutoScroll();
+ // eslint-disable-next-line no-use-before-define
+ observer.disconnect();
+ };
+
+ const startAutoScroll = () => {
+ if (autoScrollStopped || autoScrollTimer) return;
+ autoScrollTimer = setInterval(() => moveNext(), AUTOSCROLL_INTERVAL_MS);
+ };
+
+ const observer = new IntersectionObserver(([entry]) => {
+ if (autoScrollStopped) return;
+ if (entry?.isIntersecting) startAutoScroll();
+ else clearAutoScroll();
+ });
+
+ observer.observe(viewport);
+ ['mouseenter', 'pointerdown', 'keydown', 'focusin', 'touchstart'].forEach((eventName) => {
+ viewport.addEventListener(eventName, stopAutoScroll, { once: true, capture: true });
+ });
+}
+
+export default async function init(el) {
+ const items = parseItemsFromDOM(el);
+ if (!items.length) return;
+
+ el.textContent = '';
+
+ const state = { currentIndex: 0, isAnimating: false };
+ const structure = createCarouselStructure();
+ const cards = buildTrack(structure.track, items);
+ const controls = createNavControls(
+ structure.track,
+ structure.viewport,
+ items.length,
+ state,
+ cards,
+ );
+
+ el.append(structure.viewport);
+ observeResize(structure.viewport, controls.reposition);
+ setupAutoScroll(structure.viewport, controls.moveNext);
+ setupSwipe(structure.viewport, controls.moveNext, controls.movePrev);
+ setupArrowKeyNavigation(el, controls);
+}
diff --git a/creativecloud/blocks/firefly-howto/firefly-howto.css b/creativecloud/blocks/firefly-howto/firefly-howto.css
new file mode 100644
index 000000000..564c3b77d
--- /dev/null
+++ b/creativecloud/blocks/firefly-howto/firefly-howto.css
@@ -0,0 +1,225 @@
+/* Tokens */
+.firefly-howto {
+ --c-text-primary: var(--color-black);
+ --c-text-tertiary: var(--color-black);
+ --c-bg-default: var(--color-white);
+ --c-bg-active: #ece4f3;
+ --c-focus: #0054bb;
+ --s-spacing-md: 1.5rem;
+ --s-spacing-xl: 40px;
+ --acc-item-radius: 8px;
+ --acc-item-shadow: 0 2px 8px rgb(0 0 0 / 5%);
+ --anim-speed: 0.4s;
+ --anim-easing: cubic-bezier(0.4, 0, 0.2, 1);
+ --foreground-gap: 1.25rem;
+ --icon-size: 16px;
+ --icon-offset: 40px;
+ --trigger-padding-inline: var(--s-spacing-md);
+ --trigger-gap: 16px;
+ --trigger-row-gap: 4px;
+ --step-column-width: 0;
+ --step-font-size: 0.75rem;
+ --title-font-size: 1.25rem;
+ --content-font-size: 1rem;
+ --content-bottom-padding: 8px;
+ --panel-top-offset: -16px;
+ --content-right-padding: calc(var(--trigger-padding-inline) + var(--icon-offset));
+ --content-left-padding: var(--trigger-padding-inline);
+
+ font-family: var(--body-font-family);
+}
+
+/* Layout */
+.firefly-howto .foreground {
+ width: 100%;
+ max-width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: var(--foreground-gap);
+}
+
+/* Accordion Item */
+.firefly-howto .firefly-howto-item {
+ background: var(--c-bg-default);
+ border-radius: var(--acc-item-radius);
+ box-shadow: var(--acc-item-shadow);
+ transition: background-color var(--anim-speed) ease;
+ overflow: hidden;
+ border: 1px solid transparent;
+ position: relative;
+ cursor: pointer;
+}
+
+.firefly-howto .firefly-howto-item.is-open {
+ background-color: var(--c-bg-active);
+}
+
+/* Trigger/Header */
+.firefly-howto .firefly-howto-header {
+ margin: 0;
+}
+
+.firefly-howto .firefly-howto-trigger {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 1fr var(--icon-size);
+ grid-template-areas:
+ "step step"
+ "title icon";
+ row-gap: var(--trigger-row-gap);
+ column-gap: var(--trigger-gap);
+ align-items: start;
+ padding: var(--trigger-padding-inline);
+ background: none;
+ border: none;
+ text-align: left;
+ cursor: pointer;
+ color: var(--c-text-primary);
+ font-family: var(--body-font-family);
+ position: relative;
+ z-index: 1;
+ outline: none;
+}
+
+.firefly-howto .firefly-howto-item:has(.firefly-howto-trigger:focus-visible) {
+ outline: 2px solid var(--c-focus);
+ outline-offset: -2px;
+}
+
+.firefly-howto .step-indicator {
+ grid-area: step;
+ font-size: var(--step-font-size);
+ font-weight: 500;
+ color: var(--c-text-tertiary);
+ text-transform: uppercase;
+ line-height: 1.25;
+ overflow-wrap: break-word;
+}
+
+.firefly-howto .firefly-howto-item.is-open .step-indicator {
+ color: var(--c-text-primary);
+}
+
+.firefly-howto .firefly-howto-title {
+ grid-area: title;
+ font-size: var(--title-font-size);
+ font-weight: 800;
+ line-height: 1.5;
+}
+
+/* Icon */
+.firefly-howto .firefly-howto-icon {
+ grid-area: icon;
+ align-self: center;
+ display: flex;
+ width: var(--icon-size);
+ height: var(--icon-size);
+ align-items: center;
+ justify-content: center;
+ color: var(--c-text-primary);
+ z-index: 3;
+}
+
+.firefly-howto .firefly-howto-icon svg {
+ width: 100%;
+ height: 100%;
+}
+
+.firefly-howto .firefly-howto-icon .vert-line {
+ transform-origin: 50% 50%;
+ transition: transform var(--anim-speed) var(--anim-easing), opacity var(--anim-speed) var(--anim-easing);
+}
+
+.firefly-howto .firefly-howto-trigger[aria-expanded='true'] .firefly-howto-icon .vert-line {
+ transform: rotate(90deg);
+ opacity: 0;
+}
+
+/* Panel + Content */
+.firefly-howto .firefly-howto-panel {
+ display: grid;
+ grid-template-rows: 0fr;
+ visibility: hidden;
+ transition: grid-template-rows var(--anim-speed) var(--anim-easing), visibility 0s linear var(--anim-speed);
+ position: relative;
+ top: var(--panel-top-offset);
+ z-index: 2;
+}
+
+.firefly-howto .firefly-howto-panel-inner {
+ overflow: hidden;
+}
+
+.firefly-howto .firefly-howto-content {
+ padding: 0 var(--content-right-padding) var(--content-bottom-padding) var(--content-left-padding);
+ opacity: 0;
+ transition: opacity 0.2s ease;
+ font-weight: 400;
+ font-size: var(--content-font-size);
+ letter-spacing: normal;
+ line-height: 1.5;
+}
+
+.firefly-howto .firefly-howto-content > *:first-child {
+ margin-top: 0;
+}
+
+.firefly-howto .firefly-howto-content > *:last-child {
+ margin-bottom: 0;
+}
+
+.firefly-howto .foreground .firefly-howto-panel .firefly-howto-content p {
+ line-height: 1.6;
+ color: var(--c-text-primary);
+ margin: 0;
+}
+
+/* State Variants */
+.firefly-howto .firefly-howto-item.is-open .firefly-howto-panel {
+ grid-template-rows: 1fr;
+ visibility: visible;
+ transition: grid-template-rows var(--anim-speed) var(--anim-easing), visibility 0s linear 0s;
+}
+
+.firefly-howto .firefly-howto-item.is-open .firefly-howto-content {
+ opacity: 1;
+ transition: opacity var(--anim-speed) ease 0.1s;
+}
+
+/* Responsive Overrides */
+@media (min-width: 600px) {
+ .firefly-howto {
+ --step-column-width: 55px;
+ --step-font-size: 1rem;
+ --trigger-padding-inline: var(--s-spacing-xl);
+ --trigger-row-gap: 0;
+ --content-bottom-padding: 16px;
+ --panel-top-offset: -24px;
+ --content-left-padding: calc(var(--trigger-padding-inline) + var(--step-column-width) + var(--trigger-gap));
+ }
+
+ .firefly-howto .firefly-howto-trigger {
+ grid-template-columns: var(--step-column-width) 1fr var(--icon-size);
+ grid-template-areas: "step title icon";
+ align-items: baseline;
+ }
+}
+
+@media (min-width: 1200px) {
+ .firefly-howto {
+ --step-column-width: 65px;
+ --trigger-gap: var(--s-spacing-md);
+ --title-font-size: 1.5rem;
+ --content-font-size: 1.25rem;
+ --content-left-padding: calc(var(--trigger-padding-inline) + var(--step-column-width) + var(--trigger-gap));
+ }
+}
+
+/* Reduced Motion */
+@media (prefers-reduced-motion: reduce) {
+ .firefly-howto .firefly-howto-icon .vert-line,
+ .firefly-howto .firefly-howto-panel,
+ .firefly-howto .firefly-howto-content {
+ transition: none !important;
+ }
+}
diff --git a/creativecloud/blocks/firefly-howto/firefly-howto.js b/creativecloud/blocks/firefly-howto/firefly-howto.js
new file mode 100644
index 000000000..6f360564b
--- /dev/null
+++ b/creativecloud/blocks/firefly-howto/firefly-howto.js
@@ -0,0 +1,273 @@
+import { createTag } from '../../scripts/utils.js';
+
+// ===== CONFIG =====
+
+const CONFIG = {
+ DEFAULT_STEP_PREFIX: 'Step',
+ SEO_MARKER: 'seo-props',
+ STEP_TITLE_DELIMITER: '||',
+ CLICK_DRAG_THRESHOLD: 5,
+};
+
+const LANA_OPTIONS = { tags: 'firefly-howto', errorType: 'i' };
+
+const ICON_SVG = '';
+
+// ===== LOGGING =====
+
+function logError(message, error) {
+ window.lana?.log(`${message}: ${error}`, LANA_OPTIONS);
+}
+
+// ===== UTILITY FUNCTIONS =====
+
+function parseStepTitle(rawTitle) {
+ let stepPrefix = CONFIG.DEFAULT_STEP_PREFIX;
+ let title = rawTitle;
+ let fullTitle = title;
+
+ if (rawTitle.includes(CONFIG.STEP_TITLE_DELIMITER)) {
+ const parts = rawTitle.split(CONFIG.STEP_TITLE_DELIMITER);
+ stepPrefix = parts[0]?.trim() || CONFIG.DEFAULT_STEP_PREFIX;
+ title = parts[1]?.trim() || '';
+ fullTitle = `${stepPrefix} ${title}`;
+ }
+
+ return { stepPrefix, title, fullTitle };
+}
+
+// ===== PARSING (SEO PROPS) =====
+
+function parseCsv(value) {
+ return value.split(',').map((item) => item.trim()).filter(Boolean);
+}
+
+function extractSeoData(item) {
+ const seoData = {};
+ const props = item.children[1]?.querySelectorAll('p') || [];
+
+ props.forEach((p) => {
+ const text = p.textContent;
+ const splitIndex = text.indexOf(':');
+
+ if (splitIndex < 0) return;
+
+ const key = text.substring(0, splitIndex).trim().toLowerCase();
+ const value = text.substring(splitIndex + 1).trim();
+
+ if (key === 'name') seoData.name = value;
+ if (key === 'description') seoData.description = value;
+ if (key === 'total-time' || key === 'totaltime') seoData.totalTime = value;
+ if (key === 'tools' || key === 'tool') seoData.tools = parseCsv(value);
+ if (key === 'supply' || key === 'supplies') seoData.supply = parseCsv(value);
+ });
+
+ return seoData;
+}
+
+// ===== PARSING (STEPS) =====
+
+function parseStep(item, index) {
+ const innerDiv = item.querySelector('div');
+ if (!innerDiv) return null;
+
+ const titleEl = innerDiv.querySelector('h3');
+ const contentEl = innerDiv.querySelector('p');
+ const titleText = titleEl ? titleEl.innerText : '';
+ const { stepPrefix, title, fullTitle } = parseStepTitle(titleText);
+
+ return {
+ id: index,
+ title,
+ fullTitle,
+ content: contentEl ? contentEl.innerHTML : '',
+ plainText: contentEl ? contentEl.textContent : '',
+ headingId: titleEl ? titleEl.id : `acc-heading-${index}`,
+ stepPrefix,
+ };
+}
+
+function extractData(el) {
+ const steps = [];
+ let seoData = {};
+ const isSeoEnabled = el.classList.contains('seo');
+
+ Array.from(el.children).forEach((item, index) => {
+ const innerDivs = Array.from(item.children);
+ if (innerDivs.length === 0) return;
+
+ const isSeoMarker = innerDivs[0].textContent.trim() === CONFIG.SEO_MARKER;
+ if (isSeoMarker) {
+ if (isSeoEnabled && innerDivs.length > 1) {
+ seoData = { ...seoData, ...extractSeoData(item) };
+ }
+ return;
+ }
+
+ const step = parseStep(item, index);
+ if (step) steps.push(step);
+ });
+
+ return { steps, seoData };
+}
+
+// ===== INTERACTION =====
+
+function handlePointerDown(e) {
+ const item = e.currentTarget;
+ item.dataset.startX = e.clientX;
+ item.dataset.startY = e.clientY;
+}
+
+function isDragClick(e, item) {
+ const startX = parseFloat(item.dataset.startX || 0);
+ const startY = parseFloat(item.dataset.startY || 0);
+
+ if (e.clientX !== 0 || e.clientY !== 0) {
+ const diffX = Math.abs(e.clientX - startX);
+ const diffY = Math.abs(e.clientY - startY);
+ return diffX > CONFIG.CLICK_DRAG_THRESHOLD || diffY > CONFIG.CLICK_DRAG_THRESHOLD;
+ }
+
+ return false;
+}
+
+function setExpandedState(item, trigger, expanded) {
+ trigger.setAttribute('aria-expanded', expanded ? 'true' : 'false');
+ item.classList.toggle('is-open', expanded);
+}
+
+function handleItemClick(e) {
+ const item = e.currentTarget;
+ if (isDragClick(e, item)) return;
+
+ const interactiveTarget = e.target?.closest?.('a, input');
+ if (interactiveTarget) return;
+
+ const trigger = item.querySelector('.firefly-howto-trigger');
+ if (!trigger) return;
+
+ const clickedTrigger = e.target?.closest?.('.firefly-howto-trigger');
+ const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
+
+ if (isExpanded) {
+ setExpandedState(item, trigger, false);
+ } else {
+ if (!clickedTrigger) return;
+ setExpandedState(item, trigger, true);
+ }
+}
+
+// ===== DOM BUILDING =====
+
+function createAccordionItem(data) {
+ const { title, content, headingId, stepPrefix } = data;
+ const isOpen = false;
+
+ const itemWrapper = createTag('div');
+ itemWrapper.className = `firefly-howto-item ${isOpen ? 'is-open' : ''}`;
+
+ itemWrapper.addEventListener('pointerdown', handlePointerDown);
+ itemWrapper.addEventListener('click', handleItemClick);
+ const header = createTag('h3', { class: 'firefly-howto-header' });
+
+ const btn = createTag('button', { class: 'firefly-howto-trigger', id: `btn-${headingId}`, 'aria-expanded': isOpen, type: 'button', 'aria-controls': `panel-${headingId}` });
+
+ const titleSpan = createTag('span', { class: 'firefly-howto-title' });
+ titleSpan.textContent = title;
+
+ const iconSpan = createTag('span', { class: 'firefly-howto-icon' });
+ iconSpan.innerHTML = ICON_SVG;
+
+ const stepSpan = createTag('span', { class: 'step-indicator' });
+ stepSpan.textContent = `${stepPrefix} `;
+
+ btn.appendChild(stepSpan);
+ btn.appendChild(titleSpan);
+ btn.appendChild(iconSpan);
+ header.appendChild(btn);
+
+ const panel = createTag('div', { class: 'firefly-howto-panel', id: `panel-${headingId}`, role: 'region', 'aria-labelledby': `btn-${headingId}` });
+ const panelInner = createTag('div', { class: 'firefly-howto-panel-inner' });
+ const contentWrapper = createTag('div', { class: 'firefly-howto-content' });
+
+ const p = createTag('p');
+ p.innerHTML = content;
+
+ contentWrapper.appendChild(p);
+ panelInner.appendChild(contentWrapper);
+ panel.appendChild(panelInner);
+
+ itemWrapper.appendChild(header);
+ itemWrapper.appendChild(panel);
+
+ return itemWrapper;
+}
+
+// ===== SEO SCHEMA =====
+
+function buildSchema(steps, seoData) {
+ const schema = {
+ '@context': 'https://schema.org',
+ '@type': 'HowTo',
+ name: seoData.name,
+ description: seoData.description,
+ step: steps.map((item) => ({
+ '@type': 'HowToStep',
+ name: item.fullTitle,
+ text: item.plainText,
+ })),
+ };
+
+ if (seoData.totalTime) {
+ schema.totalTime = seoData.totalTime;
+ }
+
+ if (seoData.tools && seoData.tools.length > 0) {
+ schema.tool = seoData.tools.map((tool) => ({
+ '@type': 'HowToTool',
+ name: tool,
+ }));
+ }
+
+ if (seoData.supply && seoData.supply.length > 0) {
+ schema.supply = seoData.supply.map((supply) => ({
+ '@type': 'HowToSupply',
+ name: supply,
+ }));
+ }
+
+ return schema;
+}
+
+// ===== ORCHESTRATION =====
+
+function decorateContent(el) {
+ const { steps, seoData } = extractData(el);
+ const foreground = createTag('div', { class: 'foreground' });
+
+ steps.forEach((itemData) => {
+ const itemNode = createAccordionItem(itemData);
+ foreground.appendChild(itemNode);
+ });
+
+ el.innerHTML = '';
+ el.appendChild(foreground);
+
+ if (el.classList.contains('seo') && seoData.name && seoData.description) {
+ const schema = buildSchema(steps, seoData);
+ const script = createTag('script', { type: 'application/ld+json' }, JSON.stringify(schema));
+ document.head.appendChild(script);
+ }
+}
+
+// ===== PUBLIC INIT =====
+
+export default function init(el) {
+ try {
+ el.classList.add('con-block');
+ decorateContent(el);
+ } catch (err) {
+ logError('Firefly accordion init error', err);
+ }
+}
diff --git a/creativecloud/blocks/firefly-model-showcase/firefly-model-showcase.css b/creativecloud/blocks/firefly-model-showcase/firefly-model-showcase.css
index ca2769822..eabe10143 100644
--- a/creativecloud/blocks/firefly-model-showcase/firefly-model-showcase.css
+++ b/creativecloud/blocks/firefly-model-showcase/firefly-model-showcase.css
@@ -201,6 +201,12 @@
padding: 12px;
}
}
+@media screen and (max-width: 360px) {
+ .firefly-model-showcase-content .action-area a:first-child:nth-last-child(2),
+ .firefly-model-showcase-content .action-area a:first-child:nth-last-child(2) ~ a {
+ white-space: nowrap;
+ }
+}
/* End of second CTA support */
diff --git a/creativecloud/blocks/firefly-share/firefly-share.css b/creativecloud/blocks/firefly-share/firefly-share.css
new file mode 100644
index 000000000..d6a739932
--- /dev/null
+++ b/creativecloud/blocks/firefly-share/firefly-share.css
@@ -0,0 +1,276 @@
+.firefly-share {
+ --svg-icon-dimension: 24px;
+ --list-gap: 28px;
+ --copy-tooltip-background-color: #6D6D6D;
+ margin-left: auto;
+ margin-right: auto;
+ text-align: center;
+ overflow: hidden;
+}
+
+.firefly-share.inline {
+ overflow: unset;
+}
+
+.firefly-share.dark {
+ background-color: #1E1E1E;
+}
+
+.firefly-share svg.icon {
+ height: var(--svg-icon-dimension);
+ transition: filter 0.3s;
+ display: inline-block;
+ width: var(--svg-icon-dimension);
+ margin: 0;
+ color: #222;
+}
+
+.firefly-share ul.icon-container,
+.firefly-share ul.icon-container li,
+.firefly-share ul.icon-container li a {
+ display: flex;
+ align-items: center;
+ padding-bottom: 0;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+.firefly-share ul.icon-container {
+ list-style: none;
+ gap: var(--list-gap);
+ margin: 0;
+ padding: 1rem 0;
+}
+
+.firefly-share :is([role="heading"], h1, h2, h3, h4, h5, h6) {
+ margin-top: 2.5rem;
+ margin-bottom: 2.75rem;
+}
+
+
+.firefly-share.xxl-heading :is([role="heading"], h1, h2, h3, h4, h5, h6) {
+ font-size: 1.5rem;
+ font-weight: 900;
+ letter-spacing: -4.5%;
+}
+
+.firefly-share.dark :is([role="heading"], h1, h2, h3, h4, h5, h6),
+.firefly-share.dark ul.icon-container svg {
+ color: var(--color-white);
+}
+
+.firefly-share ul.icon-container svg {
+ color: #222;
+}
+
+.firefly-share li:has(.copy-to-clipboard) {
+ position: relative;
+}
+
+.firefly-share li:has(.copy-to-clipboard:not(.hide-copy-tooltip))::before {
+ content: '';
+ width: var(--svg-icon-dimension);
+ height: 5px;
+ position: absolute;
+ bottom: 100%;
+}
+
+.firefly-share .copy-to-clipboard {
+ padding: 0;
+ background: none;
+ margin: 0;
+ line-height: 0;
+ cursor: pointer;
+ position: relative;
+ transition: filter 0.3s;
+ border: none;
+ margin-left: -2px;
+}
+
+.firefly-share .icon-container li > :is(a, button) {
+ outline-offset: 1px;
+}
+
+.firefly-share :is(a, .copy-to-clipboard) svg.icon:hover {
+ filter: brightness(0.7);
+}
+
+.firefly-share .copy-to-clipboard::before,
+.firefly-share .copy-to-clipboard::after {
+ display: block;
+ position : absolute;
+ bottom: 100%;
+ transition: transform 130ms ease-in-out, opacity 130ms ease-in-out, visibility 0ms linear 130ms;
+ transition-delay: 0ms;
+ background-color: var(--copy-tooltip-background-color);
+ visibility: visible;
+ opacity: 1;
+ cursor: default;
+}
+
+.firefly-share .copy-to-clipboard::before {
+ content: attr(aria-label);
+ bottom: 100%;
+ left: -45%;
+ transform: translate(-50%, -4px);
+ color: var(--color-white);
+ padding: 4px 9px 5px;
+ font-size: 12px;
+ line-height: 1.3;
+ border-radius: 4px;
+ white-space: nowrap;
+}
+
+.firefly-share .copy-to-clipboard::after {
+ content: '';
+ left: 50%;
+ transform: translate(-50%, 0);
+ height: 4px;
+ width: 8px;
+ clip-path: polygon(0 -5%, 50% 100%, 100% -5%);
+}
+
+.firefly-share .copy-to-clipboard.hide-copy-tooltip::before,
+.firefly-share .copy-to-clipboard.hide-copy-tooltip::after {
+ visibility: hidden;
+ opacity: 0;
+}
+
+[dir="rtl"] .firefly-share ul.icon-container {
+ padding-right: 0;
+}
+
+[dir="rtl"] .firefly-share .copy-to-clipboard {
+ margin-left: 0;
+ margin-right: -2px;
+}
+
+[dir="rtl"] .firefly-share .copy-to-clipboard::before {
+ left: 150%;
+}
+
+.firefly-share.inline .copy-to-clipboard::after {
+ z-index: 8;
+}
+
+.firefly-share .copy-to-clipboard:not(.hide-copy-tooltip)::before {
+ transform: translate(-50%, -6px);
+}
+
+.firefly-share .copy-to-clipboard:not(.hide-copy-tooltip)::after {
+ transform: translate(-50%, -2px);
+}
+
+.firefly-share .copy-to-clipboard.copy-to-clipboard-copied::before {
+ content: attr(data-copied);
+}
+
+.firefly-share.inline {
+ margin: 0 auto;
+ text-align: center;
+ padding: 8px 0 16px;
+}
+
+.firefly-share.inline ul.icon-container {
+ justify-content: flex-start;
+ margin: 0;
+}
+
+main > .section > firefly-share.inline {
+ margin: 0 auto;
+}
+
+main > .section > .inline-wrapper > firefly-share.inline {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+main > .section.center firefly-share.inline ul.icon-container {
+ margin: 0 auto;
+ justify-content: center;
+}
+
+@media screen and (min-width: 360px) {
+ .firefly-share.left-mobile .tracking-header {
+ max-width: var(--grid-container-width);
+ margin: 0 auto;
+ }
+
+ .firefly-share.left-mobile :is([role="heading"], h1, h2, h3, h4, h5, h6) {
+ text-align: start;
+ }
+
+ .firefly-share.left-mobile ul.icon-container {
+ max-width: calc(var(--grid-container-width) - 2px);
+ margin-left : auto;
+ margin-right: auto;
+ justify-content: space-around;
+ }
+}
+
+@media screen and (min-width: 600px) {
+ .firefly-share.left-mobile .tracking-header {
+ max-width: 100%;
+ margin: 0;
+ }
+
+ .firefly-share.left-mobile :is([role="heading"], h1, h2, h3, h4, h5, h6) {
+ text-align: center;
+ }
+
+ .firefly-share.left-mobile ul.icon-container {
+ max-width: 100%;
+ justify-content: center;
+ }
+
+ .firefly-share :is([role="heading"], h1, h2, h3, h4, h5, h6) {
+ margin-top: 1.25rem;
+ margin-bottom: 1.25rem;
+ }
+
+ .firefly-share .copy-to-clipboard::before,
+ [dir="rtl"] firefly-share .copy-to-clipboard::before {
+ left: 50%;
+ }
+}
+
+.firefly-share .aria-live-container {
+ position: absolute;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+}
+
+@media (min-width: 900px){
+ .firefly-share {
+ --svg-icon-dimension: 40px;
+ --list-gap: 56px;
+ }
+
+ .firefly-share .copy-to-clipboard::before {
+ content: attr(aria-label);
+ bottom: 100%;
+ left: -45%;
+ transform: translate(-50%, -4px);
+ color: var(--color-white);
+ padding: 4px 9px 5px;
+ font-size: 16px;
+ line-height: 1.3;
+ border-radius: 4px;
+ white-space: nowrap;
+ }
+
+ .firefly-share :is([role="heading"], h1, h2, h3, h4, h5, h6) {
+ margin-top: 2.5rem;
+ margin-bottom: 2.5rem;
+ }
+
+ .firefly-share.xxl-heading :is([role="heading"], h1, h2, h3, h4, h5, h6) {
+ font-size: 40px;
+ font-weight: 900;
+ }
+
+ .firefly-share ul.icon-container {
+ padding: 2rem 0
+ }
+}
diff --git a/creativecloud/blocks/firefly-share/firefly-share.js b/creativecloud/blocks/firefly-share/firefly-share.js
new file mode 100644
index 000000000..655f65f5a
--- /dev/null
+++ b/creativecloud/blocks/firefly-share/firefly-share.js
@@ -0,0 +1,229 @@
+import { createTag, getConfig, getLibs } from '../../scripts/utils.js';
+
+const FIREFLY_SHARE = '/cc-shared/assets/svg/firefly-share.svg';
+
+export async function getSVGsfromFile(path, selectors) {
+ if (!path) return null;
+ const resp = await fetch(path);
+ if (!resp.ok) return null;
+
+ const text = await resp.text();
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(text, 'image/svg+xml');
+
+ if (!selectors) {
+ const svg = doc.querySelector('svg');
+ if (svg) return [{ svg }];
+ /* c8 ignore next 4 */
+ return null;
+ }
+ if (!(selectors instanceof Array)) {
+ /* eslint-disable no-param-reassign */
+ selectors = [selectors];
+ }
+
+ return selectors.map((selector) => {
+ const symbol = doc.querySelector(`#${selector}`);
+ if (!symbol) return null;
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ while (symbol.firstChild) svg.appendChild(symbol.firstChild);
+ [...symbol.attributes].forEach((attr) => svg.attributes.setNamedItem(attr.cloneNode()));
+ svg.classList.add('icon');
+ svg.classList.add(`icon-${selector}`);
+ svg.removeAttribute('id');
+ return { svg, name: selector };
+ });
+}
+
+function getPlatforms(el) {
+ const manualShares = el.querySelectorAll('a');
+ if (manualShares.length === 0) return ['facebook', 'x', 'linkedin', 'pinterest', 'reddit'];
+ const platforms = [];
+ [...manualShares].forEach((share) => {
+ const { href } = share;
+ const url = new URL(href);
+ const parts = url.host.split('.');
+ const platform = parts[parts.length - 2] !== 'twitter' ? parts[parts.length - 2] : 'x';
+ platforms.push(platform);
+ const parentP = share.closest('p');
+ parentP?.remove();
+ });
+ return platforms;
+}
+
+function getPrevHeadingLevel(block) {
+ const prevHeading = [...document.querySelectorAll('h2, h3, h4, h5, h6')]
+ .reverse()
+ /* eslint-disable-next-line no-bitwise */
+ .find((heading) => heading.compareDocumentPosition(block) & Node.DOCUMENT_POSITION_FOLLOWING);
+ const prevHeadingTag = prevHeading?.tagName.toLowerCase() ?? 'h2';
+ return prevHeadingTag.replace('h', '');
+}
+
+function toSentenceCase(str) {
+ if (!str || typeof str !== 'string') return '';
+ /* eslint-disable-next-line no-useless-escape */
+ return str.toLowerCase().replace(/(^\s*\w|[\.\!\?]\s*\w)/g, (c) => c.toUpperCase());
+}
+
+function showCopyTooltip({ copyButton, show = true, copied = false }) {
+ copyButton.classList.toggle('hide-copy-tooltip', !show);
+ copyButton.classList.toggle('copy-to-clipboard-copied', copied);
+}
+
+export default async function decorate(block) {
+ const config = getConfig();
+ const miloLibs = getLibs('/libs');
+ const { inlineBlock } = await import(`${miloLibs}/utils/inline.js`);
+ const { replaceKey } = await import(`${miloLibs}/features/placeholders.js`);
+ const { loadStyle } = await import(`${miloLibs}/utils/utils.js`);
+
+ loadStyle(`${miloLibs}/styles/inline.css`);
+ const platforms = getPlatforms(block);
+ let firstRow = block.querySelector(':scope > div');
+ let headingElem;
+
+ if (block.matches('.inline')) firstRow?.remove();
+ else if (firstRow?.textContent.trim().length) {
+ headingElem = firstRow.querySelector('p, div:not(:has(p, h1, h2, h3, h4, h5, h6))');
+ } else {
+ headingElem = createTag('p', null, toSentenceCase(await replaceKey('share-this-page', config)));
+ if (firstRow) firstRow.replaceChildren(headingElem);
+ else {
+ firstRow = createTag('div', null, headingElem);
+ block.append(firstRow);
+ }
+ }
+
+ firstRow?.classList.add('tracking-header');
+ headingElem?.setAttribute('role', 'heading');
+ headingElem?.setAttribute('aria-level', getPrevHeadingLevel(block));
+
+ const clipboardSupport = !!navigator.clipboard;
+ if (clipboardSupport) platforms.push('clipboard');
+ const svgs = await getSVGsfromFile(
+ FIREFLY_SHARE,
+ platforms,
+ );
+ if (!svgs) return;
+
+ const shareToText = toSentenceCase(await replaceKey('share-to', config));
+ const url = encodeURIComponent(window.location.href);
+ const title = document.title ?? url;
+ /* eslint-disable no-shadow */
+ const getDetails = (name, url) => {
+ switch (name) {
+ case 'facebook':
+ return {
+ title: 'Facebook',
+ href: `https://www.facebook.com/sharer/sharer.php?u=${url}`,
+ };
+ case 'x':
+ return {
+ title: 'X',
+ href: `https://x.com/share?&url=${url}`,
+ };
+ case 'linkedin':
+ return {
+ title: 'LinkedIn',
+ href: `https://www.linkedin.com/sharing/share-offsite/?url=${url}`,
+ };
+ case 'pinterest':
+ return {
+ title: 'Pinterest',
+ href: `https://pinterest.com/pin/create/button/?url=${url}&description=${title}`,
+ };
+ case 'reddit':
+ return {
+ title: 'Reddit',
+ href: `https://reddit.com/submit?url=${url}&title=${title}`,
+ };
+ default:
+ /* c8 ignore next 1 */
+ return null;
+ }
+ };
+
+ const container = createTag('ul', { class: 'icon-container' });
+ svgs.forEach(async (svg) => {
+ if (svg.name === 'clipboard') return;
+
+ const obj = getDetails(svg.name, url);
+ if (!obj) return;
+
+ const shareLink = createTag(
+ 'a',
+ {
+ title: `${shareToText} ${obj.title}`,
+ target: '_blank',
+ href: obj.href,
+ },
+ svg.svg,
+ );
+ const li = createTag('li');
+ li.appendChild(shareLink);
+ container.append(li);
+ shareLink.addEventListener('click', (e) => {
+ /* c8 ignore next 2 */
+ e.preventDefault();
+ window.open(shareLink.href, 'newwindow', 'width=600, height=400');
+ });
+ });
+
+ const clipboardSvg = svgs.find((svg) => svg.name === 'clipboard');
+ if (clipboardSvg && clipboardSupport) {
+ const clipboardToolTip = toSentenceCase(
+ await replaceKey('copy-to-clipboard', config),
+ );
+ const copiedTooltip = toSentenceCase(await replaceKey('copied', config));
+ const copyButton = createTag(
+ 'button',
+ {
+ type: 'button',
+ class: 'copy-to-clipboard hide-copy-tooltip',
+ 'aria-label': clipboardToolTip,
+ 'data-copied': `${copiedTooltip}!`,
+ },
+ clipboardSvg.svg,
+ );
+
+ let clipboardTimeout;
+ const li = createTag('li');
+ ['keydown', 'focus', 'blur', 'mouseenter', 'mouseleave'].forEach((eventType) => {
+ const target = eventType.startsWith('mouse') ? li : copyButton;
+ target.addEventListener(eventType, (event) => {
+ const { type, key } = event;
+ if (type === 'keydown' && key !== 'Escape') return;
+ showCopyTooltip({ copyButton, show: !['mouseleave', 'blur', 'keydown'].includes(type) });
+ clearTimeout(clipboardTimeout);
+ });
+ });
+
+ const copyAriaLive = createTag(
+ 'div',
+ {
+ 'aria-live': 'polite',
+ role: 'status',
+ class: 'aria-live-container',
+ },
+ );
+ li.append(copyButton, copyAriaLive);
+ container.append(li);
+ let changeText = false;
+ copyButton.addEventListener('click', (e) => {
+ /* c8 ignore next 6 */
+ e.preventDefault();
+ copyAriaLive.textContent = '';
+ navigator.clipboard.writeText(window.location.href).then(() => {
+ showCopyTooltip({ copyButton, copied: true });
+ copyAriaLive.textContent = copiedTooltip + (changeText ? '\u200b' : '');
+ changeText = !changeText;
+ clipboardTimeout = setTimeout(() => {
+ showCopyTooltip({ copyButton, show: false });
+ }, 2000);
+ });
+ });
+ }
+ block.append(container);
+ inlineBlock(block);
+}
diff --git a/creativecloud/blocks/sidenav/sidenav.js b/creativecloud/blocks/sidenav/sidenav.js
index 3fe71034a..07b5bf8a9 100644
--- a/creativecloud/blocks/sidenav/sidenav.js
+++ b/creativecloud/blocks/sidenav/sidenav.js
@@ -22,6 +22,83 @@ const computeDaaLLText = (text) => {
const generateDaaLL = (text, headline) => `${text}--${headline}`;
+const getMasLibsBaseUrl = () => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const masLibs = urlParams.get('maslibs');
+
+ if (!masLibs || masLibs.trim() === '') return null;
+
+ const sanitized = masLibs.trim().toLowerCase();
+
+ if (sanitized === 'local') {
+ return 'http://localhost:3000';
+ }
+
+ if (sanitized === 'main') {
+ return 'https://main--mas--adobecom.aem.live';
+ }
+
+ const { hostname } = window.location;
+ const extension = hostname.endsWith('.page') ? 'page' : 'live';
+
+ let branch = sanitized;
+ if (!sanitized.includes('--')) {
+ branch = `${sanitized}--mas--adobecom`;
+ }
+
+ return `https://${branch}.aem.${extension}`;
+};
+
+const getMasLibs = () => {
+ const baseUrl = getMasLibsBaseUrl();
+ if (!baseUrl) return null;
+ return `${baseUrl}/web-components/dist`;
+};
+
+const getMasComponentUrl = (componentName, masLibsBase, hostname) => {
+ if (masLibsBase) {
+ return `${masLibsBase}/${componentName}.js`;
+ }
+ const isAdobeProd = hostname === 'www.adobe.com';
+ return isAdobeProd
+ ? `https://www.adobe.com/mas/libs/${componentName}.js`
+ : `https://main--mas--adobecom.aem.live/web-components/dist/${componentName}.js`;
+};
+
+const failedExternalLoads = new Set();
+const loadingPromises = new Map();
+
+const loadMasComponent = async (componentName) => {
+ if (loadingPromises.has(componentName)) {
+ return loadingPromises.get(componentName);
+ }
+
+ if (customElements.get(componentName)) {
+ return Promise.resolve();
+ }
+
+ const loadPromise = (async () => {
+ const masLibsBase = getMasLibs();
+ const targetUrl = getMasComponentUrl(componentName, masLibsBase, window.location.hostname);
+
+ if (failedExternalLoads.has(targetUrl)) {
+ throw new Error(`Previously failed to load component from ${targetUrl}`);
+ }
+
+ try {
+ return await import(targetUrl);
+ } catch (error) {
+ failedExternalLoads.add(targetUrl);
+ throw error;
+ }
+ })();
+
+ loadingPromises.set(componentName, loadPromise);
+ loadPromise.finally(() => loadingPromises.delete(componentName));
+
+ return loadPromise;
+};
+
const getCategories = (items, isMultilevel, mapCategories) => {
const configuration = { manageTabIndex: true };
if (isMultilevel) {
@@ -169,7 +246,7 @@ export default async function init(el) {
decorateLinks(el);
const [mainRow, categoryRow] = Array.from(el.children);
const deps = Promise.all([
- import(`${libs}/deps/mas/merch-sidenav.js`),
+ loadMasComponent('merch-sidenav'),
// eslint-disable-next-line import/no-unresolved, import/no-absolute-path
import(`${libs}/deps/lit-all.min.js`),
import(`${libs}/features/spectrum-web-components/dist/theme.js`),
diff --git a/creativecloud/blocks/upload-marquee/upload-marquee.css b/creativecloud/blocks/upload-marquee/upload-marquee.css
new file mode 100644
index 000000000..fe7a94e0f
--- /dev/null
+++ b/creativecloud/blocks/upload-marquee/upload-marquee.css
@@ -0,0 +1,450 @@
+/* ===== BASE / TOKENS ===== */
+.upload-marquee-block {
+ --um-white: #fff;
+ --um-heading-text: #f2f2f2;
+ --um-body-muted: #dbdbdb;
+ --um-accent: #5681ff;
+ --um-accent-focus: #7f96ff;
+ --um-accent-strong: #3b63fb;
+ --um-accent-link: #1473e6;
+ --um-hover-bg: #0f1c52;
+ --um-radius-sm: 8px;
+ --um-radius-md: 20px;
+ --um-radius-pill: 999px;
+ --um-shadow-media: 0 2px 12px rgb(0 0 0 / 8%);
+ --um-shadow-layout: 0 4px 24px rgb(0 0 0 / 12%);
+ --um-text-shadow: 0 4px 39px rgb(0 0 0 / 25%);
+ --um-drop-zone-border: 2px dashed rgb(255 255 255 / 50%);
+ --um-cta-border: 2px solid rgb(255 255 255 / 35%);
+
+ position: relative;
+ overflow: hidden;
+}
+
+.upload-marquee-block .hide { display: none; }
+
+/* ===== BUTTON ===== */
+.upload-marquee-block .upload-action-container {
+ margin: 0 auto;
+}
+
+.upload-marquee-block .action-button {
+ border-radius: var(--um-radius-pill);
+ border: 0;
+ background: var(--um-accent-strong);
+ color: var(--um-white);
+ cursor: pointer;
+ gap: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 50px;
+ padding: 12px 24px 14px;
+ font-size: 19px;
+ font-weight: 700;
+ line-height: 24px;
+ text-decoration: none;
+}
+
+.upload-marquee-block .action-button > picture {
+ margin-inline-end: 0;
+}
+
+.upload-marquee-block .action-button > picture,
+.upload-marquee-block .action-button > picture > img {
+ width: 24px;
+ height: 24px;
+}
+
+.upload-marquee-block .action-button .icon-upload-to-cloud-outline svg {
+ height: 1.3em;
+ width: 1.3em;
+ line-height: 0;
+}
+
+.upload-marquee-block .action-button:focus-visible {
+ background: var(--um-accent-strong);
+ color: var(--um-white);
+ text-decoration: none;
+}
+
+/* ===== LAYOUT ===== */
+.upload-marquee-block .upload-marquee-layout {
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: auto auto;
+ margin: 0 auto;
+ overflow: hidden;
+}
+
+.upload-marquee-block .upload-marquee-left {
+ background: inherit;
+ color: var(--um-white);
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ margin: 48px var(--grid-margins-width) 8px;
+}
+
+.upload-marquee-block .upload-marquee-right {
+ height: auto;
+ background: inherit;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 32px var(--grid-margins-width);
+ box-sizing: border-box;
+}
+
+.upload-marquee-block .upload-marquee-media {
+ width: 100%;
+ align-self: center;
+}
+
+.upload-marquee-block .upload-marquee-right .media-container {
+ height: 100%;
+ width: 100%;
+ border-radius: var(--um-radius-sm);
+ overflow: hidden;
+ box-shadow: var(--um-shadow-media);
+}
+
+.upload-marquee-block .upload-marquee-right .media-container .video-container {
+ width: 100%;
+ height: 100%;
+}
+
+.upload-marquee-block .upload-marquee-uploads > .tablet-up,
+.upload-marquee-block .upload-marquee-uploads > .tablet-up.desktop-up,
+.upload-marquee-block .upload-marquee-uploads > .desktop-up,
+.upload-marquee-block .upload-marquee-media > .tablet-up,
+.upload-marquee-block .upload-marquee-media > .tablet-up.desktop-up,
+.upload-marquee-block .upload-marquee-media > .desktop-up {
+ display: none;
+}
+
+/* ===== CONTENT ===== */
+.upload-marquee-block .upload-marquee-content {
+ display: flex;
+ flex-direction: column;
+ text-align: left;
+ align-items: flex-start;
+}
+
+.upload-marquee-block .upload-marquee-content p,
+.upload-marquee-block .upload-marquee-content h1,
+.upload-marquee-block .upload-marquee-content h2 {
+ color: inherit;
+ margin: 0;
+ text-align: left;
+}
+
+.upload-marquee-block .upload-marquee-content p:first-child {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ font-size: var(--type-body-s-size);
+ line-height: var(--type-body-s-lh);
+ margin-bottom: 12px;
+}
+
+.upload-marquee-block .upload-marquee-content .upload-marquee-branding {
+ margin-bottom: 20px;
+ padding-bottom: 0;
+}
+
+.upload-marquee-block .upload-marquee-content .upload-marquee-branding-row {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.upload-marquee-block .upload-marquee-content .upload-marquee-branding-first picture,
+.upload-marquee-block .upload-marquee-content .upload-marquee-branding-second picture {
+ display: block;
+}
+
+.upload-marquee-block .upload-marquee-content .upload-marquee-branding-first img,
+.upload-marquee-block .upload-marquee-content .upload-marquee-branding-second img {
+ width: auto;
+ height: 30px;
+ display: block;
+ object-fit: contain;
+}
+
+.upload-marquee-block .upload-marquee-content p:first-child picture,
+.upload-marquee-block .upload-marquee-content p:first-child img {
+ height: 26px;
+ display: block;
+}
+
+.upload-marquee-block .upload-marquee-content > *:first-child > picture img,
+.upload-marquee-block .upload-marquee-content > *:first-child > picture svg {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ border-radius: inherit;
+}
+
+.upload-marquee-block .upload-marquee-content p.upload-marquee-branding .upload-marquee-branding-second picture {
+ height: 15.5px;
+ margin-top: 2.75px;
+}
+
+.upload-marquee-block .upload-marquee-content p.upload-marquee-branding .upload-marquee-branding-second img {
+ height: 15.5px;
+}
+
+.upload-marquee-block .upload-marquee-content a {
+ color: var(--um-white);
+}
+
+.upload-marquee-block .upload-marquee-content .upload-marquee-cta {
+ align-self: flex-start;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 40px;
+ padding: 9px 18px 10px;
+ background: transparent;
+ color: var(--um-white);
+ border: var(--um-cta-border);
+ border-radius: var(--um-radius-pill);
+ box-sizing: border-box;
+ font-size: 17px;
+ font-weight: 700;
+ line-height: 21px;
+ text-decoration: none;
+ transition: border-color 0.2s ease;
+}
+
+.upload-marquee-block .upload-marquee-content p:last-of-type {
+ margin-bottom: 40px;
+}
+
+.upload-marquee-block .upload-marquee-layout .upload-marquee-left .upload-marquee-content h1 {
+ color: var(--um-white);
+ text-shadow: var(--um-text-shadow);
+ font-family: var(--body-font-family);
+ font-size: 40px;
+ font-style: normal;
+ font-weight: 800;
+ line-height: 98%;
+ letter-spacing: -1.8px;
+ margin-bottom: 16px;
+}
+
+.upload-marquee-block .upload-marquee-layout .upload-marquee-left .upload-marquee-content p {
+ color: var(--um-white);
+ text-shadow: var(--um-text-shadow);
+ font-family: var(--body-font-family);
+ font-size: 18px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 136%;
+ letter-spacing: var(--letter-spacing, 0);
+ margin-bottom: 16px;
+}
+
+/* ===== DROP ZONE ===== */
+.upload-marquee-block .drop-zone-container {
+ background: transparent;
+ box-shadow: none;
+ padding: 0;
+ margin: 0;
+ max-width: none;
+}
+
+.upload-marquee-block .drop-zone {
+ border: var(--um-drop-zone-border);
+ border-radius: var(--um-radius-sm);
+ padding: 34px 24px;
+ margin: 0;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+ box-sizing: border-box;
+ background-color: inherit;
+ background-image: radial-gradient(circle, rgb(255 255 255 / 18%) 1px, transparent 1px);
+ background-size: 26px 26px;
+}
+
+.upload-marquee-block .drop-zone:hover,
+.upload-marquee-block .drop-zone.active {
+ background-image: none;
+ cursor: pointer;
+ border: 2px solid var(--um-accent);
+ background-color: var(--um-hover-bg);
+}
+
+.upload-marquee-block .drop-zone:focus-visible {
+ outline: 2px solid var(--um-accent-focus);
+ outline-offset: 2px;
+ border-color: var(--um-accent);
+}
+
+.upload-marquee-block .upload-marquee-layout .upload-marquee-left .drop-zone > p:first-child {
+ color: var(--um-white);
+ margin: 0;
+ text-align: center;
+ font-size: var(--type-body-m-size);
+}
+
+.upload-marquee-block .upload-marquee-layout .upload-marquee-left .drop-zone > p:first-child img,
+.upload-marquee-block .upload-marquee-layout .upload-marquee-left .drop-zone > p:first-child svg {
+ display: block;
+ margin: 0 auto;
+ width: 40px;
+ height: 40px;
+ opacity: 0.9;
+}
+
+.upload-marquee-block .drop-zone .upload-action-container {
+ margin: 4px 0 2px;
+}
+
+.upload-marquee-block .upload-marquee-layout .upload-marquee-left .drop-zone-container > p:last-child {
+ font-size: var(--type-body-xs-size);
+ line-height: var(--type-body-xs-lh);
+ margin-top: 16px;
+ color: var(--um-white);
+ text-align: center;
+ margin-block-end: 0;
+}
+
+.upload-marquee-block .upload-marquee-layout .upload-marquee-left .drop-zone > p:not(:first-child):not(:last-child):not(.upload-action-container) {
+ color: var(--um-white);
+ margin: 0;
+ text-align: center;
+ line-height: 1.4;
+}
+
+.upload-marquee-block .drop-zone > .drop-zone-heading {
+ color: var(--um-heading-text);
+ font-size: 21px;
+ font-weight: 700;
+ line-height: 20px;
+ letter-spacing: 0;
+ margin: 0;
+ text-align: center;
+}
+
+.upload-marquee-block .upload-marquee-layout .upload-marquee-left .drop-zone > .drop-zone-body {
+ color: var(--um-body-muted);
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 24px;
+ max-width: 380px;
+ margin: 0;
+ text-align: center;
+}
+
+.upload-marquee-block .upload-marquee-layout .upload-marquee-left .drop-zone-container > p:last-child a {
+ color: var(--um-accent-link);
+ text-decoration: underline;
+}
+
+/* ===== MEDIA ASSETS ===== */
+.upload-marquee-block .foreground .upload-marquee-layout .upload-marquee-right .media-container picture,
+.upload-marquee-block .foreground .upload-marquee-layout .upload-marquee-right .media-container picture > img,
+.upload-marquee-block .foreground .upload-marquee-layout .upload-marquee-right .media-container video {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ line-height: 0;
+}
+
+/* ===== RESPONSIVE ===== */
+@media screen and (min-width: 600px) {
+ .upload-marquee-block .upload-marquee-right .media-container {
+ border-radius: var(--um-radius-md);
+ }
+
+ .upload-marquee-block .upload-marquee-left {
+ margin: 48px var(--grid-margins-width) 0;
+ }
+
+ .upload-marquee-block .upload-marquee-uploads > .tablet-up,
+ .upload-marquee-block .upload-marquee-uploads > .tablet-up.desktop-up,
+ .upload-marquee-block .upload-marquee-media > .tablet-up,
+ .upload-marquee-block .upload-marquee-media > .tablet-up.desktop-up {
+ display: block;
+ }
+
+ .upload-marquee-block .upload-marquee-uploads > .mobile-up,
+ .upload-marquee-block .upload-marquee-uploads > .desktop-up,
+ .upload-marquee-block .upload-marquee-media > .mobile-up,
+ .upload-marquee-block .upload-marquee-media > .desktop-up {
+ display: none;
+ }
+}
+
+@media screen and (min-width: 1200px) {
+ .upload-marquee-block .upload-marquee-layout .upload-marquee-left .upload-marquee-content h1 {
+ font-size: 64px;
+ letter-spacing: -2.88px;
+ }
+
+ .upload-marquee-block .upload-marquee-left {
+ margin: 0;
+ padding: 56px 137px;
+ box-sizing: border-box;
+ }
+
+ .upload-marquee-block .upload-marquee-layout {
+ grid-template-columns: 1fr 1fr;
+ grid-template-rows: 1fr;
+ min-height: 700px;
+ overflow: hidden;
+ box-shadow: var(--um-shadow-layout);
+ }
+
+ .upload-marquee-block .upload-marquee-right {
+ min-height: auto;
+ height: 100%;
+ align-self: stretch;
+ align-items: stretch;
+ padding: 24px 24px 24px 0;
+ }
+
+ .upload-marquee-block .upload-marquee-media {
+ height: 100%;
+ }
+
+ .upload-marquee-block .upload-marquee-right .media-container {
+ min-height: 100%;
+ max-height: 100%;
+ }
+
+ .upload-marquee-block .upload-marquee-uploads > .desktop-up,
+ .upload-marquee-block .upload-marquee-uploads > .tablet-up.desktop-up,
+ .upload-marquee-block .upload-marquee-media > .desktop-up,
+ .upload-marquee-block .upload-marquee-media > .tablet-up.desktop-up {
+ display: block;
+ }
+
+ .upload-marquee-block .upload-marquee-uploads > .mobile-up,
+ .upload-marquee-block .upload-marquee-uploads > .tablet-up,
+ .upload-marquee-block .upload-marquee-media > .mobile-up,
+ .upload-marquee-block .upload-marquee-media > .tablet-up {
+ display: none;
+ }
+
+ .upload-marquee-block .upload-marquee-right .media-container .video-container .pause-play-wrapper {
+ bottom: 2%;
+ right: 2%;
+ left: auto;
+ top: auto;
+ }
+}
+
+/* ===== REDUCED MOTION ===== */
+@media (prefers-reduced-motion: reduce) {
+ .upload-marquee-block .upload-marquee-right .media-container .video-container .pause-play-wrapper {
+ display: none;
+ }
+}
diff --git a/creativecloud/blocks/upload-marquee/upload-marquee.js b/creativecloud/blocks/upload-marquee/upload-marquee.js
new file mode 100644
index 000000000..160b8512d
--- /dev/null
+++ b/creativecloud/blocks/upload-marquee/upload-marquee.js
@@ -0,0 +1,480 @@
+import { createTag, getConfig, getLibs } from '../../scripts/utils.js';
+
+// ===== CONFIG =====
+const miloLibs = getLibs('/libs');
+const VIEWPORTS = ['mobile-up', 'tablet-up', 'desktop-up'];
+const DEFAULT_DROPZONE_ICON = '/cc-shared/assets/svg/s2-icon-upload-20-n.svg';
+const ARIA_PLACEHOLDER_KEYS = {
+ dropZoneAriaLabel: 'upload-marquee-drop-zone-aria-label',
+ layoutAriaLabel: 'upload-marquee-layout-aria-label',
+ filePickerAriaSuffix: 'file-picker',
+};
+const ARIA_LABEL_DEFAULTS = {
+ dropZoneAriaLabel:
+ 'Upload your asset. Drag and drop a file, or press Enter to browse.',
+ layoutAriaLabel:
+ 'Asset upload area. Drag and drop files anywhere in this section.',
+ filePickerAriaSuffix: 'file picker',
+};
+const AnalyticsKeys = {
+ uploadAssetCTA: 'Upload asset CTA|UnityWidget',
+ editPhotosCTA: 'Edit Photos CTA|UnityWidget',
+};
+
+let uploadColumnCounter = 0;
+
+// ===== LOGGING =====
+function logUploadMarqueeInfo(message, errorType = 'i') {
+ window.lana?.log(message, { tags: 'upload-marquee', errorType });
+}
+
+// ===== ID HELPERS =====
+function nextUploadColumnId() {
+ uploadColumnCounter += 1;
+ return uploadColumnCounter;
+}
+
+function buildScopedId(prefix, columnId) {
+ return `${prefix}-${columnId}`;
+}
+
+// ===== ARIA / LOCALIZATION =====
+function createAriaLabelsLoader() {
+ let labelsPromise;
+ return async function getAriaLabels() {
+ if (!labelsPromise) {
+ labelsPromise = (async () => {
+ try {
+ const config = getConfig();
+ const { replaceKeyArray } = await import(
+ `${miloLibs}/features/placeholders.js`
+ );
+ const labelKeys = Object.keys(ARIA_PLACEHOLDER_KEYS);
+ const localizedValues = await replaceKeyArray(
+ labelKeys.map((key) => ARIA_PLACEHOLDER_KEYS[key]),
+ config,
+ );
+ return labelKeys.reduce((acc, key, index) => {
+ acc[key] = localizedValues[index] || ARIA_LABEL_DEFAULTS[key];
+ return acc;
+ }, {});
+ } catch (err) {
+ logUploadMarqueeInfo(
+ `Failed to fetch upload marquee aria labels: ${err}`,
+ );
+ return { ...ARIA_LABEL_DEFAULTS };
+ }
+ })();
+ }
+ return labelsPromise;
+ };
+}
+
+// ===== DATA EXTRACTION =====
+function extractUploadContentParts(content) {
+ const media = content.querySelector('picture, .video-container.video-holder');
+ const terms = content.querySelector('p:last-child');
+ const mediaPara = media?.closest('p');
+
+ const hasUploadMarker = (para) => para.querySelector(
+ 'span[class*=icon-share], span[class*=icon-upload], img[src$=".svg"]:not(.video-container img)',
+ );
+
+ const candidateParagraphs = [
+ ...content.querySelectorAll('p:not(:last-child)'),
+ ].filter(
+ (para) => para.textContent.trim() !== '' || para.querySelector('img, svg'),
+ );
+
+ const uploadPara = candidateParagraphs.find((para) => hasUploadMarker(para));
+
+ const contentParagraphs = candidateParagraphs.filter((para) => {
+ const isMediaOnlyPara = para === mediaPara
+ && !hasUploadMarker(para)
+ && para.textContent.trim() === '';
+ return !isMediaOnlyPara;
+ });
+
+ const textParas = contentParagraphs.filter((para) => para !== uploadPara);
+ const headingPara = textParas[0];
+ const bodyPara = textParas[1];
+
+ return {
+ media,
+ terms,
+ contentParagraphs,
+ uploadPara,
+ headingPara,
+ bodyPara,
+ };
+}
+
+// ===== DOM BUILDERS =====
+// TODO: See if it could be simplified
+function applyViewportClasses(foreground) {
+ foreground.firstElementChild?.classList.add('upload-grid');
+
+ if (
+ foreground.childElementCount === 2
+ || foreground.childElementCount === 3
+ ) {
+ [...foreground.children].forEach((child, index) => {
+ child.classList.add('upload-grid', VIEWPORTS[index]);
+ if (foreground.childElementCount === 2 && index === 1) {
+ child.className = 'upload-grid tablet-up desktop-up';
+ }
+ });
+ } else if (foreground.childElementCount === 1) {
+ foreground.firstElementChild?.classList.add(...VIEWPORTS);
+ }
+
+ return foreground;
+}
+
+function buildDropZoneIcon() {
+ const defaultIcon = createTag('p', { class: 'drop-zone-default-icon' });
+ const image = createTag('img', { src: DEFAULT_DROPZONE_ICON, alt: '' });
+ defaultIcon.setAttribute('aria-hidden', 'true');
+ defaultIcon.append(image);
+ return defaultIcon;
+}
+
+function assignDropZoneTextIds(headingPara, bodyPara, columnId) {
+ const describedByIds = [];
+
+ if (headingPara) {
+ headingPara.classList.add('drop-zone-heading');
+ headingPara.id = buildScopedId('drop-zone-heading', columnId);
+ describedByIds.push(headingPara.id);
+ }
+ if (bodyPara) {
+ bodyPara.classList.add('drop-zone-body');
+ bodyPara.id = buildScopedId('drop-zone-body', columnId);
+ describedByIds.push(bodyPara.id);
+ }
+
+ return describedByIds;
+}
+
+async function buildUploadActionControls(para, columnId, getAriaLabels) {
+ const buttonLabel = para.textContent.trim().split('|')[0].trim() || 'Upload your image';
+ const { filePickerAriaSuffix } = await getAriaLabels();
+ const button = createTag(
+ 'button',
+ {
+ type: 'button',
+ class: 'con-button blue action-button button-xl no-track',
+ 'daa-ll': AnalyticsKeys.uploadAssetCTA,
+ 'aria-label': buttonLabel,
+ },
+ para.innerHTML,
+ );
+ const input = createTag('input', {
+ type: 'file',
+ name: 'file-upload',
+ id: 'file-upload',
+ class: 'file-upload hide',
+ accept: 'image/*',
+ 'aria-label': `${buttonLabel} ${filePickerAriaSuffix}`,
+ });
+
+ para.classList.add('upload-action-container');
+ para.textContent = '';
+ para.append(button, input);
+ return { fileInput: input };
+}
+
+// ===== EVENT BINDERS =====
+function wireDropZoneAccessibility(
+ dropZone,
+ fileInput,
+ describedByIds,
+ dropZoneAriaLabel,
+) {
+ dropZone.setAttribute('role', 'button');
+ dropZone.setAttribute('tabindex', '0');
+ dropZone.setAttribute('aria-label', dropZoneAriaLabel);
+
+ if (fileInput?.id) {
+ dropZone.setAttribute('aria-controls', fileInput.id);
+ }
+ if (describedByIds.length) {
+ dropZone.setAttribute('aria-describedby', describedByIds.join(' '));
+ }
+
+ dropZone.addEventListener('click', (event) => {
+ event.stopPropagation();
+ fileInput?.click();
+ });
+
+ dropZone.addEventListener('keydown', (event) => {
+ if (event.target !== dropZone) return;
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault();
+ fileInput?.click();
+ }
+ });
+}
+
+async function buildDropZone(uploadParts, columnId, getAriaLabels) {
+ const { dropZoneAriaLabel } = await getAriaLabels();
+ const dropZone = createTag('div', { class: 'drop-zone' });
+ const { fileInput } = await buildUploadActionControls(
+ uploadParts.uploadPara,
+ columnId,
+ getAriaLabels,
+ );
+ const describedByIds = assignDropZoneTextIds(
+ uploadParts.headingPara,
+ uploadParts.bodyPara,
+ columnId,
+ );
+
+ wireDropZoneAccessibility(
+ dropZone,
+ fileInput,
+ describedByIds,
+ dropZoneAriaLabel,
+ );
+ dropZone.append(buildDropZoneIcon(), ...uploadParts.contentParagraphs);
+
+ return dropZone;
+}
+
+function replaceUploadColumnContent(
+ content,
+ mediaContainer,
+ dropZoneContainer,
+ terms,
+) {
+ content.textContent = '';
+ content.append(mediaContainer, dropZoneContainer);
+ if (terms) {
+ dropZoneContainer.append(terms);
+ }
+}
+
+function buildMarqueeContent(marqueeCell) {
+ const marqueeContent = createTag('div', { class: 'upload-marquee-content' });
+ [...marqueeCell.children].forEach((child) => marqueeContent.append(child.cloneNode(true)));
+
+ const brandingPara = marqueeContent.querySelector(':scope > p:first-child');
+ const [firstPicture, secondPicture] = brandingPara
+ ? [...brandingPara.querySelectorAll('picture')]
+ : [];
+ if (firstPicture && secondPicture) {
+ const brandingRow = createTag('span', { class: 'upload-marquee-branding-row' });
+ const firstWrap = createTag('span', { class: 'upload-marquee-branding-first' });
+ const secondWrap = createTag('span', { class: 'upload-marquee-branding-second' });
+ firstWrap.append(firstPicture.cloneNode(true));
+ secondWrap.append(secondPicture.cloneNode(true));
+ brandingRow.append(firstWrap, secondWrap);
+
+ brandingPara.textContent = '';
+ brandingPara.classList.add('upload-marquee-branding');
+ brandingPara.append(brandingRow);
+ }
+
+ const ctaLink = marqueeContent.querySelector(
+ 'p strong a[href], p:last-of-type a[href]',
+ );
+ if (ctaLink) {
+ ctaLink.classList.add('con-button', 'upload-marquee-cta', 'no-track');
+ ctaLink.setAttribute('aria-label', ctaLink.textContent.trim());
+ ctaLink.setAttribute('daa-ll', AnalyticsKeys.editPhotosCTA);
+ }
+
+ return marqueeContent;
+}
+
+function buildLayout(layoutAriaLabel) {
+ const layout = createTag('div', {
+ class: 'upload-marquee-layout',
+ role: 'region',
+ 'aria-label': layoutAriaLabel,
+ });
+ const leftCol = createTag('div', { class: 'upload-marquee-left' });
+ const rightCol = createTag('div', { class: 'upload-marquee-right' });
+ const uploadsWrapper = createTag('div', { class: 'upload-marquee-uploads' });
+ const mediaWrapper = createTag('div', { class: 'upload-marquee-media' });
+
+ return {
+ layout,
+ leftCol,
+ rightCol,
+ uploadsWrapper,
+ mediaWrapper,
+ };
+}
+
+function appendColumns(viewportContent, uploadsWrapper, mediaWrapper) {
+ viewportContent.forEach(({ media, dropZone, viewportClasses }) => {
+ if (dropZone) {
+ dropZone.classList.add(...viewportClasses);
+ uploadsWrapper.append(dropZone);
+ }
+ if (media) {
+ media.classList.add(...viewportClasses);
+ mediaWrapper.append(media);
+ }
+ });
+}
+
+function collectViewportContent(uploadRow) {
+ return [...uploadRow.children].map((content) => {
+ const media = content.querySelector(':scope > .media-container');
+ const dropZone = content.querySelector(':scope > .drop-zone-container');
+ const viewportClasses = [...content.classList].filter((cls) => VIEWPORTS.includes(cls));
+ return { media, dropZone, viewportClasses };
+ });
+}
+
+async function decorateUploadColumn(content, getAriaLabels) {
+ const columnId = nextUploadColumnId();
+ const mediaContainer = createTag('div', { class: 'media-container' });
+ const dropZoneContainer = createTag('div', { class: 'drop-zone-container' });
+ const uploadParts = extractUploadContentParts(content);
+
+ if (uploadParts.media) {
+ mediaContainer.append(uploadParts.media);
+ if (
+ uploadParts.media.parentElement?.tagName === 'P'
+ && uploadParts.media.parentElement.textContent.trim() === ''
+ ) {
+ uploadParts.media.parentElement.remove();
+ }
+ }
+
+ if (!uploadParts.uploadPara) {
+ logUploadMarqueeInfo(
+ 'Failed to create upload button for upload-marquee block.',
+ );
+ return;
+ }
+
+ const dropZone = await buildDropZone(uploadParts, columnId, getAriaLabels);
+ dropZoneContainer.append(dropZone);
+ replaceUploadColumnContent(
+ content,
+ mediaContainer,
+ dropZoneContainer,
+ uploadParts.terms,
+ );
+}
+
+function setupLayoutDragAndDrop(layout, uploadsWrapper) {
+ let activeDropZone;
+
+ const setActiveDropZone = () => {
+ const dropZones = [
+ ...uploadsWrapper.querySelectorAll(
+ ':scope > .drop-zone-container > .drop-zone',
+ ),
+ ];
+ const nextDropZone = dropZones.find((zone) => zone.offsetParent !== null) || dropZones[0];
+ if (activeDropZone && activeDropZone !== nextDropZone) {
+ activeDropZone.classList.remove('active');
+ }
+ activeDropZone = nextDropZone;
+ activeDropZone?.classList.add('active');
+ };
+
+ const clearActiveDropZone = () => {
+ activeDropZone?.classList.remove('active');
+ activeDropZone = null;
+ };
+
+ layout.addEventListener('dragenter', (event) => {
+ event.preventDefault();
+ setActiveDropZone();
+ });
+
+ layout.addEventListener('dragover', (event) => {
+ event.preventDefault();
+ event.dataTransfer.dropEffect = 'copy';
+ setActiveDropZone();
+ });
+
+ layout.addEventListener('dragleave', (event) => {
+ event.preventDefault();
+ clearActiveDropZone();
+ });
+
+ document.addEventListener('dragend', () => clearActiveDropZone());
+
+ layout.addEventListener('drop', (event) => {
+ event.preventDefault();
+ setActiveDropZone();
+ const fileInput = activeDropZone?.querySelector('.file-upload');
+ const files = event.dataTransfer?.files;
+ if (files?.length && fileInput) {
+ try {
+ fileInput.files = files;
+ } catch {
+ // TODO: Trigger lana log
+ // Some browsers may not allow assigning FileList directly.
+ }
+ fileInput.dispatchEvent(new Event('change', { bubbles: true }));
+ }
+ clearActiveDropZone();
+ });
+
+ uploadsWrapper.querySelectorAll('.drop-zone').forEach((zone) => {
+ zone.addEventListener('drop', () => {
+ clearActiveDropZone();
+ });
+ });
+
+ window.addEventListener('drop', () => clearActiveDropZone());
+ window.addEventListener('dragend', () => clearActiveDropZone());
+}
+
+export default async function init(el) {
+ const { decorateBlockBg } = await import(`${miloLibs}/utils/decorate.js`);
+ const getAriaLabels = createAriaLabelsLoader();
+
+ el.classList.add('upload-marquee-block', 'con-block');
+ const rows = el.querySelectorAll(':scope > div');
+ if (rows.length < 3) return;
+
+ const [backgroundRow, marqueeRow, uploadRow] = rows;
+
+ if (backgroundRow.textContent.trim() !== '') {
+ backgroundRow.classList.add('background');
+ decorateBlockBg(el, backgroundRow, { useHandleFocalpoint: true });
+ }
+
+ uploadRow.classList.add('foreground');
+ applyViewportClasses(uploadRow);
+
+ for (let i = 0; i < uploadRow.children.length; i += 1) {
+ // eslint-disable-next-line no-await-in-loop
+ await decorateUploadColumn(uploadRow.children[i], getAriaLabels);
+ }
+
+ const { layoutAriaLabel } = await getAriaLabels();
+ const { layout, leftCol, rightCol, uploadsWrapper, mediaWrapper } = buildLayout(layoutAriaLabel);
+
+ const marqueeCell = marqueeRow.querySelector(':scope > div');
+ if (!marqueeCell) return;
+
+ leftCol.append(buildMarqueeContent(marqueeCell));
+ appendColumns(
+ collectViewportContent(uploadRow),
+ uploadsWrapper,
+ mediaWrapper,
+ );
+
+ if (!uploadsWrapper.children.length || !mediaWrapper.children.length) return;
+
+ leftCol.append(uploadsWrapper);
+ rightCol.append(mediaWrapper);
+ layout.append(leftCol, rightCol);
+ setupLayoutDragAndDrop(layout, uploadsWrapper);
+
+ const foreground = createTag('div', { class: 'foreground' });
+ foreground.append(layout);
+
+ el.textContent = '';
+ el.append(foreground);
+}
diff --git a/creativecloud/features/parallax.js b/creativecloud/features/parallax.js
index faff46f7e..becd75bd7 100644
--- a/creativecloud/features/parallax.js
+++ b/creativecloud/features/parallax.js
@@ -24,6 +24,9 @@ function throttle(cb, delay, { trailing = false } = {}) {
function addProgressIMPL(el, NAV_HEIGHT, markers) {
let screenHeight = window.innerHeight;
let elHeight = el.offsetHeight;
+ let previousButtonTop = 0;
+ const content = el.querySelector('.firefly-model-showcase-content');
+ const initialContentHeight = content.clientHeight;
window.addEventListener(
'resize',
throttle(() => {
@@ -60,10 +63,18 @@ function addProgressIMPL(el, NAV_HEIGHT, markers) {
window.addEventListener(
'scroll',
() => {
+ /* if content height changed due to additional spacing (e.g. dylan text spacing),
+ skip the parallax animation */
+ if (initialContentHeight !== content.clientHeight) return;
+ const buttonRect = el.querySelector('.firefly-model-showcase-content .action-area a:first-of-type').getBoundingClientRect();
+ const currentButtonTop = buttonRect.top;
+ // if the button is below the fold, skip the parallax animation
+ if (previousButtonTop - screenHeight > 0 && !(previousButtonTop < 0)) return;
if (!ticking) {
requestAnimationFrame(updateProgress);
ticking = true;
}
+ previousButtonTop = currentButtonTop;
},
{ passive: true },
);
diff --git a/creativecloud/scripts/utils.js b/creativecloud/scripts/utils.js
index 79863e938..addf4a66a 100644
--- a/creativecloud/scripts/utils.js
+++ b/creativecloud/scripts/utils.js
@@ -190,7 +190,7 @@ export const [setLibs, getLibs] = (() => {
const miloLibs = setLibs('/libs');
// eslint-disable-next-line object-curly-newline
-const { createTag, localizeLink, getConfig, loadStyle, loadLink, loadScript, createIntersectionObserver } = await import(`${miloLibs}/utils/utils.js`);
+const { createTag, localizeLink, getConfig, getMetadata, loadStyle, loadLink, loadScript, createIntersectionObserver } = await import(`${miloLibs}/utils/utils.js`);
// eslint-disable-next-line max-len
export { createTag, loadStyle, loadLink, loadScript, localizeLink, createIntersectionObserver, getConfig };
@@ -451,6 +451,7 @@ export const scriptInit = async () => {
decorateArea();
(function loadStyles() {
const paths = [`${miloLibs}/styles/styles.css`];
+ if (getMetadata('theme') === 'doodlebug') paths.push('/creativecloud/styles/doodlebug.css');
paths.forEach((path) => {
const link = document.createElement('link');
link.setAttribute('rel', 'stylesheet');
@@ -458,6 +459,35 @@ export const scriptInit = async () => {
document.head.appendChild(link);
});
}());
+
+ // TODO can be removed after 2026-03-20 - this is a temporary test for lingo
+ (function setupInteractionLogging() {
+ const cutoff = new Date(2026, 2, 20, 23, 59, 59, 999);
+ const isWithinLoggingWindow = () => new Date() <= cutoff;
+
+ const opts = { capture: true };
+ let logged = false;
+ const logInteraction = (e) => {
+ if (!e.target?.closest('a')) return;
+ if (e.type === 'keydown') {
+ if (e.key !== 'Enter') return;
+ }
+ if (!isWithinLoggingWindow() || logged) return;
+ logged = true;
+ document.removeEventListener('click', logInteraction, opts);
+ document.removeEventListener('keydown', logInteraction, opts);
+ document.removeEventListener('touchstart', logInteraction, opts);
+ const firstSection = document.querySelector('main > .section');
+ const inFirstSection = firstSection?.contains(e.target);
+ const tag = inFirstSection ? 'test-lingo-user-interaction-first-section' : 'test-lingo-user-interaction-other-section';
+ const timeToInteractionMs = Math.round(performance.now());
+ window.lana?.log(`${timeToInteractionMs}`, { sampleRate: 1, severity: 'i', tags: tag });
+ };
+ document.addEventListener('click', logInteraction, opts);
+ document.addEventListener('keydown', logInteraction, opts);
+ document.addEventListener('touchstart', logInteraction, opts);
+ }());
+
(async function loadPage() {
loadLana({ clientId: 'cc' });
await loadArea();
diff --git a/creativecloud/styles/doodlebug.css b/creativecloud/styles/doodlebug.css
new file mode 100644
index 000000000..eda3e7c23
--- /dev/null
+++ b/creativecloud/styles/doodlebug.css
@@ -0,0 +1,534 @@
+:root:has(meta[name='theme'][content='doodlebug']) {
+ /* ---- Common Variables ---- */
+ --xl-rounded-corners: 20px;
+ --glass-border: 3px solid rgba(255 255 255 / 30%);
+ --content-margin: 6.51%;
+
+ @media (min-width: 600px) {
+ --xl-rounded-corners: 32px;
+ }
+
+ :where(.section:not(.dark) p) {
+ color: #5D5D5D;
+ }
+
+ /* ---- Accordion ---- */
+ .accordion {
+ margin: 0 var(--content-margin);
+ }
+
+ .accordion .descr-term button {
+ border-radius: 16px;
+ background: #F0F0F7;
+ border: 1px solid #fff;
+ }
+
+ .descr-list.accordion {
+ border-bottom: none;
+ max-width: none;
+ }
+
+ .accordion .descr-term button[aria-expanded="true"]::before {
+ content: none;
+ }
+
+ /* ---- Aside ---- */
+ .aside .foreground.container {
+ display: grid;
+ align-items: center;
+ gap: 165px;
+ width: 85%;
+ grid-template-columns: minmax(45%, 1fr) minmax(55%, 1fr);
+ }
+
+ .aside .foreground.container:has(> .image:first-child) {
+ grid-template-columns: minmax(55%, 1fr) minmax(45%, 1fr);
+ }
+
+ .aside .foreground.container .text {
+ margin-inline: 180px 0;
+ }
+
+ .aside .foreground.container:has(> .image:first-child) .text {
+ margin-inline: 0 180px;
+ }
+
+ .aside .foreground.container .image img {
+ border-radius: 20px;
+ border: var(--glass-border);
+ }
+
+ @media (max-width: 1200px) {
+ .aside .foreground.container {
+ display: grid;
+ align-items: center;
+ gap: 8.5%;
+ width: 85%;
+ grid-template-columns: minmax(45%, 1fr) minmax(55%, 1fr);
+ }
+
+ .aside .foreground.container .text {
+ margin-inline: 9.5% 0;
+ }
+
+ .aside .foreground.container:has(> .image:first-child) .text {
+ margin-inline: 0 9.5%;
+ }
+ }
+
+ /* ---- Carousel (Tablet Hinting) ---- */
+ .carousel.hinting-tablet {
+ --tablet-card-width: calc(300 / 768 * 100vw);
+ --tablet-gap: 24px;
+ --tablet-margin: 2rem;
+ }
+
+ .carousel.hinting-mobile .carousel-button-container {
+ background: none;
+ min-width: 100px;
+ gap: 6px;
+ }
+
+ .carousel.hinting-mobile .carousel-button-container .carousel-controls {
+ display: none;
+ }
+
+ .carousel.hinting-mobile .carousel-button-container .carousel-button {
+ width: 40px;
+ height: 40px;
+ background-color: #242424;
+ }
+
+ .carousel.hinting-mobile .carousel-previous path,
+ .carousel.hinting-mobile .carousel-next path {
+ fill: #fff;
+ }
+
+ .carousel.disable-circular-nav .carousel-button-container .disabled {
+ background-color: rgba(177 177 177 / 20%);
+ }
+
+ .carousel.disable-circular-nav .carousel-button-container .disabled path {
+ fill: #B1B1B1;
+ }
+
+ .carousel.hinting-mobile:has(.carousel-slide:nth-last-child(1).active) .carousel-slide:first-child {
+ visibility: hidden;
+ }
+
+ @media (min-width: 600px) and (max-width: 1199px) {
+ .carousel.hinting-tablet {
+ --tablet-card-width: calc(300 / 768 * 100vw);
+ --tablet-gap: 24px;
+ --tablet-margin: 2rem;
+ }
+
+ .carousel.hinting-tablet .carousel-button-container {
+ background: none;
+ min-width: 100px;
+ gap: 6px;
+ }
+
+ .carousel.hinting-tablet .carousel-button-container .carousel-controls {
+ display: none;
+ }
+
+ .carousel.hinting-tablet .carousel-button-container .carousel-button {
+ width: 40px;
+ height: 40px;
+ background-color: #242424;
+ }
+
+ .carousel.hinting-tablet .carousel-previous path,
+ .carousel.hinting-tablet .carousel-next path {
+ fill: #fff;
+ }
+
+ .carousel.container.hinting-tablet {
+ width: 100%;
+ }
+
+ .carousel.hinting-tablet .carousel-wrapper {
+ margin-left: var(--grid-margins-width);
+ }
+
+ .carousel.hinting-tablet .carousel-slides {
+ gap: var(--tablet-gap);
+ left: calc(-1 * var(--tablet-card-width));
+ transform: translateX(var(--tablet-card-width));
+ align-items: flex-start;
+ }
+
+ .carousel.hinting-tablet .carousel-slides.is-reversing {
+ transform: translateX(calc(-1 * var(--tablet-card-width)));
+ }
+
+ .carousel.hinting-tablet .carousel-slide {
+ flex-basis: var(--tablet-card-width);
+ }
+
+ .carousel.hinting-tablet .carousel-slides.is-ready,
+ .carousel.hinting-tablet .carousel-slides.is-ready.is-reversing {
+ transform: translateX(calc(-1 * var(--tablet-gap)));
+ }
+
+ .carousel.disable-circular-nav .carousel-button-container .disabled {
+ background-color: rgba(177 177 177 / 20%);
+ }
+
+ .carousel.disable-circular-nav .carousel-button-container .disabled path {
+ fill: #B1B1B1;
+ }
+
+ .carousel.hinting-tablet:has(.carousel-slide:nth-last-child(2).active) .carousel-slide:first-child {
+ visibility: hidden;
+ }
+
+ .carousel.disable-nav.hinting-tablet .carousel-slides {
+ left: calc(-1 * var(--tablet-card-width) - var(--tablet-margin));
+ transform: translateX(calc(var(--tablet-card-width) + var(--tablet-margin)));
+ align-items: flex-start;
+ }
+
+ .carousel.disable-nav.hinting-tablet .carousel-slides.is-reversing {
+ transform: translateX(calc(-1 * var(--tablet-card-width) - var(--tablet-margin)));
+ }
+
+ .carousel.disable-nav.hinting-tablet .carousel-slides.is-ready,
+ .carousel.disable-nav.hinting-tablet .carousel-slides.is-ready.is-reversing {
+ transform: translateX(calc(var(--tablet-margin) - var(--tablet-gap)));
+ }
+
+ .carousel.hinting-tablet .editorial-card {
+ --card-height-default: calc(var(--tablet-card-width) * 225 / 300);
+ }
+
+ .carousel.disable-nav.hinting-tablet .editorial-card .media-area::after {
+ position: absolute;
+ content: '';
+ inset: 0;
+ border: var(--glass-border);
+ pointer-events: none;
+ }
+ }
+
+ /* ---- Editorial Card ---- */
+ .editorial-card.l-shadow {
+ box-shadow: 0 5px 20px 10px #0000000F;
+ }
+
+ .editorial-card h3 {
+ letter-spacing: -3%;
+ font-weight: 800;
+
+ }
+
+ .editorial-card.s-padding-media .media-area>div {
+ padding: var(--spacing-xxs);
+ }
+
+ .editorial-card.s-padding-media.m-rounded-corners-image .media-area>div img {
+ border-radius: var(--m-rounded-corners);
+ }
+
+ .two-up .editorial-card .media-area :is(img, video),
+ .three-up .editorial-card .media-area :is(img, video),
+ .four-up .editorial-card .media-area :is(img, video) {
+ height: auto;
+ max-height: unset;
+ aspect-ratio: 284 / 269;
+ }
+
+ .editorial-card .foreground .body-l {
+ font-size: 1rem;
+ line-height: 135%;
+ letter-spacing: 1%;
+ }
+
+ .section.show-4-desktop {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(276px, 1fr));
+ gap: var(--spacing-s);
+ align-items: start;
+ padding-left: var(--grid-margins-width);
+ padding-right: var(--grid-margins-width);
+ justify-content: center;
+ }
+
+
+ @media (min-width: 1200px) {
+ .editorial-card .foreground .body-l {
+ font-size: 1.25rem;
+ letter-spacing: 1%;
+ }
+
+ .section.show-2-desktop {
+ grid-template-columns: repeat(2, minmax(0, 823px));
+ max-width: 100%;
+ padding-left: var(--content-margin);
+ padding-right: var(--content-margin);
+ justify-content: center;
+ }
+
+ .section.show-4-desktop {
+ grid-template-columns: repeat(4, minmax(0, 350px));
+ max-width: 100%;
+ padding-left: var(--content-margin);
+ padding-right: var(--content-margin);
+ gap: 10px;
+ }
+
+ .show-2-desktop .editorial-card .media-area :is(img, video) {
+ height: auto;
+ max-height: unset;
+ aspect-ratio: 410/336;
+ }
+
+ .show-2-desktop .editorial-card {
+ background: none !important;
+ box-shadow: none;
+ }
+
+ .show-2-desktop .editorial-card .media-area,
+ .show-4-desktop .editorial-card .media-area {
+ position: relative;
+ }
+
+ .show-2-desktop .editorial-card .media-area::after {
+ position: absolute;
+ content: '';
+ inset: 0;
+ border: var(--glass-border);
+ pointer-events: none;
+ }
+
+ .show-2-desktop .editorial-card .media-area,
+ .show-2-desktop .editorial-card .media-area::after {
+ border-radius: 20px;
+ }
+
+ .show-4-desktop .editorial-card .foreground > div {
+ row-gap: 24px;
+ }
+
+ .show-2-desktop .editorial-card.s-padding-media .media-area>div {
+ padding: 0;
+ }
+
+ .show-4-desktop .editorial-card .foreground .body-l {
+ line-height: 26px;
+ letter-spacing: 0.2px; /* 130% */
+ }
+ }
+
+ /* ---- Section Utilities ---- */
+ .section.two-up,
+ .section.three-up,
+ .section.four-up,
+ .section.five-up {
+ gap: var(--spacing-s);
+ }
+
+ .section.spacing-bottom-180 {
+ padding-bottom: 80px;
+ }
+
+ .section.xl-top-rounded-corners {
+ border-radius: var(--xl-rounded-corners) var(--xl-rounded-corners) 0 0;
+ }
+
+ .section.xl-bottom-rounded-corners {
+ border-radius: 0 0 var(--xl-rounded-corners) var(--xl-rounded-corners);
+ }
+
+
+
+ @media (max-width: 599px) {
+ .section.no-spacing-bottom-mobile {
+ padding-bottom: 0;
+ }
+ }
+
+ @media (min-width: 600px) {
+ .section.spacing-bottom-180 {
+ padding-bottom: 160px;
+ }
+ }
+
+ @media (min-width: 600px) and (max-width: 1199px) {
+ .section.no-spacing-bottom-tablet {
+ padding-bottom: 0;
+ }
+
+ .section.s-spacing:has(> .carousel.show-2.ups-desktop) {
+ padding-top: 0;
+ }
+ }
+
+ /* ---- Text Block ---- */
+ .text-block [class*="heading-"] {
+ font-weight: 900;
+ }
+
+ /* ---- Aside (Heading Override) ---- */
+ .aside .foreground.container .text [class*="heading-"] {
+ font-size: 2.25rem;
+ font-weight: 800;
+ letter-spacing: -3%;
+ line-height: 98%;
+ }
+
+ .section:not(.dark) .text-block:not(.dark) [class*="heading-"] {
+ color: var(--color-black);
+ }
+
+ .text-block [class*="heading-xxxl"] {
+ font-size: 2rem;
+ line-height: 105%;
+ letter-spacing: -1.44px;
+ }
+
+ .text-block.center [class*="body-l"] {
+ font-size: 1.125rem;
+ line-height: 130%;
+ }
+
+ .text-block.grid-cta .foreground {
+ --cell-width: 300px;
+
+ max-width: 100%;
+ margin: 0 var(--content-margin);
+ }
+
+ .text-block.grid-cta .foreground .cta-container .action-area {
+ display: grid;
+ grid-template-columns: 1fr;
+ justify-content: center;
+ gap: 10px;
+ max-width: 100%;
+ }
+
+ .text-block.grid-cta .foreground .cta-container .action-area .con-button {
+ display: block;
+ position: relative;
+ width: 100%;
+ height: 81px;
+ padding: 24px 48px 24px 18px;
+ border: none;
+ border-radius: 8px;
+ background: var(--color-white);
+ color: var(--text-color);
+ font-size: 1.25rem;
+ font-weight: 800;
+ line-height: 33px;
+ box-sizing: border-box;
+ text-align: left;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .text-block.grid-cta .foreground .cta-container .action-area .con-button:hover,
+ .text-block.grid-cta .foreground .cta-container .action-area .con-button:focus-visible {
+ color: var(--color-black);
+ position: relative;
+ z-index: 1;
+ box-shadow: 0 1px 4px 0 #0000000C, 0 8px 8px 0 #00000013, 0 20px 12px 0 #00000018, 0 40px 24px 0 #0000001D;
+ }
+
+ /** TODO: rtl **/
+
+ .text-block.grid-cta .foreground .cta-container a::after {
+ content: '';
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ right: 18px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: url('/cc-shared/assets/svg/s2-icon-openin-20-n.svg') no-repeat;
+ background-size: contain;
+ }
+
+ @media (min-width: 600px) {
+ .text-block.grid-cta .foreground .cta-container .action-area {
+ grid-template-columns: repeat(2,1fr);
+ }
+
+
+ .text-block.grid-cta .foreground .cta-container .action-area .con-button {
+ font-size: 1.5rem;
+ letter-spacing: -3%;
+ }
+ }
+
+ @media (min-width: 600px) and (max-width: 1199px) {
+ .text-block.left-tablet {
+ text-align: start;
+ }
+
+ .text-block.left-tablet .action-area {
+ justify-content: start;
+ }
+
+ .text-block [class*="heading-xxxl"] {
+ font-size: 2.25rem;
+ line-height: 98%;
+ letter-spacing: -1.62px;
+ }
+
+ .text-block.grid-cta .foreground {
+ --cell-width: 310px;
+ }
+ }
+
+ @media (min-width: 1200px) {
+ .text-block [class*="heading-xxxl"] {
+ font-size: 4rem;
+ line-height: 98%;
+ letter-spacing: -2.88px;
+ }
+
+ .text-block.center [class*="body-l"] {
+ font-size: 1.25rem;
+ line-height: 130%;
+ max-width: 950px;
+ margin: 0 auto;
+ }
+
+ .text-block.grid-cta .foreground {
+ --cell-width: 410px;
+ }
+
+ .text-block.grid-cta .foreground .cta-container .action-area {
+ grid-template-columns: repeat(4, 1fr);
+ }
+ }
+}
+
+/**
+TODO: Can use &[dir="rtl"] above also, & is supported by modern browsers, done this for old browsers
+**/
+
+:root[dir="rtl"]:has(meta[name='theme'][content='doodlebug']) {
+ .text-block.grid-cta .foreground .cta-container .action-area a.con-button {
+ text-align: right;
+ padding: 24px 18px 24px 48px;
+ }
+
+ .text-block.grid-cta .foreground .cta-container .action-area a.con-button::after {
+ display: none;
+ }
+
+ .text-block.grid-cta .foreground .cta-container .action-area a.con-button::before {
+ content: '';
+ position: absolute;
+ left: 18px;
+ top: 50%;
+ transform: translateY(-50%) scaleX(-1);
+ width: 20px;
+ height: 20px;
+ background: url('/cc-shared/assets/svg/s2-icon-openin-20-n.svg') no-repeat center;
+ background-size: contain;
+ }
+}