diff --git a/blocks/cards/_cards.json b/blocks/cards/_cards.json index d21983e..3adba1e 100644 --- a/blocks/cards/_cards.json +++ b/blocks/cards/_cards.json @@ -49,10 +49,6 @@ "name": "All About", "value": "all-about-card" }, - { - "name": "Important Documents", - "value": "important-documents" - }, { "name": "Key Benefits", "value": "key-benefits" @@ -73,10 +69,6 @@ "name": "Earn Rewards", "value": "earn-rewards" }, - { - "name": "Related Search", - "value": "related-search" - }, { "name": "Joining Perks", "value": "joining-perks" @@ -84,10 +76,6 @@ { "name": "Explore Other Cards", "value": "explore-other-cards" - }, - { - "name": "Image and Title", - "value": "image-and-title" } ] }, @@ -208,7 +196,8 @@ { "component": "text", "name": "cardLinkText", - "label": "Card Link Text (optional)" + "label": "Card Link Text (optional)", + "hidden": "true" }, { "component": "richtext", diff --git a/blocks/cards/cards.css b/blocks/cards/cards.css index 00eb337..e997edd 100644 --- a/blocks/cards/cards.css +++ b/blocks/cards/cards.css @@ -19,12 +19,18 @@ /* Note: Do NOT set background-color here - it causes white flash on dark-themed pages. Card variants that need backgrounds should set them explicitly. */ -.cards .grid-cards > .cards-card { +.cards .cards-card { background-color: transparent; + min-height: 427px; } -.cards .cards-card-image { +.cards .benefit-cards .cards-card-image { line-height: 0; + padding: var(--spacing-s); +} + +.cards .cards-card-body { + padding: 0 var(--spacing-s) var(--spacing-l); } .cards .grid-cards > .cards-card img { @@ -33,6 +39,20 @@ border-radius: 6px; } +.cards .benefit-cards .cards-card-image img { + max-height: 80px; + border-radius: 0; +} + +.cards .benefit-cards .cards-card-body p:has(.icon-arrow-right-white) { + position: absolute; + bottom: 12px; + right: 24px; + margin: 0; + opacity: 0.8; +} + + /* BENEFIT CARD STYLING */ /* Mobile-first base styles */ @@ -40,7 +60,7 @@ display: flex; flex-direction: row; align-items: flex-start; - gap: var(--spacing-xxs); + gap: var(--spacing-s); margin-bottom: 0; width: 100%; border-radius: 8px; @@ -50,36 +70,29 @@ transition: all 0.3s ease; } -.cards .benefit-cards .cards-card-image img { - width: 120px; - height: auto; - vertical-align: middle; - border-radius: 4px; - object-fit: cover; -} - .cards .benefit-cards .cards-card-body { - flex: 1; margin: var(--spacing-xs) 0; } .cards .benefit-cards .cards-card-body h3 { - color: var(--text-color); - font-size: var(--body-font-size-s); font-style: normal; - font-weight: 600; - line-height: normal; letter-spacing: -0.3px; - margin: 0 0 var(--spacing-xxs); + color: var(--color-white); + font-weight: 700; + line-height: 1.3; + margin: 0 0 var(--spacing-xs); + background: conic-gradient(from 142deg at 50% 35.29%, #999 0deg, #EFEFEF 48.6deg, #CACACA 104.4deg, #EFEFEF 163.8deg, #CACACA 230.4deg, #999 288deg, #EFEFEF 320.4deg); + background-clip: text; + -webkit-text-fill-color: transparent; } -.cards .benefit-cards .cards-card-body p { - color: var(--text-color); - font-size: var(--body-font-size-xs); +.cards .benefit-cards .cards-card-body p, +.cards .benefit-cards .cards-card-body a { + color: var(--color-white); + font-size: var(--body-font-size-m); font-style: normal; font-weight: 300; line-height: normal; - letter-spacing: -0.262px; margin: 0; } @@ -256,90 +269,11 @@ font-weight: 600; } -/* IMPORTANT DOCUMENTS CARD CONTAINER STYLING */ - -.cards.important-documents { - margin: var(--spacing-s) 0; -} - -/* Hide View All button for important-documents */ -.cards.important-documents .view-toggle { - display: none; -} - /* Hide View All button for earn-rewards */ .cards.earn-rewards .view-toggle { display: none; } -/* Important Documents Cards - Mobile Grid Layout */ -.cards.important-documents .grid-cards { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 16px; - padding: 0; - margin-bottom: var(--spacing-m); - margin-left: auto; - margin-right: auto; -} - -/* Important Documents Card Styling */ -.cards.important-documents .cards-card, -.cards.important-documents .important-documents-card { - display: flex; - flex-direction: column; - align-items: center; - padding: 16px 10px; - background: var(--color-white); - border-radius: 8px; - box-shadow: 0 0 8px 0 rgb(37 36 59 / 15%); - width: 100%; - gap: 10px; - min-height: 140px; - justify-content: flex-start; - position: relative; -} - -.cards.important-documents .important-documents-card-link { - align-items: center; - width: 100%; - height: 100%; - text-decoration: none; - color: inherit; -} - -/* Important Documents Card Image */ -.cards.important-documents .cards-card-image { - width: 100%; - display: flex; - justify-content: center; - align-items: center; - margin-bottom: 4px; -} - -.cards.important-documents .cards-card-image img { - width: 64px; - height: 64px; - object-fit: contain; - border-radius: 0; -} - -/* Important Documents Card Body */ -.cards.important-documents .cards-card-body p, -.cards.important-documents .cards-card-body h3 { - width: 100%; - text-align: center; - overflow-wrap: break-word; - - /* Base font styling for all text elements */ - font-size: var(--body-font-size-s); -} - -.cards.important-documents p { - margin: 0; -} - -/* Important Documents Section Title */ .section:has(.cards.all-about-card) h3 { margin: 20px auto 0; text-align: center; @@ -356,204 +290,6 @@ width: 100%; } -.dark-scheme .cards .important-documents-card { - color: var(--text-color); -} - -.section:has(.cards.related-search) .default-content-wrapper h2 { - color: #54565b; - font-size: 21px; - font-weight: 600; - margin: 0 0 var(--spacing-s); -} - -.section:has(.cards.related-search) .cards-wrapper { - padding: 0 var(--spacing-xs); -} - -/* Hide View All button for related-search */ -.cards.related-search .view-toggle { - display: none; -} - -/* Related Search Cards Container */ -.cards.related-search .grid-cards { - display: grid; - grid-template-columns: 1fr; - gap: 0; -} - -/* Related Search Card Styling */ -.cards.related-search .benefit-cards { - display: flex; - flex-direction: row; - align-items: center; - height: 90px; - padding: 0 16px; - margin-bottom: var(--spacing-s); - background: var(--color-white); - border-radius: 12px; - box-shadow: 0 2px 8px rgb(0 0 0 / 8%); - width: 100%; - gap: 16px; - position: relative; - transition: box-shadow 0.2s ease, transform 0.2s ease; -} - -/* Related Search Card Image */ -.cards.related-search .benefit-cards .cards-card-image { - flex-shrink: 0; - width: 70px; - height: 70px; - display: flex; - align-items: center; - justify-content: center; -} - -.cards.related-search .benefit-cards .cards-card-image img { - width: 100%; - height: 100%; - object-fit: contain; - border-radius: 4px; -} - -/* Related Search Card Body */ -.cards.related-search .benefit-cards .cards-card-body { - flex: 1; - margin: 0; - padding-right: 40px; -} - -.cards.related-search .benefit-cards .cards-card-body h3 { - font-size: var(--body-font-size-s); - font-weight: 600; - line-height: 1.4; - margin: 0; -} - -.cards.related-search .benefit-cards .cards-card-body h3 a { - color: inherit; - text-decoration: none; -} - -.cards.related-search .benefit-cards .cards-card-body h3 a:hover { - text-decoration: none; -} - -/* Related Search Arrow Circle */ -.cards.related-search .benefit-cards::after { - content: ''; - position: absolute; - right: 16px; - top: 50%; - transform: translateY(-50%); - width: 40px; - height: 40px; - background-color: #f0f0f0; - border-radius: 50%; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238B1B2D' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: center; - background-size: 24px 24px; -} - -/* === IMAGE AND TITLE CARD VARIANT === */ - -.cards.image-and-title .grid-cards { - display: flex; - flex-flow: row nowrap; - justify-content: space-between; - align-items: flex-start; - padding: 20px 0; - margin: 0; - gap: 0; -} - -.cards.image-and-title .view-toggle { - display: none; -} - -/* Card base - vertical on mobile */ -.cards.image-and-title .cards-card { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - padding: 16px 8px; - cursor: pointer; - gap: 8px; - flex: 1 1 0; - min-width: 0; -} - -.cards.image-and-title .cards-card:hover { - box-shadow: none; - background: transparent; -} - -.cards.image-and-title .cards-card-image { - width: 80px; - height: 80px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} - -.cards.image-and-title .cards-card-image img { - width: 54px; - height: 54px; - object-fit: contain; -} - -.cards.image-and-title .cards-card-body { - margin: 0; - padding: 0; - flex: 1 1 auto; - min-width: 0; -} - -.cards.image-and-title .cards-card-body h3, -.cards.image-and-title .cards-card-body p { - font-size: 14px; - font-weight: 700; - line-height: 130%; - margin: 0; - color: var(--color-blue-800); -} - -/* Link styling - make entire card clickable */ -.cards.image-and-title .cards-card a { - display: flex; - flex-direction: row; - align-items: center; - text-decoration: none; - color: inherit; - gap: 16px; -} - -.cards.image-and-title .cards-card a:hover { - text-decoration: none; -} - -/* Hide button containers if present */ -.cards.image-and-title .button-container { - display: none; -} - -/* Override card-clickable hover effects for image-and-title */ -.cards.image-and-title .card-clickable { - box-shadow: none; - background: transparent; - transition: none; /* Remove transition to allow animation to work */ -} - -.cards.image-and-title .card-clickable:hover { - box-shadow: none; - background: transparent; - transform: none; -} - /* === KEY BENEFITS CARD VARIANT === */ .section:has(.cards.key-benefits) { padding: 40px 0 var(--spacing-xl); @@ -599,7 +335,6 @@ position: relative; min-height: 380px; width: 100%; - box-shadow: 0 8px 10px rgb(0 0 0 / 30%); } /* Ensure parent has relative positioning for absolute children */ @@ -621,7 +356,7 @@ width: 100%; height: 100%; object-fit: cover; - opacity: 0.9; + } /* Key Benefits Card Image - Icon at top left */ @@ -629,7 +364,7 @@ display: flex; align-items: flex-start; justify-content: flex-start; - padding: 32px var(--spacing-s) var(--spacing-xs); + padding: 60px var(--spacing-s) var(--spacing-xs); position: relative; z-index: 2; } @@ -696,7 +431,6 @@ /* Title styling */ .cards.key-benefits .benefit-cards .cards-card-body h3 { color: var(--color-white); - font-size: var(--heading-font-size-xs); font-weight: 700; line-height: 1.3; margin: 0 0 var(--spacing-xs); @@ -715,7 +449,6 @@ /* Description text */ .cards.key-benefits .benefit-cards .cards-card-body p { color: rgb(255 255 255 / 80%); - font-size: var(--body-font-size-s); font-weight: 400; line-height: 1.5; margin: 0 0 var(--spacing-xxs); @@ -810,15 +543,14 @@ border-radius: 20px; overflow: hidden; position: relative; - min-height: 280px; width: 100%; - box-shadow: 0 8px 12px rgb(0 0 0 / 30%); } .cards.experience-life > .grid-cards > .cards-card, .cards.experience-life > .swiper-wrapper > .cards-card, .cards.experience-life .benefit-cards { - padding-top: 160px; + padding-top: 220px; + min-height: 480px; } /* Experience Life - Gray/silver gradient with image as background */ @@ -841,7 +573,6 @@ max-width: none; max-height: none; object-fit: cover; - opacity: 0.4; } /* Experience Life - Card Tag (e.g., "Travel") */ @@ -916,6 +647,7 @@ height: 100%; object-fit: cover; opacity: 0.8; + object-position: top; } /* Card image/icon styling (reward-points only - experience-life uses background image) */ @@ -925,7 +657,7 @@ display: flex; align-items: flex-start; justify-content: flex-start; - padding: var(--spacing-s) var(--spacing-s) 16px; + padding: 60px var(--spacing-s) 16px; position: relative; z-index: 2; } @@ -947,7 +679,6 @@ z-index: 2; display: flex; flex-direction: column; - padding: 0 var(--spacing-s) var(--spacing-s); margin: 0; flex: 1; } @@ -955,7 +686,6 @@ .cards.experience-life .benefit-cards .cards-card-body h3, .cards.reward-points .benefit-cards .cards-card-body h3 { color: var(--color-white); - font-size: var(--heading-font-size-xs); font-weight: 700; margin: 0 0 var(--spacing-xs); background: var(--gradient-silver); @@ -970,6 +700,7 @@ margin: 0 0 var(--spacing-xxs); } + .cards.experience-life .benefit-cards .cards-card-body h3 a, .cards.reward-points .benefit-cards .cards-card-body h3 a { color: var(--color-white); @@ -982,7 +713,7 @@ .cards.experience-life .benefit-cards .cards-card-body p:has(.icon-arrow-right-white), .cards.reward-points .benefit-cards .cards-card-body p:has(.icon-arrow-right-white) { position: absolute; - bottom: 24px; + bottom: 12px; right: 24px; margin: 0; } @@ -1074,7 +805,9 @@ display: flex; } -/* Blog Post Card - Individual card styling */ +/* ================================================================ + Blog Post Card - Individual card styling + ================================================================ */ .cards.blog-posts .blog-post-card { background: linear-gradient(180deg, rgb(255 255 255 / 20%) 0%, rgb(177 177 177 / 20%) 100%); border-radius: 20px; @@ -1109,9 +842,8 @@ padding: 0 15px 20px; } -/* Blog Post Card - Title */ .cards.blog-posts .blog-post-card .cards-card-body h3 { - font-size: var(--body-font-size-m); + font-size: var(--body-font-size-l); font-weight: 700; line-height: 20px; margin: 0 0 6px; @@ -1119,7 +851,7 @@ /* Blog Post Card - Description */ .cards.blog-posts .blog-post-card .cards-card-body p { - font-size: var(--body-font-size-xs); + font-size: var(--body-font-size-s); font-weight: 400; margin: 0 0 16px; } @@ -1175,11 +907,9 @@ .cards .benefit-cards { gap: 16px; width: calc(50% - 1rem); - box-shadow: 0 4px 8px 2px rgb(37 36 59 / 15%); } .cards .benefit-cards .cards-card-image img { - width: 150px; height: 156px; } @@ -1187,36 +917,10 @@ margin-top: 0; } - .cards .benefit-cards .cards-card-body h3 { - font-size: var(--body-font-size-m); - } - .cards .benefit-cards .cards-card-body p { font-size: var(--body-font-size-s); } - /* Important Documents Cards - Tablet View (600px+) */ - .cards.important-documents .grid-cards { - gap: 10px; - padding: 0; - max-width: 80%; - } - - .cards.important-documents .cards-card, - .cards.important-documents .important-documents-card { - padding: 20px var(--spacing-xs); - gap: 6px; - } - - .cards.important-documents .cards-card-image img { - width: 56px; - height: 56px; - } - - .cards.important-documents .cards-card-image { - margin-bottom: 3px; - } - /* Earn Rewards Cards - Tablet View (600px+) - Add styles here as needed */ /* Key Benefits Cards - Tablet adjustments */ @@ -1233,63 +937,6 @@ .cards.blog-posts .blog-post-card .cards-card-body h3 { font-size: var(--body-font-size-m); } - - /* Related Search Cards - Tablet (2 columns) - .section:has(.cards.related-search) { - padding: 40px 24px 60px; - } - */ - - .section:has(.cards.related-search) .default-content-wrapper h2 { - font-size: var(--heading-font-size-s); - margin-bottom: 32px; - } - - .cards.related-search .grid-cards { - grid-template-columns: repeat(2, 1fr); - gap: 20px 30px; - } - - .cards.related-search .benefit-cards { - margin-bottom: 0; - } - - .cards.related-search .benefit-cards .cards-card-image { - width: 80px; - height: 70px; - } - - .cards.related-search .benefit-cards .cards-card-body h3 { - font-size: var(--body-font-size-s); - } - - .cards.image-and-title .cards-card { - gap: 12px; - padding: 16px 12px; - } - -} - -/* Desktop - horizontal layout at 768px+ */ -@media (width >= 768px) { - .cards.image-and-title .cards-card { - flex-direction: row; - text-align: left; - gap: 16px; - padding: 16px; - align-items: center; - } - - .cards.image-and-title .cards-card-image img { - width: 80px; - height: 80px; - } - - .cards.image-and-title .cards-card-body h3, - .cards.image-and-title .cards-card-body p { - font-size: 16px; - } - } @@ -1319,6 +966,11 @@ margin-bottom: 0; } +.cards.explore-other-cards > .grid-cards > .cards-card, +.cards.explore-other-cards > .swiper-wrapper > .cards-card { + min-height: unset; +} + .explore-other-cards .card-clickable:hover { transform: none; box-shadow: none; @@ -1680,24 +1332,6 @@ /* Desktop adjustments - CLS prevention rules moved to styles.css */ @media (width >= 900px) { - /* Important Documents Cards - Desktop View (900px+) */ - .cards.important-documents .grid-cards { - grid-template-columns: repeat(4, 175px); - grid-auto-rows: 175px; - gap: 40px; - padding: 0; - justify-content: center; - margin-left: auto; - margin-right: auto; - } - - .cards.important-documents .cards-card, - .cards.important-documents .important-documents-card { - width: 175px; - height: 175px; - padding: var(--spacing-s) 26px; - } - /* Earn Rewards Cards - Desktop View (900px+) */ .section:has(.cards.earn-rewards) { padding: 40px 0; @@ -1744,13 +1378,6 @@ align-items: stretch; } -/* Key Benefits Card Base Styling */ -.cards.key-benefits > .grid-cards > .cards-card, -.cards.key-benefits .swiper-slide > .cards-card, -.cards.key-benefits .benefit-cards { - box-shadow: 0 8px 20px rgb(0 0 0 / 30%); -} - /* Joining Perks Card Desktop Styles */ .cards.joining-perks .cards-card-body, .cards.joining-perks .benefit-cards .cards-card-body { @@ -1823,23 +1450,6 @@ min-height: 462px; } - .cards.important-documents .grid-cards > .cards-card { - grid-row: 1; - width: 175px; - height: 175px; - } - - .cards.important-documents .cards-card-image img { - width: 64px; - height: 64px; - } - - .cards.important-documents .cards-card-body { - flex: 1; - align-items: center; - justify-content: center; - } - .cards.all-about-card > .grid-cards > .cards-card::after, .cards.all-about-card > .swiper-wrapper > .cards-card::after { height: 180px; @@ -1910,7 +1520,10 @@ /* Experience Life & Reward Points - Desktop */ .cards.experience-life > .grid-cards > .cards-card, .cards.experience-life > .swiper-wrapper > .cards-card, - .cards.experience-life .benefit-cards, + .cards.experience-life .benefit-cards { + min-height: 420px; + } + .cards.reward-points > .grid-cards > .cards-card, .cards.reward-points > .swiper-wrapper > .cards-card, .cards.reward-points .benefit-cards { @@ -1942,7 +1555,7 @@ } .cards.experience-life .benefit-cards .cards-card-body h3 { - font-size: var(--heading-font-size-s); + font-size: var(--heading-font-size-xs); } .cards.reward-points > .grid-cards > .cards-card .cards-card-body h3, @@ -1978,31 +1591,10 @@ font-size: var(--body-font-size-m); } - .section:has(.cards.related-search) .default-content-wrapper h2 { - font-size: var(--heading-font-size-m); - margin-bottom: 40px; - } - - .cards.related-search .grid-cards { - grid-template-columns: repeat(3, 1fr); - gap: 20px 30px; - max-width: 1120px; - margin: 0 auto; - } - - .cards.related-search .benefit-cards { - padding: 0 20px; - margin-bottom: 0; - } - - .cards.related-search .benefit-cards .cards-card-image { - width: 90px; - height: 75px; - } - - .cards.related-search .benefit-cards .cards-card-body h3 { - font-size: var(--body-font-size-s); - } + /* Blog Post Card - Description */ +.cards.blog-posts .blog-post-card .cards-card-body p { + font-size: var(--body-font-size-xs); +} .explore-other-cards .swiper-wrapper { justify-content: center; diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index c03f3d6..d32ddfe 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -11,9 +11,9 @@ import { createModal } from '../modal/modal.js'; */ /** - * Block and card field order (strict index-based parsing). + * Block and card structure (strict index-based parsing). * Cards block: 7 config rows (no "classes" – applied as CSS class on element only). - * Card item: 9 cells per card. + * Card item: always 7 cells per row; mapping matches _cards.json card model from line 164. */ /** Block config: 7 rows, one per field. "classes" is not a model field. */ @@ -26,11 +26,8 @@ const CARDS_FIELDS = [ 'modalPageDecorationImage', 'swipable', 'autoplayEnabled', 'startingCard', ]; -/** Card model field names in order (markdown/config). */ -const CARD_FIELDS = [ - 'image', 'imageAlt', 'dividerImage', 'cardTag', 'backgroundImageTexture', - 'text', 'cardLink', 'cardLinkText', 'modalContent', -]; +/** Cells per card row; order matches _cards.json card model (line 164). */ +const CARD_CELL_COUNT = 7; /** Set of allowed block config keys (avoids prototype pollution when setting dataset). */ const CARDS_FIELDS_SET = new Set(CARDS_FIELDS); @@ -346,61 +343,6 @@ function setupCardInteractivity(cardItem, shouldAddArrow = false, modalTheme = ' } } -/** - * Splits the cardContent cell (third cell) into cardTag, cards-card-body, and cards-modal-content. - * @param {HTMLElement} cardItem The card element to append to - * @param {HTMLElement} contentCell The third cell (cardContent) - */ -function splitCardContentCell(cardItem, contentCell) { - const wrapper = contentCell.querySelector('div') || contentCell; - const nodes = [...wrapper.children]; - const headingIndices = []; - nodes.forEach((el, i) => { - if (el.matches?.('h1, h2, h3, h4, h5, h6')) headingIndices.push(i); - }); - let tagNodes; let bodyNodes; let modalNodes; - if (headingIndices.length > 0) { - const first = headingIndices[0]; - const second = headingIndices[1] ?? nodes.length; - tagNodes = first > 0 ? nodes.slice(0, first) : []; - bodyNodes = nodes.slice(first, second); - modalNodes = second < nodes.length ? nodes.slice(second) : []; - } else { - if (nodes.length === 0) return; - if (nodes.length === 1) { - tagNodes = []; - bodyNodes = nodes; - modalNodes = []; - } else if (nodes.length === 2) { - tagNodes = [nodes[0]]; - bodyNodes = [nodes[1]]; - modalNodes = []; - } else { - tagNodes = [nodes[0]]; - bodyNodes = nodes.slice(1, -1); - modalNodes = [nodes.at(-1)]; - } - } - if (tagNodes.length > 0) { - const tagDiv = document.createElement('div'); - tagDiv.className = 'cards-card-tag'; - tagNodes.forEach((n) => tagDiv.appendChild(n)); - cardItem.appendChild(tagDiv); - } - if (bodyNodes.length > 0) { - const bodyDiv = document.createElement('div'); - bodyDiv.className = 'cards-card-body'; - bodyNodes.forEach((n) => bodyDiv.appendChild(n)); - cardItem.appendChild(bodyDiv); - } - if (modalNodes.length > 0) { - const modalDiv = document.createElement('div'); - modalDiv.className = 'cards-card-body cards-modal-content'; - modalNodes.forEach((n) => modalDiv.appendChild(n)); - cardItem.appendChild(modalDiv); - } -} - /** * Returns the first picture or img from a cell (from inner div if present). * @param {HTMLElement} cell Cell element @@ -412,19 +354,20 @@ function getCellPictureOrImg(cell) { return inner.querySelector?.('picture') || inner.querySelector?.('img') || null; } -/** - * Appends a cell's inner content (clone) into a wrapper and appends wrapper to cardItem. - * @param {HTMLElement} cardItem Card element - * @param {HTMLElement} cell Cell element - * @param {string} wrapperClass Class name for the wrapper div - */ -function appendCellContentAs(cardItem, cell, wrapperClass) { - const inner = cell?.querySelector('div') || cell; - if (!inner || !inner.children.length) return; - const wrap = document.createElement('div'); - wrap.className = wrapperClass; - [...inner.children].forEach((child) => wrap.appendChild(child.cloneNode(true))); - cardItem.appendChild(wrap); +/** Returns alt text from a cell that contains an image: img.alt or first paragraph text. */ +function getCellAltText(cell) { + if (!cell) return ''; + const inner = cell.querySelector('div') || cell; + const img = inner.querySelector?.('img') || (inner.tagName === 'IMG' ? inner : null); + if (img?.alt) return img.alt; + const p = inner.querySelector?.('p'); + if (p?.textContent?.trim()) return p.textContent.trim(); + return ''; +} + +function isCellEmpty(cell) { + if (!cell) return true; + return !getCellPictureOrImg(cell) && !getCellValue(cell)?.trim(); } function appendCardImageWithAlt(cardItem, pic, altText) { @@ -466,6 +409,21 @@ function appendCardTexture(cardItem, pic) { cardItem.appendChild(textureWrap); } +/** + * Appends a cell's inner content (clone) into a wrapper and appends wrapper to cardItem. + * @param {HTMLElement} cardItem Card element + * @param {HTMLElement} cell Cell element + * @param {string} wrapperClass Class name for the wrapper div + */ +function appendCellContentAs(cardItem, cell, wrapperClass) { + const inner = cell?.querySelector('div') || cell; + if (!inner || !inner.children.length) return; + const wrap = document.createElement('div'); + wrap.className = wrapperClass; + [...inner.children].forEach((child) => wrap.appendChild(child.cloneNode(true))); + cardItem.appendChild(wrap); +} + function appendCardLinkButton(cardItem, linkEl, linkText, onlySetTextIfDifferentFromHref) { if (!linkEl?.href) return; const btnWrap = document.createElement('p'); @@ -492,154 +450,64 @@ function initCardItemWithRows(cardItem, rowOrRows) { } /** - * Builds a single card from 9 cells in CARD_FIELDS order (strict index-based). - * @param {HTMLElement[]} cells Array of 9 cell elements (image, imageAlt, dividerImage, - * cardTag, backgroundImageTexture, text, cardLink, cardLinkText, modalContent) - * @param {HTMLElement|HTMLElement[]} rowOrRows Source row(s) for UE instrumentation - * @returns {HTMLElement} The card element + * 7-cell order: 0 image+imageAlt, 1 divider, 2 cardTag, 3 texture, 4 body, 5 link, 6 modal. + * Returns main/divider/texture from cells 0, 1, 3. */ -function buildCardFromCells(cells, rowOrRows) { - if (!cells || cells.length < 9) return null; - - const cardItem = document.createElement('div'); - cardItem.classList.add('cards-card'); - initCardItemWithRows(cardItem, rowOrRows); - - const pic0 = getCellPictureOrImg(cells[0]); - const dividerPic = getCellPictureOrImg(cells[2]); - appendCardImageWithAlt(cardItem, pic0, cells[1] ? getCellValue(cells[1]) : ''); - appendCardDivider(cardItem, dividerPic); - if (!pic0 && !dividerPic && cells[1]) { - appendFallbackCardImageFromCell(cardItem, cells, 1); - } - appendCellContentAs(cardItem, cells[3], 'cards-card-tag'); - appendCardTexture(cardItem, getCellPictureOrImg(cells[4])); - appendCellContentAs(cardItem, cells[5], 'cards-card-body'); - const linkEl9 = (cells[6]?.querySelector('div') || cells[6])?.querySelector?.('a[href]'); - const linkText9 = cells[7] ? getCellValue(cells[7]) : getCellValue(cells[6]); - appendCardLinkButton(cardItem, linkEl9, linkText9, false); - appendCellContentAs(cardItem, cells[8], 'cards-card-body cards-modal-content'); - return cardItem; +function getCardPictures(cells) { + if (!cells || cells.length < 4) return { main: null, divider: null, texture: null }; + return { + main: getCellPictureOrImg(cells[0]) || null, + divider: getCellPictureOrImg(cells[1]) || null, + texture: getCellPictureOrImg(cells[3]) || null, + }; } /** - * Builds a single card from 7 cells (sheet order: image, imageAlt, dividerImage, - * backgroundImageTexture, text, cardLink, modalContent). Used when card row has 7 cells. + * Builds a single card from 7 cells. Order: 0 image+imageAlt, 1 divider, 2 cardTag, + * 3 texture, 4 body text, 5 cardLink, 6 modal content. For blog-posts, divider is + * skipped when cell 2 (cardTag) is empty. */ -function buildCardFromSevenCells(cells, rowOrRows) { - if (!cells || cells.length < 7) return null; +function buildCardFromCells(cells, rowOrRows, flags = {}) { + if (!cells || cells.length < CARD_CELL_COUNT) return null; const cardItem = document.createElement('div'); cardItem.classList.add('cards-card'); initCardItemWithRows(cardItem, rowOrRows); - const pic0 = getCellPictureOrImg(cells[0]); - const dividerPic = getCellPictureOrImg(cells[2]); - appendCardImageWithAlt(cardItem, pic0, cells[1] ? getCellValue(cells[1]) : ''); - appendCardDivider(cardItem, dividerPic); - if (!pic0 && !dividerPic && cells[1]) { + const { main: pic0, divider: dividerPic, texture: texturePic } = getCardPictures(cells); + const altText = getCellAltText(cells[0]); + appendCardImageWithAlt(cardItem, pic0, altText); + const skipDivider = flags.isBlogPosts && isCellEmpty(cells[2]); + if (!skipDivider) appendCardDivider(cardItem, dividerPic); + if (!pic0 && getCellPictureOrImg(cells[1])) { appendFallbackCardImageFromCell(cardItem, cells, 1); } - appendCardTexture(cardItem, getCellPictureOrImg(cells[3])); + appendCardTexture(cardItem, texturePic); + appendCellContentAs(cardItem, cells[2], 'cards-card-tag'); appendCellContentAs(cardItem, cells[4], 'cards-card-body'); - const linkEl7 = (cells[5]?.querySelector('div') || cells[5])?.querySelector?.('a[href]'); - appendCardLinkButton(cardItem, linkEl7, getCellValue(cells[5]), true); + const linkEl = (cells[5]?.querySelector('div') || cells[5])?.querySelector?.('a[href]'); + appendCardLinkButton(cardItem, linkEl, getCellValue(cells[5]), true); appendCellContentAs(cardItem, cells[6], 'cards-card-body cards-modal-content'); return cardItem; } /** - * Builds a single card from one row with 3 cells (legacy: image | decor | content). - * @param {HTMLElement} row Row element with 3 child cells - * @returns {HTMLElement} The card element - */ -function buildCardFromThreeCells(row) { - const cells = [...row.children]; - if (cells.length < 3) return null; - const cardItem = document.createElement('div'); - cardItem.classList.add('cards-card'); - moveInstrumentation(row, cardItem); - cardItem.removeAttribute('data-aue-prop'); - cardItem.setAttribute('data-aue-type', 'container'); - cardItem.setAttribute('data-aue-label', 'Card'); - - const imageCell = cells[0].querySelector('div') || cells[0]; - const picture = imageCell.querySelector?.('picture'); - if (picture) { - const imageWrap = document.createElement('div'); - imageWrap.className = 'cards-card-image'; - imageWrap.appendChild(picture); - cardItem.appendChild(imageWrap); - } - const decorCell = cells[1].querySelector('div') || cells[1]; - const pictures = [...decorCell.querySelectorAll?.('picture') || []]; - if (pictures.length >= 1) { - const dividerWrap = document.createElement('div'); - dividerWrap.className = 'cards-card-divider'; - dividerWrap.appendChild(pictures[0]); - cardItem.appendChild(dividerWrap); - } - if (pictures.length >= 2) { - const textureWrap = document.createElement('div'); - textureWrap.className = 'cards-card-bg-texture'; - textureWrap.appendChild(pictures[1]); - cardItem.appendChild(textureWrap); - } - splitCardContentCell(cardItem, cells[2]); - return cardItem; -} - -/** - * Identifies and marks semantic elements within a card: - * - dividerImage: thin horizontal image (cards-card-divider) - * - backgroundImageTexture: large texture image (cards-card-bg-texture) - * - cardTag: simple text tag before main content (cards-card-tag) + * Identifies cardTag when two body divs exist: first tag (no headings), second main content. + * Divider/texture are set by buildCardFromCells from cell indices; no image heuristics. * @param {HTMLElement} cardItem The card element */ function identifySemanticCardElements(cardItem) { - const children = [...cardItem.children]; - - // Process all cards-card-image elements to identify dividers and textures - children.forEach((div) => { - if (!div.classList.contains('cards-card-image')) return; - - const img = div.querySelector('img'); - if (!img) return; + const bodyDivs = [...cardItem.children].filter((div) => div.classList.contains('cards-card-body')); + if (bodyDivs.length < 2) return; - const width = parseInt(img.getAttribute('width'), 10) || 0; - const height = parseInt(img.getAttribute('height'), 10) || 0; + const firstBody = bodyDivs[0]; + const secondBody = bodyDivs[1]; + const firstHasHeading = firstBody.querySelector('h1, h2, h3, h4, h5, h6'); + const secondHasHeading = secondBody.querySelector('h1, h2, h3, h4, h5, h6'); - // Divider: thin horizontal image (height < 50px, width much greater than height) - if (height > 0 && height < 50 && width > height * 3) { - div.classList.remove('cards-card-image'); - div.classList.add('cards-card-divider'); - return; - } - - // Texture: large image (both dimensions > 100px) - if (width > 100 && height > 100) { - div.classList.remove('cards-card-image'); - div.classList.add('cards-card-bg-texture'); - } - }); - - // Identify cardTag: a simple text body that appears before the main content body - // The cardTag has just text (p tag) without headings, and the next body has headings - const bodyDivs = children.filter((div) => div.classList.contains('cards-card-body')); - if (bodyDivs.length >= 2) { - const firstBody = bodyDivs[0]; - const secondBody = bodyDivs[1]; - - // Check if first body is a simple tag (no headings, just text) - // and second body has headings (the main content) - const firstHasHeading = firstBody.querySelector('h1, h2, h3, h4, h5, h6'); - const secondHasHeading = secondBody.querySelector('h1, h2, h3, h4, h5, h6'); - - if (!firstHasHeading && secondHasHeading) { - // First body is the cardTag - firstBody.classList.remove('cards-card-body'); - firstBody.classList.add('cards-card-tag'); - } + if (!firstHasHeading && secondHasHeading) { + firstBody.classList.remove('cards-card-body'); + firstBody.classList.add('cards-card-tag'); } } @@ -718,7 +586,6 @@ function globalMayuraScrollbarResizeHandler() { function getStaticImageDimensionsForBlock(block, img) { if (block.classList.contains('all-about-card')) return { width: 280, height: 350 }; - if (block.classList.contains('important-documents')) return { width: 175, height: 175 }; if (img.closest('.swiper-slide')) return { width: 232, height: 358 }; return null; } @@ -778,9 +645,6 @@ function getBlockVariantFlags(block) { || isExperienceLife || classList.contains('reward-points'); return { - isImportantDocuments: classList.contains('important-documents'), - isRelatedSearch: classList.contains('related-search'), - isImageAndTitle: classList.contains('image-and-title'), isExperienceLife, isBlogPosts: classList.contains('blog-posts'), isEarnRewards: classList.contains('earn-rewards'), @@ -824,61 +688,19 @@ function moveInstrumentationToBlock(block, rows, configRowCount) { block.setAttribute('data-aue-label', 'Cards'); } -function appendCardsFromFourCellRows(cardRows, cardsContainer) { - for (let i = 0; i < cardRows.length; i += 3) { - const group = cardRows.slice(i, i + 3); - if (group.length === 3) { - const cells = [...group[0].children, ...group[1].children, ...group[2].children]; - const cardItem = buildCardFromCells(cells, group); - if (cardItem) cardsContainer.append(cardItem); - } else if (group.length > 0) { +function buildCardsFromRows(cardRows, numCells, cardsContainer, flags = {}) { + if (numCells !== CARD_CELL_COUNT) { + if (cardRows.length > 0) { // eslint-disable-next-line no-console - console.error('Cards block: card rows group has unexpected length (expected 3, got %d). Index: %d.', group.length, i); + console.error('Cards block: unexpected card row cell count (%d). Expected %d.', numCells, CARD_CELL_COUNT); } - } -} - -function buildCardsFromRows(cardRows, numCells, cardsContainer) { - if (numCells === CARD_FIELDS.length) { - cardRows.forEach((row) => { - const cells = [...row.children]; - const cardItem = buildCardFromCells(cells, row); - if (cardItem) cardsContainer.append(cardItem); - }); - return; - } - if (numCells === 4) { - appendCardsFromFourCellRows(cardRows, cardsContainer); return; } - if (numCells === 3) { - cardRows.forEach((row) => { - const cardItem = buildCardFromThreeCells(row); - if (cardItem) cardsContainer.append(cardItem); - }); - return; - } - if (numCells === 7) { - cardRows.forEach((row) => { - const cells = [...row.children]; - const cardItem = buildCardFromSevenCells(cells, row); - if (cardItem) cardsContainer.append(cardItem); - }); - return; - } - if (numCells === 8) { - cardRows.forEach((row) => { - const cells = [...row.children]; - cells.push(document.createElement('div')); - const cardItem = buildCardFromCells(cells, row); - if (cardItem) cardsContainer.append(cardItem); - }); - return; - } - if (cardRows.length > 0) { - // eslint-disable-next-line no-console - console.error('Cards block: unexpected card row cell count (%d). Expected 3, 4, 7, 8, or 9.', numCells); - } + cardRows.forEach((row) => { + const cells = [...row.children]; + const cardItem = buildCardFromCells(cells, row, flags); + if (cardItem) cardsContainer.append(cardItem); + }); } function optimizeCardPicturesInContainer(cardsContainer, block) { @@ -903,10 +725,8 @@ function optimizeCardPicturesInContainer(cardsContainer, block) { } function getCardVariantClass(flags) { - if (flags.isImportantDocuments) return 'important-documents-card'; if (flags.isBlogPosts) return 'blog-post-card'; - const isBenefit = !flags.isEarnRewards && !flags.isJoiningPerks - && !flags.isImageAndTitle && !flags.isAllAboutCard; + const isBenefit = !flags.isEarnRewards && !flags.isJoiningPerks && !flags.isAllAboutCard; if (!isBenefit) return null; return flags.isExploreOtherCards ? 'explore-other-cards' : 'benefit-cards'; } @@ -1227,7 +1047,7 @@ export default async function decorate(block) { const cardsContainer = document.createElement('div'); cardsContainer.classList.add('grid-cards'); - buildCardsFromRows(cardRows, numCells, cardsContainer); + buildCardsFromRows(cardRows, numCells, cardsContainer, flags); if (flags.supportsSemanticElements) { cardsContainer.querySelectorAll('.cards-card').forEach((cardItem) => { @@ -1244,9 +1064,7 @@ export default async function decorate(block) { if (isSwipable) { await initSwiper(block, cardsContainer, flags, layout); } else { - const showViewAllToggle = !flags.isImportantDocuments && !flags.isRelatedSearch - && !flags.isImageAndTitle && !flags.isEarnRewards && !flags.isJoiningPerks - && !flags.isInCsCards; + const showViewAllToggle = !flags.isEarnRewards && !flags.isJoiningPerks && !flags.isInCsCards; if (showViewAllToggle) { setupViewAllViewLess(block, allCards); } diff --git a/component-models.json b/component-models.json index c2daa34..548e687 100644 --- a/component-models.json +++ b/component-models.json @@ -1355,10 +1355,6 @@ "name": "All About", "value": "all-about-card" }, - { - "name": "Important Documents", - "value": "important-documents" - }, { "name": "Key Benefits", "value": "key-benefits" @@ -1379,10 +1375,6 @@ "name": "Earn Rewards", "value": "earn-rewards" }, - { - "name": "Related Search", - "value": "related-search" - }, { "name": "Joining Perks", "value": "joining-perks" @@ -1390,10 +1382,6 @@ { "name": "Explore Other Cards", "value": "explore-other-cards" - }, - { - "name": "Image and Title", - "value": "image-and-title" } ] }, @@ -1514,7 +1502,8 @@ { "component": "text", "name": "cardLinkText", - "label": "Card Link Text (optional)" + "label": "Card Link Text (optional)", + "hidden": "true" }, { "component": "richtext", diff --git a/styles/styles.css b/styles/styles.css index f4f396d..31818d5 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -59,7 +59,7 @@ --gradient-black: linear-gradient(to bottom, transparent 0%, black 100%); --gradient-pewter: linear-gradient(270deg, #332B28 0%, #514A44 21.11%, #8B837D 50.14%, #625A50 71.74%, var(--color-pewter) 90.18%, #433C32 100%); --gradient-silver: linear-gradient(208deg, #C8C6C6 0%, #F1F0F0 53.5%, #C8C6C6 100%); - --gradient-conic-silver: conic-gradient(from 0deg at 50% 50%, rgb(194 194 194 / 100%) 0%, rgb(181 181 181 / 100%) 7%, rgb(238 238 238 / 100%) 14%, rgb(157 157 157 / 100%) 35%, rgb(255 255 255 / 100%) 58%, rgb(206 206 206 / 100%) 63%, rgb(235 234 234 / 100%) 80%, rgb(194 194 194 / 100%) 99%); + --gradient-conic-silver: conic-gradient(from 0deg at 50% 60%, rgb(194 194 194 / 100%) 0%, rgb(181 181 181 / 100%) 7%, rgb(238 238 238 / 100%) 14%, rgb(157 157 157 / 100%) 35%, rgb(255 255 255 / 100%) 58%, rgb(206 206 206 / 100%) 63%, rgb(235 234 234 / 100%) 80%, rgb(194 194 194 / 100%) 99%); --gradient-metal-blue: linear-gradient(308deg, #110140 4.16%, #1E2A8E 44.91%, #010740 88.8%); --gradient-metal-silver: linear-gradient(138deg, #999 -15.44%, #EFEFEF 0.15%, #CACACA 18.04%, #EFEFEF 37.09%, #CACACA 58.44%, #999 76.91%, #EFEFEF 87.3%); --modal-backdrop-gradient: linear-gradient(to bottom, rgb(200 198 198 / 70%) 0, rgb(200 198 198 / 95%) 1px, #C8C6C6 250px, #F1F0F0 50%, #C8C6C6 100%);