diff --git a/scripts/30k-image.py b/scripts/30k-image.py
index e69de29bb..e9db4078b 100644
--- a/scripts/30k-image.py
+++ b/scripts/30k-image.py
@@ -0,0 +1,197 @@
+#!/usr/bin/env python
+"""
+Generate a celebratory SVG image for Aider reaching 30,000 GitHub stars.
+This creates a shareable social media graphic with confetti animation.
+"""
+
+import os
+import random
+import base64
+import argparse
+from pathlib import Path
+
+# Default colors for the celebration image
+AIDER_GREEN = "#14b014"
+AIDER_BLUE = "#4C6EF5"
+DARK_COLOR = "#212529"
+LIGHT_COLOR = "#F8F9FA"
+GOLD_COLOR = "#f1c40f"
+
+# Default dimensions for social sharing
+DEFAULT_WIDTH = 1200
+DEFAULT_HEIGHT = 630
+
+
+def embed_font():
+ """Returns base64 encoded font data for the GlassTTYVT220 font."""
+ # Path to the font file
+ font_path = Path(__file__).parent.parent / "aider" / "website" / "assets" / "Glass_TTY_VT220.ttf"
+
+ # If font file doesn't exist, return empty string
+ if not font_path.exists():
+ print(f"Warning: Font file not found at {font_path}")
+ return ""
+
+ # Read and encode the font file
+ with open(font_path, "rb") as f:
+ font_data = f.read()
+
+ # Return base64 encoded font data
+ return base64.b64encode(font_data).decode('utf-8')
+
+
+def generate_confetti(count=150, width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT):
+ """Generate SVG confetti elements for the celebration."""
+ confetti = []
+ colors = [AIDER_GREEN, AIDER_BLUE, GOLD_COLOR, "#e74c3c", "#9b59b6", "#3498db", "#2ecc71"]
+
+ for i in range(count):
+ x = random.randint(0, width)
+ y = random.randint(0, height)
+ size = random.randint(5, 15)
+ color = random.choice(colors)
+ rotation = random.randint(0, 360)
+ delay = random.uniform(0, 2)
+ duration = random.uniform(1, 3)
+
+ # Randomly choose between rect (square), circle, and star shapes
+ shape_type = random.choice(["rect", "circle", "star"])
+
+ if shape_type == "rect":
+ shape = f"""
+
+
+ """
+ elif shape_type == "circle":
+ shape = f"""
+
+
+ """
+ else: # star
+ # Create a simple 5-point star
+ points = []
+ for j in range(5):
+ angle = j * 2 * 3.14159 / 5
+ x_point = x + (size * 0.5) * math.cos(angle)
+ y_point = y + (size * 0.5) * math.sin(angle)
+ points.append(f"{x_point},{y_point}")
+
+ # Inner points of the star
+ inner_angle = angle + 3.14159 / 5
+ inner_x = x + (size * 0.2) * math.cos(inner_angle)
+ inner_y = y + (size * 0.2) * math.sin(inner_angle)
+ points.append(f"{inner_x},{inner_y}")
+
+ points_str = " ".join(points)
+ shape = f"""
+
+
+
+ """
+
+ confetti.append(shape)
+
+ return "\n".join(confetti)
+
+
+def generate_celebration_svg(output_path=None, width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT):
+ """Generate a celebratory SVG for 30K GitHub stars."""
+ # Import math here to avoid the error in generate_confetti
+ import math
+
+ # Font embedding
+ font_data = embed_font()
+ font_face = f"""
+ @font-face {{
+ font-family: 'GlassTTYVT220';
+ src: url(data:font/truetype;charset=utf-8;base64,{font_data}) format('truetype');
+ font-weight: normal;
+ font-style: normal;
+ }}
+ """ if font_data else ""
+
+ # Generate confetti elements
+ confetti = generate_confetti(count=150, width=width, height=height)
+
+ # Create the SVG content
+ svg_content = f"""
+
+"""
+
+ # Write to file if output path is specified
+ if output_path:
+ with open(output_path, 'w') as f:
+ f.write(svg_content)
+ print(f"Celebration SVG saved to {output_path}")
+
+ return svg_content
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Generate a celebration SVG for Aider's 30K GitHub stars")
+ parser.add_argument('--output', '-o', type=str, default="aider-30k-stars.svg",
+ help="Output file path (default: aider-30k-stars.svg)")
+ parser.add_argument('--width', '-w', type=int, default=DEFAULT_WIDTH,
+ help=f"Image width in pixels (default: {DEFAULT_WIDTH})")
+ parser.add_argument('--height', '-ht', type=int, default=DEFAULT_HEIGHT,
+ help=f"Image height in pixels (default: {DEFAULT_HEIGHT})")
+ args = parser.parse_args()
+
+ # Generate the SVG
+ generate_celebration_svg(args.output, args.width, args.height)