feat: landing page revamp (#349)

* feat: revamp marketing landing page

Combine hero (dot grid, noise texture, agent selector, install block) from
prototype 1 with middle content (plan/code review showcases, how-it-works
toggle, key properties, compatibility, final CTA) from prototype 2.

- Hero with animated fade-up, glassy agent buttons, per-agent install commands
- AnnoReplace component: shimmer + white glove hint → click → comment popover
- How It Works section with Plan/Code Review toggle swapping images + copy
- Accurate install instructions (binary + plugin for Claude Code/OpenCode)
- Three-column footer with brand stamp
- Nav with pipe-separated links
- Agent icons with CSS invert for light/dark mode
- Scroll reveal animations for showcase sections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: interactive terminal demos, install UX, and copy refinements

- Plan review: terminal animation (ASCII art, /plan typing, agent steps,
  submit_plan(), deny → feedback loop) with viewport-aware start/pause
- Code review: same pattern (/plannotator-review, send feedback loop)
- Install instructions: multi-step copyable rows for Claude Code and
  OpenCode, accurate Pi install command, Codex plan mode caveat
- Remove redundant Get Started section (hero handles install)
- AnnoReplace component: shimmer + white glove hint → click → comment popover
- How It Works: centered toggle, notification dot, tighter spacing
- Footer: license line, tighter gaps, @plannotator handle
- Social proof bar: "Zero learning curve"
- Hero trust signal: "free & open source"
- CTA subline: "Shape what gets built. Own what ships."
- Nav: pipe-separated links from prototype 2

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: clean up landing page dead code and fix mobile/perf issues

Remove ~160 lines of dead CSS/JS from removed Get Started section,
consolidate duplicate reveal animation system, fix copy buttons
invisible on touch devices, drop unused Source Serif 4 font load,
fix footer responsive breakpoint, and clean up duplicate data attrs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mobile responsiveness, demo glove pointers, and capabilities grid

Mobile: hide install commands, promote video CTA, reorder terminal
demos above text, fix social proof bar stacking, footer 2-col layout,
glove jabs further on small screens.

Demo gloves: white glove pointer on plan deny button (flipped, right
side) and code review send button (left side) with jab animations.

Capabilities: replace icon cards with 3-column grid (Commands,
Features, Integrations) with shimmer on slash commands.

Also: send feedback button moved to bottom-left, matched deny button
color, title em-dash to hyphen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: optimize images and clean up dead assets

Convert How It Works screenshots to WebP (25MB → 470KB).
Add proper plan-review.webp and code-review.webp for terminal demos.
Remove 6 unused image assets (~27MB). Fix ASCII art alignment.
Update trust signal copy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: contributor strip below footer with build-time GitHub fetch

GraphQL query (CI) fetches code contributors, issue authors, and
discussion participants in one request. REST fallback (local dev)
for code contributors only. Results cached in-process to avoid
blocking repeated renders. Deploy workflow passes GITHUB_TOKEN.

Also: bump brand stamp and avatar opacity, footer copy cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update agent video links and Codex install instructions

OpenCode → dedicated YouTube demo, Codex → X post demo.
Codex detail steps now show !plannotator review and !plannotator last.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: agent install steps leaking across buttons

Step elements now fully cleared (innerHTML + display:none) on every
agent switch. Use inline style instead of class toggle to avoid
CSS specificity conflict with .hero-detail-step display:flex.
Add third step slot for Codex commands. Add !plannotator annotate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add restart instructions and noscript fallback

Add restart step to Claude Code and OpenCode hero install flows so
new users know hooks/plugins require a session restart. Add noscript
style so landing-reveal sections stay visible without JavaScript.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michael Ramos
2026-03-19 23:28:34 -07:00
committed by GitHub
parent 1821943d06
commit e01526457b
27 changed files with 2446 additions and 327 deletions
+2
View File
@@ -99,6 +99,8 @@ jobs:
- name: Build marketing
run: bun run build:marketing
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="m6.283 21.28 6.293-3.531.106-.306-.106-.171h-.307l-1.051-.065-3.596-.097-3.118-.13-3.021-.162-.761-.161-.712-.94.073-.469.639-.429.916.08 2.023.138 3.037.209 2.203.13 3.263.339h.518l.073-.21-.177-.129-.138-.13-3.142-2.129-3.401-2.25-1.782-1.296-.963-.656-.486-.616-.21-1.343.875-.963 1.175.08.3.08 1.19.915 2.542 1.967 3.319 2.445.486.404.194-.138.024-.097-.218-.365-1.806-3.263-1.926-3.32-.857-1.375-.227-.825c-.08-.339-.138-.624-.138-.972L8.384.177 8.935 0l1.328.177.56.486.824 1.887 1.337 2.972 2.073 4.04.607 1.199.324 1.11.121.339h.21v-.194l.17-2.276.315-2.795.307-3.596.106-1.012.501-1.214.995-.657.778.372.639.916-.088.591-.381 2.471-.745 3.87-.485 2.591h.282l.324-.324 1.311-1.74 2.203-2.754.972-1.093 1.133-1.207.728-.574h1.376l1.013 1.505-.454 1.555-1.416 1.797-1.175 1.522-1.685 2.268-1.051 1.814.097.144.25-.023 3.805-.81 2.056-.372 2.454-.421 1.11.518.12.527-.436 1.078-2.624.648-3.077.615-4.582 1.084-.057.041.065.08 2.065.195.883.047h2.162l4.025.3 1.052.696.63.851-.106.647-1.619.825-2.186-.518-5.1-1.214-1.75-.436h-.242v.145l1.458 1.425 2.671 2.412 3.346 3.11.17.769-.43.607-.453-.065-2.939-2.211-1.134-.996-2.568-2.162h-.17v.227l.591.866 3.125 4.697.162 1.441-.226.468-.81.283-.89-.162-1.829-2.568-1.888-2.891-1.522-2.592-.186.106-.898 9.677-.421.495-.972.371-.81-.615-.43-.996.43-1.967.518-2.568.422-2.041.38-2.535.226-.842-.015-.056-.185.023-1.912 2.624-2.906 3.928-2.3 2.462-.551.218-.954-.494.088-.883.533-.787 3.184-4.049 1.919-2.509 1.24-1.449-.009-.21h-.073l-8.455 5.49-1.505.194-.648-.607.08-.995.307-.324 2.542-1.749-.009.008z" style="fill:#d97757;"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><clipPath id="a"><path d="M0 0h32v32H0z"/></clipPath><g clip-path="url(#a)"><path d="M3 32V0h26v32zM22 7H10v18h12z" style="fill:#fff"/><path d="M10 13h12v12H10z" style="fill:#5a5858"/></g></svg>

After

Width:  |  Height:  |  Size: 276 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><clipPath id="a"><path d="M0 0h32v32H0z"/></clipPath><g clip-path="url(#a)"><path d="M3 32V0h26v32zM22 7H10v18h12z" style="fill:#131010"/><path d="M10 13h12v12H10z" style="fill:#cfcecd"/></g></svg>

After

Width:  |  Height:  |  Size: 279 B

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800">
<!-- P shape: outer boundary clockwise, inner hole counter-clockwise -->
<path fill="#131010" fill-rule="evenodd" d="
M165.29 165.29
H517.36
V400
H400
V517.36
H282.65
V634.72
H165.29
Z
M282.65 282.65
V400
H400
V282.65
Z
"/>
<!-- i dot -->
<path fill="#131010" d="M517.36 400 H634.72 V634.72 H517.36 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 479 B

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800">
<!-- P shape: outer boundary clockwise, inner hole counter-clockwise -->
<path fill="#fff" fill-rule="evenodd" d="
M165.29 165.29
H517.36
V400
H400
V517.36
H282.65
V634.72
H165.29
Z
M282.65 282.65
V400
H400
V282.65
Z
"/>
<!-- i dot -->
<path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="m6.283 21.28 6.293-3.531.106-.306-.106-.171h-.307l-1.051-.065-3.596-.097-3.118-.13-3.021-.162-.761-.161-.712-.94.073-.469.639-.429.916.08 2.023.138 3.037.209 2.203.13 3.263.339h.518l.073-.21-.177-.129-.138-.13-3.142-2.129-3.401-2.25-1.782-1.296-.963-.656-.486-.616-.21-1.343.875-.963 1.175.08.3.08 1.19.915 2.542 1.967 3.319 2.445.486.404.194-.138.024-.097-.218-.365-1.806-3.263-1.926-3.32-.857-1.375-.227-.825c-.08-.339-.138-.624-.138-.972L8.384.177 8.935 0l1.328.177.56.486.824 1.887 1.337 2.972 2.073 4.04.607 1.199.324 1.11.121.339h.21v-.194l.17-2.276.315-2.795.307-3.596.106-1.012.501-1.214.995-.657.778.372.639.916-.088.591-.381 2.471-.745 3.87-.485 2.591h.282l.324-.324 1.311-1.74 2.203-2.754.972-1.093 1.133-1.207.728-.574h1.376l1.013 1.505-.454 1.555-1.416 1.797-1.175 1.522-1.685 2.268-1.051 1.814.097.144.25-.023 3.805-.81 2.056-.372 2.454-.421 1.11.518.12.527-.436 1.078-2.624.648-3.077.615-4.582 1.084-.057.041.065.08 2.065.195.883.047h2.162l4.025.3 1.052.696.63.851-.106.647-1.619.825-2.186-.518-5.1-1.214-1.75-.436h-.242v.145l1.458 1.425 2.671 2.412 3.346 3.11.17.769-.43.607-.453-.065-2.939-2.211-1.134-.996-2.568-2.162h-.17v.227l.591.866 3.125 4.697.162 1.441-.226.468-.81.283-.89-.162-1.829-2.568-1.888-2.891-1.522-2.592-.186.106-.898 9.677-.421.495-.972.371-.81-.615-.43-.996.43-1.967.518-2.568.422-2.041.38-2.535.226-.842-.015-.056-.185.023-1.912 2.624-2.906 3.928-2.3 2.462-.551.218-.954-.494.088-.883.533-.787 3.184-4.049 1.919-2.509 1.24-1.449-.009-.21h-.073l-8.455 5.49-1.505.194-.648-.607.08-.995.307-.324 2.542-1.749-.009.008z" style="fill:#d97757;"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><clipPath id="a"><path d="M0 0h32v32H0z"/></clipPath><g clip-path="url(#a)"><path d="M3 32V0h26v32zM22 7H10v18h12z" style="fill:#fff"/><path d="M10 13h12v12H10z" style="fill:#5a5858"/></g></svg>

After

Width:  |  Height:  |  Size: 276 B

+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800">
<!-- P shape: outer boundary clockwise, inner hole counter-clockwise -->
<path fill="#fff" fill-rule="evenodd" d="
M165.29 165.29
H517.36
V400
H400
V517.36
H282.65
V634.72
H165.29
Z
M282.65 282.65
V400
H400
V282.65
Z
"/>
<!-- i dot -->
<path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

+68 -24
View File
@@ -1,30 +1,74 @@
---
import { getContributors } from '../lib/contributors';
const year = new Date().getFullYear();
const contributors = await getContributors();
---
<footer class="py-8 px-8 border-t border-border/30 text-center">
<p class="text-xs text-muted-foreground inline-flex items-center justify-center gap-1">
<a
href="https://github.com/backnotprop"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-1 text-foreground hover:text-primary transition-colors"
>
<svg class="w-3 h-3" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
</svg>
backnotprop
</a>
&middot;
<a
href="https://x.com/backnotprop"
target="_blank"
rel="noopener noreferrer"
class="text-foreground hover:text-primary transition-colors"
>
@backnotprop
</a>
<footer class="py-10 px-8 border-t border-border/30">
<div class="grid grid-cols-2 sm:grid-cols-3 gap-6 mb-6 text-sm">
<div>
<h4 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-3">Product</h4>
<ul class="space-y-1.5">
<li><a href="/docs/commands/plan-review/" class="text-muted-foreground hover:text-foreground transition-colors">Plan Review</a></li>
<li><a href="/docs/commands/code-review/" class="text-muted-foreground hover:text-foreground transition-colors">Code Review</a></li>
<li><a href="/docs/getting-started/installation/" class="text-muted-foreground hover:text-foreground transition-colors">Install</a></li>
</ul>
</div>
<div>
<h4 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-3">Resources</h4>
<ul class="space-y-1.5">
<li><a href="/docs/getting-started/quickstart/" class="text-muted-foreground hover:text-foreground transition-colors">Quickstart</a></li>
<li><a href="/docs/guides/claude-code/" class="text-muted-foreground hover:text-foreground transition-colors">Claude Code</a></li>
<li><a href="/docs/guides/opencode/" class="text-muted-foreground hover:text-foreground transition-colors">OpenCode</a></li>
<li><a href="/blog/" class="text-muted-foreground hover:text-foreground transition-colors">Blog</a></li>
</ul>
</div>
<div>
<h4 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-3">Community</h4>
<ul class="space-y-1.5">
<li><a href="https://github.com/backnotprop/plannotator" target="_blank" rel="noopener noreferrer" class="text-muted-foreground hover:text-foreground transition-colors">GitHub</a></li>
<li><a href="https://x.com/backnotprop" target="_blank" rel="noopener noreferrer" class="text-muted-foreground hover:text-foreground transition-colors">@backnotprop</a></li>
<li><a href="https://x.com/plannotator" target="_blank" rel="noopener noreferrer" class="text-muted-foreground hover:text-foreground transition-colors">@plannotator</a></li>
</ul>
</div>
</div>
<!-- Brand stamp -->
<div class="flex flex-col items-center gap-3 pt-6 border-t border-border/20 mb-3">
<img src="/plannotator.webp" alt="" class="w-10 h-10 object-contain -scale-x-100 opacity-70" />
<span class="text-xs font-semibold tracking-[-0.03em] text-muted-foreground/70">Plannotator</span>
</div>
<p class="text-xs text-muted-foreground/40 text-center">
&copy; {year} backnotprop.
</p>
<p class="text-xs text-muted-foreground/60 mt-2">
&copy; {year} backnotprop. All rights reserved.
<p class="text-xs text-muted-foreground/40 text-center mt-1">
Licensed under <a href="https://github.com/backnotprop/plannotator/blob/main/LICENSE-MIT" target="_blank" rel="noopener noreferrer" class="hover:text-muted-foreground/50 transition-colors">MIT</a> or <a href="https://github.com/backnotprop/plannotator/blob/main/LICENSE-APACHE" target="_blank" rel="noopener noreferrer" class="hover:text-muted-foreground/50 transition-colors">Apache 2.0</a>, at your option.
</p>
</footer>
{contributors.length > 0 && (
<div class="pt-6 pb-12 px-8 border-t border-border/20">
<p class="text-[0.6875rem] text-muted-foreground/40 text-center mb-4">Built by</p>
<div class="flex flex-wrap items-center justify-center gap-2">
{contributors.map((c) => (
<a
href={c.url}
target="_blank"
rel="noopener noreferrer"
title={`@${c.login}`}
class="contributor-avatar"
>
<img
src={`${c.avatarUrl}&s=72`}
alt={c.login}
width="36"
height="36"
loading="lazy"
class="rounded-full"
/>
</a>
))}
</div>
</div>
)}
@@ -0,0 +1,54 @@
---
interface Props {
crossed: string;
replacement: string;
}
const { crossed, replacement } = Astro.props;
---
<span class="anno-replace-wrap" id="anno-replace">
<span class="anno-replace-crossed anno-shimmer" id="anno-replace-trigger">{crossed}</span>
<img
src="/assets/whiteglove.png"
alt=""
class="anno-glove"
id="anno-glove"
aria-hidden="true"
/>
<span class="anno-replace-popover" id="anno-replace-popover">
<span class="anno-replace-popover-header">
<span class="anno-replace-popover-context" style="display:none">Comment</span>
<button class="anno-replace-popover-close" id="anno-replace-close" aria-label="Close">&times;</button>
</span>
<span class="anno-replace-popover-body">
<span class="anno-replace-popover-input">{replacement}</span>
<span class="anno-replace-popover-cursor"></span>
</span>
</span>
</span>
<script is:inline>
(function() {
var trigger = document.getElementById('anno-replace-trigger');
var popover = document.getElementById('anno-replace-popover');
if (!trigger || !popover) return;
var glove = document.getElementById('anno-glove');
var closeBtn = document.getElementById('anno-replace-close');
trigger.addEventListener('click', function() {
trigger.classList.remove('anno-shimmer');
trigger.classList.add('clicked');
if (glove) glove.classList.add('hidden');
popover.classList.add('visible');
});
if (closeBtn) {
closeBtn.addEventListener('click', function() {
popover.classList.remove('visible');
trigger.classList.remove('clicked');
trigger.classList.add('anno-shimmer');
if (glove) glove.classList.remove('hidden');
});
}
})();
</script>
@@ -0,0 +1,25 @@
import { useState, useEffect } from 'react';
export default function GitHubStars() {
const [stars, setStars] = useState<number | null>(null);
useEffect(() => {
fetch('https://api.github.com/repos/backnotprop/plannotator')
.then((r) => r.json())
.then((data) => {
if (data.stargazers_count) setStars(data.stargazers_count);
})
.catch(() => {});
}, []);
if (stars === null) return <span>Star on GitHub</span>;
return (
<span className="inline-flex items-center gap-1">
<svg className="w-3 h-3 text-accent" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 .587l3.668 7.568L24 9.306l-6 5.848 1.417 8.259L12 19.446l-7.417 3.967L6 15.154 0 9.306l8.332-1.151z" />
</svg>
{stars.toLocaleString()} stars
</span>
);
}
@@ -0,0 +1,240 @@
---
import AnnoReplace from './AnnoReplace.astro';
---
<section class="relative min-h-[85vh] flex flex-col justify-center px-8 pt-20 pb-16 overflow-visible">
<!-- Dot grid overlay -->
<div class="absolute inset-0 bg-dots opacity-[0.18] pointer-events-none"></div>
<!-- Noise grain texture -->
<div class="bg-noise"></div>
<!-- Single subtle horizon line -->
<div class="hero-horizon" aria-hidden="true"></div>
<div class="relative z-10">
<!-- Headline -->
<h1 class="animate-fade-up font-display text-4xl sm:text-5xl md:text-6xl font-bold tracking-[-0.04em] leading-[1.06]">
Plan and code review
<br />
for <span class="text-primary">any agent</span>.
</h1>
<!-- Subhead -->
<p class="animate-fade-up animate-delay-1 mt-6 text-base text-muted-foreground max-w-[42ch] leading-relaxed">
<AnnoReplace crossed="Annotate" replacement="Own your" /> agent plans before they run. Review agent-written code
with a full diff viewer. Share with your team. Your feedback goes
straight back to the agent.
</p>
<!-- Trust signal -->
<p class="animate-fade-up animate-delay-1 mt-3 text-[11px] text-muted-foreground/50 tracking-wide">
Runs locally | natively integrates with agents | free & open source
</p>
<!-- Agent selector + install area -->
<div class="animate-fade-up animate-delay-2 mt-10">
<!-- Agent selector buttons -->
<div class="flex flex-wrap items-center gap-2 mb-4" id="agent-selector">
<button
class="agent-btn active"
data-agent="claude"
data-command="curl -fsSL https://plannotator.ai/install.sh | bash"
data-video="https://www.youtube.com/watch?v=a_AT7cEN_9I"
>
<img src="/assets/icon-claude.svg" alt="" class="w-4 h-4" />
<span>Claude Code</span>
</button>
<button
class="agent-btn"
data-agent="codex"
data-command="curl -fsSL https://plannotator.ai/install.sh | bash"
data-video="https://x.com/backnotprop/status/2031201677987164572"
>
<img src="/assets/icon-codex.png" alt="" class="w-4 h-4 rounded-sm" />
<span>Codex</span>
</button>
<button
class="agent-btn"
data-agent="opencode"
data-command="curl -fsSL https://plannotator.ai/install.sh | bash"
data-video="https://youtu.be/_N7uo0EFI-U"
>
<img src="/assets/icon-opencode-dark.svg" alt="" class="w-4 h-4 icon-opencode" />
<span>OpenCode</span>
</button>
<button
class="agent-btn"
data-agent="pi"
data-command="pi install npm:@plannotator/pi-extension"
data-video="https://www.youtube.com/watch?v=XqFun9XCXPw"
>
<img src="/assets/icon-pi.svg" alt="" class="w-4 h-4 icon-pi" />
<span>Pi</span>
</button>
</div>
<!-- Install command block (desktop only) -->
<div class="hero-install group px-5 pr-12 py-3.5 text-sm max-w-xl hidden sm:flex" id="install-block">
<span class="text-muted-foreground select-none mr-2">$</span>
<span class="text-foreground select-all" id="install-command">curl -fsSL https://plannotator.ai/install.sh | bash</span>
<button
class="copy-btn"
id="install-copy-btn"
aria-label="Copy install command"
>
<svg class="copy-icon w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" />
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
</svg>
<svg class="check-icon w-3.5 h-3.5 hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 6L9 17l-5-5" />
</svg>
</button>
</div>
<!-- Detail lines (desktop only) -->
<div class="mt-2 max-w-lg hidden sm:block" id="install-detail-wrap">
<p class="text-[11px] text-muted-foreground/50" id="install-detail"></p>
<div class="hero-detail-steps hidden mt-1.5 space-y-1" id="install-steps">
<div class="hero-detail-step" id="install-step-1" style="display:none"></div>
<div class="hero-detail-step" id="install-step-2" style="display:none"></div>
<div class="hero-detail-step" id="install-step-3" style="display:none"></div>
</div>
</div>
<!-- CTAs — video is primary on mobile -->
<div class="flex flex-wrap items-center gap-3 mt-5">
<a
id="watch-demo-link"
href="https://www.youtube.com/watch?v=a_AT7cEN_9I"
target="_blank"
rel="noopener noreferrer"
class="hero-cta-btn inline-flex items-center gap-2 px-4 py-2.5 sm:py-2 rounded-lg bg-primary text-primary-foreground sm:bg-transparent sm:text-foreground border border-primary sm:border-border text-sm font-medium hover:bg-primary/90 sm:hover:bg-muted/50 transition-colors"
>
<svg class="w-4 h-4 sm:text-primary" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 5.14v14l11-7-11-7z" />
</svg>
Watch the demo
</a>
<a
href="https://share.plannotator.ai"
class="hero-cta-btn inline-flex items-center gap-2 px-4 py-2 rounded-lg border border-border text-sm text-foreground font-medium hover:bg-muted/50 transition-colors"
>
Try the demo
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
</a>
</div>
</div>
</div>
</section>
<script is:inline>
(function() {
var buttons = document.querySelectorAll('.agent-btn');
var commandEl = document.getElementById('install-command');
var detailEl = document.getElementById('install-detail');
var stepsEl = document.getElementById('install-steps');
var step1El = document.getElementById('install-step-1');
var step2El = document.getElementById('install-step-2');
var step3El = document.getElementById('install-step-3');
var videoLink = document.getElementById('watch-demo-link');
var copyBtn = document.getElementById('install-copy-btn');
var copyIcon = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>';
var checkIcon = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>';
function makeStep(el, text) {
el.innerHTML = '';
var code = document.createElement('code');
code.textContent = text;
var btn = document.createElement('button');
btn.className = 'step-copy-btn';
btn.innerHTML = copyIcon;
btn.setAttribute('aria-label', 'Copy');
btn.addEventListener('click', function() {
navigator.clipboard.writeText(text).then(function() {
btn.innerHTML = checkIcon;
setTimeout(function() { btn.innerHTML = copyIcon; }, 1500);
});
});
el.appendChild(code);
el.appendChild(btn);
}
// Agent configs: steps array means show detail steps, detail string means simple text
var agents = {
claude: {
steps: [
'/plugin marketplace add backnotprop/plannotator',
'/plugin install plannotator@plannotator',
'Restart Claude Code to activate hooks'
],
detail: 'Then run in Claude Code:'
},
codex: {
steps: [
'!plannotator review',
'!plannotator annotate <file>',
'!plannotator last'
],
detail: 'Then use in Codex:'
},
opencode: {
steps: [
'"plugin": ["@plannotator/opencode@latest"]',
'Restart OpenCode to load the plugin'
],
detail: 'Then add to opencode.json:'
},
pi: {
detail: 'Or try without installing: pi -e npm:@plannotator/pi-extension'
}
};
function setAgent(btn) {
buttons.forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
commandEl.textContent = btn.dataset.command;
if (videoLink) videoLink.href = btn.dataset.video;
var config = agents[btn.dataset.agent] || {};
detailEl.textContent = config.detail || '';
// Clear all steps completely, then rebuild
var allSteps = [step1El, step2El, step3El];
allSteps.forEach(function(el) { el.innerHTML = ''; el.style.display = 'none'; });
if (config.steps && config.steps.length) {
stepsEl.classList.remove('hidden');
for (var i = 0; i < config.steps.length && i < allSteps.length; i++) {
allSteps[i].style.display = '';
makeStep(allSteps[i], config.steps[i]);
}
} else {
stepsEl.classList.add('hidden');
}
}
buttons.forEach(function(btn) {
btn.addEventListener('click', function() { setAgent(btn); });
});
// Init default (first button)
setAgent(buttons[0]);
if (copyBtn) {
copyBtn.addEventListener('click', function() {
navigator.clipboard.writeText(commandEl.textContent).then(function() {
copyBtn.querySelector('.copy-icon').classList.add('hidden');
copyBtn.querySelector('.check-icon').classList.remove('hidden');
setTimeout(function() {
copyBtn.querySelector('.copy-icon').classList.remove('hidden');
copyBtn.querySelector('.check-icon').classList.add('hidden');
}, 1500);
});
});
}
})();
</script>
@@ -0,0 +1,27 @@
---
import GitHubStars from './GitHubStars.tsx';
---
<section class="landing-reveal border-y border-border/30 bg-card/30 py-3 px-6 md:px-12">
<div class="flex flex-col sm:flex-row flex-wrap items-center justify-center gap-x-8 gap-y-2 sm:gap-y-3 text-xs text-muted-foreground/70">
<!-- GitHub stars -->
<a
href="https://github.com/backnotprop/plannotator"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-1.5 hover:text-foreground transition-colors"
>
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
</svg>
<GitHubStars client:only="react" />
</a>
<span class="text-border/30 hidden sm:inline">|</span>
<span>One command. Hooks into your agent automatically.</span>
<span class="text-border/30 hidden sm:inline">|</span>
<span>Zero learning curve</span>
</div>
</section>
+377 -1
View File
@@ -42,7 +42,8 @@ import '../styles/global.css';
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<noscript><style>.landing-reveal{opacity:1!important;transform:none!important}</style></noscript>
</head>
<body class="min-h-screen antialiased">
<slot />
@@ -86,5 +87,380 @@ import '../styles/global.css';
pre.appendChild(btn);
});
</script>
<script is:inline>
// Install block copy buttons (landing page)
document.querySelectorAll('.install-copy-btn').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.preventDefault();
var block = btn.closest('.install-block');
var code = block.querySelector('code');
var text = code ? code.textContent : '';
navigator.clipboard.writeText(text.trim()).then(function() {
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>';
setTimeout(function() {
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>';
}, 1500);
});
});
});
</script>
<script is:inline>
// How it works toggle (plan review / code review)
(function() {
var planSteps = [
'Work with your coding agent exactly as you do today. When it proposes a plan, Plannotator intercepts the approval step automatically.',
'Instead of squinting at terminal text, you get a proper review workspace. Annotate inline, mark what needs to change, then approve or deny with structured feedback. You own the specification.',
'Your annotations are sent directly to the agent as structured feedback. No copy-pasting, no retyping. The agent revises based on exactly what you said.'
];
var codeSteps = [
'Your agent writes code. Run the review command and Plannotator opens a full diff viewer in your browser with every uncommitted change.',
'Browse the file tree, expand context, and leave line-level comments or code suggestions. Stage the files you want, send back the ones that need work.',
'Your feedback goes straight to the agent. It sees exactly which lines you commented on and what you want changed. No ambiguity.'
];
document.querySelectorAll('.hiw-toggle-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var mode = btn.dataset.hiw;
document.querySelectorAll('.hiw-toggle-btn').forEach(function(b) {
b.classList.toggle('active', b.dataset.hiw === mode);
b.setAttribute('aria-selected', b.dataset.hiw === mode ? 'true' : 'false');
});
var planImg = document.getElementById('hiw-img-plan');
var codeImg = document.getElementById('hiw-img-code');
var barTitle = document.querySelector('.hiw-bar-title');
if (planImg && codeImg) {
planImg.classList.toggle('hidden', mode === 'code');
codeImg.classList.toggle('hidden', mode === 'plan');
}
if (barTitle) {
barTitle.innerHTML = mode === 'plan'
? 'Plannotator · Plan Review'
: 'Plannotator · Code Review';
}
var steps = mode === 'plan' ? planSteps : codeSteps;
for (var i = 0; i < 3; i++) {
var el = document.getElementById('hiw-step-' + (i + 1));
if (el) el.textContent = steps[i];
}
});
});
})();
</script>
<script is:inline>
// Plan review terminal demo — starts on viewport entry
(function() {
var demo = document.getElementById('plan-demo');
var terminal = document.getElementById('plan-demo-terminal');
var screen = document.getElementById('plan-demo-screen');
var image = document.getElementById('plan-demo-image');
var typed = document.getElementById('plan-demo-typed');
var caret = document.getElementById('plan-demo-caret');
var output = document.getElementById('plan-demo-output');
var ascii = document.getElementById('plan-demo-ascii');
var denyBtn = document.getElementById('plan-demo-deny');
var glove = document.getElementById('plan-demo-glove');
if (!demo || !terminal || !screen || !image || !typed || !caret || !output || !denyBtn) return;
function scrollToBottom() {
screen.scrollTop = screen.scrollHeight;
}
var active = false;
var timers = [];
function delay(fn, ms) {
var t = setTimeout(fn, ms);
timers.push(t);
return t;
}
function clearTimers() {
timers.forEach(clearTimeout);
timers = [];
}
function typeText(el, text, speed, cb) {
var i = 0;
function tick() {
if (!active) return;
if (i < text.length) {
el.textContent += text[i];
i++;
delay(tick, speed);
} else if (cb) { cb(); }
}
tick();
}
function resetDemo() {
clearTimers();
typed.textContent = '';
output.innerHTML = '';
caret.classList.remove('hidden');
if (ascii) ascii.style.opacity = '1';
if (glove) glove.classList.remove('visible');
image.classList.remove('plan-demo-visible');
terminal.classList.remove('plan-demo-faded');
}
function showImage() {
terminal.classList.add('plan-demo-faded');
image.classList.add('plan-demo-visible');
if (glove) delay(function() { glove.classList.add('visible'); }, 600);
}
function showTerminal() {
image.classList.remove('plan-demo-visible');
terminal.classList.remove('plan-demo-faded');
}
function showOutput(text, cls) {
var line = document.createElement('div');
if (cls) line.className = cls;
line.textContent = text;
output.appendChild(line);
screen.scrollTop = screen.scrollHeight;
}
function runPhase1() {
if (!active) return;
resetDemo();
active = true;
delay(function() {
if (!active) return;
if (ascii) ascii.style.opacity = '0.4';
delay(function() {
if (!active) return;
typeText(typed, '/plan', 100, function() {
delay(function() {
if (!active) return;
caret.classList.add('hidden');
delay(function() {
if (!active) return;
showOutput('... exploring codebase', 'plan-demo-step');
delay(function() {
if (!active) return;
showOutput('... asking questions', 'plan-demo-step');
delay(function() {
if (!active) return;
showOutput('... gathering context', 'plan-demo-step');
delay(function() {
if (!active) return;
showOutput('... writing plan', 'plan-demo-step');
delay(function() {
if (!active) return;
showOutput('submit_plan()', 'plan-demo-submit');
delay(function() { if (active) showImage(); }, 900);
}, 1000);
}, 900);
}, 800);
}, 800);
}, 600);
}, 700);
});
}, 400);
}, 1200);
}
function runPhase2() {
if (!active) return;
if (glove) glove.classList.remove('visible');
showTerminal();
typed.textContent = '';
output.innerHTML = '';
caret.classList.add('hidden');
if (ascii) ascii.style.opacity = '0.4';
typed.textContent = '...';
screen.scrollTop = screen.scrollHeight;
delay(function() {
if (!active) return;
showOutput('✗ Plan denied with feedback', 'text-destructive');
delay(function() {
if (!active) return;
showOutput('"Keep Postgres — Redis adds infra', 'text-primary');
delay(function() {
if (!active) return;
showOutput('we don\'t need yet."', 'text-primary');
delay(function() {
if (!active) return;
showOutput('Updating the plan...', 'text-success');
delay(runPhase1, 3000);
}, 800);
}, 500);
}, 700);
}, 800);
}
denyBtn.addEventListener('click', runPhase2);
// Continuous observation — start/stop based on visibility
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting && !active) {
active = true;
delay(runPhase1, 600);
} else if (!entry.isIntersecting && active) {
active = false;
clearTimers();
resetDemo();
}
});
}, { threshold: 0.6 });
observer.observe(demo);
})();
</script>
<script is:inline>
// Code review terminal demo — starts on viewport entry
(function() {
var demo = document.getElementById('code-demo');
var terminal = document.getElementById('code-demo-terminal');
var screen = document.getElementById('code-demo-screen');
var image = document.getElementById('code-demo-image');
var typed = document.getElementById('code-demo-typed');
var caret = document.getElementById('code-demo-caret');
var output = document.getElementById('code-demo-output');
var ascii = document.getElementById('code-demo-ascii');
var sendBtn = document.getElementById('code-demo-send');
var glove = document.getElementById('code-demo-glove');
if (!demo || !terminal || !screen || !image || !typed || !caret || !output || !sendBtn) return;
var active = false;
var timers = [];
function delay(fn, ms) {
var t = setTimeout(fn, ms);
timers.push(t);
return t;
}
function clearTimers() {
timers.forEach(clearTimeout);
timers = [];
}
function typeText(el, text, speed, cb) {
var i = 0;
function tick() {
if (!active) return;
if (i < text.length) {
el.textContent += text[i];
i++;
delay(tick, speed);
} else if (cb) { cb(); }
}
tick();
}
function resetDemo() {
clearTimers();
typed.textContent = '';
output.innerHTML = '';
caret.classList.remove('hidden');
if (ascii) ascii.style.opacity = '1';
if (glove) glove.classList.remove('visible');
image.classList.remove('plan-demo-visible');
terminal.classList.remove('plan-demo-faded');
}
function showImage() {
terminal.classList.add('plan-demo-faded');
image.classList.add('plan-demo-visible');
if (glove) delay(function() { glove.classList.add('visible'); }, 600);
}
function showTerminal() {
image.classList.remove('plan-demo-visible');
terminal.classList.remove('plan-demo-faded');
}
function addLine(text, cls) {
var line = document.createElement('div');
if (cls) line.className = cls;
line.textContent = text;
output.appendChild(line);
screen.scrollTop = screen.scrollHeight;
}
function runPhase1() {
if (!active) return;
resetDemo();
active = true;
delay(function() {
if (!active) return;
if (ascii) ascii.style.opacity = '0.4';
delay(function() {
if (!active) return;
typeText(typed, '/plannotator-review', 60, function() {
delay(function() {
if (!active) return;
caret.classList.add('hidden');
addLine('Loading diff...', 'plan-demo-step');
delay(function() { if (active) showImage(); }, 800);
}, 600);
});
}, 400);
}, 1200);
}
function runPhase2() {
if (!active) return;
if (glove) glove.classList.remove('visible');
showTerminal();
typed.textContent = '';
output.innerHTML = '';
caret.classList.add('hidden');
if (ascii) ascii.style.opacity = '0.4';
typed.textContent = '...';
screen.scrollTop = screen.scrollHeight;
delay(function() {
if (!active) return;
addLine('Feedback sent to agent', 'text-primary');
delay(function() {
if (!active) return;
addLine('2 comments, 1 suggestion', 'plan-demo-step');
delay(function() {
if (!active) return;
addLine('Addressing feedback...', 'text-success');
delay(runPhase1, 3000);
}, 800);
}, 600);
}, 800);
}
sendBtn.addEventListener('click', runPhase2);
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting && !active) {
active = true;
delay(runPhase1, 600);
} else if (!entry.isIntersecting && active) {
active = false;
clearTimers();
resetDemo();
}
});
}, { threshold: 0.6 });
observer.observe(demo);
})();
</script>
<script is:inline>
// Scroll reveal for landing sections
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
entry.target.classList.add('revealed');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
document.querySelectorAll('.landing-reveal').forEach(function(el) {
observer.observe(el);
});
}
</script>
</body>
</html>
+73
View File
@@ -0,0 +1,73 @@
// Fetches contributors, issue authors, and discussion participants from GitHub.
// Caches the result in-process so it only fetches once per build / dev session.
type Person = { login: string; avatarUrl: string; url: string };
let cached: Person[] | null = null;
export async function getContributors(): Promise<Person[]> {
if (cached) return cached;
const people = new Map<string, Person>();
const token = import.meta.env.GITHUB_TOKEN || process.env.GITHUB_TOKEN;
try {
if (token) {
const query = `{
repository(owner: "backnotprop", name: "plannotator") {
contributors: defaultBranchRef {
target {
... on Commit {
history(first: 100) {
nodes { author { user { login avatarUrl url } } }
}
}
}
}
issues(first: 100, orderBy: { field: CREATED_AT, direction: DESC }) {
nodes { author { login avatarUrl url } }
}
discussions(first: 100, orderBy: { field: CREATED_AT, direction: DESC }) {
nodes { author { login avatarUrl url } }
}
}
}`;
const res = await fetch('https://api.github.com/graphql', {
method: 'POST',
headers: { 'Authorization': `bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query }),
});
if (res.ok) {
const json = await res.json();
const repo = json.data?.repository;
for (const node of repo?.contributors?.target?.history?.nodes || []) {
const u = node?.author?.user;
if (u?.login) people.set(u.login, u);
}
for (const node of repo?.issues?.nodes || []) {
const u = node?.author;
if (u?.login) people.set(u.login, u);
}
for (const node of repo?.discussions?.nodes || []) {
const u = node?.author;
if (u?.login) people.set(u.login, u);
}
}
} else {
const res = await fetch('https://api.github.com/repos/backnotprop/plannotator/contributors?per_page=50', {
headers: { 'Accept': 'application/vnd.github.v3+json' },
});
if (res.ok) {
const data = await res.json();
for (const c of data) {
if (c.type === 'User') {
people.set(c.login, { login: c.login, avatarUrl: c.avatar_url, url: c.html_url });
}
}
}
}
} catch {}
cached = [...people.values()];
return cached;
}
+425 -299
View File
@@ -1,320 +1,446 @@
---
import Landing from '../layouts/Landing.astro';
import Step from '../components/Step.astro';
import Base from '../layouts/Base.astro';
import Nav from '../components/Nav.astro';
import Footer from '../components/Footer.astro';
import HeroSection from '../components/landing/HeroSection.astro';
import SocialProofBar from '../components/landing/SocialProofBar.astro';
import AnnoReplace from '../components/landing/AnnoReplace.astro';
---
<Landing>
<!-- Hero -->
<section class="pt-32 pb-20 px-8">
<div class="max-w-2xl">
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 text-primary text-xs font-medium mb-6">
<span class="w-1.5 h-1.5 rounded-full bg-primary animate-pulse"></span>
For Claude Code, OpenCode & Pi
</div>
<Base
title="Plannotator - Plan & Code Review for AI Coding Agents"
description="Free, open-source plugin for Claude Code, OpenCode, Codex, and Pi. Review agent plans before they execute, annotate agent-written code, send structured feedback back — all locally."
>
<div class="min-h-screen bg-background text-foreground">
<Nav />
<div class="flex items-center gap-6 mb-4">
<h1 class="text-4xl md:text-5xl font-bold tracking-tight leading-[1.1]">
Annotate plans.
<br />
<span class="text-muted-foreground">
Not in the terminal.
</span>
</h1>
<img
src="/plannotator.webp"
alt="Plannotator logo"
class="w-20 h-20 md:w-24 md:h-24 object-contain shrink-0 -scale-x-100"
/>
</div>
<!-- Contained column — editorial feel -->
<div class="max-w-4xl mx-auto border-x border-border/30">
<p class="text-lg text-muted-foreground mb-8 max-w-lg">
Interactive Plan Review for coding agents. Mark up and refine plans visually,
share for team collaboration. Works with Claude Code, OpenCode, and Pi.
</p>
<!-- Hero -->
<HeroSection />
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
<!-- YouTube button - Claude Code -->
<!-- Social Proof -->
<SocialProofBar />
<!-- Plan Review -->
<section class="py-16 px-8 border-t border-border/30 landing-reveal">
<div class="grid md:grid-cols-2 gap-10 items-center">
<div class="order-2 md:order-1">
<p class="text-xs font-medium uppercase tracking-wider text-primary mb-3">
Plan Review
</p>
<h2 class="font-display text-xl font-semibold tracking-tight mb-3">
Annotate the plan before it executes
</h2>
<p class="text-sm text-muted-foreground mb-4 leading-relaxed">
Select text. Mark it for deletion, add a comment, or write a
replacement. Your annotations export as structured feedback
that the agent understands.
</p>
<ul class="space-y-2 text-sm text-foreground/80">
<li class="flex items-start gap-2">
<span class="text-accent mt-0.5 shrink-0">&bull;</span>
<span><span class="anno-comment">Inline comments</span> on any section</span>
</li>
<li class="flex items-start gap-2">
<span class="text-destructive mt-0.5 shrink-0">&bull;</span>
<span><span class="anno-delete">Mark deletions</span> to remove scope</span>
</li>
<li class="flex items-start gap-2">
<span class="text-primary mt-0.5 shrink-0">&bull;</span>
<span>Version history tracks every revision (plan diffs)</span>
</li>
<li class="flex items-start gap-2">
<span class="text-success mt-0.5 shrink-0">&bull;</span>
<span>Diff view shows what changed between iterations</span>
</li>
</ul>
</div>
<div class="plan-demo order-1 md:order-2" id="plan-demo">
<!-- Terminal state -->
<div class="plan-demo-terminal" id="plan-demo-terminal">
<div class="browser-frame-bar">
<div class="browser-frame-dot bg-destructive/50"></div>
<div class="browser-frame-dot bg-accent/50"></div>
<div class="browser-frame-dot bg-success/50"></div>
<span class="ml-2 text-[0.6875rem] text-muted-foreground">Terminal</span>
</div>
<div class="plan-demo-screen" id="plan-demo-screen">
<div class="plan-demo-scroll">
<pre class="plan-demo-ascii" id="plan-demo-ascii"
> ██████ ██████ ██████ ███████
██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████
██ ██ ██ ██ ██ ██
██████ ██████ ██████ ███████
█████ ██████ ███████ ███ ██ ████████
██ ██ ██ ██ ████ ██ ██
███████ ██ ███ █████ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ████ ██
██ ██ ██████ ███████ ██ ███ ██</pre>
<div class="plan-demo-input-area">
<span class="plan-demo-prompt">$</span>
<span class="plan-demo-typed" id="plan-demo-typed"></span>
<span class="plan-demo-caret" id="plan-demo-caret"></span>
</div>
<div class="plan-demo-output" id="plan-demo-output"></div>
</div>
</div>
</div>
<!-- Image state -->
<div class="plan-demo-image plan-demo-hidden" id="plan-demo-image">
<div class="browser-frame-bar">
<div class="browser-frame-dot bg-destructive/50"></div>
<div class="browser-frame-dot bg-accent/50"></div>
<div class="browser-frame-dot bg-success/50"></div>
<span class="ml-2 text-[0.6875rem] text-muted-foreground">Plannotator</span>
</div>
<div class="relative">
<img
src="/assets/plan-review.webp"
alt="Plannotator plan review with inline annotations"
class="w-full block"
loading="lazy"
/>
<div class="plan-demo-deny-bar">
<button class="plan-demo-deny-btn" id="plan-demo-deny">
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
Deny with feedback
</button>
<img
src="/assets/whiteglove.png"
alt=""
class="demo-glove"
id="plan-demo-glove"
aria-hidden="true"
/>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Code Review -->
<section class="py-16 px-8 border-t border-border/30 landing-reveal">
<div class="grid md:grid-cols-2 gap-10 items-center">
<div class="order-2 md:order-last">
<p class="text-xs font-medium uppercase tracking-wider text-secondary mb-3">
Code Review
</p>
<h2 class="font-display text-xl font-semibold tracking-tight mb-3">
Review the diff before you commit
</h2>
<p class="text-sm text-muted-foreground mb-4 leading-relaxed">
Run <code class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">{"/"}plannotator-review</code>
and get a PR-style diff viewer for your agent's uncommitted changes.
The same workflow you use for human PRs, applied to agent output.
</p>
<ul class="space-y-2 text-sm text-foreground/80">
<li class="flex items-start gap-2">
<span class="text-secondary mt-0.5 shrink-0">&bull;</span>
<span>Side-by-side or unified diff view</span>
</li>
<li class="flex items-start gap-2">
<span class="text-secondary mt-0.5 shrink-0">&bull;</span>
<span>File tree navigation</span>
</li>
<li class="flex items-start gap-2">
<span class="text-secondary mt-0.5 shrink-0">&bull;</span>
<span>Line-level annotations with code suggestions</span>
</li>
<li class="flex items-start gap-2">
<span class="text-secondary mt-0.5 shrink-0">&bull;</span>
<span>Stage or unstage files before committing</span>
</li>
</ul>
</div>
<!-- Code review demo -->
<div class="plan-demo order-1 md:order-first" id="code-demo">
<!-- Terminal state -->
<div class="code-demo-terminal" id="code-demo-terminal">
<div class="browser-frame-bar">
<div class="browser-frame-dot bg-destructive/50"></div>
<div class="browser-frame-dot bg-accent/50"></div>
<div class="browser-frame-dot bg-success/50"></div>
<span class="ml-2 text-[0.6875rem] text-muted-foreground">Terminal</span>
</div>
<div class="plan-demo-screen" id="code-demo-screen">
<div class="plan-demo-scroll">
<pre class="plan-demo-ascii" id="code-demo-ascii"
> ██████ ██████ ██████ ███████
██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████
██ ██ ██ ██ ██ ██
██████ ██████ ██████ ███████
█████ ██████ ███████ ███ ██ ████████
██ ██ ██ ██ ████ ██ ██
███████ ██ ███ █████ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ████ ██
██ ██ ██████ ███████ ██ ███ ██</pre>
<div class="plan-demo-input-area">
<span class="plan-demo-prompt">$</span>
<span class="plan-demo-typed" id="code-demo-typed"></span>
<span class="plan-demo-caret" id="code-demo-caret"></span>
</div>
<div class="plan-demo-output" id="code-demo-output"></div>
</div>
</div>
</div>
<!-- Image state -->
<div class="plan-demo-image plan-demo-hidden" id="code-demo-image">
<div class="browser-frame-bar">
<div class="browser-frame-dot bg-destructive/50"></div>
<div class="browser-frame-dot bg-accent/50"></div>
<div class="browser-frame-dot bg-success/50"></div>
<span class="ml-2 text-[0.6875rem] text-muted-foreground">Plannotator · Code Review</span>
</div>
<div class="relative">
<img
src="/assets/code-review.webp"
alt="Plannotator code review with diff annotations"
class="w-full block"
loading="lazy"
/>
<div class="code-demo-send-bar">
<img
src="/assets/whiteglove.png"
alt=""
class="demo-glove demo-glove-left"
id="code-demo-glove"
aria-hidden="true"
/>
<button class="code-demo-send-btn" id="code-demo-send">
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" />
</svg>
Send feedback
</button>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- How it works -->
<section class="pt-10 pb-16 px-8 border-t border-border/30 bg-card/30 landing-reveal" id="how-it-works">
<h2 class="font-display text-xl font-semibold tracking-tight mb-2 text-center">
How it works<span class="text-muted-foreground/50">...</span> simple.
</h2>
<div class="flex justify-center mb-8">
<div class="hiw-toggle" role="tablist" aria-label="Choose workflow">
<button class="hiw-toggle-btn active" data-hiw="plan" role="tab" aria-selected="true">Plan Review</button>
<button class="hiw-toggle-btn" data-hiw="code" role="tab" aria-selected="false">
Code Review
<span class="hiw-notify-dot"></span>
</button>
</div>
</div>
<!-- Image -->
<div class="browser-frame mb-8">
<div class="browser-frame-bar">
<div class="browser-frame-dot bg-destructive/50"></div>
<div class="browser-frame-dot bg-accent/50"></div>
<div class="browser-frame-dot bg-success/50"></div>
<span class="ml-2 text-[0.6875rem] text-muted-foreground hiw-bar-title">Plannotator · Plan Review</span>
</div>
<div class="bg-background">
<img
id="hiw-img-plan"
src="/assets/hiw-plan.webp"
alt="Plannotator plan review showing annotations on an agent's proposed plan"
class="w-full block"
loading="lazy"
/>
<img
id="hiw-img-code"
src="/assets/hiw-code.webp"
alt="Plannotator code review showing diff annotations on agent-written code"
class="w-full block hidden"
loading="lazy"
/>
</div>
</div>
<!-- Steps -->
<div class="grid sm:grid-cols-3 gap-6">
<div>
<div class="flex items-center gap-2 mb-2">
<span class="w-5 h-5 rounded-full bg-primary text-primary-foreground text-[0.6875rem] font-semibold flex items-center justify-center">1</span>
<h4 class="text-sm font-medium">Use your agent normally</h4>
</div>
<p class="text-sm text-muted-foreground leading-relaxed" id="hiw-step-1">
Work with your coding agent exactly as you do today. When it proposes a plan, Plannotator intercepts the approval step automatically.
</p>
</div>
<div>
<div class="flex items-center gap-2 mb-2">
<span class="w-5 h-5 rounded-full bg-primary text-primary-foreground text-[0.6875rem] font-semibold flex items-center justify-center">2</span>
<h4 class="text-sm font-medium">Review on a real surface</h4>
</div>
<p class="text-sm text-muted-foreground leading-relaxed" id="hiw-step-2">
Instead of squinting at terminal text, you get a proper review workspace. Annotate inline, mark what needs to change, then approve or deny with structured feedback. You own the specification.
</p>
</div>
<div>
<div class="flex items-center gap-2 mb-2">
<span class="w-5 h-5 rounded-full bg-primary text-primary-foreground text-[0.6875rem] font-semibold flex items-center justify-center">3</span>
<h4 class="text-sm font-medium">Feedback flows back</h4>
</div>
<p class="text-sm text-muted-foreground leading-relaxed" id="hiw-step-3">
Your annotations are sent directly to the agent as structured feedback. No copy-pasting, no retyping. The agent revises based on exactly what you said.
</p>
</div>
</div>
</section>
<!-- Capabilities -->
<section class="py-16 px-8 border-t border-border/30 landing-reveal">
<h2 class="font-display text-xl font-semibold tracking-tight mb-8 text-center">
Everything the plugin does
</h2>
<div class="capabilities-grid">
<div class="cap-group">
<h3 class="cap-group-label">Commands</h3>
<div class="cap-item">
<span class="cap-name">Plan review</span>
<span class="cap-desc">Automatic — hooks into your agent's plan step</span>
</div>
<div class="cap-item">
<code class="cap-cmd">/plannotator-review</code>
<span class="cap-desc">Code review for uncommitted changes or a PR URL</span>
</div>
<div class="cap-item">
<code class="cap-cmd">/plannotator-annotate</code>
<span class="cap-desc">Annotate any markdown file and send feedback</span>
</div>
<div class="cap-item">
<code class="cap-cmd">/plannotator-last</code>
<span class="cap-desc">Annotate the agent's last message</span>
</div>
</div>
<div class="cap-group">
<h3 class="cap-group-label">Features</h3>
<div class="cap-item">
<span class="cap-name">Runs locally</span>
<span class="cap-desc">Plans never leave your machine</span>
</div>
<div class="cap-item">
<span class="cap-name">Encrypted sharing</span>
<span class="cap-desc">Share plans via URL — data lives in the link itself</span>
</div>
<div class="cap-item">
<span class="cap-name">Version history</span>
<span class="cap-desc">Every plan revision saved, with diffs between versions</span>
</div>
<div class="cap-item">
<span class="cap-name">Draft auto-save</span>
<span class="cap-desc">Annotations survive server crashes and restarts</span>
</div>
</div>
<div class="cap-group">
<h3 class="cap-group-label">Integrations</h3>
<div class="cap-item">
<span class="cap-name">VS Code</span>
<span class="cap-desc">Open plans in editor tabs, diff view, editor annotations</span>
</div>
<div class="cap-item">
<span class="cap-name">Obsidian</span>
<span class="cap-desc">Auto-save plans to your vault with frontmatter and tags</span>
</div>
<div class="cap-item">
<span class="cap-name">Bear</span>
<span class="cap-desc">Save plans with nested tags and project metadata</span>
</div>
<div class="cap-item">
<span class="cap-name">GitHub PRs</span>
<span class="cap-desc">Review pull requests by URL with full diff annotations</span>
</div>
</div>
</div>
</section>
<!-- Compatibility & social proof -->
<section class="py-12 px-8 border-t border-border/30 text-center">
<p class="text-sm text-muted-foreground mb-4">
Integrations for Claude Code, OpenCode, Pi, Codex, and VS Code. Works with any agent that supports plan mode.
</p>
<div class="flex items-center justify-center gap-5">
<a
href="https://www.youtube.com/watch?v=a_AT7cEN_9I"
href="https://github.com/backnotprop/plannotator"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-secondary text-white font-medium hover:opacity-90 transition-opacity"
class="inline-flex items-center gap-1.5"
>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
</svg>
Watch for Claude Code
<img
src="https://img.shields.io/github/stars/backnotprop/plannotator?style=flat&logo=github&label=stars&color=gray"
alt="GitHub stars"
class="h-5"
loading="lazy"
/>
</a>
<!-- Open Demo button -->
<a
href="https://share.plannotator.ai"
class="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-primary text-primary-foreground font-medium hover:opacity-90 transition-opacity"
href="https://github.com/backnotprop/plannotator"
target="_blank"
rel="noopener noreferrer"
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Open Demo
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
View on GitHub &rarr;
</a>
<a
href="/docs/getting-started/installation/"
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Read the docs &rarr;
</a>
</div>
</section>
<!-- OpenCode video link -->
<a
href="https://youtu.be/_N7uo0EFI-U"
target="_blank"
rel="noopener noreferrer"
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Also watch for OpenCode &rarr;
</a>
<!-- Pi video link -->
<a
href="https://youtu.be/XqFun9XCXPw"
target="_blank"
rel="noopener noreferrer"
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
Also watch for Pi &rarr;
</a>
</div>
</div>
</section>
<!-- Problem / Solution -->
<section class="py-16 px-8 border-t border-border/30">
<div class="grid md:grid-cols-2 gap-8 max-w-2xl">
<div>
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-3">
The Problem
</h3>
<p class="text-foreground/90">
Coding agents show plans in the terminal. You approve or deny, but
giving specific feedback means typing everything out. Hard to
reference exact sections. Zero collaboration features.
</p>
</div>
<div>
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-3">
The Solution
</h3>
<p class="text-foreground/90">
Select the exact parts of the plan you want to change. Mark for
deletion, add a comment, or suggest a replacement. Share plans
with your team. Feedback flows back to your agent automatically.
</p>
</div>
</div>
</section>
<!-- Privacy & Sharing -->
<section class="py-16 px-8 border-t border-border/30">
<div class="grid md:grid-cols-2 gap-8 max-w-2xl">
<div class="flex items-start gap-4">
<div class="w-10 h-10 rounded-lg bg-secondary/10 flex items-center justify-center shrink-0">
<svg class="w-5 h-5 text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</div>
<div>
<h3 class="font-semibold mb-1">Runs locally.</h3>
<p class="text-muted-foreground text-sm">
Local plugin. No network requests. Plannotator runs entirely
in your browser. Plans never leave your machine.
<!-- Final CTA -->
<section class="py-16 px-8 border-t border-border/30 bg-card/30">
<div class="text-center">
<h2 class="font-display text-2xl font-semibold tracking-tight mb-6">
Your next plan deserves more than a rubber stamp.
</h2>
<p class="text-base text-muted-foreground mb-6">
Shape what gets built. Own what ships.
</p>
</div>
</div>
<div class="flex items-start gap-4">
<div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
<svg class="w-5 h-5 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
</svg>
</div>
<div>
<h3 class="font-semibold mb-1">Share privately.</h3>
<p class="text-muted-foreground text-sm">
Collaborate without a backend. Plans and annotations compress
into the URL itself &mdash; share a link. No accounts, no database,
no third parties. Inspired by
<div class="install-block max-w-lg mx-auto mb-4 hidden sm:flex">
<code>curl -fsSL https://plannotator.ai/install.sh | bash</code>
<button class="install-copy-btn" aria-label="Copy install command">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</button>
</div>
<div class="flex items-center justify-center gap-3 mt-5">
<a
href="https://github.com/antonmedv/textarea"
id="cta-video-link"
href="https://www.youtube.com/watch?v=a_AT7cEN_9I"
target="_blank"
rel="noopener noreferrer"
class="text-primary hover:underline"
>textarea.my</a>.
</p>
class="agent-video-btn"
>
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5.14v14l11-7-11-7z" /></svg>
Watch the demo
</a>
<a
href="/docs/getting-started/installation/"
class="text-xs text-muted-foreground hover:text-foreground transition-colors"
>
All agent guides &rarr;
</a>
</div>
</div>
</div>
<div class="flex items-start gap-4">
<div class="w-10 h-10 rounded-lg bg-[#7c3aed]/10 flex items-center justify-center shrink-0">
<svg class="w-5 h-5 text-[#7c3aed]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg>
</div>
<div>
<h3 class="font-semibold mb-1">Save to Obsidian.</h3>
<p class="text-muted-foreground text-sm">
Approved plans auto-save to your vault with frontmatter and
auto-extracted tags. Build a searchable archive of every plan
your agents create.
</p>
</div>
</div>
</section>
<Footer />
</div>
</section>
<!-- Preview -->
<section class="py-16 px-8 border-t border-border/30">
<h2 class="text-xl font-semibold mb-8 text-center">
What it looks like
</h2>
<div class="rounded-xl border border-border/50 bg-card/50 overflow-hidden shadow-2xl">
<!-- Mock Header -->
<div class="h-10 bg-card/80 border-b border-border/30 flex items-center px-4 gap-2">
<div class="flex gap-1.5">
<div class="w-2.5 h-2.5 rounded-full bg-destructive/60"></div>
<div class="w-2.5 h-2.5 rounded-full bg-accent/60"></div>
<div class="w-2.5 h-2.5 rounded-full bg-secondary/60"></div>
</div>
<div class="flex-1 flex justify-center">
<div class="flex items-center gap-1.5 text-xs text-muted-foreground">
<div class="w-4 h-4 rounded bg-primary/20"></div>
<span>Plannotator</span>
</div>
</div>
<div class="w-16"></div>
</div>
<!-- Mock Content -->
<div class="flex h-80">
<!-- Document Area -->
<div class="flex-1 p-6 bg-background/50 flex items-start justify-center">
<div class="w-full max-w-sm bg-card rounded-lg border border-border/30 p-5 shadow-lg">
<div class="h-3 w-3/4 bg-foreground/80 rounded mb-4"></div>
<div class="space-y-2 mb-4">
<div class="h-2 w-full bg-muted-foreground/30 rounded"></div>
<div class="h-2 w-5/6 bg-muted-foreground/30 rounded"></div>
<div class="flex items-center gap-1">
<div class="h-2 w-1/4 bg-muted-foreground/30 rounded"></div>
<div class="h-2 w-1/3 bg-destructive/40 rounded line-through"></div>
<div class="h-2 w-1/6 bg-muted-foreground/30 rounded"></div>
</div>
</div>
<div class="h-2.5 w-1/2 bg-foreground/60 rounded mb-3 mt-5"></div>
<div class="space-y-2 mb-4">
<div class="flex items-center gap-2">
<div class="w-1 h-1 rounded-full bg-primary/60"></div>
<div class="h-2 w-4/5 bg-muted-foreground/30 rounded"></div>
</div>
<div class="flex items-center gap-2">
<div class="w-1 h-1 rounded-full bg-primary/60"></div>
<div class="h-2 w-2/3 bg-accent/40 rounded border-b border-accent"></div>
</div>
<div class="flex items-center gap-2">
<div class="w-1 h-1 rounded-full bg-primary/60"></div>
<div class="h-2 w-3/4 bg-muted-foreground/30 rounded"></div>
</div>
</div>
<div class="bg-muted/50 rounded p-2 space-y-1.5">
<div class="h-1.5 w-1/2 bg-primary/40 rounded"></div>
<div class="h-1.5 w-2/3 bg-muted-foreground/30 rounded"></div>
<div class="h-1.5 w-1/3 bg-secondary/40 rounded"></div>
</div>
</div>
</div>
<!-- Mock Sidebar -->
<div class="w-48 border-l border-border/30 bg-card/30 p-3">
<div class="text-[10px] uppercase tracking-wider text-muted-foreground mb-3 flex items-center justify-between">
<span>Annotations</span>
<span class="bg-muted px-1.5 py-0.5 rounded text-[9px]">2</span>
</div>
<div class="space-y-2">
<div class="p-2 rounded-md border border-destructive/30 bg-destructive/5">
<div class="flex items-center gap-1 mb-1.5">
<div class="w-3 h-3 rounded bg-destructive/20"></div>
<span class="text-[9px] font-semibold text-destructive uppercase">Delete</span>
</div>
<div class="h-1.5 w-full bg-muted rounded"></div>
</div>
<div class="p-2 rounded-md border border-accent/30 bg-accent/5">
<div class="flex items-center gap-1 mb-1.5">
<div class="w-3 h-3 rounded bg-accent/20"></div>
<span class="text-[9px] font-semibold text-accent uppercase">Comment</span>
</div>
<div class="h-1.5 w-3/4 bg-muted rounded mb-1"></div>
<div class="h-1.5 w-full bg-accent/20 rounded"></div>
</div>
</div>
</div>
</div>
</div>
<p class="text-center text-sm text-muted-foreground mt-6">
Select text &rarr; annotate &rarr; export feedback
</p>
</section>
<!-- How it works -->
<section class="py-16 px-8 border-t border-border/30 bg-card/30">
<h2 class="text-xl font-semibold mb-8">How it works</h2>
<div class="space-y-6 max-w-2xl">
<Step num={1} title="Agent triggers Plannotator">
<span class="text-xs">
<strong>Claude Code:</strong> ExitPlanMode hook opens UI<br />
<strong>OpenCode:</strong> Agent calls submit_plan tool
</span>
</Step>
<Step num={2} title="Annotate visually">
Select text &rarr; choose action (delete, comment, replace) &rarr; annotations appear
in the sidebar
</Step>
<Step num={3} title="Approve or request changes">
Click approve to proceed, or provide feedback with annotations.
Feedback flows back to your agent automatically.
</Step>
</div>
</section>
<!-- Technical -->
<section class="py-16 px-8 border-t border-border/30 bg-card/30">
<h2 class="text-xl font-semibold mb-6">Technical details</h2>
<ul class="space-y-2 text-sm text-muted-foreground max-w-2xl mb-6">
<li class="flex items-center gap-2">
<span class="text-primary">&bull;</span>
Single HTML file build &mdash; runs from Bun server on random port
</li>
<li class="flex items-center gap-2">
<span class="text-primary">&bull;</span>
<strong>Claude Code:</strong> Binary + plugin with PermissionRequest hook
</li>
<li class="flex items-center gap-2">
<span class="text-primary">&bull;</span>
<strong>OpenCode:</strong> npm package with submit_plan tool
</li>
<li class="flex items-center gap-2">
<span class="text-primary">&bull;</span>
URL sharing via deflate compression &mdash; no backend required
</li>
</ul>
<a
href="https://github.com/backnotprop/plannotator"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
<span class="inline-flex items-center rounded overflow-hidden">
<span class="bg-[#121011] text-white px-2 py-1 flex items-center gap-1.5">
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
</svg>
<span class="text-xs font-medium">GitHub</span>
</span>
</span>
<span class="underline underline-offset-2">
View GitHub repository
</span>
</a>
</section>
</Landing>
</div>
</Base>
File diff suppressed because it is too large Load Diff