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

# Hook Analysis Sprint

> Upload 10 video variants with different hooks, analyze the first 3 seconds with short chunk duration, and compare emotional intensity and cognitive load scores for data-driven hook optimization

## Mavera Surfaces

| Surface                                             | Role                                                              |
| --------------------------------------------------- | ----------------------------------------------------------------- |
| **Files** (`POST /files/upload-url`, `POST /files`) | Upload each hook variant                                          |
| **Video Analysis** (`POST /video-analyses`)         | Frame-level scoring with `chunk_duration: 3` to isolate the hook  |
| **Mave** (`POST /mave/chat`)                        | Compare first-chunk metrics across variants and recommend winners |

***

## What Value Does Mavera Add?

| Value                 | How                                                                                                               |
| --------------------- | ----------------------------------------------------------------------------------------------------------------- |
| **Insurance**         | Test 10 hooks before committing media budget. Kill weak openers with data, not opinion.                           |
| **Opening new doors** | First-chunk emotional intensity and cognitive load scores reveal *why* a hook works — not just *whether* it does. |
| **Saving time**       | Running 10 hook variants through human A/B testing takes weeks. This sprint takes minutes.                        |

***

## When to Use This

* You have a hero ad concept and need to decide which opening 3 seconds to ship.
* You're producing short-form content (TikTok, Reels, Shorts) where the hook *is* the ad.
* You want to quantify the "scroll-stopping power" of different opening strategies.
* You're building a hook playbook for your creative team and need data to back guidelines.

***

## What You Need

| Requirement                        | Details                                                                                              |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Mavera API key**                 | Starts with `mvra_live_`. Get one at [Developer Settings](https://app.mavera.io/settings/developer). |
| **Workspace ID**                   | From your dashboard URL (`ws_...`).                                                                  |
| **10 video variants**              | Same ad with 10 different hooks (first 3 seconds). MP4 or MOV, 6–15 s each.                          |
| **Credits**                        | \~100–200 per video + \~15–30 for Mave. See [Credits Estimate](#credits-estimate).                   |
| **Python 3.8+** or **Node.js 18+** | `requests` for Python; native `fetch` for Node.                                                      |

```
MAVERA_API_KEY=mvra_live_your_key_here
MAVERA_WORKSPACE_ID=ws_your_workspace_id
```

<Info>
  **Why 10 variants?** Fewer than 5 doesn't give enough signal. More than 15 adds cost without proportional insight. 10 is the sweet spot for a sprint.
</Info>

***

## The Flow

```mermaid theme={"dark"}
flowchart LR
    A["10 Variants"] --> B["Upload"]
    B --> C["Video Analysis"]
    C --> D["Extract chunk[0]"]
    D --> E["Mave Report"]
```

<Steps>
  <Step title="Prepare 10 hook variants">
    Same base ad, 10 different openings. Vary strategy: question, statistic, pain point, humor, visual shock, testimonial, product close-up, text overlay, music sting, silent open.
  </Step>

  <Step title="Upload all variants via Files API">
    Name by hook strategy: `hook_question.mp4`, `hook_humor.mp4`.
  </Step>

  <Step title="Run Video Analysis with chunk_duration: 3">
    The first chunk isolates exactly the hook. Higher `frames_per_chunk` (5) gives denser sampling.
  </Step>

  <Step title="Extract and compare first-chunk metrics">
    Pull `chunks[0]` from each. Compare `emotional_intensity`, `cognitive_load`, `engagement`, `attention`.
  </Step>

  <Step title="Synthesize with Mave">
    Feed the comparison table into Mave: "Rank these hooks. Which opening strategy wins and why?"
  </Step>
</Steps>

***

## Stage 1 — Upload Hook Variants

<CodeGroup>
  ```python Python theme={"dark"}
  import os, time, json, glob, requests

  API_KEY = os.environ["MAVERA_API_KEY"]
  WORKSPACE_ID = os.environ["MAVERA_WORKSPACE_ID"]
  BASE = "https://app.mavera.io/api/v1"
  HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}

  HOOK_STRATEGIES = [
      "question", "statistic", "pain_point", "humor", "visual_shock",
      "testimonial", "product_closeup", "text_overlay", "music_sting", "silent_open",
  ]


  def upload_video(path: str) -> dict:
      with open(path, "rb") as f:
          content = f.read()
      name = os.path.basename(path)
      mime = "video/mp4" if path.lower().endswith(".mp4") else "video/quicktime"

      url_resp = requests.post(f"{BASE}/files/upload-url", headers=HEADERS, json={
          "file_name": name, "file_type": mime, "file_size": len(content), "workspace_id": WORKSPACE_ID,
      }).json()
      if "error" in url_resp:
          raise Exception(url_resp["error"]["message"])

      requests.put(url_resp["upload_url"], data=content, headers={"Content-Type": mime}).raise_for_status()

      file_rec = requests.post(f"{BASE}/files", headers=HEADERS, json={
          "name": name, "type": mime, "url": url_resp["public_url"],
          "workspace_id": WORKSPACE_ID, "file_size": len(content),
      }).json()
      if "error" in file_rec:
          raise Exception(file_rec["error"]["message"])
      return {"id": file_rec["id"], "name": name}


  def upload_hook_variants(directory: str) -> list[dict]:
      paths = sorted(glob.glob(os.path.join(directory, "*.mp4"))
                     + glob.glob(os.path.join(directory, "*.mov")))
      if not paths:
          raise FileNotFoundError(f"No video files in {directory}")
      assets = []
      for i, p in enumerate(paths):
          asset = upload_video(p)
          asset["strategy"] = HOOK_STRATEGIES[i] if i < len(HOOK_STRATEGIES) else f"variant_{i+1}"
          print(f"  [{i+1}/{len(paths)}] {asset['name']} ({asset['strategy']}) → {asset['id']}")
          assets.append(asset)
      return assets
  ```

  ```javascript JavaScript theme={"dark"}
  const fs = require("fs");
  const path = require("path");

  const API_KEY = process.env.MAVERA_API_KEY;
  const WORKSPACE_ID = process.env.MAVERA_WORKSPACE_ID;
  const BASE = "https://app.mavera.io/api/v1";
  const HEADERS = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" };

  const HOOK_STRATEGIES = [
    "question", "statistic", "pain_point", "humor", "visual_shock",
    "testimonial", "product_closeup", "text_overlay", "music_sting", "silent_open",
  ];

  async function uploadVideo(videoPath) {
    const content = fs.readFileSync(videoPath);
    const name = path.basename(videoPath);
    const mime = videoPath.toLowerCase().endsWith(".mp4") ? "video/mp4" : "video/quicktime";

    const urlResp = await fetch(`${BASE}/files/upload-url`, {
      method: "POST", headers: HEADERS,
      body: JSON.stringify({ file_name: name, file_type: mime, file_size: content.length, workspace_id: WORKSPACE_ID }),
    }).then((r) => r.json());
    if (urlResp.error) throw new Error(urlResp.error.message);

    await fetch(urlResp.upload_url, { method: "PUT", body: content, headers: { "Content-Type": mime } });

    const fileRec = await fetch(`${BASE}/files`, {
      method: "POST", headers: HEADERS,
      body: JSON.stringify({ name, type: mime, url: urlResp.public_url, workspace_id: WORKSPACE_ID, file_size: content.length }),
    }).then((r) => r.json());
    if (fileRec.error) throw new Error(fileRec.error.message);
    return { id: fileRec.id, name };
  }

  async function uploadHookVariants(directory) {
    const files = fs.readdirSync(directory).filter((f) => /\.(mp4|mov)$/i.test(f)).sort().map((f) => path.join(directory, f));
    if (!files.length) throw new Error(`No video files in ${directory}`);
    const assets = [];
    for (let i = 0; i < files.length; i++) {
      const asset = await uploadVideo(files[i]);
      asset.strategy = HOOK_STRATEGIES[i] || `variant_${i + 1}`;
      console.log(`  [${i + 1}/${files.length}] ${asset.name} (${asset.strategy}) → ${asset.id}`);
      assets.push(asset);
    }
    return assets;
  }
  ```
</CodeGroup>

***

## Stage 2 — Video Analysis with Short Chunks

The key parameter: `chunk_duration: 3`. Combined with `frames_per_chunk: 5` for maximum density in the hook window.

<CodeGroup>
  ```python Python theme={"dark"}
  def create_hook_analysis(asset_id: str, strategy: str) -> dict:
      resp = requests.post(f"{BASE}/video-analyses", headers=HEADERS, json={
          "title": f"Hook Sprint: {strategy}", "asset_id": asset_id,
          "goal": "Measure emotional intensity, cognitive load, and attention in the opening hook",
          "brand": "Brand", "product": "Product",
          "primary_intent": "Stop the scroll and drive watch-through",
          "chunk_duration": 3, "frames_per_chunk": 5, "workspace_id": WORKSPACE_ID,
      }).json()
      if "error" in resp:
          raise Exception(resp["error"]["message"])
      return resp


  def poll_analysis(analysis_id: str, timeout_min: int = 20) -> dict:
      for _ in range(timeout_min * 4):
          resp = requests.get(f"{BASE}/video-analyses/{analysis_id}", headers=HEADERS).json()
          if "error" in resp:
              raise Exception(resp["error"]["message"])
          if resp["status"] == "COMPLETED":
              return resp
          if resp["status"] == "FAILED":
              raise Exception(f"Analysis {analysis_id} failed")
          time.sleep(15)
      raise TimeoutError(f"Analysis {analysis_id} timed out")


  def analyze_all_hooks(assets: list[dict]) -> list[dict]:
      jobs = []
      for asset in assets:
          job = create_hook_analysis(asset["id"], asset["strategy"])
          print(f"  Created analysis {job['id']} for {asset['strategy']}")
          jobs.append({"analysis_id": job["id"], "name": asset["name"], "strategy": asset["strategy"]})

      results = []
      for job in jobs:
          result = poll_analysis(job["analysis_id"])
          metrics = result.get("results", {}).get("full_video_metrics", {})
          results.append({"name": job["name"], "strategy": job["strategy"],
                          "metrics": metrics, "chunks": metrics.get("chunks", [])})
          print(f"  Completed {job['strategy']}")
      return results
  ```

  ```javascript JavaScript theme={"dark"}
  async function createHookAnalysis(assetId, strategy) {
    const resp = await fetch(`${BASE}/video-analyses`, {
      method: "POST", headers: HEADERS,
      body: JSON.stringify({
        title: `Hook Sprint: ${strategy}`, asset_id: assetId,
        goal: "Measure emotional intensity, cognitive load, and attention in the opening hook",
        brand: "Brand", product: "Product",
        primary_intent: "Stop the scroll and drive watch-through",
        chunk_duration: 3, frames_per_chunk: 5, workspace_id: WORKSPACE_ID,
      }),
    }).then((r) => r.json());
    if (resp.error) throw new Error(resp.error.message);
    return resp;
  }

  async function pollAnalysis(analysisId, timeoutMin = 20) {
    for (let i = 0; i < timeoutMin * 4; i++) {
      const resp = await fetch(`${BASE}/video-analyses/${analysisId}`, { headers: HEADERS }).then((r) => r.json());
      if (resp.error) throw new Error(resp.error.message);
      if (resp.status === "COMPLETED") return resp;
      if (resp.status === "FAILED") throw new Error(`Analysis ${analysisId} failed`);
      await new Promise((r) => setTimeout(r, 15000));
    }
    throw new Error(`Analysis ${analysisId} timed out`);
  }

  async function analyzeAllHooks(assets) {
    const jobs = [];
    for (const asset of assets) {
      const job = await createHookAnalysis(asset.id, asset.strategy);
      console.log(`  Created analysis ${job.id} for ${asset.strategy}`);
      jobs.push({ analysisId: job.id, name: asset.name, strategy: asset.strategy });
    }
    const results = [];
    for (const job of jobs) {
      const result = await pollAnalysis(job.analysisId);
      const metrics = result.results?.full_video_metrics || {};
      results.push({ name: job.name, strategy: job.strategy, metrics, chunks: metrics.chunks || [] });
      console.log(`  Completed ${job.strategy}`);
    }
    return results;
  }
  ```
</CodeGroup>

<Warning>
  Setting `chunk_duration` below 3 may not provide enough frames for reliable scoring. 3 seconds is the minimum recommended.
</Warning>

***

## Stage 3 — Extract and Compare First-Chunk Metrics

The first chunk (`chunks[0]`) contains exactly the hook.

<CodeGroup>
  ```python Python theme={"dark"}
  def extract_first_chunks(results: list[dict]) -> list[dict]:
      hook_data = []
      for r in results:
          chunk = r["chunks"][0] if r["chunks"] else {}
          hook_data.append({
              "strategy": r["strategy"], "name": r["name"],
              "emotional_intensity": chunk.get("emotional_intensity", 0),
              "cognitive_load": chunk.get("cognitive_load", 0),
              "engagement": chunk.get("engagement", 0),
              "attention": chunk.get("attention", 0),
          })
      return sorted(hook_data, key=lambda x: x["emotional_intensity"], reverse=True)


  def compute_hook_score(h: dict) -> float:
      """Weighted composite: emotion 40% + engagement 30% + attention 20% + inverse cog. load 10%."""
      return (h["emotional_intensity"] * 0.4 + (h["engagement"] / 10) * 0.3
              + h["attention"] * 0.2 + (10 - h["cognitive_load"]) * 0.1)


  def format_hook_table(hook_data: list[dict]) -> str:
      lines = ["| Rank | Hook Strategy | Emotion | Cog. Load | Engagement | Attention |",
               "|------|---------------|---------|-----------|------------|-----------|"]
      for i, h in enumerate(hook_data):
          lines.append(f"| {i+1} | {h['strategy']} | {h['emotional_intensity']}/10 "
                        f"| {h['cognitive_load']}/10 | {h['engagement']}/100 | {h['attention']}/10 |")
      return "\n".join(lines)


  def format_composite_ranking(hook_data: list[dict]) -> str:
      scored = sorted(hook_data, key=compute_hook_score, reverse=True)
      lines = ["| Rank | Hook Strategy | Composite | Emotion | Engagement | Attention | Cog. Load |",
               "|------|---------------|-----------|---------|------------|-----------|-----------|"]
      for i, h in enumerate(scored):
          lines.append(f"| {i+1} | {h['strategy']} | {compute_hook_score(h):.1f}/10 "
                        f"| {h['emotional_intensity']}/10 | {h['engagement']}/100 "
                        f"| {h['attention']}/10 | {h['cognitive_load']}/10 |")
      return "\n".join(lines)
  ```

  ```javascript JavaScript theme={"dark"}
  function extractFirstChunks(results) {
    return results
      .map((r) => {
        const chunk = r.chunks[0] || {};
        return {
          strategy: r.strategy, name: r.name,
          emotionalIntensity: chunk.emotional_intensity || 0,
          cognitiveLoad: chunk.cognitive_load || 0,
          engagement: chunk.engagement || 0,
          attention: chunk.attention || 0,
        };
      })
      .sort((a, b) => b.emotionalIntensity - a.emotionalIntensity);
  }

  function computeHookScore(h) {
    return h.emotionalIntensity * 0.4 + (h.engagement / 10) * 0.3 + h.attention * 0.2 + (10 - h.cognitiveLoad) * 0.1;
  }

  function formatHookTable(hookData) {
    const lines = ["| Rank | Hook Strategy | Emotion | Cog. Load | Engagement | Attention |",
                    "|------|---------------|---------|-----------|------------|-----------|"];
    hookData.forEach((h, i) => lines.push(
      `| ${i + 1} | ${h.strategy} | ${h.emotionalIntensity}/10 | ${h.cognitiveLoad}/10 | ${h.engagement}/100 | ${h.attention}/10 |`
    ));
    return lines.join("\n");
  }

  function formatCompositeRanking(hookData) {
    const scored = [...hookData].sort((a, b) => computeHookScore(b) - computeHookScore(a));
    const lines = ["| Rank | Hook Strategy | Composite | Emotion | Engagement | Attention | Cog. Load |",
                    "|------|---------------|-----------|---------|------------|-----------|-----------|"];
    scored.forEach((h, i) => lines.push(
      `| ${i + 1} | ${h.strategy} | ${computeHookScore(h).toFixed(1)}/10 | ${h.emotionalIntensity}/10 | ${h.engagement}/100 | ${h.attention}/10 | ${h.cognitiveLoad}/10 |`
    ));
    return lines.join("\n");
  }
  ```
</CodeGroup>

### Composite Score Weights

| Metric                 | Weight | Why                                              |
| ---------------------- | ------ | ------------------------------------------------ |
| Emotional intensity    | 40%    | High-emotion hooks stop the scroll               |
| Engagement             | 30%    | Predicts watch-through                           |
| Attention              | 20%    | Keeps the viewer in the first critical seconds   |
| Inverse cognitive load | 10%    | Hooks requiring too much processing lose viewers |

<Info>
  Cognitive load is *inversely* weighted — lower is better for hooks. A hook requiring mental effort to decode loses viewers before the message lands.
</Info>

***

## Stage 4 — Mave Synthesis

<CodeGroup>
  ```python Python theme={"dark"}
  def generate_hook_report(hook_data: list[dict]) -> str:
      raw_table = format_hook_table(hook_data)
      composite = format_composite_ranking(hook_data)

      prompt = f"""You are a creative director specializing in short-form video hooks.

  ## First-Chunk Metrics (0–3 seconds)
  {raw_table}

  ## Composite Hook Score
  {composite}

  ## Your Task
  Produce a hook optimization report:
  1. **Winner** — Which hook strategy wins and why? Cite specific scores.
  2. **Runner-Up** — Second-best hook and what it does differently.
  3. **Worst Performer** — Which strategy failed and why?
  4. **Emotional Intensity Analysis** — What drives high emotion in the first 3 seconds?
  5. **Cognitive Load Traps** — Which hooks overloaded viewers?
  6. **Pattern Recognition** — Patterns across top 3 and bottom 3 hooks?
  7. **Hook Playbook** — 5 rules for scroll-stopping hooks, derived from this data.
  8. **Recommended A/B Test** — 2 hooks for paid testing and the hypothesis to validate.

  Reference strategies by name. Cite scores."""

      resp = requests.post(f"{BASE}/mave/chat", headers=HEADERS,
                           json={"message": prompt}, timeout=180).json()
      if "error" in resp:
          raise Exception(resp["error"]["message"])
      return resp["content"]
  ```

  ```javascript JavaScript theme={"dark"}
  async function generateHookReport(hookData) {
    const rawTable = formatHookTable(hookData);
    const composite = formatCompositeRanking(hookData);

    const prompt = `You are a creative director specializing in short-form video hooks.

  ## First-Chunk Metrics (0–3 seconds)
  ${rawTable}

  ## Composite Hook Score
  ${composite}

  ## Your Task
  Produce a hook optimization report:
  1. **Winner** — Which hook strategy wins? Cite scores.
  2. **Runner-Up** — Second-best and what it does differently.
  3. **Worst Performer** — Which strategy failed and why?
  4. **Emotional Intensity Analysis** — What drives high emotion in 3 seconds?
  5. **Cognitive Load Traps** — Which hooks overloaded viewers?
  6. **Pattern Recognition** — Patterns across top 3 and bottom 3?
  7. **Hook Playbook** — 5 rules for scroll-stopping hooks from this data.
  8. **Recommended A/B Test** — 2 hooks for paid testing, with hypothesis.

  Reference strategies by name. Cite scores.`;

    const resp = await fetch(`${BASE}/mave/chat`, {
      method: "POST", headers: HEADERS,
      body: JSON.stringify({ message: prompt }), signal: AbortSignal.timeout(180000),
    }).then((r) => r.json());
    if (resp.error) throw new Error(resp.error.message);
    return resp.content;
  }
  ```
</CodeGroup>

***

## Running the Full Sprint

<CodeGroup>
  ```python Python theme={"dark"}
  def run_hook_sprint(variant_directory: str = "./hook_variants"):
      assets = upload_hook_variants(variant_directory)
      results = analyze_all_hooks(assets)
      hook_data = extract_first_chunks(results)
      print(format_composite_ranking(hook_data))
      report = generate_hook_report(hook_data)

      with open("hook_analysis_report.md", "w") as f:
          f.write(f"# Hook Analysis Sprint — {time.strftime('%Y-%m-%d')}\n\n")
          f.write(format_composite_ranking(hook_data))
          f.write(f"\n\n---\n\n{report}")
      print("Saved to hook_analysis_report.md")
      return hook_data, report

  if __name__ == "__main__":
      import sys
      run_hook_sprint(sys.argv[1] if len(sys.argv) > 1 else "./hook_variants")
  ```

  ```javascript JavaScript theme={"dark"}
  async function runHookSprint(dir = "./hook_variants") {
    const assets = await uploadHookVariants(dir);
    const results = await analyzeAllHooks(assets);
    const hookData = extractFirstChunks(results);
    console.log(formatCompositeRanking(hookData));
    const report = await generateHookReport(hookData);
    const date = new Date().toISOString().split("T")[0];
    fs.writeFileSync("hook_analysis_report.md",
      `# Hook Analysis Sprint\n\n**Variants:** ${hookData.length} | **Date:** ${date}\n\n` +
      `${formatCompositeRanking(hookData)}\n\n---\n\n${report}`);
    return { hookData, report };
  }
  runHookSprint(process.argv[2] || "./hook_variants");
  ```
</CodeGroup>

***

## Example Output

```markdown theme={"dark"}
# Hook Analysis Sprint Report

**Variants tested:** 10 | **Date:** 2026-03-17

| Rank | Hook Strategy | Composite | Emotion | Cog. Load | Engagement | Attention |
|------|---------------|-----------|---------|-----------|------------|-----------|
| 1    | pain_point    | 8.8/10    | 9/10    | 3/10      | 87/100     | 9/10      |
| 2    | question      | 7.8/10    | 8/10    | 4/10      | 82/100     | 8/10      |
| 3    | visual_shock  | 7.5/10    | 9/10    | 6/10      | 79/100     | 9/10      |
| ...  | ...           | ...       | ...     | ...       | ...        | ...       |
| 10   | silent_open   | 3.5/10    | 3/10    | 2/10      | 31/100     | 4/10      |

## Winner
**pain_point** — composite 8.8/10. Highest emotion (9/10) with low cognitive
load (3/10). Emotional urgency + simplicity is the winning formula...
```

***

## Variations

<AccordionGroup>
  <Accordion title="Platform-specific hook testing">
    Run the same 10 hooks with different `primary_intent` values for TikTok vs YouTube to see if the same hooks win on both platforms.
  </Accordion>

  <Accordion title="Top 3 → Focus Group validation">
    After the sprint, run the top 3 hooks through a Focus Group for simulated audience preference data.
  </Accordion>

  <Accordion title="5-second hooks for longer-form content">
    For YouTube pre-rolls or TV spots, change `chunk_duration` to 5.
  </Accordion>

  <Accordion title="Track hook performance over time">
    Append each sprint to a CSV for quarter-over-quarter trends.
  </Accordion>

  <Accordion title="Isolate audio vs visual impact">
    Create audio-muted and audio-only (black screen) versions. Compare to separate audio vs visual emotional drivers.
  </Accordion>
</AccordionGroup>

***

## Credits Estimate

| Stage                 | Typical Cost      | Notes                           |
| --------------------- | ----------------- | ------------------------------- |
| File uploads (×10)    | 0                 | Free                            |
| Video Analysis (×10)  | 100–200 each      | Short videos (6–15 s) cost less |
| Mave synthesis        | 15–30             | Single research query           |
| **10-variant sprint** | **\~1,015–2,030** | Conservative upper bound        |
| **5-variant sprint**  | **\~515–1,030**   | Smaller test batch              |

<Tip>
  Short hook variants (6–10 seconds) keep Video Analysis costs low. You don't need the full ad — just the hook plus a few seconds of context.
</Tip>

***

## See Also

<CardGroup cols={2}>
  <Card title="Ad Creative Audit" icon="video" href="/playbooks/ad-creative-audit">
    Score a full quarter of ads, not just hooks
  </Card>

  <Card title="Competitor Reel" icon="film" href="/playbooks/competitor-reel">
    Analyze competitor hooks alongside your own
  </Card>

  <Card title="Video + Focus Group Double" icon="layer-group" href="/playbooks/video-focus-group-double">
    Layer hook scores with synthetic audience reactions
  </Card>

  <Card title="Video Analysis" icon="video" href="/features/video-analysis">
    Full metrics reference and chunk configuration
  </Card>

  <Card title="Mave Agent" icon="brain" href="/features/mave-agent">
    Research agent for synthesis and recommendations
  </Card>

  <Card title="Credits & Budget" icon="coins" href="/cookbooks/credits-budget-alerts">
    Pre-flight checks and budget alerts
  </Card>
</CardGroup>
