# openslides

[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![Tests](https://github.com/federicodeponte/openslides/actions/workflows/ci.yml/badge.svg)](https://github.com/federicodeponte/openslides/actions)

**Brand-first Python slide deck generator.** Create pitch decks, investor presentations, and sales slides with full creative control. Extract brand colors and fonts from any website, write HTML/CSS slides, export to PNG or PDF.

> Generate professional slide decks programmatically. No templates, no themes, no fighting the framework.

![openslides demo](demo.png)

```python
from openslides import Brand, export, base_html

brand = Brand.from_domain("scaile.tech")  # Auto-extract colors, fonts, logo

def slide_hero():
    return f'''
    {base_html(brand)}
    <body style="background:{brand.background}; padding:80px;">
        <h1 style="font-family:'{brand.font_headline}'; font-size:72px; color:{brand.text};">
            Get into <span style="color:{brand.primary};">ChatGPT</span> answers.
        </h1>
    </body></html>
    '''

export([slide_hero()], "/tmp/my-deck/")  # → PNG files
```

## Why openslides?

Most slide libraries force you into preset themes that never match your brand. You end up fighting the abstraction.

**openslides is different:**
- **Brand-first**: Extract colors/fonts from any website, or define manually
- **Full control**: You write HTML/CSS, the library just helps with boilerplate
- **No magic**: Brand values are explicit in your code, not hidden in themes
- **~300 lines**: Simple enough to understand in 10 minutes

## Install

```bash
pip install openslides
playwright install chromium

# Optional: for brand extraction from URLs
pip install aiohttp
```

## Quick Start

### Option 1: Extract brand from website

```python
from openslides import Brand, export, base_html

# Auto-extract colors, fonts, logo from website
brand = Brand.from_domain("stripe.com")

print(brand.primary)        # "#635bff"
print(brand.font_headline)  # "Inter"
```

### Option 2: Define brand manually

```python
brand = Brand(
    primary="#054dfe",      # Main accent (CTAs, highlights)
    secondary="#15aebf",    # Secondary accent
    background="#fdfbf5",   # Slide background
    surface="#ffffff",      # Cards, elevated surfaces
    text="#191919",         # Primary text
    muted="#6b6b6b",        # Secondary text
    font_headline="Syne",   # Headlines
    font_body="Inter",      # Body text
)
```

### Create slides

Each slide is a function returning HTML. Use f-strings to inject brand values:

```python
def slide_hero():
    return f'''
    {base_html(brand)}
    <body style="background:{brand.background}; padding:80px;">
        <div style="font-size:12px; color:{brand.primary}; text-transform:uppercase; letter-spacing:2px;">
            The Problem
        </div>
        <h1 style="font-family:'{brand.font_headline}'; font-size:64px; color:{brand.text}; margin-top:20px;">
            Your buyers don't Google anymore.
            <span style="color:{brand.muted};">They ask ChatGPT.</span>
        </h1>
    </body></html>
    '''

def slide_solution():
    return f'''
    {base_html(brand)}
    <body style="background:{brand.surface}; padding:80px;">
        <h1 style="font-family:'{brand.font_headline}'; font-size:64px;">
            We make AI recommend <span style="color:{brand.primary};">your brand.</span>
        </h1>
    </body></html>
    '''
```

### Export to PNG/PDF

```python
slides = [slide_hero(), slide_solution()]

# Export to PNG (default)
paths = export(slides, "/tmp/my-deck/")
# Creates: /tmp/my-deck/slide-01.png, slide-02.png, ...

# Export to PDF
paths = export(slides, "/tmp/my-deck/", format="pdf")
# Or use convenience function:
paths = export_pdf(slides, "/tmp/my-deck/")
```

## CLI

Build decks from the command line:

```bash
# Build to PNG
openslides build examples/scaile.py -o /tmp/scaile-deck/

# Build to PDF
openslides build examples/scaile.py -o /tmp/scaile-deck/ -f pdf

# List slides in a deck file
openslides list examples/scaile.py

# Custom dimensions
openslides build examples/scaile.py -W 1280 -H 720
```

## API Reference

### `Brand`

Dataclass holding brand assets.

**Attributes:**
- `primary` - Main accent color (hex)
- `secondary` - Secondary accent color
- `background` - Slide background color
- `surface` - Card/surface background
- `text` - Primary text color
- `muted` - Secondary/muted text color
- `font_headline` - Headline font family
- `font_body` - Body text font family
- `logo_svg` - Logo as inline SVG (optional)
- `logo_url` - Logo URL (optional)
- `name` - Brand name
- `domain` - Source domain

**Validation:**
- Color fields must be valid hex colors (e.g., `#054dfe` or `#fff`)
- Invalid colors raise `ValueError`

**Methods:**
- `Brand.from_domain(url)` - Extract brand from website (requires aiohttp)
- `brand.google_fonts_url` - Google Fonts URL for brand fonts

### `base_html(brand)`

Generate HTML boilerplate with brand fonts loaded. Returns everything up to `<body>`.

```python
base_html(brand)
# Returns: <!DOCTYPE html><html>...<head>...<link href="fonts.googleapis.com/...">...</head>
```

### `base_styles(brand)`

Generate common CSS classes using brand values. Use inside a `<style>` tag.

```python
f'<style>{base_styles(brand)}</style>'
# Provides: .headline, .body-text, .muted, .accent, .card, .label, .btn
```

### `export(slides, output_dir, format="png")`

Export slides to PNG or PDF files.

```python
paths = export(slides, "/tmp/deck/", width=1920, height=1080)
paths = export(slides, "/tmp/deck/", format="pdf")
```

### `export_pdf(slides, output_dir)`

Convenience wrapper for PDF export.

```python
paths = export_pdf(slides, "/tmp/deck/")
```

### `export_async(slides, output_dir, format="png")`

Async version of export. Use if already in async context.

### Logo Helpers

**`clearbit_logo(domain, size=40)`** - Get logo img tag via Clearbit (free, no auth):

```python
from openslides import clearbit_logo

logo = clearbit_logo("stripe.com", size=40)
# Returns: <img src="https://logo.clearbit.com/stripe.com" alt="logo" style="height:40px;" />
```

**`brand.logo_img(size=40)`** - Get logo from Brand (uses logo_svg, logo_url, or Clearbit fallback):

```python
logo = brand.logo_img(size=48)
# Uses: brand.logo_svg if set, else brand.logo_url, else Clearbit
```

**`brand.clearbit_logo_url`** - Direct Clearbit URL:

```python
url = brand.clearbit_logo_url
# Returns: "https://logo.clearbit.com/{domain}"
```

## Philosophy

The best slide decks are **brand-specific**, not generic. openslides gives you:

1. **Brand extraction** - Get colors/fonts from any website
2. **Explicit values** - Brand values visible in your code
3. **Full control** - Write whatever HTML/CSS you want
4. **Simple export** - Playwright renders to PNG

No themes. No components. No fighting the framework.

## License

MIT
