Repository: adobe/adobe-design-website
Pull request #78: feat: page header
Author: login: catheraaine
Labels: name: Ready for Dev Review
Created: 2025-05-14T04:33:28Z | Merged: 2025-05-15T17:56:58Z

## Summary of changes
- page header component

## Relevant Links
- Designs: [Figma](https://www.figma.com/design/QzvOszpLGT7fwSd6N7gaDW/Adobe-Design)
- Story: [ADB-162](https://sparkbox.atlassian.net/browse/ADB-162)

## Test URLs:
- Before: https://main--adobe-design-website--adobe.aem.page/
- After: https://page-header--adobe-design-website--adobe.aem.page/


## Validation
1. The page header is easiest to review on the homepage. 
  1. Head to / and see the new heading content. 
  1. Note that the background image will be done in a different ticket. 
1. The page header is also included, with documentation, on the pattern library page.
  1. Review the simpler version of the header to note a change in description text, button style, and button alt text.
1. Let Catherine know she's really cool for figuring this bit out. 
1. For the pages with long body copy as a header, I think we can just use the existing `hero` component, though we may need to adjust font sizes of the h1s? 


Diff:
diff --git a/blocks/page-header/page-header.css b/blocks/page-header/page-header.css
new file mode 100644
index 00000000..adfb87fe
--- /dev/null
+++ b/blocks/page-header/page-header.css
@@ -0,0 +1,122 @@
+.page-header {
+  gap: 2rem;
+
+  @media (min-width: 22.5rem) {
+    gap: 3rem;
+  }
+
+  @media (min-width: 48rem) {
+    gap: 4rem;
+  }
+
+  .page-header__title {
+    margin-block-start: 0;
+  }
+
+  .page-header__title,
+  .page-header__byline {
+    margin-block-end: 1.5rem;
+
+    @media (min-width: 48rem) {
+      margin-block-end: 2rem;
+    }
+
+    @media (min-width: 80rem) {
+      margin-block-end: 3rem;
+    }
+  }
+
+  .page-header__description {
+    margin-block-end: 1rem;
+
+    @media (min-width: 22.5rem) {
+      margin-block-end: 1.25rem;
+    }
+
+    @media (min-width: 48rem) {
+      margin-block-end: 2rem;
+    }
+  }
+
+  .page-header__description--with-byline {
+    @media (min-width: 22.5rem) {
+      margin-block-end: 1rem;
+    }
+
+    @media (min-width: 48rem) {
+      margin-block-end: 1rem;
+    }
+
+    @media (min-width: 80rem) {
+      margin-block-end: 3rem;
+    }
+  }
+
+  .page-header-byline__author {
+    margin-block-end: 0.25rem;
+  }
+
+  .page-header-byline__title {
+    margin-block-end: 0;
+  }
+
+  .page-header__button {
+    margin-block-end: 2rem;
+    width: 100%;
+
+    @media (min-width: 22.5rem) {
+      margin-block-end: 0;
+      width: unset;
+    }
+  }
+
+  .page-header__image img {
+    display: block;
+    max-width: 100%;
+    aspect-ratio: auto;
+    height: auto;
+    border-radius: 1rem;
+  }
+
+}
+
+/* adjust font sizes */
+.page-header {
+  @media (min-width: 22.5rem) {
+    h1 {
+      font-size: var(--spectrum-heading-size-xxl);
+    }
+
+    .page-header__description {
+      font-size: var(--spectrum-body-size-l);
+    }
+
+    .page-header__description--with-byline {
+      font-size: var(--spectrum-title-size-xxxl);
+    }
+  }
+
+  @media (min-width: 48rem) {
+    h1 {
+      font-size: var(--spectrum-heading-size-xxl);
+    }
+
+    .page-header__description {
+      font-size: var(--spectrum-body-size-xl);
+    }
+
+    .page-header__description--with-byline {
+      font-size: var(--spectrum-title-size-xl);
+    }
+  }
+
+  @media (min-width: 80rem) {
+    h1 {
+      font-size: var(--spectrum-heading-size-xl);
+    }
+
+    .page-header__description--with-byline {
+      font-size: var(--spectrum-title-size-xxl);
+    }
+  }
+}
diff --git a/blocks/page-header/page-header.js b/blocks/page-header/page-header.js
new file mode 100644
index 00000000..3f41a988
--- /dev/null
+++ b/blocks/page-header/page-header.js
@@ -0,0 +1,95 @@
+export default async function decorate(block) {
+  // parse data into an object
+  const pageHeaderData = {
+    title: block.children?.[0]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    image: block.children?.[1]?.children?.[0]?.firstElementChild,
+    description: block.children?.[2]?.children?.[0]?.children?.[0]?.innerHTML,
+    byline: block.children?.[3]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    featuredDescription: block.children?.[3]?.children?.[1]?.children?.[0]?.textContent?.trim(),
+    anchorNode: block.children?.[4]?.children?.[0]?.firstChild?.firstChild,
+    buttonStyle: block.children?.[4]?.children?.[1]?.children?.[0]?.textContent?.trim().toLowerCase(),
+    altText: block.children?.[4]?.children?.[2]?.children?.[0]?.textContent?.trim(),
+  };
+
+  // create the container element
+  const pageHeader = document.createElement("div");
+  pageHeader.classList.add("page-header", "grid-container");
+
+  const pageHeaderContent = document.createElement("div");
+  pageHeaderContent.classList.add("page-header__content", "grid-item", "grid-item--50");
+
+  // there should always be a title, so create it as an h1
+  const pageTitle = document.createElement("h1");
+  pageTitle.classList.add("page-header__title", "util-heading-xl");
+  pageTitle.innerText = pageHeaderData.title;
+  pageHeaderContent.append(pageTitle);
+
+  // if there is a description, add it as an h2
+  if (pageHeaderData.description) {
+    const pageDescription = document.createElement("div");
+    pageDescription.classList.add("page-header__description");
+
+    // if there is a byline, the description uses
+    // a different font class, for visual hierarchy
+    pageHeaderData.byline
+    ? pageDescription.classList.add("util-title-m", "page-header__description--with-byline")
+    : pageDescription.classList.add("util-body-m");
+
+    pageDescription.innerText = pageHeaderData.description;
+    pageHeaderContent.append(pageDescription);
+  }
+
+  // if there is a an author block, build that
+  if (pageHeaderData.byline) {
+    const bylineWrapper = document.createElement("div");
+    bylineWrapper.classList.add("page-header__byline", "page-header-byline");
+
+    const byline = document.createElement("div");
+    byline.classList.add("page-header-byline__author", "util-detail-l");
+    byline.innerText = pageHeaderData.byline;
+
+    bylineWrapper.append(byline);
+
+    if (pageHeaderData.featuredDescription) {
+      const featuredDescription = document.createElement("div");
+      featuredDescription.classList.add("page-header-byline__title", "util-body-xs");
+      featuredDescription.innerText = pageHeaderData.featuredDescription;
+
+      bylineWrapper.append(featuredDescription);
+    }
+
+    pageHeaderContent.append(bylineWrapper);
+  }
+
+  // if there's a link, build a button
+  if (pageHeaderData.anchorNode) {
+    const pageHeaderButton = pageHeaderData.anchorNode;
+
+    pageHeaderButton.classList.add(
+      "button",
+      "page-header__button",
+    );
+
+    pageHeaderData.buttonStyle
+      ? pageHeaderButton.classList.add(`button--${pageHeaderData.buttonStyle}`)
+      : pageHeaderButton.classList.add("button--primary");
+
+    if (pageHeaderData.altText) {
+      pageHeaderButton.setAttribute("aria-label", pageHeaderData.altText);
+    }
+
+    pageHeaderContent.append(pageHeaderButton);
+  }
+
+  pageHeader.append(pageHeaderContent);
+
+  // if there is an image, add the appropriate attributes
+  if (pageHeaderData.image) {
+    pageHeaderData.image.classList.add('page-header__image', "grid-item", "grid-item--50");
+    pageHeaderData.image.setAttribute('alt', '');
+    pageHeader.append(pageHeaderData.image);
+  }
+
+  // replace the parent with our new block
+  block.parentElement.replaceWith(pageHeader);
+}
diff --git a/blocks/page-header/page-header.test.js b/blocks/page-header/page-header.test.js
new file mode 100644
index 00000000..519bfea9
--- /dev/null
+++ b/blocks/page-header/page-header.test.js
@@ -0,0 +1,10 @@
+describe('Page Header Block', () => {
+  beforeAll(async () => {
+    await page.goto(`${global.BASE_URL}`);
+  });
+
+  it('should render the page header element', async () => {
+    const header = await page.$('.page-header');
+    expect(header).toExist();
+  });
+});
diff --git a/styles/styles.css b/styles/styles.css
index 7fc5c21d..b5c35779 100644
--- a/styles/styles.css
+++ b/styles/styles.css
@@ -208,10 +208,12 @@ a:hover {
     from var(--spectrum-black) r g b / 9%
   );
   --color-button-text-primary: var(--spectrum-white);
-
   background-color: var(--color-button-background-primary);
   color: var(--color-button-text-primary);
 
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+
   &:hover {
     background-color: var(--color-button-background-primary-hover);
     color: var(--color-button-text-primary);
@@ -455,7 +457,15 @@ a:hover {
 
 /* ** Grid Layout Styles **  */
 main > .section > .grid-container {
-  margin-block: 4rem;
+  margin-block: 1.25rem;
+
+  @media (min-width: 22.5rem) {
+    margin-block: 2rem;
+  }
+
+  @media (min-width: 48rem) {
+    margin-block: 4rem;
+  }
 }
 
 .grid-container {


Reviews:
user: login: catheraaine
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T05:01:38Z
user: login: catheraaine
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T05:02:43Z
user: login: catheraaine
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T05:03:04Z
user: login: catheraaine
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T05:03:46Z
user: login: catheraaine
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T05:04:36Z
user: login: arnest00
body: Spacing and naming notes!
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T05:45:35Z
user: login: catheraaine
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T14:40:27Z
user: login: catheraaine
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T14:46:12Z
user: login: catheraaine
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-15T14:54:43Z
user: login: arnest00
body: 🚀 
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-15T17:54:23Z

Review comments:
diff_hunk: @@ -0,0 +1,126 @@
+.page-header {
+  gap: 2rem;
+
+  @media (min-width: 22.5rem) {
+    gap: 3rem;
+  }
+
+  @media (min-width: 48rem) {
+    gap: 4rem;
+  }
+
+  @media (min-width: 80rem) {
+    gap: 2rem;
path: blocks/page-header/page-header.css
user: login: catheraaine
body: part of me thinks this could have been a design mistake
created_at: 2025-05-14T05:01:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,126 @@
+.page-header {
+  gap: 2rem;
+
+  @media (min-width: 22.5rem) {
+    gap: 3rem;
+  }
+
+  @media (min-width: 48rem) {
+    gap: 4rem;
+  }
+
+  @media (min-width: 80rem) {
+    gap: 2rem;
+  }
+
+  .page-header__title {
+    margin-block-start: 0;
+  }
+
+  .page-header__title,
+  .page-header__featured-content {
+    margin-block-end: 1.5rem;
+
+    @media (min-width: 48rem) {
+      margin-block-end: 2rem;
+    }
+
+    @media (min-width: 80rem) {
+      margin-block-end: 3rem;
+    }
+  }
+
+  .page-header__description {
+    margin-block-end: 1rem;
+
+    @media (min-width: 22.5rem) {
+      margin-block-end: 1.25rem;
+    }
+
+    @media (min-width: 48rem) {
+      margin-block-end: 2rem;
+    }
+  }
+
+  .page-header__description--featured {
+    @media (min-width: 22.5rem) {
+      margin-block-end: 1rem;
+    }
+
+    @media (min-width: 48rem) {
+      margin-block-end: 1rem;
+    }
+
+    @media (min-width: 80rem) {
+      margin-block-end: 3rem;
+    }
+  }
+
+  .page-header__featured-subtitle {
+    margin-block-end: 0.25rem;
+  }
+
+  .page-header__featured-description {
+    margin-block-end: 0;
+  }
+
+  .page-header__button {
+    margin-block-end: 2rem;
+    width: 100%;
+
+    @media (min-width: 22.5rem) {
+      margin-block-end: 0;
+      width: unset;
path: blocks/page-header/page-header.css
user: login: catheraaine
body: button is full-width only on smallest screens
created_at: 2025-05-14T05:02:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,126 @@
+.page-header {
+  gap: 2rem;
+
+  @media (min-width: 22.5rem) {
+    gap: 3rem;
+  }
+
+  @media (min-width: 48rem) {
+    gap: 4rem;
+  }
+
+  @media (min-width: 80rem) {
+    gap: 2rem;
+  }
+
+  .page-header__title {
+    margin-block-start: 0;
+  }
+
+  .page-header__title,
+  .page-header__featured-content {
+    margin-block-end: 1.5rem;
+
+    @media (min-width: 48rem) {
+      margin-block-end: 2rem;
+    }
+
+    @media (min-width: 80rem) {
+      margin-block-end: 3rem;
+    }
+  }
+
+  .page-header__description {
+    margin-block-end: 1rem;
+
+    @media (min-width: 22.5rem) {
+      margin-block-end: 1.25rem;
+    }
+
+    @media (min-width: 48rem) {
+      margin-block-end: 2rem;
+    }
+  }
+
+  .page-header__description--featured {
path: blocks/page-header/page-header.css
user: login: catheraaine
body: this variant styles the description differently when an author name/title exist
created_at: 2025-05-14T05:03:04Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,95 @@
+export default async function decorate(block) {
+  // parse data into an object
+  const pageHeaderData = {
+    title: block.children?.[0]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    image: block.children?.[1]?.children?.[0]?.firstElementChild,
+    description: block.children?.[2]?.children?.[0]?.children?.[0]?.innerHTML,
+    featuredSubtitle: block.children?.[3]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    featuredDescription: block.children?.[3]?.children?.[1]?.children?.[0]?.textContent?.trim(),
+    anchorNode: block.children?.[4]?.children?.[0]?.firstChild?.firstChild,
+    buttonStyle: block.children?.[4]?.children?.[1]?.children?.[0]?.textContent?.trim().toLowerCase(),
+    altText: block.children?.[4]?.children?.[2]?.children?.[0]?.textContent?.trim(),
+  };
+
+  // create the container element
+  const pageHeader = document.createElement("div");
+  pageHeader.classList.add("page-header", "grid-container");
+
+  const pageHeaderContent = document.createElement("div");
+  pageHeaderContent.classList.add("page-header", "grid-item", "grid-item--50");
+
+  // there should always be a title, so create it as an h1
+  const pageTitle = document.createElement("h1");
+  pageTitle.classList.add("page-header__title", "util-heading-xl");
+  pageTitle.innerText = pageHeaderData.title;
+  pageHeaderContent.append(pageTitle);
+
+  // if there is a description, add it as an h2
+  if (pageHeaderData.description) {
+    const pageDescription = document.createElement("div");
+    pageDescription.classList.add("page-header__description");
+
+    // if there is a featured subtitle, the description uses
+    // a different font class, for visual hierarchy
+    pageHeaderData.featuredSubtitle
path: blocks/page-header/page-header.js
user: login: catheraaine
body: I don't like how I named these, do you have suggestions? I think I am okay naming after content here, e.g. `page-header__author`
created_at: 2025-05-14T05:03:46Z
author_association: COLLABORATOR
diff_hunk: @@ -455,7 +457,11 @@ a:hover {
 
 /* ** Grid Layout Styles **  */
 main > .section > .grid-container {
-  margin-block: 4rem;
+  margin-block: 2rem;
+
+  @media (min-width: 48rem) {
+    margin-block: 4rem;
+  }
path: styles/styles.css
user: login: catheraaine
body: wanna fight about it
created_at: 2025-05-14T05:04:36Z
author_association: COLLABORATOR
diff_hunk: @@ -208,10 +208,12 @@ a:hover {
     from var(--spectrum-black) r g b / 9%
   );
   --color-button-text-primary: var(--spectrum-white);
-
   background-color: var(--color-button-background-primary);
   color: var(--color-button-text-primary);
 
+  -webkit-font-smoothing: antialiased;
path: styles/styles.css
user: login: arnest00
body: at what point should we just target all text all the time
created_at: 2025-05-15T03:36:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,126 @@
+.page-header {
+  gap: 2rem;
+
+  @media (min-width: 22.5rem) {
+    gap: 3rem;
+  }
+
+  @media (min-width: 48rem) {
+    gap: 4rem;
+  }
+
+  @media (min-width: 80rem) {
+    gap: 2rem;
path: blocks/page-header/page-header.css
user: login: arnest00
body: I'm inclined to agree. Can we get rid of it and keep it at 4rem starting from tablet screen?
created_at: 2025-05-15T04:03:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,95 @@
+export default async function decorate(block) {
+  // parse data into an object
+  const pageHeaderData = {
+    title: block.children?.[0]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    image: block.children?.[1]?.children?.[0]?.firstElementChild,
+    description: block.children?.[2]?.children?.[0]?.children?.[0]?.innerHTML,
+    featuredSubtitle: block.children?.[3]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    featuredDescription: block.children?.[3]?.children?.[1]?.children?.[0]?.textContent?.trim(),
+    anchorNode: block.children?.[4]?.children?.[0]?.firstChild?.firstChild,
+    buttonStyle: block.children?.[4]?.children?.[1]?.children?.[0]?.textContent?.trim().toLowerCase(),
+    altText: block.children?.[4]?.children?.[2]?.children?.[0]?.textContent?.trim(),
+  };
path: blocks/page-header/page-header.js
user: login: arnest00
body: That optional chaining operator is a great addition. We'll want to use it in all of our blocks with optional fields.
created_at: 2025-05-15T04:10:49Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,95 @@
+export default async function decorate(block) {
+  // parse data into an object
+  const pageHeaderData = {
+    title: block.children?.[0]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    image: block.children?.[1]?.children?.[0]?.firstElementChild,
+    description: block.children?.[2]?.children?.[0]?.children?.[0]?.innerHTML,
+    featuredSubtitle: block.children?.[3]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    featuredDescription: block.children?.[3]?.children?.[1]?.children?.[0]?.textContent?.trim(),
+    anchorNode: block.children?.[4]?.children?.[0]?.firstChild?.firstChild,
+    buttonStyle: block.children?.[4]?.children?.[1]?.children?.[0]?.textContent?.trim().toLowerCase(),
+    altText: block.children?.[4]?.children?.[2]?.children?.[0]?.textContent?.trim(),
+  };
+
+  // create the container element
+  const pageHeader = document.createElement("div");
+  pageHeader.classList.add("page-header", "grid-container");
+
+  const pageHeaderContent = document.createElement("div");
+  pageHeaderContent.classList.add("page-header", "grid-item", "grid-item--50");
+
+  // there should always be a title, so create it as an h1
+  const pageTitle = document.createElement("h1");
+  pageTitle.classList.add("page-header__title", "util-heading-xl");
+  pageTitle.innerText = pageHeaderData.title;
+  pageHeaderContent.append(pageTitle);
+
+  // if there is a description, add it as an h2
+  if (pageHeaderData.description) {
+    const pageDescription = document.createElement("div");
+    pageDescription.classList.add("page-header__description");
+
+    // if there is a featured subtitle, the description uses
+    // a different font class, for visual hierarchy
+    pageHeaderData.featuredSubtitle
path: blocks/page-header/page-header.js
user: login: arnest00
body: We're giving the outer `.grid-container` the `.page-header` class and the inner `.grid-item` the same class. Can we differentiate these if we need to? As far as naming goes, I'm not opposed either. How about (using the class names to short-hand):

```html
<page-header> <!-- assuming we're keeping this outer `div` -->
  <page-header__content>
    <page-header__title />
    <page-header__description />
    <page-header-byline> 
      <page-header-byline__author />
      <page-header-byline__job-title />
    </page-header-byline>
  </page-header__content>
  <page-header__button>
</page-header>
```

created_at: 2025-05-15T05:40:33Z
author_association: COLLABORATOR
diff_hunk: @@ -455,7 +457,11 @@ a:hover {
 
 /* ** Grid Layout Styles **  */
 main > .section > .grid-container {
-  margin-block: 4rem;
+  margin-block: 2rem;
+
+  @media (min-width: 48rem) {
+    margin-block: 4rem;
+  }
path: styles/styles.css
user: login: arnest00
body: oh boy a fight

Can we do 1.25rem starting from mobile, 2rem from tablet, and 2.5rem from small desktop?
created_at: 2025-05-15T05:45:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,95 @@
+export default async function decorate(block) {
+  // parse data into an object
+  const pageHeaderData = {
+    title: block.children?.[0]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    image: block.children?.[1]?.children?.[0]?.firstElementChild,
+    description: block.children?.[2]?.children?.[0]?.children?.[0]?.innerHTML,
+    featuredSubtitle: block.children?.[3]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    featuredDescription: block.children?.[3]?.children?.[1]?.children?.[0]?.textContent?.trim(),
+    anchorNode: block.children?.[4]?.children?.[0]?.firstChild?.firstChild,
+    buttonStyle: block.children?.[4]?.children?.[1]?.children?.[0]?.textContent?.trim().toLowerCase(),
+    altText: block.children?.[4]?.children?.[2]?.children?.[0]?.textContent?.trim(),
+  };
+
+  // create the container element
+  const pageHeader = document.createElement("div");
+  pageHeader.classList.add("page-header", "grid-container");
+
+  const pageHeaderContent = document.createElement("div");
+  pageHeaderContent.classList.add("page-header", "grid-item", "grid-item--50");
+
+  // there should always be a title, so create it as an h1
+  const pageTitle = document.createElement("h1");
+  pageTitle.classList.add("page-header__title", "util-heading-xl");
+  pageTitle.innerText = pageHeaderData.title;
+  pageHeaderContent.append(pageTitle);
+
+  // if there is a description, add it as an h2
+  if (pageHeaderData.description) {
+    const pageDescription = document.createElement("div");
+    pageDescription.classList.add("page-header__description");
+
+    // if there is a featured subtitle, the description uses
+    // a different font class, for visual hierarchy
+    pageHeaderData.featuredSubtitle
path: blocks/page-header/page-header.js
user: login: catheraaine
body: copy and paste, my old enemy
created_at: 2025-05-15T14:40:27Z
author_association: COLLABORATOR
diff_hunk: @@ -208,10 +208,12 @@ a:hover {
     from var(--spectrum-black) r g b / 9%
   );
   --color-button-text-primary: var(--spectrum-white);
-
   background-color: var(--color-button-background-primary);
   color: var(--color-button-text-primary);
 
+  -webkit-font-smoothing: antialiased;
path: styles/styles.css
user: login: catheraaine
body: it's only light text on dark backgrounds! that's why it's not in the default spec
created_at: 2025-05-15T14:46:12Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,95 @@
+export default async function decorate(block) {
+  // parse data into an object
+  const pageHeaderData = {
+    title: block.children?.[0]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    image: block.children?.[1]?.children?.[0]?.firstElementChild,
+    description: block.children?.[2]?.children?.[0]?.children?.[0]?.innerHTML,
+    featuredSubtitle: block.children?.[3]?.children?.[0]?.children?.[0]?.textContent?.trim(),
+    featuredDescription: block.children?.[3]?.children?.[1]?.children?.[0]?.textContent?.trim(),
+    anchorNode: block.children?.[4]?.children?.[0]?.firstChild?.firstChild,
+    buttonStyle: block.children?.[4]?.children?.[1]?.children?.[0]?.textContent?.trim().toLowerCase(),
+    altText: block.children?.[4]?.children?.[2]?.children?.[0]?.textContent?.trim(),
+  };
+
+  // create the container element
+  const pageHeader = document.createElement("div");
+  pageHeader.classList.add("page-header", "grid-container");
+
+  const pageHeaderContent = document.createElement("div");
+  pageHeaderContent.classList.add("page-header", "grid-item", "grid-item--50");
+
+  // there should always be a title, so create it as an h1
+  const pageTitle = document.createElement("h1");
+  pageTitle.classList.add("page-header__title", "util-heading-xl");
+  pageTitle.innerText = pageHeaderData.title;
+  pageHeaderContent.append(pageTitle);
+
+  // if there is a description, add it as an h2
+  if (pageHeaderData.description) {
+    const pageDescription = document.createElement("div");
+    pageDescription.classList.add("page-header__description");
+
+    // if there is a featured subtitle, the description uses
+    // a different font class, for visual hierarchy
+    pageHeaderData.featuredSubtitle
path: blocks/page-header/page-header.js
user: login: catheraaine
body: your dom, sir, as requested 

<img width="661" alt="Screenshot 2025-05-15 at 10 54 22 AM" src="https://github.com/user-attachments/assets/14303f9b-2e2e-4365-864d-00e26c2cf9be" />

created_at: 2025-05-15T14:54:43Z
author_association: COLLABORATOR

PR comments:
user: login: aem-code-sync[bot]
created_at: 2025-05-14T04:33:31Z
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 -->

* [3ce699d](https://github.com/adobe/adobe-design-website/commit/3ce699dc905f51c62776df4bd74580c87529efa6) :white_check_mark: (latest)
* [3eae5c3](https://github.com/adobe/adobe-design-website/commit/3eae5c31fe08690ed7448151800537db06fc9824) :white_check_mark:

<!-- commit-history-end -->
</details>
author_association: NONE
user: login: aem-code-sync[bot]
created_at: 2025-05-14T04:33:50Z
body: <!-- aem-bot-psi -->
| | Page | Scores | Audits | Google |
|-|------|--------|--------|--------|
| :iphone: | [/](https://page-header--adobe-design-website--adobe.aem.page/) | [![PERFORMANCE](https://img.shields.io/badge/PERFORMANCE-96-green?style=for-the-badge "Performance")](https://googlechrome.github.io/lighthouse/scorecalc/#device=mobile&version=12.6.0&SI=2258&FCP=2258&LCP=2258&TBT=0&CLS=0.0007288480235050046 "See Calculator") ![A11Y](https://img.shields.io/badge/A11Y-96-green?style=for-the-badge "Accessibility") ![SEO](https://img.shields.io/badge/SEO-69-orange?style=for-the-badge "SEO") ![BEST PRACTICES](https://img.shields.io/badge/BEST%20PRACTICES-96-green?style=for-the-badge "Best Practices") | ![SI](https://img.shields.io/badge/SI-2.3s-green?style=for-the-badge "Speed Index") ![FCP](https://img.shields.io/badge/FCP-2.3s-orange?style=for-the-badge "First Contentful Paint") ![LCP](https://img.shields.io/badge/LCP-2.3s-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.001-green?style=for-the-badge "Cumulative Layout Shift") | [![PSI](https://www.gstatic.com/images/icons/material/product/2x/pagespeed_16dp.png)](https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fpage-header--adobe-design-website--adobe.aem.page%2F "Run again in Google PSI") |
| :desktop_computer: | [/](https://page-header--adobe-design-website--adobe.aem.page/) | [![PERFORMANCE](https://img.shields.io/badge/PERFORMANCE-99-green?style=for-the-badge "Performance")](https://googlechrome.github.io/lighthouse/scorecalc/#device=mobile&version=12.6.0&SI=724&FCP=724&LCP=929.5&TBT=0&CLS=0.0013964936728921504 "See Calculator") ![A11Y](https://img.shields.io/badge/A11Y-96-green?style=for-the-badge "Accessibility") ![SEO](https://img.shields.io/badge/SEO-69-orange?style=for-the-badge "SEO") ![BEST PRACTICES](https://img.shields.io/badge/BEST%20PRACTICES-96-green?style=for-the-badge "Best Practices") | ![SI](https://img.shields.io/badge/SI-0.7s-green?style=for-the-badge "Speed Index") ![FCP](https://img.shields.io/badge/FCP-0.7s-green?style=for-the-badge "First Contentful Paint") ![LCP](https://img.shields.io/badge/LCP-0.9s-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.001-green?style=for-the-badge "Cumulative Layout Shift") | [![PSI](https://www.gstatic.com/images/icons/material/product/2x/pagespeed_16dp.png)](https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fpage-header--adobe-design-website--adobe.aem.page%2F "Run again in Google PSI") |

author_association: NONE