diff --git a/.github/workflows/social-images.yml b/.github/workflows/social-images.yml
new file mode 100644
index 00000000..e5a02689
--- /dev/null
+++ b/.github/workflows/social-images.yml
@@ -0,0 +1,94 @@
+name: Social Images
+
+on:
+ pull_request:
+ paths:
+ - 'content/**/*.md'
+
+jobs:
+ generate:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ with:
+ ref: ${{ github.head_ref }}
+ repository: ${{ github.event.pull_request.head.repo.full_name }}
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
+ with:
+ python-version: '3.x'
+
+ - name: Install ImageMagick
+ run: |
+ sudo apt-get update -qq
+ sudo apt-get install -y imagemagick
+ # Ubuntu ships ImageMagick 6 (binary: convert). generate.py expects `magick`
+ # (IM7 name). Create an alias so the script works without modification.
+ sudo ln -sf /usr/bin/convert /usr/local/bin/magick
+ magick -version
+
+ - name: Install Python dependencies
+ run: pip install -r scripts/social-images/requirements.txt
+
+ - name: Find changed Markdown files
+ id: diff
+ run: |
+ git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }}
+
+ git diff --name-only --diff-filter=AM FETCH_HEAD HEAD \
+ | grep '^content/.*\.md$' > /tmp/added_modified.txt || true
+
+ git diff --name-only --diff-filter=DR FETCH_HEAD HEAD \
+ | grep '^content/.*\.md$' > /tmp/deleted.txt || true
+
+ echo "has_added=$([ -s /tmp/added_modified.txt ] && echo 'true' || echo 'false')" >> "$GITHUB_OUTPUT"
+ echo "has_deleted=$([ -s /tmp/deleted.txt ] && echo 'true' || echo 'false')" >> "$GITHUB_OUTPUT"
+
+ - name: Generate social images for added/modified files
+ if: steps.diff.outputs.has_added == 'true'
+ run: |
+ while IFS= read -r f; do
+ echo "Generating: $f"
+ python3 scripts/social-images/generate.py --file "$f" --force
+ done < /tmp/added_modified.txt
+
+ - name: Remove orphaned images for deleted files
+ if: steps.diff.outputs.has_deleted == 'true'
+ run: |
+ while IFS= read -r f; do
+ # Mirror ContentFile._detect_type (generate.py:170): first segment after content/
+ type="${f#content/}"
+ type="${type%%/*}"
+ # Mirror ContentFile._derive_slug (generate.py:178): parent dir for index files, else stem
+ filename="$(basename "$f")"
+ if [[ "$filename" == "index.md" || "$filename" == "_index.md" ]]; then
+ slug="$(basename "$(dirname "$f")")"
+ else
+ slug="${filename%.md}"
+ fi
+ img="static/images/social/${type}/${slug}.png"
+ echo "Removing orphan: $img"
+ git rm -f --ignore-unmatch "$img"
+ done < /tmp/deleted.txt
+
+ - name: Commit and push images
+ if: github.event.pull_request.head.repo.full_name == github.repository
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git add static/images/social/
+ if git diff --cached --quiet; then
+ echo "No image changes to commit."
+ else
+ git commit -m "chore(social-images): sync images for changed content"
+ git push
+ fi
+
+ - name: Notice for fork PRs
+ if: github.event.pull_request.head.repo.full_name != github.repository
+ run: |
+ echo "::notice::Fork PR — social images not committed automatically. Run 'python3 scripts/social-images/generate.py' locally (needs ImageMagick + Python deps) or ask a maintainer to regenerate."
diff --git a/.gitignore b/.gitignore
index f16f4566..b9bd6a6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,4 +59,4 @@ haystack-advent
content/advent-of-haystack/*
!content/advent-of-haystack/_index.md
-static/downloads
\ No newline at end of file
+static/downloads
diff --git a/assets/jsconfig.json b/assets/jsconfig.json
index b445bbec..2d705b54 100644
--- a/assets/jsconfig.json
+++ b/assets/jsconfig.json
@@ -3,7 +3,7 @@
"baseUrl": ".",
"paths": {
"*": [
- "..\\themes\\haystack\\assets\\*"
+ "../themes/haystack/assets/*"
]
}
}
diff --git a/scripts/social-images/README.md b/scripts/social-images/README.md
new file mode 100644
index 00000000..c2b0e03f
--- /dev/null
+++ b/scripts/social-images/README.md
@@ -0,0 +1,121 @@
+# Social image generation
+
+Generates Open Graph / Twitter Card preview images for Hugo content by compositing text onto PNG templates using ImageMagick.
+
+Images are placed at `static/images/social/{type}/{slug}.png`. Hugo's `opengraph.html` partial automatically detects a generated image at that path and uses it — no front-matter changes needed in any content file.
+
+## Prerequisites
+
+```bash
+brew install imagemagick
+pip3 install -r scripts/social-images/requirements.txt
+```
+
+## CI integration
+
+`.github/workflows/social-images.yml` runs on every PR that changes a `content/**/*.md` file. It generates or removes images for the changed files and commits them back to the PR branch via `github-actions[bot]`. No local tooling required.
+
+Fork PRs emit a workflow notice instead — run `generate.py` locally or ask a maintainer.
+
+## Local usage
+
+```bash
+# Preview everything without writing files
+python3 scripts/social-images/generate.py --dry-run
+
+# Generate for all content types
+python3 scripts/social-images/generate.py
+
+# One content type only
+python3 scripts/social-images/generate.py --type release-notes
+
+# Single file
+python3 scripts/social-images/generate.py --file content/release-notes/2.25.0.md
+```
+
+## Adding templates
+
+Place one PNG per content type in `scripts/social-images/templates/`:
+
+| File | Used for |
+|---|---|
+| `blog.png` | `content/blog/` posts |
+| `release-notes.png` | `content/release-notes/` pages |
+| `tutorials.png` | `content/tutorials/` pages |
+| `cookbook.png` | `content/cookbook/` pages |
+| `Integration.png` | `content/integrations/` pages |
+| `fallback.png` | Everything else |
+
+Recommended size: **1200 × 630 px** (standard OG image ratio).
+
+## Configuration
+
+`config.yaml` controls text placement for each template. Each content type can define multiple named fields (`title`, `description`, etc.) that map to front-matter values.
+
+Use `exclude:` to skip content types entirely (e.g. blog posts that already have their own thumbnail):
+
+```yaml
+exclude:
+ - blog
+ - authors
+
+templates:
+ release-notes:
+ template: scripts/social-images/templates/release-notes.png
+ fields:
+ title:
+ font: scripts/social-images/fonts/HafferBold.ttf
+ size: 48 # font size in points
+ color: "#2558ff"
+ gravity: NorthWest # ImageMagick anchor: NorthWest, Center, South, etc.
+ left: 66 # pixel offset from the gravity anchor (horizontal)
+ top: 232 # pixel offset from the gravity anchor (vertical)
+ max_width: 830 # text wraps within this box width
+ max_height: 165 # text is clipped beyond this height
+ description:
+ font: scripts/social-images/fonts/Inter-Regular.ttf
+ size: 20
+ color: "#2558ff"
+ gravity: NorthWest
+ left: 66
+ anchor: title # position below the rendered bottom of the title field
+ gap: 15 # extra pixels of padding after the title
+ max_width: 830 # text wraps within this box width
+ max_height: 165 # text is clipped beyond this height
+```
+
+**Field reference:**
+
+| Key | Required | Description |
+|---|---|---|
+| `font` | yes | Path to a TTF file (relative to repo root) |
+| `size` | yes | Font size in points (matches Canva pt values at 96 DPI) |
+| `color` | yes | Hex color string |
+| `gravity` | yes | ImageMagick reference corner: `NorthWest`, `Center`, `South`, etc. |
+| `left` | yes* | Horizontal pixel offset from the gravity anchor (*defaults to anchor's `left` when `anchor` is set) |
+| `top` | yes* | Vertical pixel offset from the gravity anchor (*not needed when `anchor` is set) |
+| `max_width` | yes | Caption box width in pixels (controls line wrapping) |
+| `max_height` | yes | Caption box height in pixels (text is clipped if it overflows) |
+| `anchor` | no | Name of another field; positions this field's top below that field's rendered bottom edge |
+| `gap` | no | Extra pixels of padding below the anchor field (default: 0) |
+
+**Dynamic positioning with `anchor`:** when set, the canvas position is computed so that exactly `gap` pixels of visual space appear between the anchor's last text pixel and this field's first text pixel:
+
+```
+canvas_top = anchor_field.top + anchor_bottom_offset + gap - this_field_top_offset
+```
+
+`gap: 0` means the two text blocks touch with no overlap and no extra space. Both offsets are measured from each field's own caption canvas using ImageMagick's trim geometry.
+
+Available fonts (in `scripts/social-images/fonts/`):
+
+- `HafferBold.ttf`, `HafferMedium.ttf`, `HafferRegular.ttf` — Haffer (headings + body)
+- `Inter-Regular.ttf` — Inter (body text)
+
+## How Hugo picks up the images
+
+`themes/haystack/layouts/partials/opengraph.html` derives the expected image path from the page's content type and slug, then checks for the file with `os.FileExists`. Priority order:
+
+1. Explicit `images` array in front matter (manual override)
+2. Generated social image at `static/images/social/{type}/{slug}.png`
+3. Site-wide default from `config.toml`
diff --git a/scripts/social-images/config.yaml b/scripts/social-images/config.yaml
new file mode 100644
index 00000000..0d5c2ce8
--- /dev/null
+++ b/scripts/social-images/config.yaml
@@ -0,0 +1,146 @@
+# Social image generation configuration.
+#
+# Each key under `templates` maps to a content type inferred from the file path
+# (e.g. content/blog/ → "blog"). If a content type has no entry here, the
+# "fallback" template is used.
+#
+# Field keys are arbitrary — they must match a front-matter key that the script
+# knows how to read (title, description). The script skips a field silently if
+# the front-matter value is empty.
+#
+# Field options:
+# font - path to a TTF file (relative to the repo root)
+# size - font size in points (matches Canva pt values; rendered at 96 DPI)
+# color - hex color string
+# gravity - ImageMagick gravity: NorthWest, North, NorthEast, West, Center,
+# East, SouthWest, South, SouthEast
+# left - pixel offset from the gravity anchor (horizontal; required unless anchor is
+# set, in which case it defaults to the anchor field's left value)
+# top - pixel offset from the gravity anchor (vertical; required unless anchor is set)
+# max_width - caption box width in pixels (controls line wrapping)
+# max_height - caption box height in pixels (text is clipped if it overflows)
+# anchor - (optional) name of another field whose rendered bottom edge
+# determines this field's top; replaces top
+# gap - (optional) extra pixels of padding below the anchor (default 0)
+
+# Content types listed here are skipped entirely (no image generated).
+exclude:
+ - blog
+ - authors
+ - ambassador-perks
+
+templates:
+ cookbook:
+ template: scripts/social-images/templates/cookbook.png
+ fields:
+ title:
+ font: scripts/social-images/fonts/HafferBold.ttf
+ size: 48
+ color: "#2558ff"
+ gravity: NorthWest
+ left: 66
+ top: 232
+ max_width: 830
+ max_height: 165
+ description:
+ font: scripts/social-images/fonts/Inter-Regular.ttf
+ size: 20
+ color: "#2558ff"
+ gravity: NorthWest
+ left: 66
+ anchor: title
+ gap: 15
+ max_width: 830
+ max_height: 165
+
+ integrations:
+ template: scripts/social-images/templates/Integration.png
+ fields:
+ title:
+ font: scripts/social-images/fonts/HafferBold.ttf
+ size: 48
+ color: "#2558ff"
+ gravity: NorthWest
+ left: 66
+ top: 232
+ max_width: 830
+ max_height: 165
+ description:
+ font: scripts/social-images/fonts/Inter-Regular.ttf
+ size: 20
+ color: "#2558ff"
+ gravity: NorthWest
+ left: 66
+ anchor: title
+ gap: 15
+ max_width: 830
+ max_height: 165
+
+ release-notes:
+ template: scripts/social-images/templates/release-notes.png
+ fields:
+ title:
+ font: scripts/social-images/fonts/HafferBold.ttf
+ size: 48
+ color: "#2558ff"
+ gravity: NorthWest
+ left: 66
+ top: 232
+ max_width: 830
+ max_height: 165
+ description:
+ font: scripts/social-images/fonts/Inter-Regular.ttf
+ size: 20
+ color: "#2558ff"
+ gravity: NorthWest
+ left: 66
+ anchor: title
+ gap: 15
+ max_width: 830
+ max_height: 165
+
+ tutorials:
+ template: scripts/social-images/templates/tutorials.png
+ fields:
+ title:
+ font: scripts/social-images/fonts/HafferBold.ttf
+ size: 48
+ color: "#2558ff"
+ gravity: NorthWest
+ left: 66
+ top: 232
+ max_width: 830
+ max_height: 165
+ description:
+ font: scripts/social-images/fonts/Inter-Regular.ttf
+ size: 20
+ color: "#2558ff"
+ gravity: NorthWest
+ left: 66
+ anchor: title
+ gap: 15
+ max_width: 830
+ max_height: 165
+
+ fallback:
+ template: scripts/social-images/templates/fallback.png
+ fields:
+ title:
+ font: scripts/social-images/fonts/HafferBold.ttf
+ size: 48
+ color: "#2558ff"
+ gravity: NorthWest
+ left: 66
+ top: 232
+ max_width: 830
+ max_height: 165
+ description:
+ font: scripts/social-images/fonts/Inter-Regular.ttf
+ size: 20
+ color: "#2558ff"
+ gravity: NorthWest
+ left: 66
+ anchor: title
+ gap: 15
+ max_width: 830
+ max_height: 165
diff --git a/scripts/social-images/fonts/HafferBold.ttf b/scripts/social-images/fonts/HafferBold.ttf
new file mode 100644
index 00000000..ddf11ac3
Binary files /dev/null and b/scripts/social-images/fonts/HafferBold.ttf differ
diff --git a/scripts/social-images/fonts/HafferMedium.ttf b/scripts/social-images/fonts/HafferMedium.ttf
new file mode 100644
index 00000000..19037407
Binary files /dev/null and b/scripts/social-images/fonts/HafferMedium.ttf differ
diff --git a/scripts/social-images/fonts/HafferRegular.ttf b/scripts/social-images/fonts/HafferRegular.ttf
new file mode 100644
index 00000000..8c6400ee
Binary files /dev/null and b/scripts/social-images/fonts/HafferRegular.ttf differ
diff --git a/scripts/social-images/fonts/Inter-Regular.ttf b/scripts/social-images/fonts/Inter-Regular.ttf
new file mode 100644
index 00000000..b7aaca8d
Binary files /dev/null and b/scripts/social-images/fonts/Inter-Regular.ttf differ
diff --git a/scripts/social-images/generate.py b/scripts/social-images/generate.py
new file mode 100644
index 00000000..adafa2d0
--- /dev/null
+++ b/scripts/social-images/generate.py
@@ -0,0 +1,499 @@
+"""
+Generate social preview (OG) images for Hugo content files.
+
+For each content file the script:
+ 1. Detects the content type from the file path.
+ 2. Loads the matching template config from config.yaml (falls back to "fallback").
+ 3. Reads the front matter (title, description) with python-frontmatter.
+ 4. Composites text onto the PNG template using ImageMagick.
+ 5. Saves the result to static/images/social/{type}/{slug}.png.
+
+The Hugo opengraph partial picks up generated images automatically by checking
+for a file at that predictable path — no front matter changes are needed.
+
+Usage:
+ python3 scripts/social-images/generate.py # all content types
+ python3 scripts/social-images/generate.py --type blog # one type
+ python3 scripts/social-images/generate.py --file content/release-notes/2.25.0.md
+ python3 scripts/social-images/generate.py --dry-run # preview only
+
+Dependencies:
+ pip install python-frontmatter PyYAML
+ brew install imagemagick (or equivalent)
+"""
+
+import argparse
+import re
+import shutil
+import subprocess
+import sys
+from dataclasses import dataclass, field, replace
+from pathlib import Path
+from typing import Optional
+
+import frontmatter
+import yaml
+
+REPO_ROOT = Path(__file__).parent.parent.parent
+CONFIG_FILE = Path(__file__).parent / "config.yaml"
+
+# Matches emoji, pictographs, ZWJ sequences, variation selectors, skin-tone modifiers,
+# regional indicators, and enclosed characters that ImageMagick cannot render.
+_EMOJI_RE = re.compile(
+ "["
+ "\U0001F600-\U0001F64F" # emoticons
+ "\U0001F300-\U0001F5FF" # misc symbols & pictographs
+ "\U0001F680-\U0001F6FF" # transport & map
+ "\U0001F700-\U0001F77F" # alchemical symbols
+ "\U0001F780-\U0001F7FF" # geometric shapes extended
+ "\U0001F800-\U0001F8FF" # supplemental arrows-c
+ "\U0001F900-\U0001F9FF" # supplemental symbols & pictographs
+ "\U0001FA00-\U0001FA6F" # chess symbols
+ "\U0001FA70-\U0001FAFF" # symbols & pictographs extended-a
+ "\U00002702-\U000027B0" # dingbats
+ "\U000024C2-\U0001F251" # enclosed characters
+ "\U0001F1E0-\U0001F1FF" # regional indicators (flags)
+ "\U0000200D" # ZWJ
+ "\U0000FE0F" # variation selector-16
+ "\U0001F3FB-\U0001F3FF" # skin-tone modifiers
+ "]+",
+ flags=re.UNICODE,
+)
+
+
+def _strip_emoji(text: str) -> str:
+ """Remove emoji and related Unicode from *text*, then collapse extra whitespace."""
+ return re.sub(r" +", " ", _EMOJI_RE.sub("", text)).strip()
+
+
+@dataclass
+class FieldConfig:
+ font: str
+ size: int
+ color: str
+ gravity: str
+ max_width: float
+ max_height: float
+ left: float = 0.0
+ top: float = 0.0
+ anchor: Optional[str] = None
+ gap: float = 0.0
+
+ @classmethod
+ def from_dict(cls, data: dict) -> "FieldConfig":
+ return cls(
+ font=data["font"],
+ size=data["size"],
+ color=data["color"],
+ gravity=data["gravity"],
+ max_width=data["max_width"],
+ max_height=data["max_height"],
+ left=data.get("left", 0.0),
+ top=data.get("top", 0.0),
+ anchor=data.get("anchor"),
+ gap=data.get("gap", 0.0),
+ )
+
+
+@dataclass
+class TemplateConfig:
+ template: str
+ fields: dict = field(default_factory=dict)
+
+ @classmethod
+ def from_dict(cls, data: dict) -> "TemplateConfig":
+ return cls(
+ template=data["template"],
+ fields={
+ name: FieldConfig.from_dict(cfg)
+ for name, cfg in (data.get("fields") or {}).items()
+ },
+ )
+
+
+class Config:
+ def __init__(self, config_file: Path):
+ with open(config_file) as f:
+ doc = yaml.safe_load(f)
+ self._templates: dict[str, TemplateConfig] = {
+ name: TemplateConfig.from_dict(data) for name, data in doc["templates"].items()
+ }
+ self._exclude: set[str] = set(doc.get("exclude", []))
+
+ def resolve(self, content_type: str) -> Optional[TemplateConfig]:
+ return self._templates.get(content_type) or self._templates.get("fallback")
+
+ def is_excluded(self, content_type: str) -> bool:
+ return content_type in self._exclude
+
+ @property
+ def fallback(self) -> Optional[TemplateConfig]:
+ return self._templates.get("fallback")
+
+
+@dataclass
+class ContentFile:
+ md_path: Path
+ content_type: str
+ slug: str
+ title: str
+ description: str
+ _metadata: dict = field(default_factory=dict)
+
+ @classmethod
+ def load(cls, md_path: Path, repo_root: Path) -> "ContentFile":
+ content_type = cls._detect_type(md_path, repo_root)
+ slug = cls._derive_slug(md_path)
+ post = frontmatter.load(str(md_path))
+ return cls(
+ md_path=md_path,
+ content_type=content_type,
+ slug=slug,
+ title=_strip_emoji(post.get("title") or post.get("name") or ""),
+ description=_strip_emoji(post.get("description") or ""),
+ _metadata=dict(post.metadata),
+ )
+
+ @property
+ def text_values(self) -> dict[str, str]:
+ result: dict[str, str] = {"title": self.title, "description": self.description}
+ for key, value in self._metadata.items():
+ if key in result:
+ continue
+ if isinstance(value, list):
+ result[key] = ", ".join(str(v) for v in value)
+ elif value is not None:
+ result[key] = str(value)
+ return result
+
+ @staticmethod
+ def _detect_type(md_path: Path, repo_root: Path) -> str:
+ try:
+ rel = md_path.relative_to(repo_root / "content")
+ except ValueError:
+ return "fallback"
+ return rel.parts[0]
+
+ @staticmethod
+ def _derive_slug(md_path: Path) -> str:
+ if md_path.name in ("index.md", "_index.md"):
+ return md_path.parent.name
+ return md_path.stem
+
+
+class ImageCompositor:
+ def __init__(self, repo_root: Path):
+ self._repo_root = repo_root
+
+ def measure_text_height(self, field_cfg: FieldConfig, text: str) -> int:
+ """Return the pixel distance from the caption box top to the bottom of rendered text.
+
+ Uses ImageMagick's %@ bounding-box format (WxH+X+Y) and returns Y+H so that
+ the result accounts for any top padding the caption renderer adds above the glyphs.
+ The last character is replaced with "g" before measuring so the bounding box always
+ includes a descender, giving consistent heights regardless of actual last-line content.
+ """
+ # Replace the last character with "g" so the bounding box always includes
+ # a descender, giving consistent heights regardless of actual last-line content.
+ normalized = text[:-1] + "g" if text else text
+ safe_text = normalized.replace("\\", "\\\\").replace("'", "\\'")
+ result = subprocess.run([
+ "magick", "-density", "96",
+ "-background", "none", "-fill", "black",
+ "-font", str(self._repo_root / field_cfg.font),
+ "-pointsize", str(field_cfg.size),
+ "-size", f"{int(field_cfg.max_width)}x{int(field_cfg.max_height)}",
+ f"caption:{safe_text}",
+ "-format", "%@\n", "info:",
+ ], capture_output=True, text=True, check=True)
+ # %@ returns "WxH+X+Y" — Y is top offset of text, H is glyph height.
+ # Y + H is the bottom edge of the text relative to the caption box top.
+ m = re.match(r"\d+x(\d+)\+\d+\+(\d+)", result.stdout.strip())
+ if m:
+ return int(m.group(2)) + int(m.group(1))
+ return int(result.stdout.strip())
+
+ def fit_font_size(self, field_cfg: FieldConfig, text: str) -> int:
+ """Binary-search for the largest font size where text fits within max_height.
+
+ The size in field_cfg is treated as the maximum; returns a value in [1, size].
+ Measurement uses an unconstrained height so ImageMagick clipping cannot mask overflow.
+ """
+ # Remove the height constraint so the bounding box reflects true text height.
+ unconstrained = replace(field_cfg, max_height=10_000)
+ lo, hi = 1, field_cfg.size
+ best = lo
+ while lo <= hi:
+ mid = (lo + hi) // 2
+ height = self.measure_text_height(replace(unconstrained, size=mid), text)
+ if height <= field_cfg.max_height:
+ best = mid
+ lo = mid + 1
+ else:
+ hi = mid - 1
+ return best
+
+ def build_command(
+ self,
+ template_path: Path,
+ fields: dict[str, FieldConfig],
+ text_values: dict[str, str],
+ output_path: Path,
+ ) -> list[str]:
+ """
+ Build an ImageMagick `convert` command that composites one caption layer
+ per configured field onto the template image.
+
+ Fields may use `anchor` and `gap` instead of a fixed `top` value — the
+ top edge is then computed as anchor_field.top + rendered_height + gap.
+
+ Font sizes are treated as maximums: each field's size is reduced as needed
+ so the full text always fits within max_height.
+ """
+ effective_fields = {
+ name: (
+ replace(cfg, size=self.fit_font_size(cfg, text_values[name].replace("\\n", "\n")))
+ if text_values.get(name)
+ else cfg
+ )
+ for name, cfg in fields.items()
+ }
+ measured_heights = self._measure_anchors(effective_fields, text_values)
+ cmd = ["magick", "-density", "96", str(template_path)]
+
+ for field_name, field_cfg in effective_fields.items():
+ text = text_values.get(field_name, "")
+ if not text:
+ continue
+ x, y = self._resolve_position(field_cfg, effective_fields, measured_heights)
+ cmd += self._field_args(field_cfg, text, x, y)
+
+ cmd.append(str(output_path))
+ return cmd
+
+ def composite(
+ self,
+ template_path: Path,
+ fields: dict[str, FieldConfig],
+ text_values: dict[str, str],
+ output_path: Path,
+ ) -> None:
+ """Run ImageMagick. Raises FileNotFoundError or CalledProcessError on failure."""
+ cmd = self.build_command(template_path, fields, text_values, output_path)
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
+
+ def _measure_anchors(
+ self, fields: dict[str, FieldConfig], text_values: dict[str, str]
+ ) -> dict[str, int]:
+ measured: dict[str, int] = {}
+ for field_cfg in fields.values():
+ anchor_name = field_cfg.anchor
+ if anchor_name and anchor_name not in measured and anchor_name in fields:
+ anchor_field = fields[anchor_name]
+ anchor_text = text_values.get(anchor_name, "").replace("\\n", "\n")
+ if anchor_text:
+ measured[anchor_name] = self.measure_text_height(anchor_field, anchor_text)
+ return measured
+
+ def _resolve_position(
+ self,
+ field_cfg: FieldConfig,
+ all_fields: dict[str, FieldConfig],
+ measured_heights: dict[str, int],
+ ) -> tuple[float, float]:
+ if field_cfg.anchor and field_cfg.anchor in all_fields:
+ anchor_field = all_fields[field_cfg.anchor]
+ x = field_cfg.left if field_cfg.left else anchor_field.left
+ y = anchor_field.top + measured_heights.get(field_cfg.anchor, 0) + field_cfg.gap
+ else:
+ x, y = field_cfg.left, field_cfg.top
+ return x, y
+
+ def _field_args(
+ self, field_cfg: FieldConfig, text: str, x: float, y: float
+ ) -> list[str]:
+ text = text.replace("\\n", "\n")
+ safe_text = text.replace("\\", "\\\\").replace("'", "\\'")
+ return [
+ "(", "-background", "none",
+ "-fill", field_cfg.color,
+ "-font", str(self._repo_root / field_cfg.font),
+ "-pointsize", str(field_cfg.size),
+ "-size", f"{int(field_cfg.max_width)}x{int(field_cfg.max_height)}",
+ f"caption:{safe_text}",
+ ")", "-gravity", field_cfg.gravity,
+ "-geometry", f"+{int(x)}+{int(y)}", "-composite",
+ ]
+
+
+class FileProcessor:
+ def __init__(self, config: Config, compositor: ImageCompositor, repo_root: Path):
+ self._config = config
+ self._compositor = compositor
+ self._repo_root = repo_root
+
+ def _rel(self, path: Path) -> Path:
+ try:
+ return path.relative_to(self._repo_root)
+ except ValueError:
+ return path
+
+ def process(self, md_path: Path, dry_run: bool, force: bool = False) -> bool:
+ """Process a single content file. Returns True on success or skip."""
+ content_file = ContentFile.load(md_path, self._repo_root)
+
+ if self._config.is_excluded(content_file.content_type):
+ print(f" skip {self._rel(md_path)} (excluded content type: {content_file.content_type})")
+ return True
+
+ base_cfg = self._config.resolve(content_file.content_type)
+ if not base_cfg:
+ print(f" error {self._rel(md_path)}: no template config and no fallback defined in config.yaml")
+ return False
+
+ resolution = self._resolve_template(content_file, base_cfg)
+ if resolution is None:
+ # Template missing with no fallback — skip message already printed
+ return True
+
+ template_path, template_cfg = resolution
+ output_dir = self._repo_root / "static/images/social" / content_file.content_type
+ output_path = output_dir / f"{content_file.slug}.png"
+
+ if dry_run:
+ self._print_dry_run(content_file, output_path)
+ return True
+
+ if output_path.exists() and not force:
+ print(f" skip {self._rel(md_path)} (already exists, use --force to regenerate)")
+ return True
+
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+ try:
+ self._compositor.composite(
+ template_path, template_cfg.fields, content_file.text_values, output_path
+ )
+ except FileNotFoundError:
+ print(" error: ImageMagick `convert` not found. Install with: brew install imagemagick")
+ return False
+ except subprocess.CalledProcessError as exc:
+ print(f" error {self._rel(md_path)}: ImageMagick failed\n{exc.stderr.strip()}")
+ return False
+
+ print(f" ok {self._rel(md_path)} → {self._rel(output_path)}")
+ return True
+
+ def _resolve_template(
+ self, content_file: ContentFile, template_cfg: TemplateConfig
+ ) -> Optional[tuple[Path, TemplateConfig]]:
+ template_path = self._repo_root / template_cfg.template
+ if template_path.exists():
+ return template_path, template_cfg
+
+ fallback = self._config.fallback
+ if fallback:
+ fallback_path = self._repo_root / fallback.template
+ if fallback_path.exists():
+ print(f" warn {self._rel(content_file.md_path)}: template not found, using fallback")
+ merged = TemplateConfig(
+ template=fallback.template,
+ fields=template_cfg.fields or fallback.fields,
+ )
+ return fallback_path, merged
+
+ print(f" skip {self._rel(content_file.md_path)}: template not found at {self._rel(template_path)}")
+ return None
+
+ def _print_dry_run(self, content_file: ContentFile, output_path: Path) -> None:
+ print(f" dry {self._rel(content_file.md_path)}")
+ print(f" type={content_file.content_type} slug={content_file.slug}")
+ print(f" output={self._rel(output_path)}")
+ for field_name, text in content_file.text_values.items():
+ preview = (text[:60] + "…") if len(text) > 60 else text
+ print(f" {field_name}: {preview!r}")
+
+
+class FileCollector:
+ def __init__(self, repo_root: Path):
+ self._repo_root = repo_root
+
+ def collect(
+ self,
+ content_type: Optional[str] = None,
+ single_file: Optional[Path] = None,
+ ) -> list[Path]:
+ if single_file:
+ return [single_file.resolve()]
+
+ content_root = self._repo_root / "content"
+ if content_type:
+ dirs = [content_root / content_type]
+ else:
+ dirs = [d for d in content_root.iterdir() if d.is_dir()]
+
+ files: list[Path] = []
+ for d in dirs:
+ if d.exists():
+ files.extend(d.rglob("*.md"))
+ return sorted(files)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Generate social preview images for Haystack Hugo content."
+ )
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument(
+ "--type",
+ metavar="TYPE",
+ help="Only process files of this content type (e.g. blog, release-notes, tutorials).",
+ )
+ group.add_argument(
+ "--file",
+ metavar="PATH",
+ type=Path,
+ help="Process a single markdown file.",
+ )
+ parser.add_argument(
+ "--dry-run",
+ action="store_true",
+ help="Print what would be generated without writing any files.",
+ )
+ parser.add_argument(
+ "--force",
+ action="store_true",
+ help="Regenerate images even if the output file already exists.",
+ )
+ args = parser.parse_args()
+
+ if not shutil.which("magick"):
+ print("Error: ImageMagick `magick` command not found.")
+ print("Install with: brew install imagemagick")
+ sys.exit(1)
+
+ config = Config(CONFIG_FILE)
+ collector = FileCollector(REPO_ROOT)
+ compositor = ImageCompositor(REPO_ROOT)
+ processor = FileProcessor(config, compositor, REPO_ROOT)
+
+ files = collector.collect(args.type, args.file)
+ if not files:
+ print("No matching content files found.")
+ sys.exit(0)
+
+ print(f"Processing {len(files)} file(s)…\n")
+ ok = 0
+ failed = 0
+ for md_path in files:
+ if processor.process(md_path, args.dry_run, args.force):
+ ok += 1
+ else:
+ failed += 1
+
+ print(f"\nDone: {ok} ok, {failed} failed.")
+ if failed:
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/social-images/requirements.txt b/scripts/social-images/requirements.txt
new file mode 100644
index 00000000..0a920eb1
--- /dev/null
+++ b/scripts/social-images/requirements.txt
@@ -0,0 +1,2 @@
+python-frontmatter
+PyYAML
diff --git a/scripts/social-images/templates/Integration.png b/scripts/social-images/templates/Integration.png
new file mode 100644
index 00000000..9e71dccc
Binary files /dev/null and b/scripts/social-images/templates/Integration.png differ
diff --git a/scripts/social-images/templates/blog.png b/scripts/social-images/templates/blog.png
new file mode 100644
index 00000000..666e4ed8
Binary files /dev/null and b/scripts/social-images/templates/blog.png differ
diff --git a/scripts/social-images/templates/cookbook.png b/scripts/social-images/templates/cookbook.png
new file mode 100644
index 00000000..ab46d4c3
Binary files /dev/null and b/scripts/social-images/templates/cookbook.png differ
diff --git a/scripts/social-images/templates/fallback.png b/scripts/social-images/templates/fallback.png
new file mode 100644
index 00000000..06b1ca9f
Binary files /dev/null and b/scripts/social-images/templates/fallback.png differ
diff --git a/scripts/social-images/templates/release-notes.png b/scripts/social-images/templates/release-notes.png
new file mode 100644
index 00000000..b62b8f67
Binary files /dev/null and b/scripts/social-images/templates/release-notes.png differ
diff --git a/scripts/social-images/templates/tutorials.png b/scripts/social-images/templates/tutorials.png
new file mode 100644
index 00000000..2a129378
Binary files /dev/null and b/scripts/social-images/templates/tutorials.png differ
diff --git a/scripts/social-images/test-placement.py b/scripts/social-images/test-placement.py
new file mode 100644
index 00000000..180b1ac7
--- /dev/null
+++ b/scripts/social-images/test-placement.py
@@ -0,0 +1,139 @@
+"""
+Visual placement test for social image text fields.
+
+Generates an image with the configured text AND draws a colored bounding box
+around each field so you can see exactly where left/top and max_width/max_height
+land on the template.
+
+Usage:
+ python3 scripts/social-images/test-placement.py
+ python3 scripts/social-images/test-placement.py --type blog
+ python3 scripts/social-images/test-placement.py --type cookbook --title "My Long Recipe Title Here"
+"""
+
+import argparse
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+
+from generate import Config, FieldConfig, ImageCompositor
+
+REPO_ROOT = Path(__file__).parent.parent.parent
+CONFIG_FILE = Path(__file__).parent / "config.yaml"
+
+SAMPLE_TEXTS = {
+ "title": "Retrieval-Augmented Generation with Custom Embeddings",
+ "description": "Learn how to build a production-ready RAG pipeline using Haystack 2.x with custom embedding models and vector stores.",
+}
+
+FIELD_COLORS = ["#ff4444", "#44aaff", "#44ff88", "#ffaa44"]
+
+
+def build_cmd(
+ compositor: ImageCompositor,
+ template_path: Path,
+ fields: dict[str, FieldConfig],
+ text_values: dict[str, str],
+ output_path: Path,
+) -> list[str]:
+ """Build the base composite command then inject bounding-box debug overlays."""
+ # Use the shared compositor to get the base command (without the output path)
+ base = compositor.build_command(template_path, fields, text_values, output_path)
+ # Remove the output path appended by build_command — we'll re-add it at the end
+ base = base[:-1]
+
+ # Pre-compute positions for bounding boxes using the compositor's internals.
+ # We replicate the anchor measurement to know x/y per field.
+ measured_heights = compositor._measure_anchors(fields, text_values)
+
+ for i, (field_name, field_cfg) in enumerate(fields.items()):
+ text = text_values.get(field_name, "")
+ if not text:
+ continue
+ x, y = compositor._resolve_position(field_cfg, fields, measured_heights)
+ box_color = FIELD_COLORS[i % len(FIELD_COLORS)]
+
+ base += [
+ "-fill", "none",
+ "-stroke", box_color,
+ "-strokewidth", "2",
+ "-draw", f"rectangle {int(x)},{int(y)} {int(x + field_cfg.max_width)},{int(y + field_cfg.max_height)}",
+ ]
+ base += [
+ "-fill", box_color,
+ "-font", str(REPO_ROOT / field_cfg.font),
+ "-pointsize", "18",
+ "-annotate", f"+{int(x) + 4}+{int(y) + 18}",
+ f"{field_name} (left={int(x)}, top={int(y)})",
+ ]
+
+ base.append(str(output_path))
+ return base
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Visual placement test for social image fields.")
+ parser.add_argument("--type", default="cookbook", metavar="TYPE", help="Content type to test (default: cookbook)")
+ parser.add_argument("--title", metavar="TEXT", help="Override sample title text")
+ parser.add_argument("--description", metavar="TEXT", help="Override sample description text")
+ args = parser.parse_args()
+
+ if not shutil.which("magick"):
+ print("Error: ImageMagick `magick` not found. Install with: brew install imagemagick")
+ sys.exit(1)
+
+ config = Config(CONFIG_FILE)
+ compositor = ImageCompositor(REPO_ROOT)
+
+ template_cfg = config.resolve(args.type)
+ if not template_cfg:
+ print(f"Error: no template config found for type '{args.type}'")
+ sys.exit(1)
+
+ template_path = REPO_ROOT / template_cfg.template
+ if not template_path.exists():
+ fallback = config.fallback
+ fallback_path = REPO_ROOT / fallback.template if fallback else None
+ if fallback_path and fallback_path.exists():
+ print(f"Template not found, using fallback: {fallback_path.name}")
+ template_path = fallback_path
+ else:
+ print(f"Error: no template found at {template_path}")
+ sys.exit(1)
+
+ output_dir = REPO_ROOT / "static" / "images" / "social" / args.type
+ output_dir.mkdir(parents=True, exist_ok=True)
+ output_path = output_dir / "_placement-test.png"
+
+ text_values = dict(SAMPLE_TEXTS)
+ if args.title:
+ text_values["title"] = args.title
+ if args.description:
+ text_values["description"] = args.description
+
+ fields = template_cfg.fields
+ print(f"Type: {args.type}")
+ print(f"Template: {template_path}")
+ print(f"Output: {output_path}")
+ print()
+ for field_name, field_cfg in fields.items():
+ if field_cfg.anchor:
+ print(f" {field_name}: left=inherited from '{field_cfg.anchor}', top=anchored below '{field_cfg.anchor}' + gap={int(field_cfg.gap)}, "
+ f"max_width={int(field_cfg.max_width)}, max_height={int(field_cfg.max_height)}")
+ else:
+ print(f" {field_name}: left={int(field_cfg.left)}, top={int(field_cfg.top)}, "
+ f"max_width={int(field_cfg.max_width)}, max_height={int(field_cfg.max_height)}")
+ print()
+
+ cmd = build_cmd(compositor, template_path, fields, text_values, output_path)
+ try:
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
+ print(f"Generated: {output_path}")
+ except subprocess.CalledProcessError as exc:
+ print(f"ImageMagick failed:\n{exc.stderr.strip()}")
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/static/images/social/advent-of-haystack/advent-of-haystack.png b/static/images/social/advent-of-haystack/advent-of-haystack.png
new file mode 100644
index 00000000..429e7fac
Binary files /dev/null and b/static/images/social/advent-of-haystack/advent-of-haystack.png differ
diff --git a/static/images/social/ambassadors/ambassadors.png b/static/images/social/ambassadors/ambassadors.png
new file mode 100644
index 00000000..e2730d3c
Binary files /dev/null and b/static/images/social/ambassadors/ambassadors.png differ
diff --git a/static/images/social/benchmarks/benchmarks.png b/static/images/social/benchmarks/benchmarks.png
new file mode 100644
index 00000000..9edbdff7
Binary files /dev/null and b/static/images/social/benchmarks/benchmarks.png differ
diff --git a/static/images/social/benchmarks/v0.10.0.png b/static/images/social/benchmarks/v0.10.0.png
new file mode 100644
index 00000000..f2cc1eb5
Binary files /dev/null and b/static/images/social/benchmarks/v0.10.0.png differ
diff --git a/static/images/social/benchmarks/v0.5.0.png b/static/images/social/benchmarks/v0.5.0.png
new file mode 100644
index 00000000..e4da852f
Binary files /dev/null and b/static/images/social/benchmarks/v0.5.0.png differ
diff --git a/static/images/social/benchmarks/v0.6.0.png b/static/images/social/benchmarks/v0.6.0.png
new file mode 100644
index 00000000..0f8a8b57
Binary files /dev/null and b/static/images/social/benchmarks/v0.6.0.png differ
diff --git a/static/images/social/benchmarks/v0.7.0.png b/static/images/social/benchmarks/v0.7.0.png
new file mode 100644
index 00000000..47c80df9
Binary files /dev/null and b/static/images/social/benchmarks/v0.7.0.png differ
diff --git a/static/images/social/benchmarks/v0.8.0.png b/static/images/social/benchmarks/v0.8.0.png
new file mode 100644
index 00000000..70b94f7e
Binary files /dev/null and b/static/images/social/benchmarks/v0.8.0.png differ
diff --git a/static/images/social/benchmarks/v0.9.0.png b/static/images/social/benchmarks/v0.9.0.png
new file mode 100644
index 00000000..512a74d3
Binary files /dev/null and b/static/images/social/benchmarks/v0.9.0.png differ
diff --git a/static/images/social/benchmarks/v1.9.0.png b/static/images/social/benchmarks/v1.9.0.png
new file mode 100644
index 00000000..ebac8946
Binary files /dev/null and b/static/images/social/benchmarks/v1.9.0.png differ
diff --git a/static/images/social/cookbook/cookbook.png b/static/images/social/cookbook/cookbook.png
new file mode 100644
index 00000000..8bc084f5
Binary files /dev/null and b/static/images/social/cookbook/cookbook.png differ
diff --git a/static/images/social/gsoc/contributor-guidance.png b/static/images/social/gsoc/contributor-guidance.png
new file mode 100644
index 00000000..65d3dd0c
Binary files /dev/null and b/static/images/social/gsoc/contributor-guidance.png differ
diff --git a/static/images/social/gsoc/gsoc.png b/static/images/social/gsoc/gsoc.png
new file mode 100644
index 00000000..f9f1656b
Binary files /dev/null and b/static/images/social/gsoc/gsoc.png differ
diff --git a/static/images/social/gsoc/projects.png b/static/images/social/gsoc/projects.png
new file mode 100644
index 00000000..b8396b36
Binary files /dev/null and b/static/images/social/gsoc/projects.png differ
diff --git a/static/images/social/integrations/aimlapi.png b/static/images/social/integrations/aimlapi.png
new file mode 100644
index 00000000..1aff89da
Binary files /dev/null and b/static/images/social/integrations/aimlapi.png differ
diff --git a/static/images/social/integrations/amazon-bedrock.png b/static/images/social/integrations/amazon-bedrock.png
new file mode 100644
index 00000000..39556a5c
Binary files /dev/null and b/static/images/social/integrations/amazon-bedrock.png differ
diff --git a/static/images/social/integrations/amazon-sagemaker.png b/static/images/social/integrations/amazon-sagemaker.png
new file mode 100644
index 00000000..70bfb6dd
Binary files /dev/null and b/static/images/social/integrations/amazon-sagemaker.png differ
diff --git a/static/images/social/integrations/anthropic.png b/static/images/social/integrations/anthropic.png
new file mode 100644
index 00000000..165c6214
Binary files /dev/null and b/static/images/social/integrations/anthropic.png differ
diff --git a/static/images/social/integrations/apify.png b/static/images/social/integrations/apify.png
new file mode 100644
index 00000000..1891b999
Binary files /dev/null and b/static/images/social/integrations/apify.png differ
diff --git a/static/images/social/integrations/arcadedb.png b/static/images/social/integrations/arcadedb.png
new file mode 100644
index 00000000..dc0735dd
Binary files /dev/null and b/static/images/social/integrations/arcadedb.png differ
diff --git a/static/images/social/integrations/arize-phoenix.png b/static/images/social/integrations/arize-phoenix.png
new file mode 100644
index 00000000..e8b2eb11
Binary files /dev/null and b/static/images/social/integrations/arize-phoenix.png differ
diff --git a/static/images/social/integrations/arize.png b/static/images/social/integrations/arize.png
new file mode 100644
index 00000000..fe2ea5d8
Binary files /dev/null and b/static/images/social/integrations/arize.png differ
diff --git a/static/images/social/integrations/assemblyai.png b/static/images/social/integrations/assemblyai.png
new file mode 100644
index 00000000..b74cec0c
Binary files /dev/null and b/static/images/social/integrations/assemblyai.png differ
diff --git a/static/images/social/integrations/astradb.png b/static/images/social/integrations/astradb.png
new file mode 100644
index 00000000..c2e67649
Binary files /dev/null and b/static/images/social/integrations/astradb.png differ
diff --git a/static/images/social/integrations/azure-ai-search.png b/static/images/social/integrations/azure-ai-search.png
new file mode 100644
index 00000000..c355fdbe
Binary files /dev/null and b/static/images/social/integrations/azure-ai-search.png differ
diff --git a/static/images/social/integrations/azure-cosmos-db.png b/static/images/social/integrations/azure-cosmos-db.png
new file mode 100644
index 00000000..e8688fb4
Binary files /dev/null and b/static/images/social/integrations/azure-cosmos-db.png differ
diff --git a/static/images/social/integrations/azure-doc-intelligence.png b/static/images/social/integrations/azure-doc-intelligence.png
new file mode 100644
index 00000000..70b81635
Binary files /dev/null and b/static/images/social/integrations/azure-doc-intelligence.png differ
diff --git a/static/images/social/integrations/azure.png b/static/images/social/integrations/azure.png
new file mode 100644
index 00000000..2047f3c1
Binary files /dev/null and b/static/images/social/integrations/azure.png differ
diff --git a/static/images/social/integrations/bright-data.png b/static/images/social/integrations/bright-data.png
new file mode 100644
index 00000000..ff3d8d91
Binary files /dev/null and b/static/images/social/integrations/bright-data.png differ
diff --git a/static/images/social/integrations/browserbase.png b/static/images/social/integrations/browserbase.png
new file mode 100644
index 00000000..6e764ca9
Binary files /dev/null and b/static/images/social/integrations/browserbase.png differ
diff --git a/static/images/social/integrations/burr.png b/static/images/social/integrations/burr.png
new file mode 100644
index 00000000..e8507172
Binary files /dev/null and b/static/images/social/integrations/burr.png differ
diff --git a/static/images/social/integrations/cerebras.png b/static/images/social/integrations/cerebras.png
new file mode 100644
index 00000000..e7eecd54
Binary files /dev/null and b/static/images/social/integrations/cerebras.png differ
diff --git a/static/images/social/integrations/chainlit.png b/static/images/social/integrations/chainlit.png
new file mode 100644
index 00000000..14bfd060
Binary files /dev/null and b/static/images/social/integrations/chainlit.png differ
diff --git a/static/images/social/integrations/chroma-documentstore.png b/static/images/social/integrations/chroma-documentstore.png
new file mode 100644
index 00000000..dd1536f3
Binary files /dev/null and b/static/images/social/integrations/chroma-documentstore.png differ
diff --git a/static/images/social/integrations/cohere.png b/static/images/social/integrations/cohere.png
new file mode 100644
index 00000000..01c71b51
Binary files /dev/null and b/static/images/social/integrations/cohere.png differ
diff --git a/static/images/social/integrations/comet-api.png b/static/images/social/integrations/comet-api.png
new file mode 100644
index 00000000..9aa1d545
Binary files /dev/null and b/static/images/social/integrations/comet-api.png differ
diff --git a/static/images/social/integrations/context-ai.png b/static/images/social/integrations/context-ai.png
new file mode 100644
index 00000000..a8f0046b
Binary files /dev/null and b/static/images/social/integrations/context-ai.png differ
diff --git a/static/images/social/integrations/couchbase-document-store.png b/static/images/social/integrations/couchbase-document-store.png
new file mode 100644
index 00000000..01d08774
Binary files /dev/null and b/static/images/social/integrations/couchbase-document-store.png differ
diff --git a/static/images/social/integrations/deepeval.png b/static/images/social/integrations/deepeval.png
new file mode 100644
index 00000000..a608f87b
Binary files /dev/null and b/static/images/social/integrations/deepeval.png differ
diff --git a/static/images/social/integrations/deepl.png b/static/images/social/integrations/deepl.png
new file mode 100644
index 00000000..791920fd
Binary files /dev/null and b/static/images/social/integrations/deepl.png differ
diff --git a/static/images/social/integrations/docling.png b/static/images/social/integrations/docling.png
new file mode 100644
index 00000000..e52a4839
Binary files /dev/null and b/static/images/social/integrations/docling.png differ
diff --git a/static/images/social/integrations/duckduckgo-api-websearch.png b/static/images/social/integrations/duckduckgo-api-websearch.png
new file mode 100644
index 00000000..29e204ff
Binary files /dev/null and b/static/images/social/integrations/duckduckgo-api-websearch.png differ
diff --git a/static/images/social/integrations/elasticsearch-document-store.png b/static/images/social/integrations/elasticsearch-document-store.png
new file mode 100644
index 00000000..072d8ee0
Binary files /dev/null and b/static/images/social/integrations/elasticsearch-document-store.png differ
diff --git a/static/images/social/integrations/elevenlabs.png b/static/images/social/integrations/elevenlabs.png
new file mode 100644
index 00000000..e49468f3
Binary files /dev/null and b/static/images/social/integrations/elevenlabs.png differ
diff --git a/static/images/social/integrations/exa.png b/static/images/social/integrations/exa.png
new file mode 100644
index 00000000..ca2c7c4f
Binary files /dev/null and b/static/images/social/integrations/exa.png differ
diff --git a/static/images/social/integrations/faiss.png b/static/images/social/integrations/faiss.png
new file mode 100644
index 00000000..47b27a93
Binary files /dev/null and b/static/images/social/integrations/faiss.png differ
diff --git a/static/images/social/integrations/fastembed.png b/static/images/social/integrations/fastembed.png
new file mode 100644
index 00000000..816e6b7f
Binary files /dev/null and b/static/images/social/integrations/fastembed.png differ
diff --git a/static/images/social/integrations/fastrag.png b/static/images/social/integrations/fastrag.png
new file mode 100644
index 00000000..7e863d6a
Binary files /dev/null and b/static/images/social/integrations/fastrag.png differ
diff --git a/static/images/social/integrations/featherlessai.png b/static/images/social/integrations/featherlessai.png
new file mode 100644
index 00000000..30dd9e64
Binary files /dev/null and b/static/images/social/integrations/featherlessai.png differ
diff --git a/static/images/social/integrations/firecrawl.png b/static/images/social/integrations/firecrawl.png
new file mode 100644
index 00000000..66b7a8cd
Binary files /dev/null and b/static/images/social/integrations/firecrawl.png differ
diff --git a/static/images/social/integrations/flow-judge.png b/static/images/social/integrations/flow-judge.png
new file mode 100644
index 00000000..953f4af5
Binary files /dev/null and b/static/images/social/integrations/flow-judge.png differ
diff --git a/static/images/social/integrations/github.png b/static/images/social/integrations/github.png
new file mode 100644
index 00000000..d69dc8b9
Binary files /dev/null and b/static/images/social/integrations/github.png differ
diff --git a/static/images/social/integrations/google-ai.png b/static/images/social/integrations/google-ai.png
new file mode 100644
index 00000000..15c70ee9
Binary files /dev/null and b/static/images/social/integrations/google-ai.png differ
diff --git a/static/images/social/integrations/google-genai.png b/static/images/social/integrations/google-genai.png
new file mode 100644
index 00000000..c2bb68e3
Binary files /dev/null and b/static/images/social/integrations/google-genai.png differ
diff --git a/static/images/social/integrations/google-vertex-ai.png b/static/images/social/integrations/google-vertex-ai.png
new file mode 100644
index 00000000..c69a8565
Binary files /dev/null and b/static/images/social/integrations/google-vertex-ai.png differ
diff --git a/static/images/social/integrations/groq.png b/static/images/social/integrations/groq.png
new file mode 100644
index 00000000..a90123e8
Binary files /dev/null and b/static/images/social/integrations/groq.png differ
diff --git a/static/images/social/integrations/hanlp.png b/static/images/social/integrations/hanlp.png
new file mode 100644
index 00000000..1a6a96da
Binary files /dev/null and b/static/images/social/integrations/hanlp.png differ
diff --git a/static/images/social/integrations/huggingface.png b/static/images/social/integrations/huggingface.png
new file mode 100644
index 00000000..f4896ff2
Binary files /dev/null and b/static/images/social/integrations/huggingface.png differ
diff --git a/static/images/social/integrations/instructor-embedder.png b/static/images/social/integrations/instructor-embedder.png
new file mode 100644
index 00000000..81f4481e
Binary files /dev/null and b/static/images/social/integrations/instructor-embedder.png differ
diff --git a/static/images/social/integrations/integrations.png b/static/images/social/integrations/integrations.png
new file mode 100644
index 00000000..253696a2
Binary files /dev/null and b/static/images/social/integrations/integrations.png differ
diff --git a/static/images/social/integrations/isaacus.png b/static/images/social/integrations/isaacus.png
new file mode 100644
index 00000000..de628f32
Binary files /dev/null and b/static/images/social/integrations/isaacus.png differ
diff --git a/static/images/social/integrations/jina.png b/static/images/social/integrations/jina.png
new file mode 100644
index 00000000..241a4030
Binary files /dev/null and b/static/images/social/integrations/jina.png differ
diff --git a/static/images/social/integrations/kreuzberg.png b/static/images/social/integrations/kreuzberg.png
new file mode 100644
index 00000000..88f8e89c
Binary files /dev/null and b/static/images/social/integrations/kreuzberg.png differ
diff --git a/static/images/social/integrations/lancedb.png b/static/images/social/integrations/lancedb.png
new file mode 100644
index 00000000..781e6a76
Binary files /dev/null and b/static/images/social/integrations/lancedb.png differ
diff --git a/static/images/social/integrations/langfuse.png b/static/images/social/integrations/langfuse.png
new file mode 100644
index 00000000..6666ec67
Binary files /dev/null and b/static/images/social/integrations/langfuse.png differ
diff --git a/static/images/social/integrations/lara.png b/static/images/social/integrations/lara.png
new file mode 100644
index 00000000..f38aa718
Binary files /dev/null and b/static/images/social/integrations/lara.png differ
diff --git a/static/images/social/integrations/libreoffice-file-converter.png b/static/images/social/integrations/libreoffice-file-converter.png
new file mode 100644
index 00000000..a1e6ae34
Binary files /dev/null and b/static/images/social/integrations/libreoffice-file-converter.png differ
diff --git a/static/images/social/integrations/llama_cpp.png b/static/images/social/integrations/llama_cpp.png
new file mode 100644
index 00000000..dd612b54
Binary files /dev/null and b/static/images/social/integrations/llama_cpp.png differ
diff --git a/static/images/social/integrations/llama_stack.png b/static/images/social/integrations/llama_stack.png
new file mode 100644
index 00000000..2f0ab313
Binary files /dev/null and b/static/images/social/integrations/llama_stack.png differ
diff --git a/static/images/social/integrations/llamafile.png b/static/images/social/integrations/llamafile.png
new file mode 100644
index 00000000..cd33bf4a
Binary files /dev/null and b/static/images/social/integrations/llamafile.png differ
diff --git a/static/images/social/integrations/lmformatenforcer.png b/static/images/social/integrations/lmformatenforcer.png
new file mode 100644
index 00000000..4513afe3
Binary files /dev/null and b/static/images/social/integrations/lmformatenforcer.png differ
diff --git a/static/images/social/integrations/markitdown.png b/static/images/social/integrations/markitdown.png
new file mode 100644
index 00000000..2fe934df
Binary files /dev/null and b/static/images/social/integrations/markitdown.png differ
diff --git a/static/images/social/integrations/mastodon-fetcher.png b/static/images/social/integrations/mastodon-fetcher.png
new file mode 100644
index 00000000..2115c628
Binary files /dev/null and b/static/images/social/integrations/mastodon-fetcher.png differ
diff --git a/static/images/social/integrations/mcp.png b/static/images/social/integrations/mcp.png
new file mode 100644
index 00000000..fb0821d1
Binary files /dev/null and b/static/images/social/integrations/mcp.png differ
diff --git a/static/images/social/integrations/meta_llama.png b/static/images/social/integrations/meta_llama.png
new file mode 100644
index 00000000..62bc6a4a
Binary files /dev/null and b/static/images/social/integrations/meta_llama.png differ
diff --git a/static/images/social/integrations/milvus-document-store.png b/static/images/social/integrations/milvus-document-store.png
new file mode 100644
index 00000000..34ea7029
Binary files /dev/null and b/static/images/social/integrations/milvus-document-store.png differ
diff --git a/static/images/social/integrations/mistral.png b/static/images/social/integrations/mistral.png
new file mode 100644
index 00000000..191cc12a
Binary files /dev/null and b/static/images/social/integrations/mistral.png differ
diff --git a/static/images/social/integrations/mixedbread-ai.png b/static/images/social/integrations/mixedbread-ai.png
new file mode 100644
index 00000000..973e8162
Binary files /dev/null and b/static/images/social/integrations/mixedbread-ai.png differ
diff --git a/static/images/social/integrations/mlflow.png b/static/images/social/integrations/mlflow.png
new file mode 100644
index 00000000..dec42fdb
Binary files /dev/null and b/static/images/social/integrations/mlflow.png differ
diff --git a/static/images/social/integrations/mongodb.png b/static/images/social/integrations/mongodb.png
new file mode 100644
index 00000000..da8e3096
Binary files /dev/null and b/static/images/social/integrations/mongodb.png differ
diff --git a/static/images/social/integrations/monsterapi.png b/static/images/social/integrations/monsterapi.png
new file mode 100644
index 00000000..2c37abb2
Binary files /dev/null and b/static/images/social/integrations/monsterapi.png differ
diff --git a/static/images/social/integrations/needle.png b/static/images/social/integrations/needle.png
new file mode 100644
index 00000000..e1dd4386
Binary files /dev/null and b/static/images/social/integrations/needle.png differ
diff --git a/static/images/social/integrations/neo4j-document-store.png b/static/images/social/integrations/neo4j-document-store.png
new file mode 100644
index 00000000..265e8e09
Binary files /dev/null and b/static/images/social/integrations/neo4j-document-store.png differ
diff --git a/static/images/social/integrations/notion-extractor.png b/static/images/social/integrations/notion-extractor.png
new file mode 100644
index 00000000..675420ec
Binary files /dev/null and b/static/images/social/integrations/notion-extractor.png differ
diff --git a/static/images/social/integrations/nvidia.png b/static/images/social/integrations/nvidia.png
new file mode 100644
index 00000000..cef5e902
Binary files /dev/null and b/static/images/social/integrations/nvidia.png differ
diff --git a/static/images/social/integrations/ollama.png b/static/images/social/integrations/ollama.png
new file mode 100644
index 00000000..dc1540d8
Binary files /dev/null and b/static/images/social/integrations/ollama.png differ
diff --git a/static/images/social/integrations/opea.png b/static/images/social/integrations/opea.png
new file mode 100644
index 00000000..fc0f9e9c
Binary files /dev/null and b/static/images/social/integrations/opea.png differ
diff --git a/static/images/social/integrations/openai.png b/static/images/social/integrations/openai.png
new file mode 100644
index 00000000..ef0715db
Binary files /dev/null and b/static/images/social/integrations/openai.png differ
diff --git a/static/images/social/integrations/openlit.png b/static/images/social/integrations/openlit.png
new file mode 100644
index 00000000..7a568c7c
Binary files /dev/null and b/static/images/social/integrations/openlit.png differ
diff --git a/static/images/social/integrations/openrouter.png b/static/images/social/integrations/openrouter.png
new file mode 100644
index 00000000..60dacf26
Binary files /dev/null and b/static/images/social/integrations/openrouter.png differ
diff --git a/static/images/social/integrations/opensearch-document-store.png b/static/images/social/integrations/opensearch-document-store.png
new file mode 100644
index 00000000..b74b80b8
Binary files /dev/null and b/static/images/social/integrations/opensearch-document-store.png differ
diff --git a/static/images/social/integrations/openstreetmap.png b/static/images/social/integrations/openstreetmap.png
new file mode 100644
index 00000000..cda4f4d2
Binary files /dev/null and b/static/images/social/integrations/openstreetmap.png differ
diff --git a/static/images/social/integrations/openwebui.png b/static/images/social/integrations/openwebui.png
new file mode 100644
index 00000000..5675cd62
Binary files /dev/null and b/static/images/social/integrations/openwebui.png differ
diff --git a/static/images/social/integrations/opik.png b/static/images/social/integrations/opik.png
new file mode 100644
index 00000000..41db8afd
Binary files /dev/null and b/static/images/social/integrations/opik.png differ
diff --git a/static/images/social/integrations/optimum.png b/static/images/social/integrations/optimum.png
new file mode 100644
index 00000000..68372743
Binary files /dev/null and b/static/images/social/integrations/optimum.png differ
diff --git a/static/images/social/integrations/paddleocr.png b/static/images/social/integrations/paddleocr.png
new file mode 100644
index 00000000..fd8d21fe
Binary files /dev/null and b/static/images/social/integrations/paddleocr.png differ
diff --git a/static/images/social/integrations/pgvector-documentstore.png b/static/images/social/integrations/pgvector-documentstore.png
new file mode 100644
index 00000000..aad2cc0d
Binary files /dev/null and b/static/images/social/integrations/pgvector-documentstore.png differ
diff --git a/static/images/social/integrations/pinecone-document-store.png b/static/images/social/integrations/pinecone-document-store.png
new file mode 100644
index 00000000..8f485670
Binary files /dev/null and b/static/images/social/integrations/pinecone-document-store.png differ
diff --git a/static/images/social/integrations/praisonai.png b/static/images/social/integrations/praisonai.png
new file mode 100644
index 00000000..1ebacfd2
Binary files /dev/null and b/static/images/social/integrations/praisonai.png differ
diff --git a/static/images/social/integrations/prior-labs.png b/static/images/social/integrations/prior-labs.png
new file mode 100644
index 00000000..241eb88f
Binary files /dev/null and b/static/images/social/integrations/prior-labs.png differ
diff --git a/static/images/social/integrations/pyversity.png b/static/images/social/integrations/pyversity.png
new file mode 100644
index 00000000..2048f185
Binary files /dev/null and b/static/images/social/integrations/pyversity.png differ
diff --git a/static/images/social/integrations/qdrant-document-store.png b/static/images/social/integrations/qdrant-document-store.png
new file mode 100644
index 00000000..69ac515b
Binary files /dev/null and b/static/images/social/integrations/qdrant-document-store.png differ
diff --git a/static/images/social/integrations/ragas.png b/static/images/social/integrations/ragas.png
new file mode 100644
index 00000000..c8b76d2e
Binary files /dev/null and b/static/images/social/integrations/ragas.png differ
diff --git a/static/images/social/integrations/ray.png b/static/images/social/integrations/ray.png
new file mode 100644
index 00000000..de2e9872
Binary files /dev/null and b/static/images/social/integrations/ray.png differ
diff --git a/static/images/social/integrations/sambanova.png b/static/images/social/integrations/sambanova.png
new file mode 100644
index 00000000..44afca8b
Binary files /dev/null and b/static/images/social/integrations/sambanova.png differ
diff --git a/static/images/social/integrations/serpex.png b/static/images/social/integrations/serpex.png
new file mode 100644
index 00000000..11474e4f
Binary files /dev/null and b/static/images/social/integrations/serpex.png differ
diff --git a/static/images/social/integrations/singlestore.png b/static/images/social/integrations/singlestore.png
new file mode 100644
index 00000000..48fca100
Binary files /dev/null and b/static/images/social/integrations/singlestore.png differ
diff --git a/static/images/social/integrations/snowflake.png b/static/images/social/integrations/snowflake.png
new file mode 100644
index 00000000..7d09f530
Binary files /dev/null and b/static/images/social/integrations/snowflake.png differ
diff --git a/static/images/social/integrations/stackit.png b/static/images/social/integrations/stackit.png
new file mode 100644
index 00000000..0e1fc65f
Binary files /dev/null and b/static/images/social/integrations/stackit.png differ
diff --git a/static/images/social/integrations/titanml-takeoff.png b/static/images/social/integrations/titanml-takeoff.png
new file mode 100644
index 00000000..08dc5c63
Binary files /dev/null and b/static/images/social/integrations/titanml-takeoff.png differ
diff --git a/static/images/social/integrations/togetherai.png b/static/images/social/integrations/togetherai.png
new file mode 100644
index 00000000..f7f4ad6d
Binary files /dev/null and b/static/images/social/integrations/togetherai.png differ
diff --git a/static/images/social/integrations/tonic-textual.png b/static/images/social/integrations/tonic-textual.png
new file mode 100644
index 00000000..40f20a6d
Binary files /dev/null and b/static/images/social/integrations/tonic-textual.png differ
diff --git a/static/images/social/integrations/traceloop.png b/static/images/social/integrations/traceloop.png
new file mode 100644
index 00000000..e4edf50f
Binary files /dev/null and b/static/images/social/integrations/traceloop.png differ
diff --git a/static/images/social/integrations/trafilatura.png b/static/images/social/integrations/trafilatura.png
new file mode 100644
index 00000000..def34aec
Binary files /dev/null and b/static/images/social/integrations/trafilatura.png differ
diff --git a/static/images/social/integrations/unstructured-file-converter.png b/static/images/social/integrations/unstructured-file-converter.png
new file mode 100644
index 00000000..3c3933a9
Binary files /dev/null and b/static/images/social/integrations/unstructured-file-converter.png differ
diff --git a/static/images/social/integrations/valkey.png b/static/images/social/integrations/valkey.png
new file mode 100644
index 00000000..f42d878d
Binary files /dev/null and b/static/images/social/integrations/valkey.png differ
diff --git a/static/images/social/integrations/valyu.png b/static/images/social/integrations/valyu.png
new file mode 100644
index 00000000..d60b5982
Binary files /dev/null and b/static/images/social/integrations/valyu.png differ
diff --git a/static/images/social/integrations/vllm.png b/static/images/social/integrations/vllm.png
new file mode 100644
index 00000000..c5fb871e
Binary files /dev/null and b/static/images/social/integrations/vllm.png differ
diff --git a/static/images/social/integrations/voyage.png b/static/images/social/integrations/voyage.png
new file mode 100644
index 00000000..8740af34
Binary files /dev/null and b/static/images/social/integrations/voyage.png differ
diff --git a/static/images/social/integrations/watsonx.png b/static/images/social/integrations/watsonx.png
new file mode 100644
index 00000000..a3703fc4
Binary files /dev/null and b/static/images/social/integrations/watsonx.png differ
diff --git a/static/images/social/integrations/weaviate-document-store.png b/static/images/social/integrations/weaviate-document-store.png
new file mode 100644
index 00000000..20355c90
Binary files /dev/null and b/static/images/social/integrations/weaviate-document-store.png differ
diff --git a/static/images/social/integrations/weights-and-bias-tracer.png b/static/images/social/integrations/weights-and-bias-tracer.png
new file mode 100644
index 00000000..844c9bbc
Binary files /dev/null and b/static/images/social/integrations/weights-and-bias-tracer.png differ
diff --git a/static/images/social/overview/demo.png b/static/images/social/overview/demo.png
new file mode 100644
index 00000000..667b5387
Binary files /dev/null and b/static/images/social/overview/demo.png differ
diff --git a/static/images/social/overview/intro.png b/static/images/social/overview/intro.png
new file mode 100644
index 00000000..94be3cac
Binary files /dev/null and b/static/images/social/overview/intro.png differ
diff --git a/static/images/social/overview/overview.png b/static/images/social/overview/overview.png
new file mode 100644
index 00000000..ebd0f8e3
Binary files /dev/null and b/static/images/social/overview/overview.png differ
diff --git a/static/images/social/overview/quick-start.png b/static/images/social/overview/quick-start.png
new file mode 100644
index 00000000..044f508c
Binary files /dev/null and b/static/images/social/overview/quick-start.png differ
diff --git a/static/images/social/overview/roadmap.png b/static/images/social/overview/roadmap.png
new file mode 100644
index 00000000..0beccce6
Binary files /dev/null and b/static/images/social/overview/roadmap.png differ
diff --git a/static/images/social/overview/use-cases.png b/static/images/social/overview/use-cases.png
new file mode 100644
index 00000000..3f2b32bc
Binary files /dev/null and b/static/images/social/overview/use-cases.png differ
diff --git a/static/images/social/pages/community.png b/static/images/social/pages/community.png
new file mode 100644
index 00000000..73ec4df9
Binary files /dev/null and b/static/images/social/pages/community.png differ
diff --git a/static/images/social/pages/hacktoberfest.png b/static/images/social/pages/hacktoberfest.png
new file mode 100644
index 00000000..3797bca8
Binary files /dev/null and b/static/images/social/pages/hacktoberfest.png differ
diff --git a/static/images/social/pages/nlp-resources.png b/static/images/social/pages/nlp-resources.png
new file mode 100644
index 00000000..4eeabd77
Binary files /dev/null and b/static/images/social/pages/nlp-resources.png differ
diff --git a/static/images/social/pages/pages.png b/static/images/social/pages/pages.png
new file mode 100644
index 00000000..e6c1f492
Binary files /dev/null and b/static/images/social/pages/pages.png differ
diff --git a/static/images/social/release-notes/2.0.0.png b/static/images/social/release-notes/2.0.0.png
new file mode 100644
index 00000000..621004e7
Binary files /dev/null and b/static/images/social/release-notes/2.0.0.png differ
diff --git a/static/images/social/release-notes/2.0.1.png b/static/images/social/release-notes/2.0.1.png
new file mode 100644
index 00000000..1ef99a6b
Binary files /dev/null and b/static/images/social/release-notes/2.0.1.png differ
diff --git a/static/images/social/release-notes/2.1.0.png b/static/images/social/release-notes/2.1.0.png
new file mode 100644
index 00000000..09594743
Binary files /dev/null and b/static/images/social/release-notes/2.1.0.png differ
diff --git a/static/images/social/release-notes/2.1.1.png b/static/images/social/release-notes/2.1.1.png
new file mode 100644
index 00000000..a6acf0dc
Binary files /dev/null and b/static/images/social/release-notes/2.1.1.png differ
diff --git a/static/images/social/release-notes/2.1.2.png b/static/images/social/release-notes/2.1.2.png
new file mode 100644
index 00000000..f70d592c
Binary files /dev/null and b/static/images/social/release-notes/2.1.2.png differ
diff --git a/static/images/social/release-notes/2.10.0.png b/static/images/social/release-notes/2.10.0.png
new file mode 100644
index 00000000..eb7da994
Binary files /dev/null and b/static/images/social/release-notes/2.10.0.png differ
diff --git a/static/images/social/release-notes/2.10.1.png b/static/images/social/release-notes/2.10.1.png
new file mode 100644
index 00000000..203922dd
Binary files /dev/null and b/static/images/social/release-notes/2.10.1.png differ
diff --git a/static/images/social/release-notes/2.10.2.png b/static/images/social/release-notes/2.10.2.png
new file mode 100644
index 00000000..139ee615
Binary files /dev/null and b/static/images/social/release-notes/2.10.2.png differ
diff --git a/static/images/social/release-notes/2.10.3.png b/static/images/social/release-notes/2.10.3.png
new file mode 100644
index 00000000..f7b7abf0
Binary files /dev/null and b/static/images/social/release-notes/2.10.3.png differ
diff --git a/static/images/social/release-notes/2.11.0.png b/static/images/social/release-notes/2.11.0.png
new file mode 100644
index 00000000..4e5417a2
Binary files /dev/null and b/static/images/social/release-notes/2.11.0.png differ
diff --git a/static/images/social/release-notes/2.11.1.png b/static/images/social/release-notes/2.11.1.png
new file mode 100644
index 00000000..b944b2fd
Binary files /dev/null and b/static/images/social/release-notes/2.11.1.png differ
diff --git a/static/images/social/release-notes/2.11.2.png b/static/images/social/release-notes/2.11.2.png
new file mode 100644
index 00000000..8b7af536
Binary files /dev/null and b/static/images/social/release-notes/2.11.2.png differ
diff --git a/static/images/social/release-notes/2.12.0.png b/static/images/social/release-notes/2.12.0.png
new file mode 100644
index 00000000..9271e6f2
Binary files /dev/null and b/static/images/social/release-notes/2.12.0.png differ
diff --git a/static/images/social/release-notes/2.12.1.png b/static/images/social/release-notes/2.12.1.png
new file mode 100644
index 00000000..7f69ec33
Binary files /dev/null and b/static/images/social/release-notes/2.12.1.png differ
diff --git a/static/images/social/release-notes/2.12.2.png b/static/images/social/release-notes/2.12.2.png
new file mode 100644
index 00000000..5a332a16
Binary files /dev/null and b/static/images/social/release-notes/2.12.2.png differ
diff --git a/static/images/social/release-notes/2.13.0.png b/static/images/social/release-notes/2.13.0.png
new file mode 100644
index 00000000..c823f16a
Binary files /dev/null and b/static/images/social/release-notes/2.13.0.png differ
diff --git a/static/images/social/release-notes/2.13.1.png b/static/images/social/release-notes/2.13.1.png
new file mode 100644
index 00000000..e7650b66
Binary files /dev/null and b/static/images/social/release-notes/2.13.1.png differ
diff --git a/static/images/social/release-notes/2.13.2.png b/static/images/social/release-notes/2.13.2.png
new file mode 100644
index 00000000..85362996
Binary files /dev/null and b/static/images/social/release-notes/2.13.2.png differ
diff --git a/static/images/social/release-notes/2.14.0.png b/static/images/social/release-notes/2.14.0.png
new file mode 100644
index 00000000..c2605704
Binary files /dev/null and b/static/images/social/release-notes/2.14.0.png differ
diff --git a/static/images/social/release-notes/2.14.1.png b/static/images/social/release-notes/2.14.1.png
new file mode 100644
index 00000000..a53c0a2d
Binary files /dev/null and b/static/images/social/release-notes/2.14.1.png differ
diff --git a/static/images/social/release-notes/2.14.2.png b/static/images/social/release-notes/2.14.2.png
new file mode 100644
index 00000000..a065b317
Binary files /dev/null and b/static/images/social/release-notes/2.14.2.png differ
diff --git a/static/images/social/release-notes/2.14.3.png b/static/images/social/release-notes/2.14.3.png
new file mode 100644
index 00000000..f39722a8
Binary files /dev/null and b/static/images/social/release-notes/2.14.3.png differ
diff --git a/static/images/social/release-notes/2.15.0.png b/static/images/social/release-notes/2.15.0.png
new file mode 100644
index 00000000..b8d27e31
Binary files /dev/null and b/static/images/social/release-notes/2.15.0.png differ
diff --git a/static/images/social/release-notes/2.15.1.png b/static/images/social/release-notes/2.15.1.png
new file mode 100644
index 00000000..4dbfaaaf
Binary files /dev/null and b/static/images/social/release-notes/2.15.1.png differ
diff --git a/static/images/social/release-notes/2.15.2.png b/static/images/social/release-notes/2.15.2.png
new file mode 100644
index 00000000..6e9aecdc
Binary files /dev/null and b/static/images/social/release-notes/2.15.2.png differ
diff --git a/static/images/social/release-notes/2.16.0.png b/static/images/social/release-notes/2.16.0.png
new file mode 100644
index 00000000..a11ee34a
Binary files /dev/null and b/static/images/social/release-notes/2.16.0.png differ
diff --git a/static/images/social/release-notes/2.16.1.png b/static/images/social/release-notes/2.16.1.png
new file mode 100644
index 00000000..474bf78d
Binary files /dev/null and b/static/images/social/release-notes/2.16.1.png differ
diff --git a/static/images/social/release-notes/2.17.0.png b/static/images/social/release-notes/2.17.0.png
new file mode 100644
index 00000000..35b2bf76
Binary files /dev/null and b/static/images/social/release-notes/2.17.0.png differ
diff --git a/static/images/social/release-notes/2.17.1.png b/static/images/social/release-notes/2.17.1.png
new file mode 100644
index 00000000..76cb01a1
Binary files /dev/null and b/static/images/social/release-notes/2.17.1.png differ
diff --git a/static/images/social/release-notes/2.18.0.png b/static/images/social/release-notes/2.18.0.png
new file mode 100644
index 00000000..4e9ab76a
Binary files /dev/null and b/static/images/social/release-notes/2.18.0.png differ
diff --git a/static/images/social/release-notes/2.18.1.png b/static/images/social/release-notes/2.18.1.png
new file mode 100644
index 00000000..32c7e136
Binary files /dev/null and b/static/images/social/release-notes/2.18.1.png differ
diff --git a/static/images/social/release-notes/2.19.0.png b/static/images/social/release-notes/2.19.0.png
new file mode 100644
index 00000000..5549e39d
Binary files /dev/null and b/static/images/social/release-notes/2.19.0.png differ
diff --git a/static/images/social/release-notes/2.2.0.png b/static/images/social/release-notes/2.2.0.png
new file mode 100644
index 00000000..e2748b6a
Binary files /dev/null and b/static/images/social/release-notes/2.2.0.png differ
diff --git a/static/images/social/release-notes/2.2.1.png b/static/images/social/release-notes/2.2.1.png
new file mode 100644
index 00000000..cfe7405e
Binary files /dev/null and b/static/images/social/release-notes/2.2.1.png differ
diff --git a/static/images/social/release-notes/2.2.2.png b/static/images/social/release-notes/2.2.2.png
new file mode 100644
index 00000000..9038dbab
Binary files /dev/null and b/static/images/social/release-notes/2.2.2.png differ
diff --git a/static/images/social/release-notes/2.2.3.png b/static/images/social/release-notes/2.2.3.png
new file mode 100644
index 00000000..9aeb948d
Binary files /dev/null and b/static/images/social/release-notes/2.2.3.png differ
diff --git a/static/images/social/release-notes/2.2.4.png b/static/images/social/release-notes/2.2.4.png
new file mode 100644
index 00000000..f58196ee
Binary files /dev/null and b/static/images/social/release-notes/2.2.4.png differ
diff --git a/static/images/social/release-notes/2.20.0.png b/static/images/social/release-notes/2.20.0.png
new file mode 100644
index 00000000..6eb249fc
Binary files /dev/null and b/static/images/social/release-notes/2.20.0.png differ
diff --git a/static/images/social/release-notes/2.21.0.png b/static/images/social/release-notes/2.21.0.png
new file mode 100644
index 00000000..612e33ae
Binary files /dev/null and b/static/images/social/release-notes/2.21.0.png differ
diff --git a/static/images/social/release-notes/2.22.0.png b/static/images/social/release-notes/2.22.0.png
new file mode 100644
index 00000000..64df4e4e
Binary files /dev/null and b/static/images/social/release-notes/2.22.0.png differ
diff --git a/static/images/social/release-notes/2.23.0.png b/static/images/social/release-notes/2.23.0.png
new file mode 100644
index 00000000..0c49d428
Binary files /dev/null and b/static/images/social/release-notes/2.23.0.png differ
diff --git a/static/images/social/release-notes/2.24.0.png b/static/images/social/release-notes/2.24.0.png
new file mode 100644
index 00000000..cd1a8246
Binary files /dev/null and b/static/images/social/release-notes/2.24.0.png differ
diff --git a/static/images/social/release-notes/2.24.1.png b/static/images/social/release-notes/2.24.1.png
new file mode 100644
index 00000000..8d5e29a4
Binary files /dev/null and b/static/images/social/release-notes/2.24.1.png differ
diff --git a/static/images/social/release-notes/2.25.0.png b/static/images/social/release-notes/2.25.0.png
new file mode 100644
index 00000000..676ae522
Binary files /dev/null and b/static/images/social/release-notes/2.25.0.png differ
diff --git a/static/images/social/release-notes/2.25.1.png b/static/images/social/release-notes/2.25.1.png
new file mode 100644
index 00000000..fb116ee0
Binary files /dev/null and b/static/images/social/release-notes/2.25.1.png differ
diff --git a/static/images/social/release-notes/2.25.2.png b/static/images/social/release-notes/2.25.2.png
new file mode 100644
index 00000000..132798df
Binary files /dev/null and b/static/images/social/release-notes/2.25.2.png differ
diff --git a/static/images/social/release-notes/2.26.0.png b/static/images/social/release-notes/2.26.0.png
new file mode 100644
index 00000000..2cd29c35
Binary files /dev/null and b/static/images/social/release-notes/2.26.0.png differ
diff --git a/static/images/social/release-notes/2.27.0.png b/static/images/social/release-notes/2.27.0.png
new file mode 100644
index 00000000..20af75ed
Binary files /dev/null and b/static/images/social/release-notes/2.27.0.png differ
diff --git a/static/images/social/release-notes/2.28.0.png b/static/images/social/release-notes/2.28.0.png
new file mode 100644
index 00000000..aa4468b3
Binary files /dev/null and b/static/images/social/release-notes/2.28.0.png differ
diff --git a/static/images/social/release-notes/2.29.0.png b/static/images/social/release-notes/2.29.0.png
new file mode 100644
index 00000000..bd7c64fe
Binary files /dev/null and b/static/images/social/release-notes/2.29.0.png differ
diff --git a/static/images/social/release-notes/2.3.0.png b/static/images/social/release-notes/2.3.0.png
new file mode 100644
index 00000000..00389b67
Binary files /dev/null and b/static/images/social/release-notes/2.3.0.png differ
diff --git a/static/images/social/release-notes/2.3.1.png b/static/images/social/release-notes/2.3.1.png
new file mode 100644
index 00000000..027a2d58
Binary files /dev/null and b/static/images/social/release-notes/2.3.1.png differ
diff --git a/static/images/social/release-notes/2.30.0.png b/static/images/social/release-notes/2.30.0.png
new file mode 100644
index 00000000..535496a4
Binary files /dev/null and b/static/images/social/release-notes/2.30.0.png differ
diff --git a/static/images/social/release-notes/2.30.1.png b/static/images/social/release-notes/2.30.1.png
new file mode 100644
index 00000000..0cddb174
Binary files /dev/null and b/static/images/social/release-notes/2.30.1.png differ
diff --git a/static/images/social/release-notes/2.30.2.png b/static/images/social/release-notes/2.30.2.png
new file mode 100644
index 00000000..271eb65a
Binary files /dev/null and b/static/images/social/release-notes/2.30.2.png differ
diff --git a/static/images/social/release-notes/2.4.0.png b/static/images/social/release-notes/2.4.0.png
new file mode 100644
index 00000000..74b20a16
Binary files /dev/null and b/static/images/social/release-notes/2.4.0.png differ
diff --git a/static/images/social/release-notes/2.5.0.png b/static/images/social/release-notes/2.5.0.png
new file mode 100644
index 00000000..495b61e2
Binary files /dev/null and b/static/images/social/release-notes/2.5.0.png differ
diff --git a/static/images/social/release-notes/2.5.1.png b/static/images/social/release-notes/2.5.1.png
new file mode 100644
index 00000000..5b0b6407
Binary files /dev/null and b/static/images/social/release-notes/2.5.1.png differ
diff --git a/static/images/social/release-notes/2.6.0.png b/static/images/social/release-notes/2.6.0.png
new file mode 100644
index 00000000..13c3f417
Binary files /dev/null and b/static/images/social/release-notes/2.6.0.png differ
diff --git a/static/images/social/release-notes/2.6.1.png b/static/images/social/release-notes/2.6.1.png
new file mode 100644
index 00000000..9018cd72
Binary files /dev/null and b/static/images/social/release-notes/2.6.1.png differ
diff --git a/static/images/social/release-notes/2.7.0.png b/static/images/social/release-notes/2.7.0.png
new file mode 100644
index 00000000..ad6ab865
Binary files /dev/null and b/static/images/social/release-notes/2.7.0.png differ
diff --git a/static/images/social/release-notes/2.8.0.png b/static/images/social/release-notes/2.8.0.png
new file mode 100644
index 00000000..69301168
Binary files /dev/null and b/static/images/social/release-notes/2.8.0.png differ
diff --git a/static/images/social/release-notes/2.8.1.png b/static/images/social/release-notes/2.8.1.png
new file mode 100644
index 00000000..026b94f1
Binary files /dev/null and b/static/images/social/release-notes/2.8.1.png differ
diff --git a/static/images/social/release-notes/2.9.0.png b/static/images/social/release-notes/2.9.0.png
new file mode 100644
index 00000000..5158142d
Binary files /dev/null and b/static/images/social/release-notes/2.9.0.png differ
diff --git a/static/images/social/release-notes/release-notes.png b/static/images/social/release-notes/release-notes.png
new file mode 100644
index 00000000..9c00390c
Binary files /dev/null and b/static/images/social/release-notes/release-notes.png differ
diff --git a/static/images/social/release-notes/v2.20.0.png b/static/images/social/release-notes/v2.20.0.png
new file mode 100644
index 00000000..629c1755
Binary files /dev/null and b/static/images/social/release-notes/v2.20.0.png differ
diff --git a/static/images/social/release-notes/v2.22.0.png b/static/images/social/release-notes/v2.22.0.png
new file mode 100644
index 00000000..3243acb7
Binary files /dev/null and b/static/images/social/release-notes/v2.22.0.png differ
diff --git a/static/images/social/release-notes/v2.25.1.png b/static/images/social/release-notes/v2.25.1.png
new file mode 100644
index 00000000..50910826
Binary files /dev/null and b/static/images/social/release-notes/v2.25.1.png differ
diff --git a/static/images/social/spring-into-haystack/challenge.png b/static/images/social/spring-into-haystack/challenge.png
new file mode 100644
index 00000000..e045e53f
Binary files /dev/null and b/static/images/social/spring-into-haystack/challenge.png differ
diff --git a/static/images/social/spring-into-haystack/spring-into-haystack.png b/static/images/social/spring-into-haystack/spring-into-haystack.png
new file mode 100644
index 00000000..85dfe7e8
Binary files /dev/null and b/static/images/social/spring-into-haystack/spring-into-haystack.png differ
diff --git a/static/images/social/tutorials/27_First_RAG_Pipeline.png b/static/images/social/tutorials/27_First_RAG_Pipeline.png
new file mode 100644
index 00000000..65d84f7d
Binary files /dev/null and b/static/images/social/tutorials/27_First_RAG_Pipeline.png differ
diff --git a/static/images/social/tutorials/28_Structured_Output_With_OpenAI.png b/static/images/social/tutorials/28_Structured_Output_With_OpenAI.png
new file mode 100644
index 00000000..596400d7
Binary files /dev/null and b/static/images/social/tutorials/28_Structured_Output_With_OpenAI.png differ
diff --git a/static/images/social/tutorials/29_Serializing_Pipelines.png b/static/images/social/tutorials/29_Serializing_Pipelines.png
new file mode 100644
index 00000000..3e9f9b97
Binary files /dev/null and b/static/images/social/tutorials/29_Serializing_Pipelines.png differ
diff --git a/static/images/social/tutorials/30_File_Type_Preprocessing_Index_Pipeline.png b/static/images/social/tutorials/30_File_Type_Preprocessing_Index_Pipeline.png
new file mode 100644
index 00000000..d39d93ed
Binary files /dev/null and b/static/images/social/tutorials/30_File_Type_Preprocessing_Index_Pipeline.png differ
diff --git a/static/images/social/tutorials/31_Metadata_Filtering.png b/static/images/social/tutorials/31_Metadata_Filtering.png
new file mode 100644
index 00000000..f20d6073
Binary files /dev/null and b/static/images/social/tutorials/31_Metadata_Filtering.png differ
diff --git a/static/images/social/tutorials/32_Classifying_Documents_and_Queries_by_Language.png b/static/images/social/tutorials/32_Classifying_Documents_and_Queries_by_Language.png
new file mode 100644
index 00000000..805733a0
Binary files /dev/null and b/static/images/social/tutorials/32_Classifying_Documents_and_Queries_by_Language.png differ
diff --git a/static/images/social/tutorials/33_Hybrid_Retrieval.png b/static/images/social/tutorials/33_Hybrid_Retrieval.png
new file mode 100644
index 00000000..b850be25
Binary files /dev/null and b/static/images/social/tutorials/33_Hybrid_Retrieval.png differ
diff --git a/static/images/social/tutorials/34_Extractive_QA_Pipeline.png b/static/images/social/tutorials/34_Extractive_QA_Pipeline.png
new file mode 100644
index 00000000..884c9d8a
Binary files /dev/null and b/static/images/social/tutorials/34_Extractive_QA_Pipeline.png differ
diff --git a/static/images/social/tutorials/35_Evaluating_RAG_Pipelines.png b/static/images/social/tutorials/35_Evaluating_RAG_Pipelines.png
new file mode 100644
index 00000000..6697e085
Binary files /dev/null and b/static/images/social/tutorials/35_Evaluating_RAG_Pipelines.png differ
diff --git a/static/images/social/tutorials/36_Building_Fallbacks_with_Conditional_Routing.png b/static/images/social/tutorials/36_Building_Fallbacks_with_Conditional_Routing.png
new file mode 100644
index 00000000..b68f40d0
Binary files /dev/null and b/static/images/social/tutorials/36_Building_Fallbacks_with_Conditional_Routing.png differ
diff --git a/static/images/social/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.png b/static/images/social/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.png
new file mode 100644
index 00000000..0a00275e
Binary files /dev/null and b/static/images/social/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.png differ
diff --git a/static/images/social/tutorials/39_Embedding_Metadata_for_Improved_Retrieval.png b/static/images/social/tutorials/39_Embedding_Metadata_for_Improved_Retrieval.png
new file mode 100644
index 00000000..da8167b7
Binary files /dev/null and b/static/images/social/tutorials/39_Embedding_Metadata_for_Improved_Retrieval.png differ
diff --git a/static/images/social/tutorials/40_Building_Chat_Application_with_Function_Calling.png b/static/images/social/tutorials/40_Building_Chat_Application_with_Function_Calling.png
new file mode 100644
index 00000000..179d1417
Binary files /dev/null and b/static/images/social/tutorials/40_Building_Chat_Application_with_Function_Calling.png differ
diff --git a/static/images/social/tutorials/41_Query_Classification_with_TransformersTextRouter_and_TransformersZeroShotTextRouter.png b/static/images/social/tutorials/41_Query_Classification_with_TransformersTextRouter_and_TransformersZeroShotTextRouter.png
new file mode 100644
index 00000000..b67d281a
Binary files /dev/null and b/static/images/social/tutorials/41_Query_Classification_with_TransformersTextRouter_and_TransformersZeroShotTextRouter.png differ
diff --git a/static/images/social/tutorials/42_Sentence_Window_Retriever.png b/static/images/social/tutorials/42_Sentence_Window_Retriever.png
new file mode 100644
index 00000000..40883d32
Binary files /dev/null and b/static/images/social/tutorials/42_Sentence_Window_Retriever.png differ
diff --git a/static/images/social/tutorials/43_Building_a_Tool_Calling_Agent.png b/static/images/social/tutorials/43_Building_a_Tool_Calling_Agent.png
new file mode 100644
index 00000000..5c7f09e2
Binary files /dev/null and b/static/images/social/tutorials/43_Building_a_Tool_Calling_Agent.png differ
diff --git a/static/images/social/tutorials/44_Creating_Custom_SuperComponents.png b/static/images/social/tutorials/44_Creating_Custom_SuperComponents.png
new file mode 100644
index 00000000..c1688b87
Binary files /dev/null and b/static/images/social/tutorials/44_Creating_Custom_SuperComponents.png differ
diff --git a/static/images/social/tutorials/45_Creating_a_Multi_Agent_System.png b/static/images/social/tutorials/45_Creating_a_Multi_Agent_System.png
new file mode 100644
index 00000000..6b7acc23
Binary files /dev/null and b/static/images/social/tutorials/45_Creating_a_Multi_Agent_System.png differ
diff --git a/static/images/social/tutorials/46_Multimodal_RAG.png b/static/images/social/tutorials/46_Multimodal_RAG.png
new file mode 100644
index 00000000..323414be
Binary files /dev/null and b/static/images/social/tutorials/46_Multimodal_RAG.png differ
diff --git a/static/images/social/tutorials/47_Human_in_the_Loop_Agent.png b/static/images/social/tutorials/47_Human_in_the_Loop_Agent.png
new file mode 100644
index 00000000..a83228dd
Binary files /dev/null and b/static/images/social/tutorials/47_Human_in_the_Loop_Agent.png differ
diff --git a/static/images/social/tutorials/48_Conversational_RAG.png b/static/images/social/tutorials/48_Conversational_RAG.png
new file mode 100644
index 00000000..13e48701
Binary files /dev/null and b/static/images/social/tutorials/48_Conversational_RAG.png differ
diff --git a/static/images/social/tutorials/guide_evaluation.png b/static/images/social/tutorials/guide_evaluation.png
new file mode 100644
index 00000000..1fd7f4c3
Binary files /dev/null and b/static/images/social/tutorials/guide_evaluation.png differ
diff --git a/static/images/social/tutorials/tutorials.png b/static/images/social/tutorials/tutorials.png
new file mode 100644
index 00000000..bfbd79c5
Binary files /dev/null and b/static/images/social/tutorials/tutorials.png differ
diff --git a/themes/haystack/layouts/partials/opengraph.html b/themes/haystack/layouts/partials/opengraph.html
index 6f2dae70..85af2c20 100644
--- a/themes/haystack/layouts/partials/opengraph.html
+++ b/themes/haystack/layouts/partials/opengraph.html
@@ -48,18 +48,33 @@
"
/>
-{{- with $.Params.images -}}
- {{- range first 6 . }}
+{{- $slug := "" -}}
+{{- with .File -}}
+ {{- $slug = .ContentBaseName -}}
+ {{- if or (eq $slug "index") (eq $slug "_index") -}}
+ {{- $slug = path.Base (strings.TrimSuffix .Dir "/") -}}
+ {{- end -}}
+{{- end -}}
+{{- $socialImageStatic := printf "static/images/social/%s/%s.png" .Section $slug -}}
+{{- $socialImageURL := printf "/images/social/%s/%s.png" .Section $slug -}}
+{{- $hasSocialImage := and $slug (os.FileExists $socialImageStatic) -}}
+
+{{- if $.Params.images -}}
+ {{- range first 6 $.Params.images }}
{{ end -}}
+{{- else if $hasSocialImage -}}
+
{{- else -}}
{{- with $.Site.Params.images }}
{{ end -}}
{{- end -}}
-{{- with $.Params.images -}}
-
+{{- if $.Params.images -}}
+
+{{- else if $hasSocialImage -}}
+
{{- else -}}
{{- with $.Site.Params.images }}