SectionsSimpleApril 21, 2026

Product Feature Grid

A 3-column product feature section with centered badge overlays on product images, serif heading with italic emphasis, description, and a CTA button. Cream background, editorial styling. Shopify CMS-ready.

View Full Demo →

Gifts & Favors

The gift they'll actually use.

Baby showers. Birthdays. Just because. Our minis are the perfect way to share better skincare — or try a new scent yourself.

demo.jsx
import ProductFeatureGrid from "./index.jsx";
import { productFeatureGrid } from "../demo-data.js";

export default function ProductFeatureGridDemo() {
  return <ProductFeatureGrid {...productFeatureGrid} />;
}
index.jsx
"use client";

import styles from "./styles.module.css";

/*
  ── Shopify CMS Integration ──
  To pull products from Shopify instead of static data:

  // Storefront API query for a specific collection:
  // const COLLECTION_QUERY = `
  //   query ($handle: String!) {
  //     collection(handle: $handle) {
  //       products(first: 6) {
  //         nodes {
  //           id
  //           title
  //           handle
  //           priceRange {
  //             minVariantPrice { amount currencyCode }
  //           }
  //           featuredImage {
  //             url
  //             altText
  //           }
  //           metafield(namespace: "custom", key: "badge_label") {
  //             value
  //           }
  //           metafield(namespace: "custom", key: "details_line") {
  //             value
  //           }
  //         }
  //       }
  //     }
  //   }
  // `;
  //
  // Map response to items:
  // const items = data.collection.products.nodes.map(product => ({
  //   title: product.title,
  //   href: `/products/${product.handle}`,
  //   image: product.featuredImage?.url,
  //   badge: product.metafield?.value || product.title,
  //   details: product.metafield?.value ||
  //     `${product.priceRange.minVariantPrice.currencyCode}$${product.priceRange.minVariantPrice.amount}`,
  // }));
*/

export default function ProductFeatureGrid({
  label,
  heading,
  headingEmphasis,
  description,
  items = [],
  ctaLabel,
  ctaHref = "#",
}) {
  const renderHeading = () => {
    if (!headingEmphasis || !heading) return heading;
    const parts = heading.split(headingEmphasis);
    if (parts.length < 2) return heading;
    return (
      <>
        {parts[0]}<em>{headingEmphasis}</em>{parts[1]}
      </>
    );
  };

  return (
    <section className={styles.section}>
      {label && <p className={styles.label}>{label}</p>}
      {heading && <h2 className={styles.heading}>{renderHeading()}</h2>}
      {description && <p className={styles.description}>{description}</p>}

      <div className={styles.grid}>
        {items.map((item, i) => (
          <a key={i} href={item.href || "#"} className={styles.card}>
            <div className={styles.imageWrap}>
              <img src={item.image} alt={item.title} className={styles.image} />
              {item.badge && (
                <span className={styles.badge}>{item.badge}</span>
              )}
            </div>
            <h3 className={styles.title}>{item.title}</h3>
            {item.details && <p className={styles.details}>{item.details}</p>}
          </a>
        ))}
      </div>

      {ctaLabel && (
        <div className={styles.ctaWrap}>
          <a href={ctaHref} className={styles.cta}>{ctaLabel}</a>
        </div>
      )}
    </section>
  );
}
styles.module.css
.section {
  width: 100%;
  background: #f1ebe7;
  padding: 6rem 2rem 8rem;
  text-align: center;
}

/* ---- Label ---- */
.label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: #555;
  margin-bottom: 1.5rem;
}

/* ---- Heading ---- */
.heading {
  font-size: clamp(1.75rem, 3.5vw, 3rem);
  font-weight: 400;
  line-height: 1.2;
  color: #1a1a1a;
  margin-bottom: 1.25rem;
  font-family: Georgia, "Times New Roman", serif;
}

.heading em {
  font-style: italic;
}

/* ---- Description ---- */
.description {
  font-size: clamp(14px, 1.1vw, 16px);
  line-height: 1.6;
  color: #555;
  max-width: 50ch;
  margin: 0 auto 3rem;
}

/* ---- Grid ---- */
.grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  max-width: 1100px;
  margin: 0 auto;
}

/* ---- Card ---- */
.card {
  text-decoration: none;
  color: inherit;
  text-align: left;
  display: block;
}

/* ---- Image ---- */
.imageWrap {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: 0.25rem;
  margin-bottom: 1rem;
}

.image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s ease;
}

.card:hover .image {
  transform: scale(1.03);
}

/* ---- Badge ---- */
.badge {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: #1a1a1a;
  background: rgba(255, 255, 255, 0.85);
  padding: 0.5rem 1rem;
  white-space: nowrap;
  pointer-events: none;
}

/* ---- Title ---- */
.title {
  font-family: Georgia, "Times New Roman", serif;
  font-size: clamp(1rem, 1.4vw, 1.25rem);
  font-weight: 400;
  line-height: 1.3;
  color: #1a1a1a;
  margin-bottom: 0.25rem;
}

/* ---- Details ---- */
.details {
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  color: #777;
}

/* ---- CTA ---- */
.ctaWrap {
  margin-top: 3rem;
  text-align: center;
}

.cta {
  display: inline-block;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: #fff;
  background: #2a2a2a;
  padding: 1.1rem 2.5rem;
  text-decoration: none;
  transition: background 0.3s ease;
}

.cta:hover {
  background: #000;
}

/* ---- Desktop ---- */
@media (min-width: 768px) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ---- Mobile ---- */
@media (max-width: 767px) {
  .section {
    padding: 4rem 1rem 5rem;
  }

  .grid {
    gap: 2.5rem;
  }
}

May 11, 2026

SECTIONS

Dual Push Cards

Two-up CTA card section with scroll-driven parallax on each card's background image. Cards scale down and drift vertically as you scroll past. Glassmorphic blur buttons at bottom-left. Stacks on mobile, side-by-side grid on desktop.

May 4, 2026

SECTIONS

Portfolio Grid

A responsive portfolio showcase section with a header tagline, blinking cursor counters, a 2-up/4-col project card grid with hover-zoom images and data-label metadata, plus a full-width CTA button. Scroll-triggered fade and move-up animations via GSAP.

May 1, 2026

SECTIONS

Logo Wall Cycle

A responsive logo grid that cycles through brand logos with smooth GSAP-powered swap animations. Shows 8 logos on desktop and 6 on tablet, shuffling hidden logos into view on a timed loop. Pauses when out of viewport or tab is hidden.