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

# Generate branded decks, proposals, and reports

> Map company branding into a reviewed document theme, render approved content, and preserve the source version with each export.

export const AgentSetupPrompt = ({children, variant = "setup"}) => {
  const isRecipe = variant === "recipe";
  const promptLabel = isRecipe ? "recipe prompt" : "setup prompt";
  const [copyState, setCopyState] = useState("idle");
  const contentRef = useRef(null);
  const detailsRef = useRef(null);
  const timerRef = useRef(null);
  const mountedRef = useRef(true);
  const pendingRef = useRef(false);
  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
      if (timerRef.current !== null) clearTimeout(timerRef.current);
    };
  }, []);
  const copyPrompt = useCallback(async () => {
    if (pendingRef.current) return;
    pendingRef.current = true;
    if (timerRef.current !== null) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }
    setCopyState("copying");
    try {
      const code = contentRef.current?.querySelector("pre code") ?? contentRef.current?.querySelector("pre");
      const prompt = code?.textContent;
      if (typeof prompt !== "string" || !prompt.trim()) {
        throw new Error("Prompt is not available.");
      }
      if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
        throw new Error("Clipboard access is not available.");
      }
      await navigator.clipboard.writeText(prompt);
      if (!mountedRef.current) return;
      setCopyState("copied");
      timerRef.current = setTimeout(() => {
        if (mountedRef.current) setCopyState("idle");
        timerRef.current = null;
      }, 4000);
    } catch {
      if (!mountedRef.current) return;
      if (detailsRef.current) detailsRef.current.open = true;
      setCopyState("error");
    } finally {
      pendingRef.current = false;
    }
  }, []);
  const feedback = copyState === "copied" ? `${isRecipe ? "Recipe" : "Setup"} prompt copied. Paste it into your coding agent.` : copyState === "error" ? "Couldn't copy automatically. The prompt is open below. Select and copy the text manually." : "";
  return <section className="agent-setup-prompt not-prose" aria-label={`Agent ${promptLabel}`}>
      <style>{`
        .agent-setup-prompt {
          --asp-surface: oklch(0.985 0.005 255);
          --asp-border: oklch(0.9 0.012 255);
          --asp-title: oklch(0.25 0.025 255);
          --asp-text: oklch(0.45 0.021 255);
          --asp-accent: oklch(0.49 0.2 264);
          --asp-accent-hover: oklch(0.44 0.19 264);
          --asp-on-accent: oklch(0.985 0.005 255);
          --asp-focus: oklch(0.6 0.19 255);
          margin: 1.5rem 0;
          border: 1px solid var(--asp-border);
          border-radius: 0.875rem;
          background: var(--asp-surface);
          color: var(--asp-title);
          font-family: inherit;
          min-width: 0;
        }
        .dark .agent-setup-prompt {
          --asp-surface: oklch(0.2 0.014 255);
          --asp-border: oklch(0.33 0.016 255);
          --asp-title: oklch(0.96 0.006 255);
          --asp-text: oklch(0.78 0.013 255);
          --asp-accent: oklch(0.73 0.15 255);
          --asp-accent-hover: oklch(0.8 0.11 255);
          --asp-on-accent: oklch(0.19 0.025 255);
          --asp-focus: oklch(0.78 0.14 255);
        }
        .agent-setup-prompt__header { padding: 1.125rem 1.25rem; }
        .agent-setup-prompt__row {
          display: flex;
          align-items: flex-start;
          justify-content: space-between;
          gap: 1.25rem;
        }
        .agent-setup-prompt__intro { min-width: 0; }
        .agent-setup-prompt__title {
          margin: 0;
          font-size: 1.125rem;
          font-weight: 650;
          line-height: 1.4;
          color: var(--asp-title);
        }
        .agent-setup-prompt__subtitle {
          max-width: 65ch;
          margin: 0.375rem 0 0;
          font-size: 0.875rem;
          line-height: 1.6;
          color: var(--asp-text);
        }
        .agent-setup-prompt__copy {
          display: inline-flex;
          flex-shrink: 0;
          align-items: center;
          justify-content: center;
          gap: 0.5rem;
          min-height: 2.75rem;
          padding: 0.625rem 0.875rem;
          border: 1px solid transparent;
          border-radius: 0.5rem;
          background: var(--asp-accent);
          color: var(--asp-on-accent);
          font: inherit;
          font-size: 0.8125rem;
          font-weight: 600;
          line-height: 1.4;
          cursor: pointer;
        }
        .agent-setup-prompt__copy:hover:not(:disabled) {
          background: var(--asp-accent-hover);
        }
        .agent-setup-prompt__copy:disabled { cursor: wait; }
        .agent-setup-prompt__copy:focus-visible,
        .agent-setup-prompt__summary:focus-visible {
          outline: 2px solid var(--asp-focus);
          outline-offset: 3px;
        }
        .agent-setup-prompt__feedback {
          margin: 0.75rem 0 0;
          font-size: 0.8125rem;
          line-height: 1.5;
          color: var(--asp-text);
        }
        .agent-setup-prompt__feedback:empty { margin: 0; }
        .agent-setup-prompt__details { border-top: 1px solid var(--asp-border); }
        .agent-setup-prompt__summary {
          display: flex;
          align-items: center;
          gap: 0.5rem;
          padding: 0.75rem 1.25rem;
          border-radius: 0 0 0.875rem 0.875rem;
          font-size: 0.8125rem;
          font-weight: 500;
          line-height: 1.5;
          color: var(--asp-text);
          list-style: none;
          cursor: pointer;
        }
        .agent-setup-prompt__summary::-webkit-details-marker { display: none; }
        .agent-setup-prompt__summary:hover { color: var(--asp-title); }
        .agent-setup-prompt__details[open] .agent-setup-prompt__chevron {
          transform: rotate(90deg);
        }
        .agent-setup-prompt__content { min-width: 0; padding: 0 1.25rem 1rem; }
        .agent-setup-prompt__content > :first-child { margin-top: 0; }
        .agent-setup-prompt__content > :last-child { margin-bottom: 0; }
        .agent-setup-prompt__content pre {
          max-height: 32rem;
          overflow: auto;
          white-space: pre-wrap;
          overflow-wrap: anywhere;
        }
        @media (max-width: 640px) {
          .agent-setup-prompt__header { padding: 1rem; }
          .agent-setup-prompt__row { flex-direction: column; gap: 0.875rem; }
          .agent-setup-prompt__copy { width: 100%; }
          .agent-setup-prompt__summary { padding: 0.75rem 1rem; }
          .agent-setup-prompt__content { padding: 0 1rem 1rem; }
        }
      `}</style>

      <div className="agent-setup-prompt__header">
        <div className="agent-setup-prompt__row">
          <div className="agent-setup-prompt__intro">
            <h2 className="agent-setup-prompt__title">
              {isRecipe ? "Have your agent implement this recipe" : "Set up Context.dev"}
            </h2>
            <p className="agent-setup-prompt__subtitle">
              {isRecipe ? "Copy this prompt into your coding agent to build the recipe in your project." : "Paste into your coding agent. It will ask about your goal and project before making changes."}
            </p>
          </div>
          <button className="agent-setup-prompt__copy" type="button" onClick={copyPrompt} disabled={copyState === "copying"} aria-busy={copyState === "copying"}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" aria-hidden="true" focusable="false">
              {copyState === "copied" ? <path d="m5 12 4 4L19 6" strokeLinecap="round" strokeLinejoin="round" /> : <>
                  <rect x="8" y="8" width="12" height="12" rx="2" />
                  <path d="M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3" />
                </>}
            </svg>
            {copyState === "copied" ? "Copied" : copyState === "copying" ? "Copying…" : `Copy ${promptLabel}`}
          </button>
        </div>
        <p className="agent-setup-prompt__feedback" role="status" aria-live="polite" aria-atomic="true">
          {feedback}
        </p>
      </div>

      <details className="agent-setup-prompt__details" ref={detailsRef}>
        <summary className="agent-setup-prompt__summary">
          <svg className="agent-setup-prompt__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" aria-hidden="true" focusable="false">
            <path d="m9 5 7 7-7 7" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
          {`View ${promptLabel}`}
        </summary>
        <div className="agent-setup-prompt__content" ref={contentRef}>
          {children}
        </div>
      </details>
    </section>;
};

<AgentSetupPrompt variant="recipe">
  ```text Recipe prompt theme={null}
  Implement this recipe in my project:
  https://docs.context.dev/use-cases/branded-documents

  Read the recipe and linked API guides, inspect this project's stack, and build the complete flow using its existing conventions.

  Turn a verified company domain into a reviewed document theme using Brand and optional Styleguide data. Apply approved content to a reusable report, proposal, or slide template and provide preview and export using the project's renderer. Keep author and client identities distinct, handle missing assets or fonts, preserve edits, and record the source and theme version for each export.

  Reuse existing Context.dev configuration and keep secret API keys on the server. Add focused tests, run the relevant checks, and document setup and how to try the result.
  ```
</AgentSetupPrompt>

Apply a company's identity to a reusable report, proposal, or slide template. Context.dev provides logos and observed website styles; your application supplies the content model, renderer, and PDF or presentation export.

[ChatSlide uses brand data for generated decks](https://www.context.dev/blog/chatslide-turns-ai-generated-slides-into-on-brand-decks-with-brand-dev), and [Kyndir creates branded client deliverables](https://www.context.dev/blog/kyndir-turns-brand-guides-into-client-ready-deliverables-with-context-dev). The workflow below uses a two-section React report that you can print to PDF.

## Gather a small source bundle

Start with an API key from the [Quickstart](/quickstart) and a verified company domain. On your server, retrieve the [Brand profile](/guides/retrieve-brand-by-domain) and, optionally, its [Styleguide](/guides/extract-design-system-from-website). Those guides include requests for every SDK.

| Source                               | Keep for the document                                                  |
| ------------------------------------ | ---------------------------------------------------------------------- |
| `brand.title` and `brand.domain`     | Suggested display name and lookup provenance                           |
| `brand.logos[]`                      | Reviewed asset, dimensions, and preferred background                   |
| `brand.colors[]`                     | Palette candidates; distinguish `source: "site"` from `source: "logo"` |
| Styleguide                           | Typography and layout references for an editor                         |
| Selected website Markdown, if needed | Sourced descriptions or claims that a reviewer approves                |

Treat each call independently. An unavailable Styleguide can use the template's default typography. A missing Brand profile can use a user-entered name and neutral theme. Gather website copy only when the document needs it; the client's website does not supply your proposal's pricing, commitments, or project results.

Save the domain, retrieval time, and an application version for the bundle. A retrieval timestamp describes when you received the response, not necessarily when the source website last changed.

## Approve the document theme

Keep the template contract smaller than the upstream response. The following application model is deliberately independent of the API schema:

```typescript document-theme.ts theme={null}
export type DocumentTheme = {
  clientName: string;
  accent: string;
  accentText: string;
  fontStack: string;
  logo: { url: string; width: number; height: number } | null;
  sourceDomain: string;
  sourceVersion: string;
};

export function accentText(hex: string) {
  if (!/^#[0-9a-f]{6}$/i.test(hex)) throw new Error("Use a six-digit hex color");
  const rgb = [1, 3, 5].map((offset) => parseInt(hex.slice(offset, offset + 2), 16) / 255);
  const [r, g, b] = rgb.map((c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
  const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
  const blackContrast = (luminance + 0.05) / 0.05;
  const whiteContrast = 1.05 / (luminance + 0.05);
  return blackContrast >= whiteContrast ? "#000000" : "#ffffff";
}

export function approveTheme(input: {
  clientName: string;
  accent?: string;
  logo?: DocumentTheme["logo"];
  sourceDomain: string;
  sourceVersion: string;
}): DocumentTheme {
  const accent = input.accent ?? "#334155";
  const logo = input.logo ?? null;
  if (logo && (
    new URL(logo.url).protocol !== "https:" ||
    !Number.isFinite(logo.width) || !Number.isFinite(logo.height) ||
    logo.width <= 0 || logo.height <= 0
  )) throw new Error("Approve a valid HTTPS logo with dimensions");

  return {
    ...input,
    accent,
    accentText: accentText(accent),
    logo,
    fontStack: "Arial, Helvetica, sans-serif",
  };
}
```

Call `approveTheme` with an editor's chosen color and logo. Keep headings and body text on a neutral surface; use the accent in bounded elements such as a label or rule. If you enable custom fonts, approve a loadable, licensed font and retain a tested fallback stack.

Use Brand assets for this workflow. Logo Link is for direct image embedding and does not allow downloading, persisting, or rehosting its returned image files.

## Render approved content

Keep author and client identities separate. In a proposal, **Prepared by** names the sender and **Prepared for** names the recipient; client styling does not imply that the client authored or approved the document.

The following React component imports the theme type from the previous example. Supply approved text and HTTPS source URLs. React escapes the text instead of accepting model-generated HTML.

```tsx ClientReport.tsx theme={null}
import type { DocumentTheme } from "./document-theme";

type Report = {
  title: string;
  author: string;
  date: string;
  summary: string;
  findings: Array<{ heading: string; text: string; sourceUrl: string | null }>;
};

export function ClientReport({ theme, report }: {
  theme: DocumentTheme;
  report: Report;
}) {
  const logo = theme.logo;
  const width = logo ? Math.min(160, 48 * logo.width / logo.height) : 0;
  const height = logo ? width * logo.height / logo.width : 0;

  return (
    <article className="client-report" style={{ fontFamily: theme.fontStack }}>
      <section className="report-page">
        <header>
          {logo && <img src={logo.url} alt={`${theme.clientName} logo`}
            width={width} height={height} style={{ objectFit: "contain" }} />}
          <p>Prepared for {theme.clientName}</p>
          <p>Prepared by {report.author} · {report.date}</p>
        </header>
        <span style={{ background: theme.accent, color: theme.accentText,
          display: "inline-block", padding: "6px 12px" }}>Client report</span>
        <h1>{report.title}</h1>
        <p>{report.summary}</p>
      </section>
      <section className="report-page">
        <h2>Findings</h2>
        {report.findings.map((finding, index) => (
          <section className="report-finding" key={index}>
            <h3>{finding.heading}</h3>
            <p>{finding.text}</p>
            {finding.sourceUrl && <p><a href={finding.sourceUrl}>Source</a></p>}
          </section>
        ))}
        <footer>Theme version: {theme.sourceVersion}</footer>
      </section>
    </article>
  );
}
```

```css report.css theme={null}
@page { size: A4; margin: 16mm; }
.client-report { color: #171717; background: #fff; line-height: 1.5; }
.report-page { max-width: 178mm; margin: 0 auto 32px; overflow-wrap: anywhere; }
.report-page img { max-width: 100%; }
.report-finding { break-inside: avoid; }
.client-report footer { margin-top: 24px; font-size: 12px; }
@media print {
  .report-page { margin: 0; break-after: page; }
  .report-page:last-child { break-after: auto; }
  .client-report { print-color-adjust: exact; }
}
```

For an editable slide deck, map the same approved theme and content fields to your slide renderer's master, text boxes, and image placements. That renderer creates the presentation file; Context.dev does not export a PDF or slide deck from the Brand response.

## Inspect the exported artifact

Before printing or running an export worker, wait for fonts and images to finish loading. If an asset fails, show the missing-asset state and let the user choose a replacement or the neutral theme. A preview that loaded an image earlier is not proof that a separate export worker can access it.

Render the same short report for two companies and a neutral fallback. Check long client names, very wide wordmarks, long findings, missing images, and the actual exported pages. Avoid clipping overflow to force a two-page count; shorten approved copy or allow another page instead.

Save the output with the content version, template version, approved theme, source-bundle version, and export time. Store manual logo and color choices separately from automatic candidates so a refresh creates a reviewable revision.

<CardGroup cols={2}>
  <Card title="Personalized sales demos" icon="presentation-screen" href="/use-cases/personalized-sales-demos">
    Combine prospect branding with sourced meeting context.
  </Card>

  <Card title="Generate branded websites" icon="browser" href="/use-cases/generate-branded-websites">
    Map the same brand context into editable website tokens.
  </Card>
</CardGroup>
