> ## Documentation Index
> Fetch the complete documentation index at: https://beta-docs.replo.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a Holiday Promotion Page

> Build a seasonal promotion page with a curated collection and a time-boxed offer.

export const DeviceToTemplate = ({desktopImageSource, mobileImageSource, imageAlt = "Template preview", buttonCta = "View Template", buttonUrl = "#"}) => {
  const [isMobile, setIsMobile] = useState(false);
  const [isDragging, setIsDragging] = useState(false);
  const [dragStart, setDragStart] = useState({
    x: 0,
    y: 0
  });
  const [scrollStart, setScrollStart] = useState({
    x: 0,
    y: 0
  });
  const [velocity, setVelocity] = useState({
    x: 0,
    y: 0
  });
  const [lastPos, setLastPos] = useState({
    x: 0,
    y: 0
  });
  const [lastTime, setLastTime] = useState(0);
  const [scrollContainer, setScrollContainer] = useState(null);
  const showToggle = desktopImageSource && mobileImageSource;
  const currentImage = showToggle && isMobile ? mobileImageSource : desktopImageSource;
  const handleMouseDown = (event, container) => {
    setIsDragging(true);
    setDragStart({
      x: event.clientX,
      y: event.clientY
    });
    setScrollStart({
      x: container.scrollLeft,
      y: container.scrollTop
    });
    setLastPos({
      x: event.clientX,
      y: event.clientY
    });
    setLastTime(Date.now());
    setVelocity({
      x: 0,
      y: 0
    });
    setScrollContainer(container);
    event.preventDefault();
  };
  const handleMouseMove = (event, container) => {
    if (!isDragging) return;
    const now = Date.now();
    const elapsedMs = now - lastTime;
    if (elapsedMs > 0) {
      const deltaX = event.clientX - dragStart.x;
      const deltaY = event.clientY - dragStart.y;
      const velocityX = (event.clientX - lastPos.x) / elapsedMs;
      const velocityY = (event.clientY - lastPos.y) / elapsedMs;
      container.scrollLeft = scrollStart.x - deltaX;
      container.scrollTop = scrollStart.y - deltaY;
      setVelocity({
        x: velocityX,
        y: velocityY
      });
      setLastPos({
        x: event.clientX,
        y: event.clientY
      });
      setLastTime(now);
    }
    event.preventDefault();
  };
  const handleMouseUp = () => {
    setIsDragging(false);
    if (scrollContainer && (Math.abs(velocity.x) > 0.1 || Math.abs(velocity.y) > 0.1)) {
      startMomentumScroll();
    }
  };
  const startMomentumScroll = () => {
    let currentVelocity = {
      ...velocity
    };
    const friction = 0.95;
    const minVelocity = 0.1;
    const animate = () => {
      if (!scrollContainer) return;
      scrollContainer.scrollLeft -= currentVelocity.x * 16;
      scrollContainer.scrollTop -= currentVelocity.y * 16;
      currentVelocity.x *= friction;
      currentVelocity.y *= friction;
      if (Math.abs(currentVelocity.x) > minVelocity || Math.abs(currentVelocity.y) > minVelocity) {
        requestAnimationFrame(animate);
      }
    };
    requestAnimationFrame(animate);
  };
  useEffect(() => {
    const handleGlobalMouseUp = () => {
      setIsDragging(false);
    };
    window.addEventListener("mouseup", handleGlobalMouseUp);
    return () => {
      window.removeEventListener("mouseup", handleGlobalMouseUp);
    };
  }, []);
  const ChevronIcon = <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="m9 18 6-6-6-6" />
    </svg>;
  return <div className="device-wrapper" style={{
    width: "100%",
    display: "flex",
    flexDirection: "column",
    alignItems: "center",
    position: "relative",
    paddingTop: showToggle ? "16px" : "0"
  }}>
      {}
      {showToggle && <div style={{
    display: "flex",
    gap: "0.5rem",
    alignItems: "center",
    padding: "0.25rem",
    backgroundColor: "#f3f4f6",
    borderRadius: "9999px",
    position: "absolute",
    top: "0",
    left: "50%",
    transform: "translateX(-50%)",
    zIndex: 20
  }}>
          <button onClick={() => setIsMobile(false)} style={{
    padding: "0.375rem 0.75rem",
    fontSize: "0.8125rem",
    fontWeight: "500",
    fontFamily: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
    border: "none",
    borderRadius: "9999px",
    cursor: "pointer",
    backgroundColor: !isMobile ? "white" : "transparent",
    color: !isMobile ? "#111827" : "#6b7280",
    boxShadow: !isMobile ? "0 1px 3px 0 rgba(0, 0, 0, 0.1)" : "none",
    transition: "all 0.2s ease"
  }}>
            Desktop
          </button>
          <button onClick={() => setIsMobile(true)} style={{
    padding: "0.375rem 0.75rem",
    fontSize: "0.8125rem",
    fontWeight: "500",
    fontFamily: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
    border: "none",
    borderRadius: "9999px",
    cursor: "pointer",
    backgroundColor: isMobile ? "white" : "transparent",
    color: isMobile ? "#111827" : "#6b7280",
    boxShadow: isMobile ? "0 1px 3px 0 rgba(0, 0, 0, 0.1)" : "none",
    transition: "all 0.2s ease"
  }}>
            Mobile
          </button>
        </div>}

      {}
      <div style={{
    position: "relative",
    display: "flex",
    justifyContent: "center",
    alignItems: "center",
    width: "100%"
  }} onMouseUp={handleMouseUp} onMouseLeave={handleMouseUp}>
        {isMobile && mobileImageSource ? <div className="device device-iphone-14-pro device-mobile-responsive" style={{
    transform: "scale(0.7)",
    transformOrigin: "center center"
  }}>
            <div className="device-frame">
              <div className="device-screen" style={{
    overflow: "auto",
    overflowX: "hidden",
    cursor: isDragging ? "grabbing" : "grab",
    userSelect: "none"
  }} onMouseDown={event => {
    handleMouseDown(event, event.currentTarget);
  }} onMouseMove={event => {
    handleMouseMove(event, event.currentTarget);
  }}>
                <img src={mobileImageSource} alt={imageAlt} loading="lazy" style={{
    width: "100%",
    height: "auto",
    display: "block",
    pointerEvents: "none"
  }} draggable="false" />
              </div>
            </div>
            <div className="device-stripe"></div>
            <div className="device-header"></div>
            <div className="device-sensors"></div>
            <div className="device-btns"></div>
            <div className="device-power"></div>
            <div className="device-home"></div>
          </div> : desktopImageSource ? <div className="device device-macbook-pro device-desktop-responsive" style={{
    transform: "scale(0.85)",
    transformOrigin: "center center"
  }}>
            <div className="device-frame">
              <div className="device-screen" style={{
    overflow: "auto",
    overflowX: "hidden",
    cursor: isDragging ? "grabbing" : "grab",
    userSelect: "none"
  }} onMouseDown={event => {
    handleMouseDown(event, event.currentTarget);
  }} onMouseMove={event => {
    handleMouseMove(event, event.currentTarget);
  }}>
                <img src={desktopImageSource} alt={imageAlt} loading="lazy" style={{
    width: "100%",
    height: "auto",
    display: "block",
    pointerEvents: "none"
  }} draggable="false" />
              </div>
            </div>
            <div className="device-stripe"></div>
            <div className="device-header"></div>
            <div className="device-sensors"></div>
            <div className="device-btns"></div>
            <div className="device-power"></div>
            <div className="device-home"></div>
          </div> : null}

        {}
        {currentImage && <div style={{
    position: "absolute",
    top: "50%",
    left: "50%",
    transform: "translate(-50%, -50%)",
    zIndex: 10
  }}>
            <a href={buttonUrl} target="_blank" rel="noopener noreferrer" style={{
    backgroundColor: "rgb(39, 74, 226)",
    color: "white",
    padding: "0.75rem 1.5rem",
    fontSize: "1rem",
    fontWeight: "600",
    fontFamily: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
    border: "none",
    borderRadius: "9999px",
    cursor: "pointer",
    textAlign: "center",
    lineHeight: "1.5rem",
    whiteSpace: "nowrap",
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    WebkitFontSmoothing: "antialiased",
    boxSizing: "border-box",
    textDecoration: "none",
    transition: "background-color 0.2s ease",
    gap: "0.5rem"
  }} onMouseEnter={event => {
    event.currentTarget.style.backgroundColor = "rgb(59, 94, 246)";
  }} onMouseLeave={event => {
    event.currentTarget.style.backgroundColor = "rgb(39, 74, 226)";
  }}>
              {buttonCta}
              {ChevronIcon}
            </a>
          </div>}
      </div>

      {}
      <style>
        {`
          .device-screen {
            scrollbar-width: none; /* Firefox */
            -ms-overflow-style: none; /* IE and Edge */
          }
          
          .device-screen::-webkit-scrollbar {
            display: none; /* Chrome, Safari, Opera */
          }

          /* Desktop - compensate for scaling with negative margins */
          .device-mobile-responsive {
            margin-top: -80px;
            margin-bottom: -80px;
          }

          .device-desktop-responsive {
            margin-top: 16px;
            margin-bottom: 16px;
          }

          /* Mobile viewports - simplified with fixed height and clipping */
          @media (max-width: 768px) {
            .device-wrapper {
              overflow: hidden;
            }

            .device-mobile-responsive {
              transform: scale(0.5) !important;
              margin-top: -160px;
              margin-bottom: -160px;
            }

            .device-desktop-responsive {
              transform: scale(0.5) !important;
              margin-top: -60px;
              margin-bottom: -60px;
            }
          }
        `}
      </style>
    </div>;
};

<DeviceToTemplate desktopImageSource="/images/use-cases/holiday-promo/desktop.png" mobileImageSource="/images/use-cases/holiday-promo/mobile.png" imageAlt="Holiday Promotion Landing Page Example" buttonCta="View Template" buttonUrl="https://try.replo.app/TvzVQfS" />

## Why Holiday Promotion Pages Work

Holiday shoppers behave differently. They're gift-hunting on a deadline, browsing for inspiration, and looking for deals. A standard product page doesn't serve these buyers.

Dedicated holiday promotion pages solve this by:

* **Curating products by gifting intent**: stocking stuffers, splurge-worthy items, gift sets
* **Creating urgency**: countdown timers, limited-time discounts, shipping deadlines
* **Reducing friction**: quick-add buttons, bundled offers, clear price points
* **Capturing seasonal traffic**: dedicated URLs for paid campaigns and email sends

## Step 1: Start Your Holiday Promotion Page with a Prompt

### Starter prompt

Be specific about your offer, product categories, and holiday theme.

```
Rebuild https://templates-8aa4.reploshops.com/holiday-scent-shop-1 exactly.

Structure:
- Hero section with a countdown timer and sitewide discount banner (e.g., "25% OFF SITEWIDE - LIMITED TIME")
- Scrolling category marquee (e.g., "festive favorites • seasonal delights • holiday essentials")
- "Giftable Combos" section with product cards and quick-add buttons
- "Seasonal Specials" section featuring 3 curated collections with price-point callouts (e.g., "Stocking Stuffers starting at $19")
- "Best Sellers" section with top-performing products
- Optional: Collaboration or "Guest" section highlighting partner products or influencer picks

Use warm holiday colors (reds, creams, golds). Products should have clear images, prices, and quick-add functionality. The page should feel curated and giftable, not like a generic product grid.
```

## Step 2: Customize Your Content

Replace the AI-generated content with your actual products, pricing, and holiday offer.

<AccordionGroup>
  <Accordion title="Set up your holiday offer">
    Your headline offer drives the page. Make it specific and compelling.

    ```
    Update the holiday offer:

    Discount: [X]% off sitewide (or specific category)
    Deadline: [Date] or countdown timer ending at [time]
    Shipping cutoff: "Order by [Date] for holiday delivery"

    Hero headline: [e.g., "THE HOLIDAY SHOP"]
    Banner text: [e.g., "LIMITED TIME: 25% OFF SITEWIDE"]
    ```

    <Tip>
      Countdown timers increase urgency, but only if they're real. Use them for actual sale endings or shipping deadlines, not fake scarcity.
    </Tip>
  </Accordion>

  <Accordion title="Build your product sections">
    Organize products by gifting intent, not just category. Holiday shoppers think in terms of "gifts for her" or "under \$50", not product type.

    ```
    Create these product sections:

    Section 1: Giftable Combos
    Products: [Bundle 1], [Bundle 2], [Bundle 3]
    Price points: Show both regular and discounted prices
    Add "quick add" buttons for impulse purchases

    Section 2: Seasonal Specials
    Collection 1: [Name] starting at $[X], [Product 1], [Product 2], [Product 3]
    Collection 2: [Name] starting at $[X], [Product 1], [Product 2], [Product 3]
    Collection 3: [Name] starting at $[X], [Product 1], [Product 2], [Product 3]

    Section 3: Best Sellers
    Show your top 3-6 products with pricing and quick-add buttons
    ```

    <Note>
      Price-point callouts like "starting at \$19" work better than category names. They help shoppers self-select by budget.
    </Note>
  </Accordion>

  <Accordion title="Add a scrolling marquee">
    Marquees create movement and guide shoppers through the page. Use them to highlight categories or create a festive feel.

    ```
    Add a scrolling marquee with these categories:
    [holiday essentials] • [festive favorites] • [seasonal delights] • [gifts under $50]

    Or use engagement copy:
    "keep scrolling 🔥" • "product lovers keep scrolling" • "more gifts below"
    ```
  </Accordion>

  <Accordion title="Include collaborations or curated picks">
    If you have influencer partnerships, guest curators, or brand collaborations, feature them in a dedicated section.

    ```
    Add a "Guest Picks" or "Collaborations" section:

    Collaborator 1: [Name], Link to their collection
    Collaborator 2: [Name], Link to their collection
    Collaborator 3: [Name], Link to their collection

    Include profile images or brand logos for each collaborator.
    ```
  </Accordion>
</AccordionGroup>

## Step 3: Personalize the Design

Holiday pages should feel festive but on-brand. Balance seasonal elements with your existing visual identity.

<Tabs>
  <Tab title="From a URL">
    Reference a holiday page you admire to pull design inspiration.

    **How to:**

    ```
    Can you replicate the layout from [input URL] and apply it to my holiday page?

    I like how it uses [countdown timer placement, product card styling, color palette, section transitions].
    ```

    This works well when you've found a holiday page structure that feels both festive and high-converting.
  </Tab>

  <Tab title="From a Screenshot">
    Drop an image to show the exact visual style you want.

    **How to:**

    ```
    Can you apply the layout from this image to my holiday promotion page?

    I want the same [hero treatment, product grid layout, color scheme, marquee style].
    ```

    This is useful when you have a design from your creative team or a competitor reference.
  </Tab>

  <Tab title="From a Prompt">
    Use natural language to describe the holiday aesthetic you want.

    **Example:**

    ```
    I want my holiday page to feel [warm and festive but not tacky].

    Use [rich reds, creams, and gold accents]. The hero should have a [cozy, premium feel] with a [prominent countdown timer].

    Product cards should be [clean with clear pricing]. Sections should have [generous spacing] and [subtle holiday touches without being overwhelming].

    Avoid [generic holiday clip art or cheesy fonts]. Keep it [elevated and brand-consistent].
    ```

    <Tip>
      Holiday pages don't need to scream "holidays" with every element. A few well-placed seasonal touches (color accents, festive copy, countdown timer) create the mood without overwhelming your brand.
    </Tip>
  </Tab>
</Tabs>

## Step 4: Set Up Your Links

Use [select mode](/features/edit-mode) to select product cards, collection cards, and CTAs. Once selected:

```
This product card should link to [Product URL].
```

For collection sections:

```
This "Stocking Stuffers" card should link to [Collection URL].
This "Shop Now" button should link to [Holiday Collection URL].
```

For collaborator sections:

```
Update all collaborator cards to link to their collections:
[Collaborator 1] → [Collection URL 1]
[Collaborator 2] → [Collection URL 2]
[Collaborator 3] → [Collection URL 3]
```

## Step 5: Publish

Once your holiday promotion page is ready, click **Publish** in the top-right corner:

1. **Publish to your Replo domain**: every workspace gets a default domain (e.g. `yourshop.replosites.com`). Use this for testing or sharing preview links.
2. **Add a custom domain**: connect your own domain to publish the page live under your brand's URL.

[*Read more about Publishing and Custom Domains*](/publishing)*.*

Click **Publish** again to make your holiday promotion page live.

## Holiday Promotion Performance Tips

Holiday pages drive revenue when they're promoted correctly and optimized for seasonal buyer behavior:

* **Run paid traffic directly to the holiday page**: don't send holiday campaign traffic to your homepage. Dedicated landing pages convert 2-3x better than generic pages.
* **Use shipping deadline urgency**: "Order by Dec 15 for guaranteed holiday delivery" is more compelling than a generic countdown. Real deadlines outperform manufactured urgency.
* **Test price-point sections**: "Gifts Under \$50" often converts better than category-based sections like "Skincare Gifts." Let shoppers self-select by budget.
* **Add quick-add functionality**: holiday shoppers buy multiple items. Quick-add buttons reduce friction for multi-item purchases.
* **Promote bundles prominently**: gift sets and bundles increase AOV by 40-60% during holiday periods. Feature them early in the page.
* **Track AOV by traffic source**: holiday email traffic typically has higher AOV than paid social. Segment your analytics to understand true performance.
* **Retarget holiday page visitors**: visitors who browse but don't buy are warm leads. Retarget them with shipping deadline reminders and low-stock alerts.

<Warning>
  Don't use the same holiday page all season. Update the countdown timer, swap in new products, and refresh the offer as inventory changes. Stale pages lose urgency.
</Warning>

## Holiday Page Conversion Benchmarks

Track these metrics to optimize your holiday promotion page:

**Holiday Page CVR**: percentage of visitors who purchase. Typical range: 3-8% for holiday-specific traffic (higher than year-round averages).

**Holiday AOV**: average order value from the holiday page. Should be 20-40% higher than regular AOV due to bundling and gifting behavior.

**Time on Page**: holiday shoppers browse longer when curating gifts. 2-4 minutes is typical for high-intent holiday traffic.

**Add-to-Cart Rate**: percentage of visitors who add at least one item. Target 15-25% for well-optimized holiday pages.

**Multi-Item Rate**: percentage of orders with 2+ items. Holiday pages should drive 40-60% multi-item orders due to gifting behavior.

<Tip>
  Holiday pages aren't just for Q4. Use the same format for Valentine's Day, Mother's Day, Father's Day, and back-to-school. The structure works for any gifting occasion, just swap the theme and products.
</Tip>
