Repository: adobecom/express-milo
Pull request #248: [Q1 FY25 | SEO] Discover More Cards
Author: login: jsandland
Labels: name: Ready for Stage
description: All requested changes have been made, approvals received, ready for next stage batch
Created: 2025-03-12T21:54:04Z | Merged: 2025-03-13T22:21:59Z

Describe your specific features or fixes:
* Implement New Discover More Cards Variant
* Figma: https://www.figma.com/design/BZFcWZY01SWY9FRXB4cIVv/Standout-Create-Page-test---CCEX-120989?node-id=5037-146716&m=dev

Authoring Example: [milo-discover-more-cards.docx](https://adobe.sharepoint.com/:w:/r/sites/adobecom/Express/website/drafts/jsandlan/milo-discover-more-cards.docx?d=w238f71656e01437d86b02cae4922829d&csf=1&web=1&e=XYEhFc)

Resolves: [MWPW-166954](https://jira.corp.adobe.com/browse/MWPW-166954)
<img width="1454" alt="discover-more-cards" src="https://github.com/user-attachments/assets/414d0e42-c1de-4354-84f2-80614f3a0d84" />


Test URLs:
- Pre Migration Link: https://adobe.com/express/
- Before: https://main--express-milo--adobecom.aem.page/express/ (Variant does not exist though Discover Cards can be seen on homepage)
- After: https://milo-discover-more-cards--express-milo--adobecom.aem.page/drafts/jsandlan/milo-discover-more-cards



Diff:
diff --git a/express/code/blocks/discover-cards/discover-cards.css b/express/code/blocks/discover-cards/discover-cards.css
index 7cf639c11..432766508 100644
--- a/express/code/blocks/discover-cards/discover-cards.css
+++ b/express/code/blocks/discover-cards/discover-cards.css
@@ -37,7 +37,49 @@ main .section .discover-cards {
     overflow: hidden;
     padding: 16px;
     margin: 9px -7px 10px auto;
+}
+.discover-cards.flip {
+  height: 550px;
+}
+.discover-cards.flip .cards-container .card {
+  background: unset;
+  border: none;
+}
+
+.discover-cards.flip h1, 
+.discover-cards.flip h2 {
+  font-size: var(--heading-font-size-s);
+}
+/* Default style - centered */
+.discover-cards.flip .cards-container.gallery {
+  display: grid;
+  grid-auto-flow: column;
+  grid-auto-columns: 250px;
+  gap: 16px;
+  overflow-x: scroll;
+  scrollbar-width: none;
+  scroll-snap-type: x mandatory;
+  scroll-behavior: smooth;
+  padding: 3px 16px;
+  margin-top: 29px;
+  justify-content: center;
+  width: fit-content;
+  max-width: 100vw;
+  margin-left: auto;
+  margin-right: auto;
+}
 
+/* Switch to left-aligned on smaller screens */
+@media (max-width: 1050px) {
+  .discover-cards.flip .cards-container.gallery {
+    justify-content: start;
+    width: 100%;
+  }
+}
+
+.discover-cards.flip .cards-container .card.gallery--item {
+  margin: 0;
+  padding: 0;
 }
 .discover-cards .center-title {
     text-align: center;
@@ -128,6 +170,13 @@ main .section .discover-cards {
     }
 }
 
+@media (min-width: 1000px) {
+  .discover-cards.flip h1,
+  .discover-cards.flip h2 {
+    font-size: var(--heading-font-size-l);
+  }
+}
+
 @media (min-width: 1680px) {
     .discover-cards .cards-container {
         margin: auto;
@@ -140,16 +189,24 @@ main .section .discover-cards {
     }
 }
 
-.gallery {
-    display: flex;
-    flex-wrap: nowrap;
-    gap: 16px;
-    overflow-x: scroll;
-    scrollbar-width: none;
-    scroll-snap-type: x mandatory;
-    scroll-behavior: smooth;
-    padding: 0 16px;
-    scroll-padding: 0 16px;
+.gallery,
+.discover-cards.flip .gallery {
+  display: flex;
+  flex-wrap: nowrap;
+  gap: 16px;
+  overflow-x: scroll;
+  scrollbar-width: none;
+  scroll-snap-type: x mandatory;
+  scroll-behavior: smooth;
+  padding: 0 16px;
+  scroll-padding: 0 16px;
+}
+
+.discover-cards.flip .gallery {
+  display: grid;
+  grid-auto-flow: column;
+  grid-auto-columns: 250px;
+  justify-content: center;
 }
 
 .gallery::-webkit-scrollbar {
@@ -231,3 +288,108 @@ main .section .discover-cards {
     width: 30px;
     background-color: #686DF4;
 }
+
+.discover-cards.flip .card {
+  background-color: transparent;
+  min-width: initial;
+  width: 250px;
+  max-width: initial;
+  height: 380px;
+  max-height: 380px;
+  cursor: pointer;
+}
+
+.discover-cards.flip .flip-card-inner {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  text-align: center;
+  transition: transform 350ms ease-out;
+  transform-style: preserve-3d;
+}
+
+.discover-cards.flip .card.is-flipped .flip-card-inner {
+  transform: rotateY(180deg);
+}
+
+.discover-cards.flip .flip-card-front, 
+.discover-cards.flip .flip-card-back {
+  font-size: 22px;
+  text-align: left;
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  -webkit-backface-visibility: hidden;
+  backface-visibility: hidden;
+  background: linear-gradient(180deg, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.6));
+  border-radius: 16px;
+  padding: 32px;
+  box-sizing: border-box;
+}
+.discover-cards.flip .flip-card-back {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  padding: 0;
+  transform: rotateY(180deg);
+}
+
+.discover-cards.flip .flip-card-back .scrollable-content {
+  position: absolute;
+  top: 24px;
+  left: 16px;
+  right: 16px;
+  bottom: 48px;
+  overflow-y: auto;
+  padding: 0;
+  line-height: 1.3;
+  font-size: 16px;
+}
+
+.discover-cards.flip .flip-card-back .scrollable-content::-webkit-scrollbar {
+  display: none;
+}
+
+.discover-cards.flip .flip-card-front img {
+  width: 200px;
+  height: 200px;
+}
+
+.discover-cards.flip .plus-icon-wrapper,
+.discover-cards.flip .minus-icon-wrapper {
+  position: absolute;
+  bottom: 16px;
+  right: 16px;
+  width: 24px;
+  height: 24px;
+  background-color: black;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.discover-cards.flip .plus-icon-wrapper img,
+.discover-cards.flip .minus-icon-wrapper img {
+  width: 12px;
+  height: 12px;
+}
+
+.discover-cards.flip .minus-icon-wrapper {
+  position: absolute;
+  bottom: 16px;
+  right: 16px;
+  width: 24px;
+  height: 24px;
+  background-color: black;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1;
+}
+
+.discover-cards.flip .cards-container.gallery::-webkit-scrollbar {
+  display: none;
+}
+
diff --git a/express/code/blocks/discover-cards/discover-cards.js b/express/code/blocks/discover-cards/discover-cards.js
index d9aa91918..46bcd8eef 100644
--- a/express/code/blocks/discover-cards/discover-cards.js
+++ b/express/code/blocks/discover-cards/discover-cards.js
@@ -1,4 +1,4 @@
-import { getLibs, yieldToMain, decorateButtonsDeprecated } from '../../scripts/utils.js';
+import { getLibs, yieldToMain, decorateButtonsDeprecated, getIconElementDeprecated } from '../../scripts/utils.js';
 import { debounce, throttle } from '../../scripts/utils/hofs.js';
 
 let createTag;
@@ -113,27 +113,89 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverFlipCards = block.classList.contains('flip');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
+
   const cardParagraphs = [[]];
-  cards.forEach((card) => {
+  cards.forEach((card, index) => {
     card.classList.add('card');
 
-    cardsWrapper.appendChild(card);
+    card.setAttribute('aria-setsize', numCards);
+    card.setAttribute('aria-posinset', index + 1);
+
     const cardDivs = [...card.children];
 
     cardDivs.forEach((element) => {
+      const img = element.querySelector('picture img');
+      if (isDiscoverFlipCards) {
+        if (img) {
+          card.cardImage = img;
+        } else {
+          const [titleDiv, detailsDiv] = element.children;
+          if (titleDiv && detailsDiv) {
+            card.cardTitle = titleDiv.textContent.trim();
+            card.cardDetails = detailsDiv.textContent.trim();
+          }
+        }
+
+        if (card.cardImage && card.cardTitle && card.cardDetails) {
+          const flipCardInner = createTag('div', { class: 'flip-card-inner' });
+          const frontFace = createTag('div', { class: 'flip-card-front' });
+          const backFace = createTag('div', { class: 'flip-card-back' });
+
+          card.setAttribute('tabindex', '0');
+          card.setAttribute('role', 'button');
+          card.setAttribute('aria-label', `Learn more about ${card.cardTitle}`);
+
+          const plusIconWrapper = createTag('div', { class: 'plus-icon-wrapper' });
+          plusIconWrapper.append(getIconElementDeprecated('plus-icon'));
+          frontFace.append(card.cardImage, card.cardTitle, plusIconWrapper);
+
+          const scrollableContent = createTag('div', { class: 'scrollable-content' });
+          scrollableContent.textContent = card.cardDetails;
+          const minusIconWrapper = createTag('div', { class: 'minus-icon-wrapper' });
+          minusIconWrapper.append(getIconElementDeprecated('minus-icon'));
+          backFace.append(scrollableContent, minusIconWrapper);
+
+          flipCardInner.append(frontFace, backFace);
+          card.replaceChildren(flipCardInner);
+
+          card.addEventListener('click', () => {
+            card.classList.toggle('is-flipped');
+            const isFlipped = card.classList.contains('is-flipped');
+            card.setAttribute(
+              'aria-label',
+              isFlipped ? `Go back to ${card.cardTitle}` : `Learn more about ${card.cardTitle}`,
+            );
+          });
+
+          card.addEventListener('keydown', (e) => {
+            if (e.key === 'Enter' || e.key === ' ') {
+              e.preventDefault();
+              card.click();
+            }
+          });
+        }
+        return;
+      }
+
       const textHeader = element.querySelector('h4');
       const textBody = element.querySelector('p');
       if (textHeader && textBody) {
@@ -143,13 +205,15 @@ export default async function decorate(block) {
         cardParagraphs[0].push(element);
       }
 
-      element.querySelector('picture img')?.classList.add('short');
+      img?.classList.add('short');
       if (element.tagName === 'H2') {
         element.classList.add('card-title');
       } else if (element.querySelector('a.button')) {
         element.classList.add('cta-section');
       }
     });
+
+    cardsWrapper.appendChild(card);
   });
 
   block.appendChild(cardsWrapper);
diff --git a/express/code/icons/minus-icon.svg b/express/code/icons/minus-icon.svg
new file mode 100644
index 000000000..102c5be20
--- /dev/null
+++ b/express/code/icons/minus-icon.svg
@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <circle cx="8" cy="8" r="8" fill="black"/>
+  <path d="M4 8H12" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
+</svg> 
diff --git a/express/code/icons/plus-circle-icon.svg b/express/code/icons/plus-circle-icon.svg
new file mode 100644
index 000000000..b5a9350a4
--- /dev/null
+++ b/express/code/icons/plus-circle-icon.svg
@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
+  <circle cx="8" cy="8" r="8" fill="black"/>
+  <path d="M8 4V12M4 8H12" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
+</svg> 


Reviews:
user: login: fullcolorcoder
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-12T22:02:30Z
user: login: echen-adobe
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-12T22:06:55Z
user: login: JingleH
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T22:13:06Z
user: login: jsandland
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T22:21:19Z
user: login: JingleH
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T22:29:14Z
user: login: jsandland
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T22:34:28Z
user: login: jsandland
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T22:35:35Z
user: login: jsandland
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T22:37:35Z
user: login: jsandland
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T22:40:09Z
user: login: jsandland
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T22:40:15Z
user: login: JingleH
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T22:42:51Z
user: login: fullcolorcoder
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T14:30:57Z
user: login: jsandland
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T17:47:33Z
user: login: JingleH
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-13T19:11:26Z

Review comments:
diff_hunk: @@ -37,7 +37,49 @@ main .section .discover-cards {
     overflow: hidden;
     padding: 16px;
     margin: 9px -7px 10px auto;
+}
+.discover-cards.more {
+  height: 550px;
+}
+.discover-cards.more .cards-container .card {
+  background: unset;
+  border: none;
+}
+
+.discover-cards.more h1, 
+.discover-cards.more h2 {
+  font-size: 22px;
path: express/code/blocks/discover-cards/discover-cards.css
user: login: JingleH
body: use variables --heading-font-size-s
created_at: 2025-03-12T22:00:58Z
author_association: COLLABORATOR
diff_hunk: @@ -140,16 +189,24 @@ main .section .discover-cards {
     }
 }
 
-.gallery {
-    display: flex;
-    flex-wrap: nowrap;
-    gap: 16px;
-    overflow-x: scroll;
-    scrollbar-width: none;
-    scroll-snap-type: x mandatory;
-    scroll-behavior: smooth;
-    padding: 0 16px;
-    scroll-padding: 0 16px;
+.gallery,
path: express/code/blocks/discover-cards/discover-cards.css
user: login: JingleH
body: this might affect other galleries on the same page right? Maybe this line is not wanted?
created_at: 2025-03-12T22:02:32Z
author_association: COLLABORATOR
diff_hunk: @@ -231,3 +288,108 @@ main .section .discover-cards {
     width: 30px;
     background-color: #686DF4;
 }
+
+.discover-cards.more .card {
+  background-color: transparent;
+  min-width: 250px;
+  width: 250px;
path: express/code/blocks/discover-cards/discover-cards.css
user: login: JingleH
body: do you have cases where both need to be defined to be the same values?
created_at: 2025-03-12T22:03:22Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
path: express/code/blocks/discover-cards/discover-cards.js
user: login: JingleH
body: I feel like this variant shouldn't be called "more" which is too generic. Maybe "flip" would be a better variant name
created_at: 2025-03-12T22:04:27Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
+
   const cardParagraphs = [[]];
-  cards.forEach((card) => {
+  cards.forEach((card, index) => {
     card.classList.add('card');
 
-    cardsWrapper.appendChild(card);
+    card.setAttribute('aria-setsize', numCards);
+    card.setAttribute('aria-posinset', index + 1);
+
     const cardDivs = [...card.children];
 
     cardDivs.forEach((element) => {
+      const isImg = element.querySelector('picture img');
+      if (isDiscoverMoreCards) {
+        if (isImg) {
+          card.cardImage = isImg;
+        } else {
+          const [titleDiv, detailsDiv] = element.children;
+          if (titleDiv && detailsDiv) {
+            card.cardTitle = titleDiv.textContent.trim();
+            card.cardDetails = detailsDiv.textContent.trim();
+          }
+        }
+
+        if (card.cardImage && card.cardTitle && card.cardDetails) {
+          const flipCardInner = createTag('div', { class: 'flip-card-inner' });
+          const frontFace = createTag('div', { class: 'flip-card-front' });
+          const backFace = createTag('div', { class: 'flip-card-back' });
+
+          card.setAttribute('tabindex', '0');
+          card.setAttribute('role', 'button');
+          card.setAttribute('aria-label', `Learn more about ${card.cardTitle}`);
+
+          const plusIconWrapper = createTag('div', { class: 'plus-icon-wrapper' });
+          plusIconWrapper.append(getIconElementDeprecated('plus-icon'));
+          frontFace.append(card.cardImage, card.cardTitle, plusIconWrapper);
+
+          const scrollableContent = createTag('div', { class: 'scrollable-content' });
+          scrollableContent.textContent = card.cardDetails;
+          const minusIconWrapper = createTag('div', { class: 'minus-icon-wrapper' });
+          minusIconWrapper.append(getIconElementDeprecated('minus-icon'));
+          backFace.append(scrollableContent, minusIconWrapper);
+
+          flipCardInner.append(frontFace, backFace);
+          card.innerHTML = '';
+          card.append(flipCardInner);
+
+          card.addEventListener('click', () => {
+            card.classList.toggle('is-flipped');
+            const isFlipped = card.classList.contains('is-flipped');
+            card.setAttribute(
+              'aria-label',
+              isFlipped ? `Go back to ${card.cardTitle}` : `Learn more about ${card.cardTitle}`,
path: express/code/blocks/discover-cards/discover-cards.js
user: login: JingleH
body: do these need to be authorable? What about localization?
created_at: 2025-03-12T22:05:47Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
+
   const cardParagraphs = [[]];
-  cards.forEach((card) => {
+  cards.forEach((card, index) => {
     card.classList.add('card');
 
-    cardsWrapper.appendChild(card);
+    card.setAttribute('aria-setsize', numCards);
+    card.setAttribute('aria-posinset', index + 1);
+
     const cardDivs = [...card.children];
 
     cardDivs.forEach((element) => {
+      const isImg = element.querySelector('picture img');
path: express/code/blocks/discover-cards/discover-cards.js
user: login: JingleH
body: nit: usually variable names starting with "is" are boolean values, which can be confusing in your case. Maybe just call it img
created_at: 2025-03-12T22:06:53Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
+
   const cardParagraphs = [[]];
-  cards.forEach((card) => {
+  cards.forEach((card, index) => {
     card.classList.add('card');
 
-    cardsWrapper.appendChild(card);
+    card.setAttribute('aria-setsize', numCards);
+    card.setAttribute('aria-posinset', index + 1);
+
     const cardDivs = [...card.children];
 
     cardDivs.forEach((element) => {
+      const isImg = element.querySelector('picture img');
+      if (isDiscoverMoreCards) {
+        if (isImg) {
+          card.cardImage = isImg;
+        } else {
+          const [titleDiv, detailsDiv] = element.children;
+          if (titleDiv && detailsDiv) {
+            card.cardTitle = titleDiv.textContent.trim();
+            card.cardDetails = detailsDiv.textContent.trim();
+          }
+        }
+
+        if (card.cardImage && card.cardTitle && card.cardDetails) {
+          const flipCardInner = createTag('div', { class: 'flip-card-inner' });
+          const frontFace = createTag('div', { class: 'flip-card-front' });
+          const backFace = createTag('div', { class: 'flip-card-back' });
+
+          card.setAttribute('tabindex', '0');
+          card.setAttribute('role', 'button');
+          card.setAttribute('aria-label', `Learn more about ${card.cardTitle}`);
path: express/code/blocks/discover-cards/discover-cards.js
user: login: JingleH
body: same as below: do these need to be authorable? What about localization?
created_at: 2025-03-12T22:07:07Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
+
   const cardParagraphs = [[]];
-  cards.forEach((card) => {
+  cards.forEach((card, index) => {
     card.classList.add('card');
 
-    cardsWrapper.appendChild(card);
+    card.setAttribute('aria-setsize', numCards);
+    card.setAttribute('aria-posinset', index + 1);
+
     const cardDivs = [...card.children];
 
     cardDivs.forEach((element) => {
+      const isImg = element.querySelector('picture img');
+      if (isDiscoverMoreCards) {
+        if (isImg) {
+          card.cardImage = isImg;
+        } else {
+          const [titleDiv, detailsDiv] = element.children;
+          if (titleDiv && detailsDiv) {
+            card.cardTitle = titleDiv.textContent.trim();
+            card.cardDetails = detailsDiv.textContent.trim();
+          }
+        }
+
+        if (card.cardImage && card.cardTitle && card.cardDetails) {
+          const flipCardInner = createTag('div', { class: 'flip-card-inner' });
+          const frontFace = createTag('div', { class: 'flip-card-front' });
+          const backFace = createTag('div', { class: 'flip-card-back' });
+
+          card.setAttribute('tabindex', '0');
+          card.setAttribute('role', 'button');
+          card.setAttribute('aria-label', `Learn more about ${card.cardTitle}`);
+
+          const plusIconWrapper = createTag('div', { class: 'plus-icon-wrapper' });
+          plusIconWrapper.append(getIconElementDeprecated('plus-icon'));
+          frontFace.append(card.cardImage, card.cardTitle, plusIconWrapper);
+
+          const scrollableContent = createTag('div', { class: 'scrollable-content' });
+          scrollableContent.textContent = card.cardDetails;
+          const minusIconWrapper = createTag('div', { class: 'minus-icon-wrapper' });
+          minusIconWrapper.append(getIconElementDeprecated('minus-icon'));
+          backFace.append(scrollableContent, minusIconWrapper);
+
+          flipCardInner.append(frontFace, backFace);
+          card.innerHTML = '';
+          card.append(flipCardInner);
path: express/code/blocks/discover-cards/discover-cards.js
user: login: JingleH
body: these 2 lines could potentially be condensed into card.replaceWith(flipCardInner)
created_at: 2025-03-12T22:09:00Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
path: express/code/blocks/discover-cards/discover-cards.js
user: login: JingleH
body: same as below: do these need to be authorable? What about localization?
created_at: 2025-03-12T22:11:28Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
path: express/code/blocks/discover-cards/discover-cards.js
user: login: JingleH
body: same as below: do these need to be authorable? What about localization?
created_at: 2025-03-12T22:11:38Z
author_association: COLLABORATOR
diff_hunk: @@ -128,6 +170,13 @@ main .section .discover-cards {
     }
 }
 
+@media (min-width: 1000px) {
+  .discover-cards.more h1,
+  .discover-cards.more h2 {
+    font-size: 36px;
path: express/code/blocks/discover-cards/discover-cards.css
user: login: JingleH
body: same as above: use --heading-font-size-l
created_at: 2025-03-12T22:12:57Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
+
   const cardParagraphs = [[]];
-  cards.forEach((card) => {
+  cards.forEach((card, index) => {
     card.classList.add('card');
 
-    cardsWrapper.appendChild(card);
+    card.setAttribute('aria-setsize', numCards);
+    card.setAttribute('aria-posinset', index + 1);
+
     const cardDivs = [...card.children];
 
     cardDivs.forEach((element) => {
+      const isImg = element.querySelector('picture img');
+      if (isDiscoverMoreCards) {
+        if (isImg) {
+          card.cardImage = isImg;
+        } else {
+          const [titleDiv, detailsDiv] = element.children;
+          if (titleDiv && detailsDiv) {
+            card.cardTitle = titleDiv.textContent.trim();
+            card.cardDetails = detailsDiv.textContent.trim();
+          }
+        }
+
+        if (card.cardImage && card.cardTitle && card.cardDetails) {
+          const flipCardInner = createTag('div', { class: 'flip-card-inner' });
+          const frontFace = createTag('div', { class: 'flip-card-front' });
+          const backFace = createTag('div', { class: 'flip-card-back' });
+
+          card.setAttribute('tabindex', '0');
+          card.setAttribute('role', 'button');
+          card.setAttribute('aria-label', `Learn more about ${card.cardTitle}`);
+
+          const plusIconWrapper = createTag('div', { class: 'plus-icon-wrapper' });
+          plusIconWrapper.append(getIconElementDeprecated('plus-icon'));
+          frontFace.append(card.cardImage, card.cardTitle, plusIconWrapper);
+
+          const scrollableContent = createTag('div', { class: 'scrollable-content' });
+          scrollableContent.textContent = card.cardDetails;
+          const minusIconWrapper = createTag('div', { class: 'minus-icon-wrapper' });
+          minusIconWrapper.append(getIconElementDeprecated('minus-icon'));
+          backFace.append(scrollableContent, minusIconWrapper);
+
+          flipCardInner.append(frontFace, backFace);
+          card.innerHTML = '';
+          card.append(flipCardInner);
+
+          card.addEventListener('click', () => {
+            card.classList.toggle('is-flipped');
+            const isFlipped = card.classList.contains('is-flipped');
+            card.setAttribute(
+              'aria-label',
+              isFlipped ? `Go back to ${card.cardTitle}` : `Learn more about ${card.cardTitle}`,
path: express/code/blocks/discover-cards/discover-cards.js
user: login: jsandland
body: No authoring requirements were given for these.
created_at: 2025-03-12T22:21:19Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
+
   const cardParagraphs = [[]];
-  cards.forEach((card) => {
+  cards.forEach((card, index) => {
     card.classList.add('card');
 
-    cardsWrapper.appendChild(card);
+    card.setAttribute('aria-setsize', numCards);
+    card.setAttribute('aria-posinset', index + 1);
+
     const cardDivs = [...card.children];
 
     cardDivs.forEach((element) => {
+      const isImg = element.querySelector('picture img');
+      if (isDiscoverMoreCards) {
+        if (isImg) {
+          card.cardImage = isImg;
+        } else {
+          const [titleDiv, detailsDiv] = element.children;
+          if (titleDiv && detailsDiv) {
+            card.cardTitle = titleDiv.textContent.trim();
+            card.cardDetails = detailsDiv.textContent.trim();
+          }
+        }
+
+        if (card.cardImage && card.cardTitle && card.cardDetails) {
+          const flipCardInner = createTag('div', { class: 'flip-card-inner' });
+          const frontFace = createTag('div', { class: 'flip-card-front' });
+          const backFace = createTag('div', { class: 'flip-card-back' });
+
+          card.setAttribute('tabindex', '0');
+          card.setAttribute('role', 'button');
+          card.setAttribute('aria-label', `Learn more about ${card.cardTitle}`);
+
+          const plusIconWrapper = createTag('div', { class: 'plus-icon-wrapper' });
+          plusIconWrapper.append(getIconElementDeprecated('plus-icon'));
+          frontFace.append(card.cardImage, card.cardTitle, plusIconWrapper);
+
+          const scrollableContent = createTag('div', { class: 'scrollable-content' });
+          scrollableContent.textContent = card.cardDetails;
+          const minusIconWrapper = createTag('div', { class: 'minus-icon-wrapper' });
+          minusIconWrapper.append(getIconElementDeprecated('minus-icon'));
+          backFace.append(scrollableContent, minusIconWrapper);
+
+          flipCardInner.append(frontFace, backFace);
+          card.innerHTML = '';
+          card.append(flipCardInner);
+
+          card.addEventListener('click', () => {
+            card.classList.toggle('is-flipped');
+            const isFlipped = card.classList.contains('is-flipped');
+            card.setAttribute(
+              'aria-label',
+              isFlipped ? `Go back to ${card.cardTitle}` : `Learn more about ${card.cardTitle}`,
path: express/code/blocks/discover-cards/discover-cards.js
user: login: JingleH
body: could you check with the designers and authors? Because technically as an accessibility practice, those labels should be in different languages when deployed internationally. Otherwise users will only hear about those labels in the hardcoded English values.
But it can be a follow up too, if it's going to be deployed on EN pages first.
@fullcolorcoder are you familiar with how Milo supports authoring for this?
created_at: 2025-03-12T22:29:14Z
author_association: COLLABORATOR
diff_hunk: @@ -140,16 +189,24 @@ main .section .discover-cards {
     }
 }
 
-.gallery {
-    display: flex;
-    flex-wrap: nowrap;
-    gap: 16px;
-    overflow-x: scroll;
-    scrollbar-width: none;
-    scroll-snap-type: x mandatory;
-    scroll-behavior: smooth;
-    padding: 0 16px;
-    scroll-padding: 0 16px;
+.gallery,
path: express/code/blocks/discover-cards/discover-cards.css
user: login: jsandland
body: these are the default rules that existed for the gallery - in order to avoid duplicating rules - I added the variant rule to all the rules that are identical
created_at: 2025-03-12T22:34:28Z
author_association: COLLABORATOR
diff_hunk: @@ -231,3 +288,108 @@ main .section .discover-cards {
     width: 30px;
     background-color: #686DF4;
 }
+
+.discover-cards.more .card {
+  background-color: transparent;
+  min-width: 250px;
+  width: 250px;
path: express/code/blocks/discover-cards/discover-cards.css
user: login: jsandland
body: If I don't include min-width- the cards collapse when the view gets small - these rules ensure no matter how wide the viewport - the cards are always the same size
created_at: 2025-03-12T22:35:35Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
+
   const cardParagraphs = [[]];
-  cards.forEach((card) => {
+  cards.forEach((card, index) => {
     card.classList.add('card');
 
-    cardsWrapper.appendChild(card);
+    card.setAttribute('aria-setsize', numCards);
+    card.setAttribute('aria-posinset', index + 1);
+
     const cardDivs = [...card.children];
 
     cardDivs.forEach((element) => {
+      const isImg = element.querySelector('picture img');
+      if (isDiscoverMoreCards) {
+        if (isImg) {
+          card.cardImage = isImg;
+        } else {
+          const [titleDiv, detailsDiv] = element.children;
+          if (titleDiv && detailsDiv) {
+            card.cardTitle = titleDiv.textContent.trim();
+            card.cardDetails = detailsDiv.textContent.trim();
+          }
+        }
+
+        if (card.cardImage && card.cardTitle && card.cardDetails) {
+          const flipCardInner = createTag('div', { class: 'flip-card-inner' });
+          const frontFace = createTag('div', { class: 'flip-card-front' });
+          const backFace = createTag('div', { class: 'flip-card-back' });
+
+          card.setAttribute('tabindex', '0');
+          card.setAttribute('role', 'button');
+          card.setAttribute('aria-label', `Learn more about ${card.cardTitle}`);
path: express/code/blocks/discover-cards/discover-cards.js
user: login: jsandland
body: I have reached out to design but no heard back - can do a fast follow in a future pr
created_at: 2025-03-12T22:37:35Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
path: express/code/blocks/discover-cards/discover-cards.js
user: login: jsandland
body: will do a fast follow
created_at: 2025-03-12T22:40:09Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
path: express/code/blocks/discover-cards/discover-cards.js
user: login: jsandland
body: will do a fast follow
created_at: 2025-03-12T22:40:15Z
author_association: COLLABORATOR
diff_hunk: @@ -231,3 +288,108 @@ main .section .discover-cards {
     width: 30px;
     background-color: #686DF4;
 }
+
+.discover-cards.more .card {
+  background-color: transparent;
+  min-width: 250px;
+  width: 250px;
path: express/code/blocks/discover-cards/discover-cards.css
user: login: JingleH
body: i see, there are conflicting rules for `
.discover-cards .cards-container .card
`

I would recommend making them `min-width: initial` to be clearer that you're intentionally overriding other rules tho
created_at: 2025-03-12T22:42:50Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
+
   const cardParagraphs = [[]];
-  cards.forEach((card) => {
+  cards.forEach((card, index) => {
     card.classList.add('card');
 
-    cardsWrapper.appendChild(card);
+    card.setAttribute('aria-setsize', numCards);
+    card.setAttribute('aria-posinset', index + 1);
+
     const cardDivs = [...card.children];
 
     cardDivs.forEach((element) => {
+      const isImg = element.querySelector('picture img');
+      if (isDiscoverMoreCards) {
+        if (isImg) {
+          card.cardImage = isImg;
+        } else {
+          const [titleDiv, detailsDiv] = element.children;
+          if (titleDiv && detailsDiv) {
+            card.cardTitle = titleDiv.textContent.trim();
+            card.cardDetails = detailsDiv.textContent.trim();
+          }
+        }
+
+        if (card.cardImage && card.cardTitle && card.cardDetails) {
+          const flipCardInner = createTag('div', { class: 'flip-card-inner' });
+          const frontFace = createTag('div', { class: 'flip-card-front' });
+          const backFace = createTag('div', { class: 'flip-card-back' });
+
+          card.setAttribute('tabindex', '0');
+          card.setAttribute('role', 'button');
+          card.setAttribute('aria-label', `Learn more about ${card.cardTitle}`);
+
+          const plusIconWrapper = createTag('div', { class: 'plus-icon-wrapper' });
+          plusIconWrapper.append(getIconElementDeprecated('plus-icon'));
+          frontFace.append(card.cardImage, card.cardTitle, plusIconWrapper);
+
+          const scrollableContent = createTag('div', { class: 'scrollable-content' });
+          scrollableContent.textContent = card.cardDetails;
+          const minusIconWrapper = createTag('div', { class: 'minus-icon-wrapper' });
+          minusIconWrapper.append(getIconElementDeprecated('minus-icon'));
+          backFace.append(scrollableContent, minusIconWrapper);
+
+          flipCardInner.append(frontFace, backFace);
+          card.innerHTML = '';
+          card.append(flipCardInner);
+
+          card.addEventListener('click', () => {
+            card.classList.toggle('is-flipped');
+            const isFlipped = card.classList.contains('is-flipped');
+            card.setAttribute(
+              'aria-label',
+              isFlipped ? `Go back to ${card.cardTitle}` : `Learn more about ${card.cardTitle}`,
path: express/code/blocks/discover-cards/discover-cards.js
user: login: fullcolorcoder
body: @jsandland @JingleH Placeholders can handle this, and authoring is generally aware of that but if it can be avoided I'd favor that and I suspect they would too if given the option. Since this isn't just a test/jdi, I'd pair up with Earnest and come to an agreement since authoring efforts would be duplicated if we change the approach as a fast follow.
created_at: 2025-03-13T14:30:57Z
author_association: COLLABORATOR
diff_hunk: @@ -113,27 +113,90 @@ export async function buildGallery(
 }
 
 export default async function decorate(block) {
+  const isDiscoverMoreCards = block.classList.contains('more');
   await Promise.all([import(`${getLibs()}/utils/utils.js`), decorateButtonsDeprecated(block)]).then(([utils]) => {
     ({ createTag } = utils);
   });
   const firstChild = block.querySelector(':scope > div:first-child');
 
-  if (firstChild && firstChild.querySelector('h3')) {
+  if (firstChild && firstChild.querySelector('h1, h2, h3')) {
     firstChild.classList.add('center-title');
+    const header = firstChild.querySelector('h1, h2, h3');
+    header.setAttribute('aria-label', `${header.textContent.trim()} cards`);
     block.insertBefore(firstChild, block.firstChild);
   }
 
   const cardsWrapper = createTag('div', { class: 'cards-container' });
+  cardsWrapper.setAttribute('role', 'region');
 
   const cards = block.querySelectorAll(':scope > div:not(:first-child)');
+  const numCards = cards.length;
+  cardsWrapper.setAttribute('aria-label', `${numCards} cards in this section`);
+
   const cardParagraphs = [[]];
-  cards.forEach((card) => {
+  cards.forEach((card, index) => {
     card.classList.add('card');
 
-    cardsWrapper.appendChild(card);
+    card.setAttribute('aria-setsize', numCards);
+    card.setAttribute('aria-posinset', index + 1);
+
     const cardDivs = [...card.children];
 
     cardDivs.forEach((element) => {
+      const isImg = element.querySelector('picture img');
+      if (isDiscoverMoreCards) {
+        if (isImg) {
+          card.cardImage = isImg;
+        } else {
+          const [titleDiv, detailsDiv] = element.children;
+          if (titleDiv && detailsDiv) {
+            card.cardTitle = titleDiv.textContent.trim();
+            card.cardDetails = detailsDiv.textContent.trim();
+          }
+        }
+
+        if (card.cardImage && card.cardTitle && card.cardDetails) {
+          const flipCardInner = createTag('div', { class: 'flip-card-inner' });
+          const frontFace = createTag('div', { class: 'flip-card-front' });
+          const backFace = createTag('div', { class: 'flip-card-back' });
+
+          card.setAttribute('tabindex', '0');
+          card.setAttribute('role', 'button');
+          card.setAttribute('aria-label', `Learn more about ${card.cardTitle}`);
+
+          const plusIconWrapper = createTag('div', { class: 'plus-icon-wrapper' });
+          plusIconWrapper.append(getIconElementDeprecated('plus-icon'));
+          frontFace.append(card.cardImage, card.cardTitle, plusIconWrapper);
+
+          const scrollableContent = createTag('div', { class: 'scrollable-content' });
+          scrollableContent.textContent = card.cardDetails;
+          const minusIconWrapper = createTag('div', { class: 'minus-icon-wrapper' });
+          minusIconWrapper.append(getIconElementDeprecated('minus-icon'));
+          backFace.append(scrollableContent, minusIconWrapper);
+
+          flipCardInner.append(frontFace, backFace);
+          card.innerHTML = '';
+          card.append(flipCardInner);
+
+          card.addEventListener('click', () => {
+            card.classList.toggle('is-flipped');
+            const isFlipped = card.classList.contains('is-flipped');
+            card.setAttribute(
+              'aria-label',
+              isFlipped ? `Go back to ${card.cardTitle}` : `Learn more about ${card.cardTitle}`,
path: express/code/blocks/discover-cards/discover-cards.js
user: login: jsandland
body: @JingleH @fullcolorcoder I've chatted with Ernest and he thinks this may warrant planning for how to localize / make authorable the screen reader text that is in english only for these changes  That being said he said that at the moment this shouldn't be a blocker for this variant.
created_at: 2025-03-13T17:47:33Z
author_association: COLLABORATOR

PR comments:
user: login: aem-code-sync[bot]
created_at: 2025-03-12T21:54:09Z
body: <!-- aem-bot-dash -->
Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch and validate page speed.
In case there are problems, just click a checkbox below to rerun the respective action.

- [ ] <!-- action-psi --> Re-run PSI checks
- [ ] <!-- action-sync --> Re-sync branch

<details>
<summary>Commits</summary>
<!-- commit-history -->

* [eb898eb](https://github.com/adobecom/express-milo/commit/eb898eb699d72ca1d1efb63651a2b981c5e38214) :white_check_mark: (latest)
* [15ca064](https://github.com/adobecom/express-milo/commit/15ca064400a38e628cdcda26b2f0930320dfbe86) :white_check_mark:
* [c45dbcf](https://github.com/adobecom/express-milo/commit/c45dbcf54d662b69eaa28b6ad9cba67014e77eec) :white_check_mark:
* [bf7ca0d](https://github.com/adobecom/express-milo/commit/bf7ca0d62d0728f2e9f23eb0d94df86307456649) :white_check_mark:
* [09049a9](https://github.com/adobecom/express-milo/commit/09049a9e1a4de3f0c03d75d080933a4d67123ab3) :white_check_mark:
* [c57392b](https://github.com/adobecom/express-milo/commit/c57392becef58559c84ae22626661d627f8d06ed) :white_check_mark:
* [ca5c68a](https://github.com/adobecom/express-milo/commit/ca5c68a16d98ac6772855e7588900cdc0ff4203e) :white_check_mark:
* [36d5a45](https://github.com/adobecom/express-milo/commit/36d5a45059217c51f686fab9abc3fc967641417f) :white_check_mark:
* [f465eb9](https://github.com/adobecom/express-milo/commit/f465eb9513784ba0edf65aac27bc8c47c9764c87) :white_check_mark:
* [60ee1e4](https://github.com/adobecom/express-milo/commit/60ee1e4fa80249e21ec2da69a5302184ac43f8f6) :white_check_mark:
* [263d1f0](https://github.com/adobecom/express-milo/commit/263d1f0d859e4257d868aa9bfaaf7fd7156b6e5b) :white_check_mark:
* [fd1ac47](https://github.com/adobecom/express-milo/commit/fd1ac47862a34e3f85d208696640cb75c990c1ea) :white_check_mark:
* [7101164](https://github.com/adobecom/express-milo/commit/7101164e3fff7be5944a8fd9be54dcdfae1205fa) :white_check_mark:
* [53dc451](https://github.com/adobecom/express-milo/commit/53dc451ad7a2ba20c7fd3ff54846f93ad6113435) :white_check_mark:
* [7004b60](https://github.com/adobecom/express-milo/commit/7004b60a036a1df0b86c3abaeb444ee3492fc8fe) :white_check_mark:
* [5e06c8c](https://github.com/adobecom/express-milo/commit/5e06c8c3bebf952d8cbaaceeaa97e402b8fd107d) :white_check_mark:
* [b367f29](https://github.com/adobecom/express-milo/commit/b367f2953aa8b2b18b45f8b78bbb96f3992c8c05) :white_check_mark:
* [c124e2e](https://github.com/adobecom/express-milo/commit/c124e2e35fc78fcfa6f709668e4dcaf32cea43c4) :white_check_mark:
* [2306f06](https://github.com/adobecom/express-milo/commit/2306f060571f18196676eb936194df3f402dd1bb) :white_check_mark:
* [887bc3f](https://github.com/adobecom/express-milo/commit/887bc3fbaa37661bfb00cc67cb3c8ec397c9fb21) :white_check_mark:
* [7be2206](https://github.com/adobecom/express-milo/commit/7be22060995862182b026a81341a9bc01ef0cb84) :white_check_mark:
* [6f185d1](https://github.com/adobecom/express-milo/commit/6f185d1b2a1f6908f25cd1f3494cc8adbbf8fc4e) :white_check_mark:
* [f50df92](https://github.com/adobecom/express-milo/commit/f50df92051e84e371690a7fd4ccb71404e3904d6) :x:
* [cc7f13e](https://github.com/adobecom/express-milo/commit/cc7f13e2045a03fed819cf9b2603c6ba97821baa) :x:
* [ed0553a](https://github.com/adobecom/express-milo/commit/ed0553a2cb4720f06cf16312716d8ce182bfd850) :x:
* [b6f944a](https://github.com/adobecom/express-milo/commit/b6f944a1a5cd721c7aa00d2e8c524598d6187c1e) :x:
* [f2ce602](https://github.com/adobecom/express-milo/commit/f2ce60265e94118de26a60739e3b8cb2a837985c) :x:
* [944b389](https://github.com/adobecom/express-milo/commit/944b389d757fc9544e6bd2828347d0f315837804) :x:
* [c7d62d4](https://github.com/adobecom/express-milo/commit/c7d62d4e9d235642442e3520e3f744dd20986a5c) :x:
* [637b4ba](https://github.com/adobecom/express-milo/commit/637b4bae948db4dc4923adfb5e71e33d483728dd) :x:

<!-- commit-history-end -->
</details>
author_association: NONE
user: login: aem-code-sync[bot]
created_at: 2025-03-12T21:54:40Z
body: <!-- aem-bot-psi -->
| | Page | Scores | Audits |
|-|------|--------|--------|
| :iphone: | [/drafts/jsandlan/milo-discover-more-cards](https://milo-discover-more-cards--express-milo--adobecom.aem.page/drafts/jsandlan/milo-discover-more-cards) | [![PERFORMANCE](https://img.shields.io/badge/PERFORMANCE-68-orange?style=for-the-badge "Performance")](https://googlechrome.github.io/lighthouse/scorecalc/#device=mobile&version=12.4.0&SI=10428.143995326232&FCP=1326.3777500000003&LCP=3533.0967500000006&TBT=529&CLS=0.0014502519033141987 "See Calculator") ![A11Y](https://img.shields.io/badge/A11Y-95-green?style=for-the-badge "Accessibility") ![SEO](https://img.shields.io/badge/SEO-58-orange?style=for-the-badge "SEO") ![BEST PRACTICES](https://img.shields.io/badge/BEST%20PRACTICES-71-orange?style=for-the-badge "Best Practices") | ![SI](https://img.shields.io/badge/SI-10.4s-red?style=for-the-badge "Speed Index") ![FCP](https://img.shields.io/badge/FCP-1.3s-green?style=for-the-badge "First Contentful Paint") ![LCP](https://img.shields.io/badge/LCP-3.5s-orange?style=for-the-badge "Largest Contentful Paint") ![TBT](https://img.shields.io/badge/TBT-530ms-orange?style=for-the-badge "Total Blocking Time") ![CLS](https://img.shields.io/badge/CLS-0.001-green?style=for-the-badge "Cumulative Layout Shift") |
| :desktop_computer: | [/drafts/jsandlan/milo-discover-more-cards](https://milo-discover-more-cards--express-milo--adobecom.aem.page/drafts/jsandlan/milo-discover-more-cards) | [![PERFORMANCE](https://img.shields.io/badge/PERFORMANCE-89-orange?style=for-the-badge "Performance")](https://googlechrome.github.io/lighthouse/scorecalc/#device=mobile&version=12.4.0&SI=4215.637808375008&FCP=365.155&LCP=1084.0852499999999&TBT=2.5&CLS=0.006970069505800544 "See Calculator") ![A11Y](https://img.shields.io/badge/A11Y-91-green?style=for-the-badge "Accessibility") ![SEO](https://img.shields.io/badge/SEO-58-orange?style=for-the-badge "SEO") ![BEST PRACTICES](https://img.shields.io/badge/BEST%20PRACTICES-70-orange?style=for-the-badge "Best Practices") | ![SI](https://img.shields.io/badge/SI-4.2s-red?style=for-the-badge "Speed Index") ![FCP](https://img.shields.io/badge/FCP-0.4s-green?style=for-the-badge "First Contentful Paint") ![LCP](https://img.shields.io/badge/LCP-1.1s-green?style=for-the-badge "Largest Contentful Paint") ![TBT](https://img.shields.io/badge/TBT-0ms-green?style=for-the-badge "Total Blocking Time") ![CLS](https://img.shields.io/badge/CLS-0.007-green?style=for-the-badge "Cumulative Layout Shift") |

author_association: NONE
user: login: codecov-commenter
created_at: 2025-03-12T21:55:06Z
body: ## [Codecov](https://app.codecov.io/gh/adobecom/express-milo/pull/248?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=adobecom) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 67.57%. Comparing base [(`1d7887e`)](https://app.codecov.io/gh/adobecom/express-milo/commit/1d7887e4a5772696c9fd0a1c9c97f12be9467775?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=adobecom) to head [(`a444830`)](https://app.codecov.io/gh/adobecom/express-milo/commit/a44483088d47cbe7bb3a581c2f6db9485aa6e519?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=adobecom).
> Report is 95 commits behind head on stage.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##            stage     #248      +/-   ##
==========================================
+ Coverage   67.12%   67.57%   +0.45%     
==========================================
  Files          63       64       +1     
  Lines       10353    10513     +160     
==========================================
+ Hits         6949     7104     +155     
- Misses       3404     3409       +5     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/adobecom/express-milo/pull/248?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=adobecom).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=adobecom).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: JingleH
created_at: 2025-03-13T19:45:35Z
body: followup ticket: https://jira.corp.adobe.com/browse/MWPW-169789
author_association: COLLABORATOR