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>
@@ -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 |
|
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 |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
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 |
|
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,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 |
|
Before Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 58 KiB |
@@ -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>
|
||||
·
|
||||
<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">
|
||||
© {year} backnotprop.
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground/60 mt-2">
|
||||
© {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">×</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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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">•</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">•</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">•</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">•</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">•</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">•</span>
|
||||
<span>File tree navigation</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-secondary mt-0.5 shrink-0">•</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">•</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 →
|
||||
</a>
|
||||
<a
|
||||
href="/docs/getting-started/installation/"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Read the docs →
|
||||
</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 →
|
||||
</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 →
|
||||
</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 — 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 →
|
||||
</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 → annotate → 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 → choose action (delete, comment, replace) → 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">•</span>
|
||||
Single HTML file build — runs from Bun server on random port
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<span class="text-primary">•</span>
|
||||
<strong>Claude Code:</strong> Binary + plugin with PermissionRequest hook
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<span class="text-primary">•</span>
|
||||
<strong>OpenCode:</strong> npm package with submit_plan tool
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<span class="text-primary">•</span>
|
||||
URL sharing via deflate compression — 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>
|
||||
|
||||