diff --git a/docs/README.skills.md b/docs/README.skills.md
index 05243683..7377d4c1 100644
--- a/docs/README.skills.md
+++ b/docs/README.skills.md
@@ -196,6 +196,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to
| [prd](../skills/prd/SKILL.md) | Generate high-quality Product Requirements Documents (PRDs) for software systems and AI-powered features. Includes executive summaries, user stories, technical specifications, and risk analysis. | None |
| [project-workflow-analysis-blueprint-generator](../skills/project-workflow-analysis-blueprint-generator/SKILL.md) | Comprehensive technology-agnostic prompt generator for documenting end-to-end application workflows. Automatically detects project architecture patterns, technology stacks, and data flow patterns to generate detailed implementation blueprints covering entry points, service layers, data access, error handling, and testing approaches across multiple technologies including .NET, Java/Spring, React, and microservices architectures. | None |
| [prompt-builder](../skills/prompt-builder/SKILL.md) | Guide users through creating high-quality GitHub Copilot prompts with proper structure, tools, and best practices. | None |
+| [publish-to-pages](../skills/publish-to-pages/SKILL.md) | Publish presentations and web content to GitHub Pages. Converts PPTX, PDF, HTML, or Google Slides to a live GitHub Pages URL. Handles repo creation, file conversion, Pages enablement, and returns the live URL. Use when the user wants to publish, deploy, or share a presentation or HTML file via GitHub Pages. | `scripts/convert-pdf.py`
`scripts/convert-pptx.py`
`scripts/publish.sh` |
| [pytest-coverage](../skills/pytest-coverage/SKILL.md) | Run pytest tests with coverage, discover lines missing coverage, and increase coverage to 100%. | None |
| [python-mcp-server-generator](../skills/python-mcp-server-generator/SKILL.md) | Generate a complete MCP server project in Python with tools, resources, and proper configuration | None |
| [quasi-coder](../skills/quasi-coder/SKILL.md) | Expert 10x engineer skill for interpreting and implementing code from shorthand, quasi-code, and natural language descriptions. Use when collaborators provide incomplete code snippets, pseudo-code, or descriptions with potential typos or incorrect terminology. Excels at translating non-technical or semi-technical descriptions into production-quality code. | None |
diff --git a/skills/publish-to-pages/SKILL.md b/skills/publish-to-pages/SKILL.md
new file mode 100644
index 00000000..a13a1286
--- /dev/null
+++ b/skills/publish-to-pages/SKILL.md
@@ -0,0 +1,90 @@
+---
+name: publish-to-pages
+description: 'Publish presentations and web content to GitHub Pages. Converts PPTX, PDF, HTML, or Google Slides to a live GitHub Pages URL. Handles repo creation, file conversion, Pages enablement, and returns the live URL. Use when the user wants to publish, deploy, or share a presentation or HTML file via GitHub Pages.'
+---
+
+# publish-to-pages
+
+Publish any presentation or web content to GitHub Pages in one shot.
+
+## 1. Prerequisites Check
+
+Run these silently. Only surface errors:
+
+```bash
+command -v gh >/dev/null || echo "MISSING: gh CLI — install from https://cli.github.com"
+gh auth status &>/dev/null || echo "MISSING: gh not authenticated — run 'gh auth login'"
+command -v python3 >/dev/null || echo "MISSING: python3 (needed for PPTX conversion)"
+```
+
+`poppler-utils` is optional (PDF conversion via `pdftoppm`). Don't block on it.
+
+## 2. Input Detection
+
+Determine input type from what the user provides:
+
+| Input | Detection |
+|-------|-----------|
+| HTML file | Extension `.html` or `.htm` |
+| PPTX file | Extension `.pptx` |
+| PDF file | Extension `.pdf` |
+| Google Slides URL | URL contains `docs.google.com/presentation` |
+
+Ask the user for a **repo name** if not provided. Default: filename without extension.
+
+## 3. Conversion
+
+### HTML
+No conversion needed. Use the file directly as `index.html`.
+
+### PPTX
+Run the conversion script:
+```bash
+python3 SKILL_DIR/scripts/convert-pptx.py INPUT_FILE /tmp/output.html
+```
+If `python-pptx` is missing, tell the user: `pip install python-pptx`
+
+### PDF
+Convert with the included script (requires `poppler-utils` for `pdftoppm`):
+```bash
+python3 SKILL_DIR/scripts/convert-pdf.py INPUT_FILE /tmp/output.html
+```
+Each page is rendered as a PNG and base64-embedded into a self-contained HTML with slide navigation.
+If `pdftoppm` is missing, tell the user: `apt install poppler-utils` (or `brew install poppler` on macOS).
+
+### Google Slides
+1. Extract the presentation ID from the URL (the long string between `/d/` and `/`)
+2. Download as PPTX:
+```bash
+curl -L "https://docs.google.com/presentation/d/PRESENTATION_ID/export/pptx" -o /tmp/slides.pptx
+```
+3. Then convert the PPTX using the convert script above.
+
+## 4. Publishing
+
+### Visibility
+Repos are created **public** by default. If the user specifies `private` (or wants a private repo), use `--private` — but note that GitHub Pages on private repos requires a Pro, Team, or Enterprise plan.
+
+### Publish
+```bash
+bash SKILL_DIR/scripts/publish.sh /path/to/index.html REPO_NAME public "Description"
+```
+
+Pass `private` instead of `public` if the user requests it.
+
+The script creates the repo, pushes `index.html`, and enables GitHub Pages.
+
+## 5. Output
+
+Tell the user:
+- **Repository:** `https://github.com/USERNAME/REPO_NAME`
+- **Live URL:** `https://USERNAME.github.io/REPO_NAME/`
+- **Note:** Pages takes 1-2 minutes to go live.
+
+## Error Handling
+
+- **Repo already exists:** Suggest appending a number (`my-slides-2`) or a date (`my-slides-2026`).
+- **Pages enablement fails:** Still return the repo URL. User can enable Pages manually in repo Settings.
+- **PPTX conversion fails:** Tell user to run `pip install python-pptx`.
+- **PDF conversion fails:** Suggest installing `poppler-utils` (`apt install poppler-utils` or `brew install poppler`).
+- **Google Slides download fails:** The presentation may not be publicly accessible. Ask user to make it viewable or download the PPTX manually.
diff --git a/skills/publish-to-pages/scripts/convert-pdf.py b/skills/publish-to-pages/scripts/convert-pdf.py
new file mode 100755
index 00000000..01c99e73
--- /dev/null
+++ b/skills/publish-to-pages/scripts/convert-pdf.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+"""Convert a PDF to a self-contained HTML presentation.
+
+Each page is rendered as a PNG image (via pdftoppm) and base64-embedded
+into a single HTML file with slide navigation (arrows, swipe, click).
+
+Requirements: poppler-utils (pdftoppm)
+Usage: python3 convert-pdf.py input.pdf [output.html]
+"""
+
+import base64
+import glob
+import os
+import subprocess
+import sys
+import tempfile
+from pathlib import Path
+
+
+def convert(pdf_path: str, output_path: str | None = None, dpi: int = 150):
+ pdf_path = str(Path(pdf_path).resolve())
+ if not Path(pdf_path).exists():
+ print(f"Error: {pdf_path} not found")
+ sys.exit(1)
+
+ # Check for pdftoppm
+ if subprocess.run(["which", "pdftoppm"], capture_output=True).returncode != 0:
+ print("Error: pdftoppm not found. Install poppler-utils:")
+ print(" apt install poppler-utils # Debian/Ubuntu")
+ print(" brew install poppler # macOS")
+ sys.exit(1)
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ prefix = os.path.join(tmpdir, "page")
+ result = subprocess.run(
+ ["pdftoppm", "-png", "-r", str(dpi), pdf_path, prefix],
+ capture_output=True, text=True
+ )
+ if result.returncode != 0:
+ print(f"Error converting PDF: {result.stderr}")
+ sys.exit(1)
+
+ pages = sorted(glob.glob(f"{prefix}-*.png"))
+ if not pages:
+ print("Error: No pages rendered from PDF")
+ sys.exit(1)
+
+ slides_html = []
+ for i, page_path in enumerate(pages, 1):
+ with open(page_path, "rb") as f:
+ b64 = base64.b64encode(f.read()).decode()
+ slides_html.append(
+ f''
+ )
+
+ # Try to extract title from filename
+ title = Path(pdf_path).stem.replace("-", " ").replace("_", " ")
+
+ html = f'''
+
+
{content}
' + + +def convert(pptx_path, output_path=None): + prs = Presentation(pptx_path) + slide_width = prs.slide_width + slide_height = prs.slide_height + aspect_ratio = slide_width / slide_height if slide_height else 16/9 + + slides_html = [] + + for i, slide in enumerate(prs.slides, 1): + bg_style = get_slide_background(slide, prs) + elements = [] + + for shape in sorted(slide.shapes, key=lambda s: (s.top or 0, s.left or 0)): + left, top, width, height = get_shape_position(shape, slide_width, slide_height) + pos_style = f"position:absolute;left:{left:.1f}%;top:{top:.1f}%;width:{width:.1f}%;height:{height:.1f}%" + + # Image + if shape.shape_type == 13 or hasattr(shape, "image"): + data_uri = extract_image(shape) + if data_uri: + elements.append( + f'| {cell_text} | ' + table_html += "