> ## 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 an Influencer Collab Page

> Build a dedicated landing page for an influencer collection or brand partnership.

export const Device = ({prompt, desktopImageSource, mobileImageSource, imageAlt = "Template preview", buttonCta = "Build in Replo"}) => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  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 API_BASE = "https://api.replo.app";
  const APP_SIGNUP_URL = "https://dashboard.replo.app/auth/signup";
  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 LoaderIcon = <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" style={{
    animation: "spin 1s linear infinite"
  }}>
      <path d="M21 12a9 9 0 1 1-6.219-8.56" />
    </svg>;
  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>;
  async function postJSON(url, body, headers = {}, timeoutMs = 120000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...headers
        },
        body: JSON.stringify(body ?? ({})),
        signal: controller.signal
      });
      const responseText = await response.text();
      if (!response.ok) throw new Error(`API Error: ${response.status}`);
      return responseText ? JSON.parse(responseText) : {};
    } finally {
      clearTimeout(timeoutId);
    }
  }
  async function handleTryPrompt(event) {
    event.preventDefault();
    event.stopPropagation();
    setIsLoading(true);
    setError(null);
    try {
      const body = {
        prompt,
        file: null
      };
      const {seed} = await postJSON(`${API_BASE}/api/v1/marketing/issue-marketing-site-jwt`, body);
      if (!seed) throw new Error("No seed returned from API");
      const url = new URL(APP_SIGNUP_URL);
      url.hash = `seed=${encodeURIComponent(seed)}`;
      window.open(url.toString(), "_blank");
      setIsLoading(false);
    } catch (caughtError) {
      console.error("Failed to generate prompt:", caughtError);
      const message = caughtError instanceof Error ? caughtError.message : null;
      setError(message ?? "Something went wrong. Please try again.");
      setIsLoading(false);
    }
  }
  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
  }}>
            <button type="button" onClick={handleTryPrompt} disabled={isLoading} 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: isLoading ? "not-allowed" : "pointer",
    textAlign: "center",
    lineHeight: "1.5rem",
    whiteSpace: "nowrap",
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    WebkitFontSmoothing: "antialiased",
    boxSizing: "border-box",
    opacity: isLoading ? 0.7 : 1,
    transition: "background-color 0.2s ease"
  }} onMouseEnter={event => {
    if (!isLoading) {
      event.currentTarget.style.backgroundColor = "rgb(59, 94, 246)";
    }
  }} onMouseLeave={event => {
    if (!isLoading) {
      event.currentTarget.style.backgroundColor = "rgb(39, 74, 226)";
    }
  }}>
              <span style={{
    visibility: isLoading ? "hidden" : "visible",
    display: "inline-flex",
    alignItems: "center",
    gap: "0.5rem"
  }}>
                {buttonCta}
                {ChevronIcon}
              </span>
              {isLoading && <span style={{
    position: "absolute",
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center"
  }}>
                  {LoaderIcon}
                </span>}
            </button>
          </div>}
      </div>

      {}
      <style>
        {`
          @keyframes spin {
            from {
              transform: rotate(0deg);
            }
            to {
              transform: rotate(360deg);
            }
          }
          
          .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>

      {}
      {error && <div style={{
    marginTop: "12px",
    padding: "12px 16px",
    backgroundColor: "#fee",
    border: "1px solid #fcc",
    borderRadius: "6px",
    color: "#c33",
    fontSize: "14px",
    textAlign: "center"
  }} role="alert">
          {error}
        </div>}
    </div>;
};

<Device
  prompt={`Recreate this collaboration page for me: https://www.warbyparker.com/emma-chamberlain-2024. Adapt it to my brand and collaboration details.

Structure:
- Hero section announcing the collaboration with large lifestyle image
- Story section explaining the partnership and inspiration
- Product showcase featuring 6-8 items from the collection
- Editorial photo gallery showing the collection in use
- Call-to-action sections driving to shop the full collection
- Social proof or quote from the collaborator

Use an editorial layout that feels premium. Mix lifestyle photography with clean product shots. Make the collaborator's name prominent throughout.`}
  mobileImageSource="/images/use-cases/collaboration/mobile.png"
  desktopImageSource="/images/use-cases/collaboration/desktop.png"
  buttonCta="Build in Replo"
  imageAlt="Collab Collection Page Example"
/>

## Step 1: Start Your Collaboration Page with a Prompt

### Starter prompt

The more specific you are about the collaboration details and visual direction, the better the output.

```
Recreate this collaboration page for me: https://www.warbyparker.com/emma-chamberlain-2024. Adapt it to my brand and collaboration details.

Structure:
- Hero section announcing the collaboration with large lifestyle image
- Story section explaining the partnership and inspiration
- Product showcase featuring 6-8 items from the collection
- Editorial photo gallery showing the collection in use
- Call-to-action sections driving to shop the full collection
- Social proof or quote from the collaborator

Use an editorial layout that feels premium. Mix lifestyle photography with clean product shots. Make the collaborator's name prominent throughout.
```

## Step 2: Customize Your Content

Replace the AI-generated content with your actual collaboration details, product lineup, and brand story.

<AccordionGroup>
  <Accordion title="Update collaboration details">
    Replace placeholder content with your actual partnership information.

    ```
    Update the collaboration details:

    Partner: [Influencer/Designer Name]
    Collection Name: [Brand] x [Partner Name]
    Launch Date: [Date]
    Story: [2-3 sentences about the inspiration and creative process]

    Hero Headline: [Main collaboration announcement]
    Subheadline: [Supporting text about the collection]
    ```
  </Accordion>

  <Accordion title="Add your product lineup">
    Collaboration pages convert best when they showcase the hero products immediately. Feature your highest-margin items and best sellers first.

    ```
    Feature these products from the collection:

    Product 1: [Name]
    Image: [URL]
    Price: $[X]

    Product 2: [Name]
    Image: [URL]
    Price: $[X]

    Product 3: [Name]
    Image: [URL]
    Price: $[X]

    Continue for 6-8 products total. Show prices upfront, hiding pricing on collaboration pages typically lowers CVR by 30-40%.
    ```
  </Accordion>

  <Accordion title="Add lifestyle imagery">
    Collaboration pages need strong visual storytelling. Use images of the collaborator wearing or using the products.

    ```
    Replace the lifestyle images with:

    Hero image: [URL of main collaboration hero shot]
    Gallery image 1: [URL showing collaborator with products]
    Gallery image 2: [URL showing product details]
    Gallery image 3: [URL showing lifestyle context]

    Use high-resolution images (minimum 1920px wide) for the hero section.
    ```

    <Tip>
      If you don't have professional photoshoot assets yet, launch with product photography and lifestyle images from the influencer's existing content. You can update the page with campaign assets when they're ready.
    </Tip>
  </Accordion>

  <Accordion title="Add collaborator quote or story">
    Including the collaborator's voice increases authenticity and can improve conversion by 15-25% for their audience.

    ```
    Add this quote section:

    Quote: "[Actual quote from collaborator about the collection or partnership]"
    Attribution: [Collaborator Name]

    Or story section:
    [2-3 paragraphs about the creative process, inspiration, or what makes this collaboration unique]
    ```
  </Accordion>
</AccordionGroup>

## Step 3: Personalize the Design

The output will use your brand styles automatically. But collaboration pages often need a hybrid visual identity that represents both partners.

<Tabs>
  <Tab title="From a URL">
    Reference any URL to pull design inspiration, whether it's a competitor's collaboration page or your partner's brand aesthetic.

    **How to:**

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

    I like how it uses [large hero images, split-screen layouts, bold typography, editorial spacing].
    ```

    This works well when you want to match the visual sophistication of successful collaboration launches.
  </Tab>

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

    **How to:**

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

    I want the same [hero treatment, product grid, color scheme, typography hierarchy].
    ```

    This is especially useful when your collaborator has specific design preferences or brand guidelines you need to follow.
  </Tab>

  <Tab title="From a Prompt">
    Use natural language to describe the visual identity. Be specific about how you want to balance both brand aesthetics.

    **Example:**

    ```
    I want this collaboration page to [blend our minimal aesthetic with the collaborator's bold, colorful style].

    Use [our brand fonts and layout structure], but incorporate [their signature colors] in accents, CTAs, and product highlights.

    The hero section should feel [premium and editorial], while the product showcase should feel [energetic and approachable].
    ```

    <Tip>
      For high-profile collaborations, consider creating a unique color palette or visual treatment that doesn't exist on either brand's main site. This signals exclusivity and can increase perceived value.
    </Tip>
  </Tab>
</Tabs>

## Step 4: Set Up Your Links

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

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

Or to update all product links at once:

```
Update all product cards to link to their collection products:
[Product 1] → [URL 1]
[Product 2] → [URL 2]
[Product 3] → [URL 3]
```

For collection CTAs:

```
This "Shop the Collection" button should link to [Collection URL or dedicated Shopify collection page].
```

<Note>
  If your collaboration has limited inventory, link directly to product pages instead of collection pages. This reduces friction and prevents out-of-stock disappointment.
</Note>

## Step 5: Publish

Once your collaboration 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 to share preview links with your collaborator for approval.
2. **Add a custom domain**: connect your own domain to publish under your brand's URL (e.g. `yourbrand.com/pages/collaboration-name`).

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

Click **Publish** again to make your collaboration page live.

## Collaboration Page Performance Tips

Collaboration pages have short, intense traffic spikes. Optimize for that launch window:

* **Launch timing matters**: coordinate your paid media push with when the collaborator posts to their audience. Simultaneous traffic from both sides drives the highest conversion.
* **Track source-specific performance**: traffic from the collaborator's audience typically has higher CVR but lower AOV than your existing customers. Set different target CAC by source.
* **Use urgency correctly**: "Limited edition" or "While supplies last" increases urgency without requiring actual inventory scarcity. But if you claim limited quantities, track inventory in real-time.
* **Retarget immediately**: collaboration launches generate large awareness spikes. Set up retargeting campaigns in advance to capture interest from visitors who don't convert on first visit.
* **Test collaborator prominence**: some brands see better performance when the influencer's name is dominant in the hero. Others convert better when the brand is primary. Test headline order: "\[Brand] x \[Influencer]" vs "\[Influencer] x \[Brand]".
* **Email both lists**: if your collaborator has an email list, coordinate sends. Traffic from email typically converts 2-3x higher than cold social traffic.

<Warning>
  Don't launch a collaboration page without coordinating the publish time with your partner. Unsynced launches leave traffic on the table, if they announce before your page is live, or vice versa, you'll lose the momentum spike.
</Warning>
