{"id":51145089,"url":"https://github.com/saddamarbaa/linkedin-post-studio","last_synced_at":"2026-06-26T02:03:17.243Z","repository":{"id":355326484,"uuid":"1227494333","full_name":"saddamarbaa/linkedin-post-studio","owner":"saddamarbaa","description":"App for generating LinkedIn thumbnails and captions for AI, ML, and coding creators powered by Claude","archived":false,"fork":false,"pushed_at":"2026-05-10T05:23:44.000Z","size":790,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-10T06:48:09.982Z","etag":null,"topics":["claude-code","nextjs","rectjs","talwindcss","typescript","zustand"],"latest_commit_sha":null,"homepage":"https://linkedin-post-studio-six.vercel.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saddamarbaa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-02T19:02:58.000Z","updated_at":"2026-05-10T05:23:49.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/saddamarbaa/linkedin-post-studio","commit_stats":null,"previous_names":["saddamarbaa/linkedin-post-studio"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/saddamarbaa/linkedin-post-studio","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saddamarbaa%2Flinkedin-post-studio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saddamarbaa%2Flinkedin-post-studio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saddamarbaa%2Flinkedin-post-studio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saddamarbaa%2Flinkedin-post-studio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saddamarbaa","download_url":"https://codeload.github.com/saddamarbaa/linkedin-post-studio/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saddamarbaa%2Flinkedin-post-studio/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34799571,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude-code","nextjs","rectjs","talwindcss","typescript","zustand"],"created_at":"2026-06-26T02:03:14.738Z","updated_at":"2026-06-26T02:03:17.230Z","avatar_url":"https://github.com/saddamarbaa.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LinkedIn Post Studio\n\nGenerate scroll-stopping 1200×1200 LinkedIn graphics for AI/ML/coding creators. Pick a template, choose a theme, edit content, download a PNG. Optional Claude-powered \"Quick AI\" turns a one-line idea into 4 thumbnail variants and a matching caption.\n\n\u003e **Source of truth:** [SPEC.md](SPEC.md) — read it before changing the system. This README is the operator's guide; the spec is the contract.\n\n---\n\n## Table of contents\n\n- [Features](#features)\n- [Tech stack](#tech-stack)\n- [Quick start](#quick-start)\n- [Environment variables](#environment-variables)\n- [Available scripts](#available-scripts)\n- [Project structure](#project-structure)\n- [Architecture](#architecture)\n- [API routes](#api-routes)\n- [Conventions and rules](#conventions-and-rules)\n- [Deployment](#deployment)\n- [Performance and accessibility](#performance-and-accessibility)\n- [Troubleshooting](#troubleshooting)\n- [Contributing](#contributing)\n\n---\n\n## Features\n\n- **13 templates** across three groups — Quick AI (`thumbnail`), ML/AI (`concept`, `formula`, `compare`, `dayLog`, `curve`, `network`), and Basic (`code`, `quote`, `tips`, `announce`, `explainer`, `carousel`).\n- **6 themes** — `terminal`, `cosmic`, `ocean`, `sunset`, `minimal`, `paper`. See [SPEC §4.1](SPEC.md#41-theme).\n- **Quick AI** — Claude generates 4 thumbnail variants (`shock` / `question` / `stat` / `reveal`) from a single idea.\n- **Caption generator** — produces a 200–400 word LinkedIn caption tuned to the graphic, in `professional` / `casual` / `bold` / `storytelling` tones.\n- **Hero image upload** — 4 layouts (`hero`, `split`, `background`, `inline`); PNG/JPEG/WebP up to 5 MB.\n- **Inline SVG rendering** — every template is a pure 1200×1200 `\u003csvg\u003e`. PNG export is rasterized at download time only.\n- **Server-side AI** — `ANTHROPIC_API_KEY` never reaches the client bundle.\n\n---\n\n## Tech stack\n\n| Layer        | Choice                       | Version    |\n|--------------|------------------------------|------------|\n| Framework    | Next.js (App Router, Turbopack) | `16.2.x` |\n| UI runtime   | React                        | `19.x`     |\n| Language     | TypeScript (strict)          | `5.x`      |\n| Styling      | Tailwind CSS (CSS-first)     | `v4.1.x`   |\n| Components   | shadcn/ui (`new-york`)       | CLI v4     |\n| State        | Zustand                      | `v5.x`     |\n| AI SDK       | `@anthropic-ai/sdk`          | `v0.92.x`  |\n| Default model| `claude-opus-4-7` (fallback `claude-sonnet-4-6`) | — |\n| Toasts       | `sonner`                     | latest     |\n| Node         | `\u003e=20.0.0`                   | required   |\n\nThe stack is **locked**; deviations require updating [SPEC §2](SPEC.md#2-tech-stack-locked-may-2026).\n\n---\n\n## Quick start\n\n### Prerequisites\n\n- Node `\u003e=20.0.0`\n- An Anthropic API key (optional — the app runs without one; AI features are hidden)\n\n### Install and run\n\n```bash\n# 1. install dependencies\nnpm install\n\n# 2. configure environment\ncp .env.example .env.local\n# then edit .env.local and paste your ANTHROPIC_API_KEY\n\n# 3. start the dev server (Turbopack)\nnpm run dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000).\n\n### Production build\n\n```bash\nnpm run build\nnpm run start\n```\n\n---\n\n## Environment variables\n\nCopy [.env.example](.env.example) to `.env.local` and fill in real values. Never commit `.env.local`.\n\n| Variable               | Required | Scope        | Purpose                                      |\n|------------------------|----------|--------------|----------------------------------------------|\n| `ANTHROPIC_API_KEY`    | optional | server only  | Powers Quick AI and caption generation. If unset, AI routes return `503 { error: 'AI not configured' }` and the UI hides AI affordances. |\n| `NEXT_PUBLIC_APP_URL`  | optional | client + server | Used for Open Graph / social card URLs.   |\n\nGet a key at [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys).\n\n---\n\n## Available scripts\n\n```bash\nnpm run dev      # Start Next dev server with Turbopack\nnpm run build    # Production build\nnpm run start    # Run the production build\nnpm run lint     # ESLint (eslint-config-next)\n```\n\n\u003e Test and typecheck scripts are not yet wired up — see [SPEC §15](SPEC.md#15-testing) for the planned setup (Vitest + Testing Library + Playwright).\n\n---\n\n## Project structure\n\n```\napp/\n  api/\n    generate-thumbnails/route.ts   POST → 4 thumbnail variants\n    generate-caption/route.ts      POST → LinkedIn caption\n    explain-code/route.ts          POST → code explanation bullets\n  layout.tsx                       Root layout (fonts, Toaster)\n  page.tsx                         Studio entry; reads ANTHROPIC_API_KEY server-side\n  globals.css                      Tailwind v4 + @theme tokens\n\ncomponents/\n  studio/      Shell — StudioLayout, DownloadButton\n  templates/   PURE SVG templates, one file each\n  controls/    Inputs that mutate the Zustand store\n  shared/      Reused inside templates (PostFooter, PostBackground, HeroImage)\n  ai/          CaptionGenerator\n  ui/          shadcn-generated primitives — do not hand-edit\n\nlib/\n  api/claude.ts        Server-only Anthropic client factory\n  utils/               SvgExporter, TextWrapper, file validation\n  prompts/             AI prompt templates (source of truth)\n  constants/           THEMES, TEMPLATES registries\n  store/               Zustand v5 store\n\ntypes/\n  studio.ts            All shared types (Theme, Template, Content, Author, …)\n```\n\nFull target tree in [SPEC §3.3](SPEC.md#33-folder-structure).\n\n---\n\n## Architecture\n\n### Data flow\n\n```\nUser input ─► Zustand store ─► Pure SVG template ─► \u003csvg\u003e in \u003cPreviewPanel\u003e\n                  │                                        │\n                  │                                        └─► SvgExporter ─► PNG download\n                  │\n                  └─► (optional) /api/* Route Handler ─► Anthropic SDK ─► Claude\n```\n\n### Layering rules\n\n1. **Templates are pure.** Files in [components/templates/](components/templates/) take `theme` + `content` props and return SVG. No `useState`, no `useEffect`, no `fetch`, no store access.\n2. **Zustand is the only mutable state.** Content is keyed by template `kind` in `contentByKind` so users don't lose work when switching templates.\n3. **AI calls happen only in `app/api/*` Route Handlers.** Client components call `fetch('/api/...')` via custom hooks — never directly.\n4. **Rendering is inline SVG only.** No `\u003ccanvas\u003e` for layout. The only canvas use is inside [SvgExporter](lib/utils/svg-export.ts) when rasterizing to PNG at export.\n5. **Export uses base64 data URLs**, not blob URLs (blob URLs break inside iframe sandboxes).\n\nSee [SPEC §3.2](SPEC.md#32-layering-rules) for the full rationale.\n\n---\n\n## API routes\n\nAll routes return JSON; errors are `{ error: string }` with appropriate status codes.\n\n### `POST /api/generate-thumbnails`\n\n```ts\n// request\n{ idea: string }    // 5–200 chars\n\n// response 200\n{\n  variants: [\n    { style: 'shock'    | 'question' | 'stat' | 'reveal',\n      hook: string,     // ≤ 35 chars (truncated server-side with …)\n      subline: string,  // ≤ 30 chars\n      context: string } // ≤ 25 chars\n    // ×4\n  ]\n}\n```\n\n### `POST /api/generate-caption`\n\n```ts\n// request\n{\n  tone: 'professional' | 'casual' | 'bold' | 'storytelling';\n  authorHandle: string;\n  graphicSummary: string;\n}\n\n// response 200\n{ caption: string }   // 200–400 words, → for bullets, 5–8 hashtags\n```\n\n### `POST /api/explain-code`\n\n```ts\n// request\n{ code: string; language: string }\n\n// response 200\n{ points: string[] }  // 3–5 bullets, each ≤ 90 chars\n```\n\nFull contracts in [SPEC §6](SPEC.md#6-api-contracts). Prompt templates live in [lib/prompts/](lib/prompts/).\n\n---\n\n## Conventions and rules\n\nThese cut across files and are easy to miss. The full list lives in [CLAUDE.md](CLAUDE.md); the load-bearing ones:\n\n- **No `any`.** Template content is a discriminated union on `kind` — exhaust it everywhere.\n- **No `React.forwardRef`.** React 19 — pass `ref` as a regular prop. Use `React.ComponentProps\u003c...\u003e` for prop typing.\n- **No `tailwind.config.js`.** Theme tokens live in [app/globals.css](app/globals.css) under `@theme { }`. Plugins via `@plugin \"...\";`.\n- **Templates are pure SVG components** — see layering rules above.\n- **AI calls are server-only.** `ANTHROPIC_API_KEY` must never reach the client bundle.\n- **Image uploads convert to base64 data URLs** via `FileReader`. PNG/JPEG/WebP only, 5 MB max. Never store blob URLs.\n- **Visual contract is rigid:** 1200×1200 viewBox, 80px padding, divider at `y=1060`, footer `y=1060–1200`. See [SPEC §5](SPEC.md#5-visual--svg-specification).\n\n---\n\n## Deployment\n\n### Vercel (recommended)\n\n```bash\nvercel --prod\n```\n\nProject settings:\n\n- Framework preset: **Next.js**\n- Build command: `next build` (default)\n- Output directory: `.next` (default)\n- Environment variables: add `ANTHROPIC_API_KEY` to **Production** and **Preview** scopes.\n\n### Other targets\n\n- **Netlify** — works via the Next.js Adapter API (stable in 16.2).\n- **Cloudflare Pages** — works via the OpenNext adapter.\n- **Self-host** — `next start` behind nginx on Node 20+.\n\nSee [SPEC §16](SPEC.md#16-deployment).\n\n---\n\n## Performance and accessibility\n\n### Performance budget\n\n| Metric                              | Target          |\n|-------------------------------------|-----------------|\n| First load JS (page route)          | ≤ 200 KB gzip   |\n| LCP (preview render)                | ≤ 1.5s on M2 / fast 4G |\n| Time-to-PNG download                | ≤ 800ms after click |\n| Claude thumbnail generation (p50)   | ≤ 4s            |\n\nTactics: per-icon Lucide imports, `next/dynamic` for heavy templates (`CurveTemplate`, `NetworkTemplate`), server-rendered SVG where no interactivity is needed.\n\n### Accessibility\n\n- All inputs use shadcn `\u003cLabel\u003e` bound via `htmlFor`.\n- Color contrast ≥ 4.5:1 for body text on every theme.\n- Keyboard: `Tab` cycles through controls; the preview region is `aria-live=\"polite\"`.\n- `prefers-reduced-motion` disables hover transitions.\n\n---\n\n## Troubleshooting\n\n**Quick AI buttons don't appear.**\n`ANTHROPIC_API_KEY` is missing or empty. Add it to `.env.local` and restart the dev server. The page reads the key server-side at render time — see [app/page.tsx](app/page.tsx).\n\n**PNG download fails or produces a broken file.**\nConfirm export uses base64 data URLs (not blob URLs). The `SvgExporter` fallback shows a sonner toast suggesting *Right-click → Save image as…*.\n\n**Thumbnail variants come back over the length limit.**\nServer-side truncation (with `…`) lives in the route handler, not in the prompt. If you change the prompt, keep the truncation step.\n\n**Package manager.**\nThis project uses `npm`. A `package-lock.json` is checked in — don't introduce other lockfiles (`pnpm-lock.yaml`, `yarn.lock`).\n\n---\n\n## Contributing\n\n1. Read [SPEC.md](SPEC.md) and [CLAUDE.md](CLAUDE.md).\n2. Branch from `main`.\n3. Keep templates pure; keep AI calls server-side; keep types exhaustive.\n4. Update the spec changelog ([SPEC §20](SPEC.md#20-changelog)) when behavior or contracts change.\n5. Run `npm run lint` before pushing.\n\nOpen questions tracked in [SPEC §18](SPEC.md#18-open-questions) — flag these in PR discussions rather than silently deciding.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaddamarbaa%2Flinkedin-post-studio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaddamarbaa%2Flinkedin-post-studio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaddamarbaa%2Flinkedin-post-studio/lists"}