import { dcxAsset } from './assets.js'; (() => { "use strict"; const DETAIL_META = { product: { "Purpose": ["Why the product exists and what success looks like.", "/assets/product/product-purpose-foil.png"], "Positioning": ["The strategic space this product should occupy.", "/assets/product/product-positioning-foil.png"], "Primary conversion": ["The single action the experience should make inevitable.", "/assets/product/product-primary-conversion-foil.png"], "What must be clear first": ["The facts people need before anything else.", "/assets/product/product-clear-first-foil.png"], "Product principles": ["The rules that keep every product decision aligned.", "/assets/product/product-principles-foil.png"], "Operating context": ["Where and how the product must work.", "/assets/product/product-operating-context-foil.png"], "Surfaces": ["The chosen experiences and the job each one performs.", "/assets/product/product-surfaces-foil.png"], }, brand: { "Personality": ["The character every expression should carry.", "/assets/brand/brand-personality-foil.png"], "Voice": ["How the brand sounds—and what it deliberately avoids.", "/assets/brand/brand-voice-foil.png"], "Principles": ["The rules that turn character into consistent choices.", "/assets/brand/brand-principles-foil.png"], "Commitments": ["The promises the brand must keep.", "/assets/brand/brand-commitments-foil.png"], "Named references": ["Useful signals to borrow without becoming an imitation."], "Anti-reference": ["The direction the brand must deliberately avoid."], "Marks": ["The identity assets already available to the system."], "Boards and references": ["Visual evidence that should inform the work."], "Assets provided": ["The source material available for production."], "Brand assets": ["Logos, moodboards, and visual references available to the system."], }, color: { "The cue": ["", "/assets/color/color-palette-foil.png"], "Also generated": ["Adjacent directions retained as useful context."], "Palette": ["The core color relationships for the experience.", "/assets/color/color-palette-foil.png"], "Strategy per surface": ["How the palette changes emphasis across contexts.", "/assets/color/color-strategy-per-surface-foil.png"], }, typography: { "The pair": ["Two typefaces with distinct, complementary jobs.", "/assets/typography/typography-pair-foil.png"], "Type scale": ["The hierarchy that gives content pace and proportion.", "/assets/typography/typography-type-scale-foil.png"], "In running text": ["How the pair behaves when the content gets real."], }, components: { "Buttons": ["Variants, sizes, icons, and loading states."], "Input fields": ["States, sizes, affixes, and multiline input."], "Cards": ["Content, media, horizontal, and action compositions."], }, material: { "The page, as chosen": ["What each selected surface represents in this design context."], "Motion per surface": ["How movement supports each context without becoming spectacle."], "Motion": ["How movement should support the experience."], "Accessibility": ["The rules that keep the material system readable and operable."], "Layout structure": ["The spatial system that organizes the page.", "/assets/material/material-layout-structure-foil.png"], "Boundaries per surface": ["Where separation is visible—and where space does the work.", "/assets/material/material-boundaries-per-surface-foil.png"], "Corners per surface": ["How edge character changes with context.", "/assets/material/material-corners-per-surface-foil.png"], "Depth per surface": ["How hierarchy is expressed without decorative elevation.", "/assets/material/material-depth-per-surface-foil.png"], "Iconography": ["The chosen icon library, its character, grid, stroke, and license."], }, hooks: { "How it runs": [""], "Built-in rules": ["Choose what the detector watches."], "Custom rules": [""], }, }; let syncFrame = 0; const slugify = (value) => value .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, ""); const wrapPrincipleContent = (section, category) => { section.querySelectorAll(":scope .dcx-principles > li").forEach((item) => { let copy = item.querySelector(":scope > .dcx-detail-principle-copy"); if (!copy) { copy = document.createElement("div"); copy.className = "dcx-detail-principle-copy"; while (item.firstChild) copy.appendChild(item.firstChild); item.appendChild(copy); } if (category === "product" && section.dataset.label === "Product principles") { const detail = copy.querySelector(":scope > strong:first-child")?.nextSibling; if (detail?.nodeType === Node.TEXT_NODE) { detail.textContent = detail.textContent.replace(/^\s*[—–-]+\s*/, " "); } } }); }; const demoteBodyHeadings = (body) => { body.querySelectorAll("h3").forEach((heading) => { const replacement = document.createElement("h4"); [...heading.attributes].forEach((attribute) => { replacement.setAttribute(attribute.name, attribute.value); }); while (heading.firstChild) replacement.appendChild(heading.firstChild); heading.replaceWith(replacement); }); }; const placeProductPlatform = (section, category) => { if (category !== "product" || section.dataset.label !== "Purpose") return; const callout = section.querySelector(":scope .dcx-purpose .dcx-callout"); const name = callout?.querySelector(":scope > .dcx-callout-name"); const platform = section.querySelector(":scope .dcx-purpose > .dcx-platform-pill"); if (!callout || !name || !platform) return; platform.classList.remove("dcx-chip"); name.after(platform); }; const removeProvenanceNotes = (section) => { section.querySelectorAll(":scope .dcx-fan-note").forEach((note) => note.remove()); }; const compactColorArticle = (article) => { if (article.dataset.dcxColorCompacted === "true") return; const sections = new Map( [...article.querySelectorAll(":scope > .dcx-block[data-label]")] .map((section) => [section.dataset.label, section]), ); const cueSection = sections.get("The cue"); const paletteSection = sections.get("Palette"); const strategySection = sections.get("Strategy per surface"); const cue = cueSection?.querySelector(":scope > .dcx-cue"); const fan = paletteSection?.querySelector(":scope > .dcx-fan"); cueSection?.querySelector(":scope > .dcx-fan-note")?.remove(); strategySection?.querySelector(":scope > .dcx-fan-note")?.remove(); sections.get("Roles and values")?.remove(); sections.get("Interview direction")?.remove(); if (cue && fan) { const cueCard = cue.querySelector(":scope > .dcx-cue-card"); const panels = [...fan.querySelectorAll(":scope > .dcx-fan-panel")]; if (cueCard && panels.length) { cueCard.querySelector(":scope > .dcx-cue-tag")?.remove(); cueCard.querySelector(":scope > .dcx-cue-name")?.remove(); const bands = document.createElement("ul"); bands.className = "dcx-cue-bands"; bands.setAttribute("aria-label", "Committed palette"); panels.forEach((panel) => { const role = panel.querySelector(":scope .dcx-fan-name")?.textContent.trim() || "Color"; const value = panel.querySelector(":scope .dcx-fan-value")?.textContent.trim() || ""; const color = panel.style.getPropertyValue("--panel-swatch"); const ink = panel.style.getPropertyValue("--panel-ink"); const item = document.createElement("li"); item.className = "dcx-cue-band-item"; const swatch = document.createElement("div"); swatch.className = "dcx-cue-band"; swatch.style.setProperty("--band-color", color); swatch.style.setProperty("--band-ink", ink); const hex = document.createElement("code"); hex.className = "dcx-cue-band-value"; hex.textContent = value; swatch.appendChild(hex); const label = document.createElement("span"); label.className = "dcx-cue-band-label"; label.textContent = role; item.append(swatch, label); bands.appendChild(item); }); cueCard.querySelectorAll(":scope > .dcx-cue-role").forEach((role) => role.remove()); cueCard.appendChild(bands); paletteSection.remove(); } } const heroLede = article.querySelector(":scope > header > .dcx-lede"); if (heroLede) heroLede.textContent = "Committed palette and per-surface strategy."; article.dataset.dcxColorCompacted = "true"; }; const replaceMaterialPagePreviews = (article) => { const section = article.querySelector(':scope > .dcx-block[data-label="The page, as chosen"]'); const previews = section?.querySelector(":scope > .dcx-surfaces"); if (!previews) return; /* One definition per chosen surface, published by the data layer at render time (design-context.js renderDocument). If the list is missing the original preview boards stay, which is a visible fallback rather than an empty section. */ const entries = Array.isArray(window.dcxSurfaceDefs) ? window.dcxSurfaceDefs : []; if (!entries.length) return; const definitions = document.createElement("dl"); definitions.className = "dcx-defs"; entries.forEach(({ label, description }) => { const item = document.createElement("div"); item.className = "dcx-def"; const term = document.createElement("dt"); term.textContent = label; const detail = document.createElement("dd"); detail.textContent = description; item.append(term, detail); definitions.appendChild(item); }); previews.replaceWith(definitions); }; const enhanceSection = (section, category, iconIndex) => { const label = section.dataset.label || "Section"; const [ledeText, icon] = DETAIL_META[category]?.[label] || ["The decisions that define this part of the system."]; const slug = `${category}-${slugify(label)}`; section.querySelector(":scope > .dcx-block-label")?.remove(); const body = document.createElement("div"); body.className = "dcx-detail-section-body"; while (section.firstChild) body.appendChild(section.firstChild); const heading = document.createElement("div"); heading.className = "dcx-detail-section-heading"; const title = document.createElement("h3"); title.className = "dcx-detail-section-title"; title.id = `dcx-${slug}-title`; title.textContent = label; heading.appendChild(title); if (ledeText) { const lede = document.createElement("p"); lede.className = "dcx-detail-section-lede"; lede.textContent = ledeText; heading.appendChild(lede); } const header = document.createElement("header"); header.className = "dcx-detail-section-head"; header.appendChild(heading); if (icon) { header.classList.add("dcx-detail-section-head--with-icon"); const figure = document.createElement("figure"); figure.className = "dcx-detail-section-icon"; figure.setAttribute("aria-hidden", "true"); figure.setAttribute("data-dcx-hide-on-error", ""); const image = document.createElement("img"); image.src = dcxAsset(icon); image.alt = ""; image.width = 256; image.height = 256; image.decoding = "async"; if (section.closest(".dcx-article")?.dataset.dcxCategory || iconIndex > 0) image.loading = "lazy"; figure.appendChild(image); header.appendChild(figure); } section.classList.add("dcx-detail-section", `dcx-detail-section--${slugify(label)}`); section.id = `dcx-${slug}`; if (!section.closest(".dcx-article")?.dataset.dcxCategory) { section.setAttribute("role", "region"); section.setAttribute("aria-labelledby", title.id); } section.append(header, body); demoteBodyHeadings(body); body.querySelector(":scope > .dcx-list")?.classList.add("dcx-detail-list"); body.querySelector(":scope > .dcx-defs")?.classList.add("dcx-detail-defs"); wrapPrincipleContent(section, category); placeProductPlatform(section, category); removeProvenanceNotes(section); }; const enhanceArticle = (article, category) => { if (article.dataset.dcxDetailEnhanced === category) return; if (category === "color") compactColorArticle(article); if (category === "typography") { article.querySelectorAll(":scope .dcx-pair-why").forEach((note) => note.remove()); article.querySelector(':scope > .dcx-block[data-label="Interview direction"]')?.remove(); } if (category === "iconography") { article.querySelector(':scope > .dcx-block[data-label="The hand"]')?.remove(); } if (category === "material") { replaceMaterialPagePreviews(article); article.querySelectorAll(":scope > .dcx-block[data-label] > .dcx-fan-note") .forEach((note) => note.remove()); } article.classList.add("dcx-detail-article", `dcx-detail-article--${category}`); article.querySelector(":scope > header")?.classList.add("dcx-detail-hero"); let iconIndex = 0; article.querySelectorAll(":scope > .dcx-block[data-label]").forEach((section) => { const hasIcon = Boolean(DETAIL_META[category]?.[section.dataset.label]?.[1]); enhanceSection(section, category, iconIndex); if (hasIcon) iconIndex += 1; }); article.dataset.dcxDetailEnhanced = category; }; const syncDetails = () => { syncFrame = 0; const expander = document.querySelector(".dcx-expander"); const continuousArticles = expander?.querySelectorAll('.dcx-main > .dcx-article[data-dcx-category]'); if (continuousArticles?.length) { continuousArticles.forEach((article) => { const category = article.dataset.dcxCategory; if (category !== "audience" && DETAIL_META[category]) enhanceArticle(article, category); }); return; } const activeItem = expander?.querySelector(".dcx-nav-list > li.is-active[data-category]"); const article = expander?.querySelector(".dcx-main > .dcx-article"); const category = activeItem?.dataset.category; if (!article || !category || category === "audience" || !DETAIL_META[category]) return; enhanceArticle(article, category); }; const scheduleSync = () => { if (syncFrame) return; syncFrame = requestAnimationFrame(syncDetails); }; const observer = new MutationObserver(scheduleSync); observer.observe(document.body, { childList: true, subtree: true }); window.addEventListener("pageshow", scheduleSync); scheduleSync(); })();