mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
Update documentation landing page
Signed-off-by: Alex Hancock <alexhancock@block.xyz>
This commit is contained in:
@@ -0,0 +1,589 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>goose — Your native open source AI agent</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Cash Sans';
|
||||
src: url('https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Cash Sans';
|
||||
src: url('https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Medium.woff2') format('woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Cash Sans';
|
||||
src: url('https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Semibold.woff2') format('woff2');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Cash Sans';
|
||||
src: url('https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Bold.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
:root {
|
||||
--bg-app: #ffffff;
|
||||
--bg-subtle: #f0f0f0;
|
||||
--border-subtle: #e8e8e8;
|
||||
--border-standard: #959595;
|
||||
--text-prominent: #101010;
|
||||
--text-standard: #1e1e1e;
|
||||
--text-subtle: #666666;
|
||||
--text-inverse: #ffffff;
|
||||
--grey-10: #101010;
|
||||
--grey-20: #1e1e1e;
|
||||
--grey-50: #666666;
|
||||
--green-link: #0f6636;
|
||||
--btn-bg: #000000;
|
||||
}
|
||||
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
font-family: 'Cash Sans', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background: var(--bg-app);
|
||||
color: var(--text-standard);
|
||||
line-height: 1.5;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
a { color: var(--green-link); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Navbar ── */
|
||||
.navbar {
|
||||
position: sticky; top: 0; z-index: 100;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0 2rem; height: 60px;
|
||||
background: var(--bg-app);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
.navbar-logo { height: 28px; }
|
||||
.navbar-links { display: flex; gap: 2rem; }
|
||||
.navbar-links a {
|
||||
font-size: 0.9rem; color: var(--text-standard);
|
||||
font-weight: 500; text-decoration: none;
|
||||
}
|
||||
.navbar-links a:hover { color: var(--text-subtle); text-decoration: none; }
|
||||
|
||||
/* ── Hero ── */
|
||||
#hero-container {
|
||||
width: 100%; overflow: hidden; background: #000;
|
||||
position: relative; user-select: none; cursor: crosshair;
|
||||
}
|
||||
#hero-inner { position: relative; margin: 0 auto; }
|
||||
|
||||
.hero-logo-layer {
|
||||
position: absolute; inset: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hero-logo-layer img {
|
||||
height: 45%; width: auto; max-width: 80%; object-fit: contain;
|
||||
}
|
||||
|
||||
#flipped-layer {
|
||||
position: absolute; inset: 0; pointer-events: none; z-index: 2;
|
||||
}
|
||||
#flipped-svg { position: absolute; inset: 0; }
|
||||
#flipped-logo-wrap {
|
||||
position: absolute; inset: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
#flipped-logo-wrap img {
|
||||
height: 45%; width: auto; max-width: 80%; object-fit: contain;
|
||||
}
|
||||
|
||||
#grid-overlay {
|
||||
position: relative; z-index: 3;
|
||||
display: grid;
|
||||
}
|
||||
.grid-cell {
|
||||
box-shadow: inset 0 0 0 0.5px rgba(255,255,255,0.06);
|
||||
}
|
||||
|
||||
/* ── Intro ── */
|
||||
.intro {
|
||||
padding: 5rem 2rem 3rem; text-align: center;
|
||||
max-width: 820px; margin: 0 auto;
|
||||
}
|
||||
.badge-pill {
|
||||
display: inline-block; padding: 6px 18px; border-radius: 999px;
|
||||
border: 1px solid var(--border-subtle); font-size: 0.78rem;
|
||||
letter-spacing: 0.06em; text-transform: uppercase;
|
||||
color: var(--text-subtle); margin-bottom: 2rem;
|
||||
}
|
||||
.intro h1 {
|
||||
font-size: clamp(2rem, 5vw, 3.2rem); font-weight: 700;
|
||||
line-height: 1.15; color: var(--text-prominent);
|
||||
margin-bottom: 1.25rem; letter-spacing: -0.02em;
|
||||
}
|
||||
.intro .subtitle {
|
||||
font-size: 1.2rem; line-height: 1.7; color: var(--text-subtle);
|
||||
max-width: 600px; margin: 0 auto 2.5rem;
|
||||
}
|
||||
.intro-actions {
|
||||
display: flex; gap: 1rem; justify-content: center;
|
||||
flex-wrap: wrap; margin-bottom: 3.5rem;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex; align-items: center;
|
||||
padding: 10px 28px; border-radius: 999px;
|
||||
font-size: 1rem; font-weight: 600;
|
||||
text-decoration: none; transition: opacity 0.2s;
|
||||
cursor: pointer; border: none;
|
||||
}
|
||||
.btn:hover { opacity: 0.85; text-decoration: none; }
|
||||
.btn-primary { background: var(--btn-bg); color: var(--text-inverse); }
|
||||
.btn-outline {
|
||||
background: transparent; color: var(--text-standard);
|
||||
border: 1.5px solid var(--border-standard);
|
||||
}
|
||||
.stats {
|
||||
display: flex; gap: 3rem; justify-content: center; flex-wrap: wrap;
|
||||
}
|
||||
.stat-num {
|
||||
font-size: 1.6rem; font-weight: 700;
|
||||
color: var(--text-prominent); line-height: 1;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 0.82rem; color: var(--text-subtle); margin-top: 4px;
|
||||
}
|
||||
|
||||
/* ── Sections ── */
|
||||
.section { padding: 4rem 2rem; }
|
||||
.section-alt { background: var(--bg-subtle); }
|
||||
.container { max-width: 1100px; margin: 0 auto; }
|
||||
.section-header { text-align: center; margin-bottom: 2.5rem; }
|
||||
.section-header h2 {
|
||||
font-size: 1.75rem; font-weight: 600;
|
||||
color: var(--text-prominent); margin-bottom: 0.75rem;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.section-header p {
|
||||
max-width: 680px; margin: 0 auto;
|
||||
color: var(--text-subtle); font-size: 1.05rem; line-height: 1.65;
|
||||
}
|
||||
|
||||
/* ── Cards ── */
|
||||
.card-grid-3 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem; margin-bottom: 1.5rem;
|
||||
}
|
||||
.card-grid-4 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 1.25rem;
|
||||
}
|
||||
.card {
|
||||
padding: 1.75rem; border-radius: 16px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
background: var(--bg-app);
|
||||
transition: border-color 0.25s, box-shadow 0.25s, transform 0.25s;
|
||||
}
|
||||
.card:hover {
|
||||
border-color: var(--border-standard);
|
||||
box-shadow: 0 2px 24px rgba(0,0,0,0.06);
|
||||
}
|
||||
.card-sm { padding: 1.4rem; }
|
||||
.card-icon { font-size: 1.5rem; margin-bottom: 0.75rem; }
|
||||
.card-sm .card-icon { font-size: 1.25rem; margin-bottom: 0.5rem; }
|
||||
.card h3 {
|
||||
font-size: 1.1rem; font-weight: 600;
|
||||
color: var(--text-prominent); margin-bottom: 0.5rem;
|
||||
}
|
||||
.card-sm h3 { font-size: 0.95rem; margin-bottom: 0.4rem; }
|
||||
.card p {
|
||||
font-size: 0.9rem; color: var(--text-subtle);
|
||||
line-height: 1.65; margin: 0;
|
||||
}
|
||||
.card-sm p { font-size: 0.85rem; line-height: 1.6; }
|
||||
.card-link {
|
||||
font-size: 0.9rem; font-weight: 500; margin-top: 0.5rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* ── Community cards ── */
|
||||
.community-card {
|
||||
text-decoration: none; color: inherit; display: block;
|
||||
}
|
||||
.community-card:hover { text-decoration: none; color: inherit; }
|
||||
.community-card:hover .card {
|
||||
border-color: var(--border-standard);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
/* ── Install ── */
|
||||
.install-block { max-width: 720px; margin: 0 auto; }
|
||||
.install-center { text-align: center; margin-bottom: 1.75rem; }
|
||||
.install-platforms {
|
||||
margin-top: 0.75rem; font-size: 0.9rem; color: var(--text-subtle);
|
||||
}
|
||||
.install-divider {
|
||||
text-align: center; margin-bottom: 1.75rem;
|
||||
font-size: 0.85rem; color: var(--text-subtle);
|
||||
}
|
||||
.terminal {
|
||||
border-radius: 16px; overflow: hidden;
|
||||
border: 1px solid var(--border-subtle);
|
||||
background: #101010;
|
||||
}
|
||||
.terminal-bar {
|
||||
display: flex; gap: 6px; padding: 12px 16px; background: #1e1e1e;
|
||||
}
|
||||
.terminal-dot {
|
||||
width: 10px; height: 10px; border-radius: 50%; background: #666;
|
||||
}
|
||||
.terminal pre {
|
||||
padding: 1.25rem 1.5rem; margin: 0;
|
||||
font-size: 0.85rem; line-height: 1.7;
|
||||
color: #e0e0e0; overflow-x: auto;
|
||||
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
}
|
||||
|
||||
/* ── Video ── */
|
||||
.video-wrap {
|
||||
max-width: 800px; margin: 0 auto; aspect-ratio: 16/9;
|
||||
}
|
||||
.video-wrap iframe {
|
||||
width: 100%; height: 100%; border: none; border-radius: 16px;
|
||||
}
|
||||
|
||||
/* ── Footer ── */
|
||||
.footer {
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
padding: 2rem; text-align: center;
|
||||
font-size: 0.85rem; color: var(--text-subtle);
|
||||
}
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 640px) {
|
||||
.navbar-links { display: none; }
|
||||
.intro { padding: 3rem 1.5rem 2rem; }
|
||||
.section { padding: 3rem 1.5rem; }
|
||||
.stats { gap: 2rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar">
|
||||
<img src="static/img/goose.svg" alt="goose" class="navbar-logo" style="filter: brightness(0);">
|
||||
<div class="navbar-links">
|
||||
<a href="#">Docs</a>
|
||||
<a href="#">Blog</a>
|
||||
<a href="#">Extensions</a>
|
||||
<a href="#">Skills</a>
|
||||
<a href="#">GitHub</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero -->
|
||||
<header id="hero-container">
|
||||
<div id="hero-inner">
|
||||
<!-- Layer 1: white logo on black (default) -->
|
||||
<div class="hero-logo-layer" style="z-index:1;">
|
||||
<img src="static/img/goose-logo-white.png" alt="goose logo">
|
||||
</div>
|
||||
<!-- Layer 2: flipped tiles + black logo (revealed by clip) -->
|
||||
<div id="flipped-layer">
|
||||
<svg id="flipped-svg" width="0" height="0">
|
||||
<defs>
|
||||
<clipPath id="flipped-clip"></clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#flipped-clip)">
|
||||
<rect id="flipped-bg" fill="#ffffff" />
|
||||
</g>
|
||||
</svg>
|
||||
<div id="flipped-logo-wrap" style="clip-path: url(#flipped-clip);">
|
||||
<img src="static/img/goose-logo-black.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Layer 3: interactive grid -->
|
||||
<div id="grid-overlay"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Intro -->
|
||||
<section class="intro">
|
||||
<span class="badge-pill">Open Source · Apache 2.0 · Linux Foundation</span>
|
||||
<h1>Your native open source AI agent</h1>
|
||||
<p class="subtitle">Desktop app, CLI, and API — for code, workflows, and everything in between.</p>
|
||||
<div class="intro-actions">
|
||||
<a class="btn btn-primary" href="#">Install goose</a>
|
||||
<a class="btn btn-outline" href="#">Quickstart</a>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div><div class="stat-num">38k+</div><div class="stat-label">GitHub stars</div></div>
|
||||
<div><div class="stat-num">400+</div><div class="stat-label">Contributors</div></div>
|
||||
<div><div class="stat-num">70+</div><div class="stat-label">MCP extensions</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>What goose does</h2>
|
||||
<p>goose is a general-purpose AI agent that runs on your machine. Not just for code — use it for research, writing, automation, data analysis, or anything you need to get done.</p>
|
||||
</div>
|
||||
<div class="card-grid-3">
|
||||
<div class="card">
|
||||
<div class="card-icon">🖥️</div>
|
||||
<h3>Desktop app, CLI, and API</h3>
|
||||
<p>A native desktop app for macOS, Linux, and Windows. A full CLI for terminal workflows. An API to embed it anywhere. Built in Rust for performance and portability.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon">🔌</div>
|
||||
<h3>Extensible</h3>
|
||||
<p>Connect to 70+ extensions — databases, APIs, browsers, GitHub, Google Drive, and more — via the <a href="https://modelcontextprotocol.io/">Model Context Protocol</a> open standard. Add community skills, or build your own.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon">🤖</div>
|
||||
<h3>Any LLM, including your subscriptions</h3>
|
||||
<p>Works with 15+ providers — Anthropic, OpenAI, Google, Ollama, OpenRouter, Azure, Bedrock, and more. Use API keys or your existing Claude, ChatGPT, or Gemini subscriptions via ACP.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-grid-4">
|
||||
<div class="card card-sm">
|
||||
<div class="card-icon">📋</div>
|
||||
<h3>Recipes</h3>
|
||||
<p>Capture workflows as portable YAML configs. Share with your team, run in CI, include instructions, extensions, parameters, and subrecipes.</p>
|
||||
</div>
|
||||
<div class="card card-sm">
|
||||
<div class="card-icon">🧩</div>
|
||||
<h3>MCP Apps</h3>
|
||||
<p>Extensions can render interactive UIs directly inside goose Desktop — buttons, forms, visualizations. A new way to build agent-powered tools.</p>
|
||||
</div>
|
||||
<div class="card card-sm">
|
||||
<div class="card-icon">🔀</div>
|
||||
<h3>Subagents</h3>
|
||||
<p>Spawn independent subagents to handle tasks in parallel — code review, research, file processing — keeping the main conversation clean.</p>
|
||||
</div>
|
||||
<div class="card card-sm">
|
||||
<div class="card-icon">🔒</div>
|
||||
<h3>Security</h3>
|
||||
<p>Prompt injection detection, tool permission controls, sandbox mode, and an adversary reviewer that watches for unsafe actions.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Standards -->
|
||||
<section class="section section-alt">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>Built on open standards</h2>
|
||||
</div>
|
||||
<div class="card-grid-3">
|
||||
<div class="card" style="display:flex;flex-direction:column;gap:0.75rem;">
|
||||
<h3 style="margin:0;">Model Context Protocol</h3>
|
||||
<p style="flex:1;"><a href="https://modelcontextprotocol.io/">MCP</a> is the open standard for connecting AI agents to tools and data sources. goose was one of the earliest adopters and has one of the deepest integrations in the ecosystem — with 70+ documented extensions and growing.</p>
|
||||
<a class="card-link" href="#">Browse MCP extensions →</a>
|
||||
</div>
|
||||
<div class="card" style="display:flex;flex-direction:column;gap:0.75rem;">
|
||||
<h3 style="margin:0;">Agent Client Protocol</h3>
|
||||
<p style="flex:1;"><a href="https://agentclientprotocol.com/">ACP</a> is a standard for communicating with coding agents. goose works as an ACP server — connect from Zed, JetBrains, or VS Code — and can use ACP agents like Claude Code and Codex as providers.</p>
|
||||
<a class="card-link" href="#">goose as ACP server →</a>
|
||||
</div>
|
||||
<div class="card" style="display:flex;flex-direction:column;gap:0.75rem;">
|
||||
<h3 style="margin:0;">Agentic AI Foundation</h3>
|
||||
<p style="flex:1;">goose is part of the <a href="https://aaif.io/">Agentic AI Foundation</a> at the Linux Foundation — ensuring the project remains vendor-neutral, community-governed, and open for the long term.</p>
|
||||
<a class="card-link" href="https://aaif.io/">Learn about AAIF →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Community -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>Community</h2>
|
||||
<p>An active community of developers, contributors, and users building extensions, sharing recipes, and pushing the boundaries of what local AI agents can do.</p>
|
||||
</div>
|
||||
<div class="card-grid-4">
|
||||
<a class="community-card" href="https://discord.gg/goose-oss">
|
||||
<div class="card card-sm">
|
||||
<div class="card-icon">💬</div>
|
||||
<h3>Discord</h3>
|
||||
<p>Ask questions, share what you've built, get help from the community.</p>
|
||||
</div>
|
||||
</a>
|
||||
<a class="community-card" href="https://github.com/aaif-goose/goose">
|
||||
<div class="card card-sm">
|
||||
<div class="card-icon">🐙</div>
|
||||
<h3>GitHub</h3>
|
||||
<p>Star, fork, file issues, contribute code. goose is built in the open.</p>
|
||||
</div>
|
||||
</a>
|
||||
<a class="community-card" href="#">
|
||||
<div class="card card-sm">
|
||||
<div class="card-icon">🧩</div>
|
||||
<h3>Extensions</h3>
|
||||
<p>Browse community-built MCP extensions and add your own.</p>
|
||||
</div>
|
||||
</a>
|
||||
<a class="community-card" href="#">
|
||||
<div class="card card-sm">
|
||||
<div class="card-icon">📝</div>
|
||||
<h3>Blog</h3>
|
||||
<p>Tutorials, deep dives, release notes, and community spotlights.</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Install -->
|
||||
<section class="section section-alt">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>Get started</h2>
|
||||
</div>
|
||||
<div class="install-block">
|
||||
<div class="install-center">
|
||||
<a class="btn btn-primary" href="#">Download the desktop app</a>
|
||||
<p class="install-platforms">Available for macOS, Linux, and Windows</p>
|
||||
</div>
|
||||
<div class="install-divider">or install the CLI</div>
|
||||
<div class="terminal">
|
||||
<div class="terminal-bar">
|
||||
<span class="terminal-dot"></span>
|
||||
<span class="terminal-dot"></span>
|
||||
<span class="terminal-dot"></span>
|
||||
</div>
|
||||
<pre><code>curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | bash</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Video -->
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>See goose in action</h2>
|
||||
</div>
|
||||
<div class="video-wrap">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/D-DpDunrbpo"
|
||||
title="vibe coding with goose"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<p>goose is an open source project under the Agentic AI Foundation at the Linux Foundation.</p>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const CELL = 48;
|
||||
const ROWS = 10;
|
||||
|
||||
const container = document.getElementById('hero-container');
|
||||
const inner = document.getElementById('hero-inner');
|
||||
const gridOverlay = document.getElementById('grid-overlay');
|
||||
const svg = document.getElementById('flipped-svg');
|
||||
const clipPath = document.getElementById('flipped-clip');
|
||||
const flippedBg = document.getElementById('flipped-bg');
|
||||
|
||||
let cols = 0, rows = ROWS, width = 0, height = 0;
|
||||
const TRAIL_MS = 1000;
|
||||
|
||||
// Map of idx -> { rect, timer }
|
||||
const trail = new Map();
|
||||
|
||||
function addTrail(idx) {
|
||||
if (trail.has(idx)) {
|
||||
// Already lit — just reset its timer
|
||||
clearTimeout(trail.get(idx).timer);
|
||||
trail.get(idx).timer = setTimeout(function() { removeTrail(idx); }, TRAIL_MS);
|
||||
return;
|
||||
}
|
||||
const col = idx % cols;
|
||||
const row = Math.floor(idx / cols);
|
||||
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
rect.setAttribute('x', col * CELL);
|
||||
rect.setAttribute('y', row * CELL);
|
||||
rect.setAttribute('width', CELL);
|
||||
rect.setAttribute('height', CELL);
|
||||
clipPath.appendChild(rect);
|
||||
const timer = setTimeout(function() { removeTrail(idx); }, TRAIL_MS);
|
||||
trail.set(idx, { rect: rect, timer: timer });
|
||||
}
|
||||
|
||||
function removeTrail(idx) {
|
||||
const entry = trail.get(idx);
|
||||
if (!entry) return;
|
||||
clearTimeout(entry.timer);
|
||||
if (entry.rect.parentNode) entry.rect.parentNode.removeChild(entry.rect);
|
||||
trail.delete(idx);
|
||||
}
|
||||
|
||||
function clearAllTrails() {
|
||||
trail.forEach(function(entry, idx) {
|
||||
clearTimeout(entry.timer);
|
||||
if (entry.rect.parentNode) entry.rect.parentNode.removeChild(entry.rect);
|
||||
});
|
||||
trail.clear();
|
||||
}
|
||||
|
||||
function buildGrid() {
|
||||
const w = container.offsetWidth;
|
||||
cols = Math.floor(w / CELL);
|
||||
width = cols * CELL;
|
||||
height = rows * CELL;
|
||||
|
||||
inner.style.width = width + 'px';
|
||||
inner.style.height = height + 'px';
|
||||
|
||||
svg.setAttribute('width', width);
|
||||
svg.setAttribute('height', height);
|
||||
svg.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
|
||||
flippedBg.setAttribute('width', width);
|
||||
flippedBg.setAttribute('height', height);
|
||||
|
||||
gridOverlay.style.gridTemplateColumns = 'repeat(' + cols + ', ' + CELL + 'px)';
|
||||
gridOverlay.style.gridTemplateRows = 'repeat(' + rows + ', ' + CELL + 'px)';
|
||||
gridOverlay.style.width = width + 'px';
|
||||
gridOverlay.style.height = height + 'px';
|
||||
|
||||
gridOverlay.innerHTML = '';
|
||||
clearAllTrails();
|
||||
|
||||
const total = cols * rows;
|
||||
for (let i = 0; i < total; i++) {
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'grid-cell';
|
||||
cell.style.width = CELL + 'px';
|
||||
cell.style.height = CELL + 'px';
|
||||
cell.addEventListener('mouseenter', function() {
|
||||
addTrail(i);
|
||||
});
|
||||
gridOverlay.appendChild(cell);
|
||||
}
|
||||
}
|
||||
|
||||
buildGrid();
|
||||
window.addEventListener('resize', buildGrid);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+737
-285
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user