feat: add showcase site
3a8a782 parent: 3273cfe modified
package.json +2 -1 | @@ -9,7 +9,8 @@ | ||
| 9 | 9 | "build": "pnpm --filter @skilly/ui build", |
| 10 | 10 | "start": "pnpm --filter skilly start", |
| 11 | 11 | "test": "vitest run", |
| 12 | - "typecheck": "tsc -p packages/core && tsc -p packages/server && tsc -p packages/cli" | |
| 12 | + "typecheck": "tsc -p packages/core && tsc -p packages/server && tsc -p packages/cli", | |
| 13 | + "site:build": "node site/build.mjs" | |
| 13 | 14 | }, |
| 14 | 15 | "devDependencies": { |
| 15 | 16 | "@types/node": "^22.10.0", |
| @@ -9,7 +9,8 @@ | |||
| 9 | "build": "pnpm --filter @skilly/ui build", | 9 | "build": "pnpm --filter @skilly/ui build", |
| 10 | "start": "pnpm --filter skilly start", | 10 | "start": "pnpm --filter skilly start", |
| 11 | "test": "vitest run", | 11 | "test": "vitest run", |
| 12 | - "typecheck": "tsc -p packages/core && tsc -p packages/server && tsc -p packages/cli" | 12 | + "typecheck": "tsc -p packages/core && tsc -p packages/server && tsc -p packages/cli", |
| 13 | + "site:build": "node site/build.mjs" | ||
| 13 | }, | 14 | }, |
| 14 | "devDependencies": { | 15 | "devDependencies": { |
| 15 | "@types/node": "^22.10.0", | 16 | "@types/node": "^22.10.0", |
added
site/build.mjs +15 -0 | new file mode 100644 | ||
| @@ -0,0 +1,15 @@ | ||
| 1 | +// Builds the showcase site into site/dist: the static files of site/public, plus the screenshots and | |
| 2 | +// the favicon, taken from the repository so they have a single source. | |
| 3 | +// No dependencies: `node site/build.mjs`, then open site/dist/index.html or serve the folder. | |
| 4 | +import { cpSync, rmSync } from 'node:fs' | |
| 5 | +import { fileURLToPath } from 'node:url' | |
| 6 | + | |
| 7 | +const root = (path) => fileURLToPath(new URL(`../${path}`, import.meta.url)) | |
| 8 | +const dist = root('site/dist') | |
| 9 | + | |
| 10 | +rmSync(dist, { recursive: true, force: true }) | |
| 11 | +cpSync(root('site/public'), dist, { recursive: true }) | |
| 12 | +cpSync(root('media/screenshots'), `${dist}/screenshots`, { recursive: true }) | |
| 13 | +cpSync(root('packages/ui/public/favicon.svg'), `${dist}/favicon.svg`) | |
| 14 | + | |
| 15 | +console.log(`Site built in ${dist}`) | |
| new file mode 100644 | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | +// Builds the showcase site into site/dist: the static files of site/public, plus the screenshots and | ||
| 2 | +// the favicon, taken from the repository so they have a single source. | ||
| 3 | +// No dependencies: `node site/build.mjs`, then open site/dist/index.html or serve the folder. | ||
| 4 | +import { cpSync, rmSync } from 'node:fs' | ||
| 5 | +import { fileURLToPath } from 'node:url' | ||
| 6 | + | ||
| 7 | +const root = (path) => fileURLToPath(new URL(`../${path}`, import.meta.url)) | ||
| 8 | +const dist = root('site/dist') | ||
| 9 | + | ||
| 10 | +rmSync(dist, { recursive: true, force: true }) | ||
| 11 | +cpSync(root('site/public'), dist, { recursive: true }) | ||
| 12 | +cpSync(root('media/screenshots'), `${dist}/screenshots`, { recursive: true }) | ||
| 13 | +cpSync(root('packages/ui/public/favicon.svg'), `${dist}/favicon.svg`) | ||
| 14 | + | ||
| 15 | +console.log(`Site built in ${dist}`) | ||
added
site/public/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-400-normal.woff2 differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-400-normal.woff2 differ | Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-400-normal.woff2 differ | ||
added
site/public/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-500-normal.woff2 differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-500-normal.woff2 differ | Binary files /dev/null and b/site/public/fonts/ibm-plex-mono-latin-500-normal.woff2 differ | ||
added
site/public/fonts/instrument-sans-latin-400-normal.woff2 +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/site/public/fonts/instrument-sans-latin-400-normal.woff2 differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/site/public/fonts/instrument-sans-latin-400-normal.woff2 differ | Binary files /dev/null and b/site/public/fonts/instrument-sans-latin-400-normal.woff2 differ | ||
added
site/public/fonts/instrument-sans-latin-500-normal.woff2 +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/site/public/fonts/instrument-sans-latin-500-normal.woff2 differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/site/public/fonts/instrument-sans-latin-500-normal.woff2 differ | Binary files /dev/null and b/site/public/fonts/instrument-sans-latin-500-normal.woff2 differ | ||
added
site/public/fonts/instrument-sans-latin-600-normal.woff2 +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/site/public/fonts/instrument-sans-latin-600-normal.woff2 differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/site/public/fonts/instrument-sans-latin-600-normal.woff2 differ | Binary files /dev/null and b/site/public/fonts/instrument-sans-latin-600-normal.woff2 differ | ||
added
site/public/index.html +193 -0 | new file mode 100644 | ||
| @@ -0,0 +1,193 @@ | ||
| 1 | +<!doctype html> | |
| 2 | +<html lang="en"> | |
| 3 | +<head> | |
| 4 | + <meta charset="utf-8"> | |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | + <title>skilly: stop copying agent skills between projects</title> | |
| 7 | + <meta name="description" content="One catalog for your Claude Code and Codex skills, rules and MCP servers. Each project gets only what it needs. Open source."> | |
| 8 | + <link rel="canonical" href="https://skilly.eventuallycoding.com/"> | |
| 9 | + <meta name="color-scheme" content="light dark"> | |
| 10 | + <meta name="theme-color" content="#f5f4f1" media="(prefers-color-scheme: light)"> | |
| 11 | + <meta name="theme-color" content="#121110" media="(prefers-color-scheme: dark)"> | |
| 12 | + | |
| 13 | + <meta property="og:type" content="website"> | |
| 14 | + <meta property="og:url" content="https://skilly.eventuallycoding.com/"> | |
| 15 | + <meta property="og:title" content="skilly: stop copying agent skills between projects"> | |
| 16 | + <meta property="og:description" content="One catalog for your Claude Code and Codex skills, rules and MCP servers. Each project gets only what it needs."> | |
| 17 | + <meta property="og:image" content="https://skilly.eventuallycoding.com/screenshots/project.png"> | |
| 18 | + <meta name="twitter:card" content="summary_large_image"> | |
| 19 | + | |
| 20 | + <link rel="icon" type="image/svg+xml" href="favicon.svg"> | |
| 21 | + <link rel="preload" href="fonts/ibm-plex-mono-latin-400-normal.woff2" as="font" type="font/woff2" crossorigin> | |
| 22 | + <link rel="preload" href="fonts/instrument-sans-latin-400-normal.woff2" as="font" type="font/woff2" crossorigin> | |
| 23 | + <link rel="stylesheet" href="styles.css"> | |
| 24 | + <script>document.documentElement.classList.add('js')</script> | |
| 25 | + <script src="main.js" defer></script> | |
| 26 | +</head> | |
| 27 | +<body> | |
| 28 | + <a class="skip" href="#main">Skip to content</a> | |
| 29 | + | |
| 30 | + <header class="top"> | |
| 31 | + <a class="brand" href="./" aria-label="skilly, home"> | |
| 32 | + <span class="mark" aria-hidden="true"><i></i><i></i><i></i><i></i></span> | |
| 33 | + <span>skilly</span> | |
| 34 | + </a> | |
| 35 | + <nav class="top-nav" aria-label="Sections"> | |
| 36 | + <a href="#tour">Tour</a> | |
| 37 | + <a href="#how">How it works</a> | |
| 38 | + <a href="#try">Try it</a> | |
| 39 | + </nav> | |
| 40 | + <a class="btn btn-ink btn-sm" href="https://rickub.com/hlassiege/skilly">Source code</a> | |
| 41 | + </header> | |
| 42 | + | |
| 43 | + <main id="main"> | |
| 44 | + <section class="hero"> | |
| 45 | + <h1>Stop copying skills from project to project.</h1> | |
| 46 | + <p class="lead">One catalog for your skills, rules and MCP servers.</p> | |
| 47 | + <div class="actions"> | |
| 48 | + <a class="btn btn-ink" href="#try">Try it</a> | |
| 49 | + <a class="btn btn-line" href="https://rickub.com/hlassiege/skilly">Source code</a> | |
| 50 | + </div> | |
| 51 | + </section> | |
| 52 | + | |
| 53 | + <section class="tour" id="tour" aria-labelledby="tour-title"> | |
| 54 | + <h2 class="sr-only" id="tour-title">A tour of skilly</h2> | |
| 55 | + <div class="tour-tabs" role="tablist" aria-label="Screens" aria-orientation="vertical"> | |
| 56 | + <button role="tab" id="tab-project" aria-controls="shot-project" aria-selected="true"> | |
| 57 | + <span class="tab-title">Pick what each project uses</span> | |
| 58 | + <span class="tab-desc">Add a preset or a single skill. skilly writes the files.</span> | |
| 59 | + </button> | |
| 60 | + <button role="tab" id="tab-review" aria-controls="shot-review" aria-selected="false" tabindex="-1"> | |
| 61 | + <span class="tab-title">See every change first</span> | |
| 62 | + <span class="tab-desc">Nothing is written until you apply. Edited a file by hand? skilly asks first.</span> | |
| 63 | + </button> | |
| 64 | + <button role="tab" id="tab-global" aria-controls="shot-global" aria-selected="false" tabindex="-1"> | |
| 65 | + <span class="tab-title">Slim down your global config</span> | |
| 66 | + <span class="tab-desc">Move the skills you rarely use out of <code>~/.claude</code>.</span> | |
| 67 | + </button> | |
| 68 | + <button role="tab" id="tab-catalog" aria-controls="shot-catalog" aria-selected="false" tabindex="-1"> | |
| 69 | + <span class="tab-title">All your items in one place</span> | |
| 70 | + <span class="tab-desc">Import what you already have, or write new ones.</span> | |
| 71 | + </button> | |
| 72 | + <button role="tab" id="tab-presets" aria-controls="shot-presets" aria-selected="false" tabindex="-1"> | |
| 73 | + <span class="tab-title">Bundle what goes together</span> | |
| 74 | + <span class="tab-desc">A Nuxt preset, an SEO preset. One click per project.</span> | |
| 75 | + </button> | |
| 76 | + <button role="tab" id="tab-marketplaces" aria-controls="shot-marketplaces" aria-selected="false" tabindex="-1"> | |
| 77 | + <span class="tab-title">Grab skills from others</span> | |
| 78 | + <span class="tab-desc">Anthropic, Vercel, or any git repo.</span> | |
| 79 | + </button> | |
| 80 | + </div> | |
| 81 | + <div class="tour-shots"> | |
| 82 | + <div class="frame" id="shot-project" role="tabpanel" aria-labelledby="tab-project"><img src="screenshots/project.png" width="2880" height="1800" alt="A project in skilly with its presets and items" fetchpriority="high"></div> | |
| 83 | + <div class="frame" id="shot-review" role="tabpanel" aria-labelledby="tab-review"><img src="screenshots/review.png" width="2880" height="1800" alt="The review screen listing the files that will change" loading="lazy"></div> | |
| 84 | + <div class="frame" id="shot-global" role="tabpanel" aria-labelledby="tab-global"><img src="screenshots/global.png" width="2880" height="1800" alt="Global items with suggestions to move them to projects" loading="lazy"></div> | |
| 85 | + <div class="frame" id="shot-catalog" role="tabpanel" aria-labelledby="tab-catalog"><img src="screenshots/catalog.png" width="2880" height="1800" alt="The catalog with a skill opened in the editor" loading="lazy"></div> | |
| 86 | + <div class="frame" id="shot-presets" role="tabpanel" aria-labelledby="tab-presets"><img src="screenshots/presets.png" width="2880" height="1120" alt="Presets and the projects that use them" loading="lazy"></div> | |
| 87 | + <div class="frame" id="shot-marketplaces" role="tabpanel" aria-labelledby="tab-marketplaces"><img src="screenshots/marketplaces.png" width="2880" height="1800" alt="Browsing the skills of a marketplace" loading="lazy"></div> | |
| 88 | + </div> | |
| 89 | + </section> | |
| 90 | + | |
| 91 | + <section class="why" aria-labelledby="why-title"> | |
| 92 | + <h2 id="why-title">Agent setups get messy fast</h2> | |
| 93 | + <div class="why-list"> | |
| 94 | + <div> | |
| 95 | + <h3>Copies everywhere</h3> | |
| 96 | + <p>You copy a skill to a new project. Now you have two versions.</p> | |
| 97 | + </div> | |
| 98 | + <div> | |
| 99 | + <h3>Everything in global</h3> | |
| 100 | + <p>Every skill loads in every session, even where it is useless.</p> | |
| 101 | + </div> | |
| 102 | + <div> | |
| 103 | + <h3>No overview</h3> | |
| 104 | + <p>Which skills do you have? Which project uses them? No idea.</p> | |
| 105 | + </div> | |
| 106 | + </div> | |
| 107 | + </section> | |
| 108 | + | |
| 109 | + <section class="how" id="how" aria-labelledby="how-title"> | |
| 110 | + <h2 id="how-title">One catalog, plain files in each project</h2> | |
| 111 | + <p class="section-lead">Your projects keep working without skilly.</p> | |
| 112 | + | |
| 113 | + <div class="flow" data-flow> | |
| 114 | + <div class="panel cat"> | |
| 115 | + <div class="panel-head"> | |
| 116 | + <span class="name">catalog</span> | |
| 117 | + <span class="path">git repo</span> | |
| 118 | + </div> | |
| 119 | + <ul class="items"> | |
| 120 | + <li><span class="tag">skill</span>nuxt</li> | |
| 121 | + <li><span class="tag">skill</span>code-review</li> | |
| 122 | + <li><span class="tag">skill</span>seo-audit</li> | |
| 123 | + <li><span class="tag">rule</span>typescript</li> | |
| 124 | + <li><span class="tag">rule</span>writing</li> | |
| 125 | + <li><span class="tag">mcp</span>nuxt-docs</li> | |
| 126 | + <li><span class="tag">preset</span>nuxt-saas</li> | |
| 127 | + </ul> | |
| 128 | + </div> | |
| 129 | + | |
| 130 | + <div class="apply" aria-hidden="true"><span>apply</span></div> | |
| 131 | + | |
| 132 | + <div class="projects"> | |
| 133 | + <div class="panel proj"> | |
| 134 | + <div class="panel-head"><span class="name">acme-shop</span><span class="agents">claude, codex</span></div> | |
| 135 | + <ul class="files"> | |
| 136 | + <li style="--i:0">.claude/skills/nuxt/SKILL.md</li> | |
| 137 | + <li style="--i:1">.agents/skills/nuxt/SKILL.md</li> | |
| 138 | + <li style="--i:2">.claude/rules/typescript.md</li> | |
| 139 | + <li style="--i:3">.mcp.json</li> | |
| 140 | + </ul> | |
| 141 | + </div> | |
| 142 | + <div class="panel proj"> | |
| 143 | + <div class="panel-head"><span class="name">billing-api</span><span class="agents">codex</span></div> | |
| 144 | + <ul class="files"> | |
| 145 | + <li style="--i:4">.agents/skills/code-review/SKILL.md</li> | |
| 146 | + <li style="--i:5">AGENTS.md</li> | |
| 147 | + <li style="--i:6">.codex/config.toml</li> | |
| 148 | + </ul> | |
| 149 | + </div> | |
| 150 | + <div class="panel proj"> | |
| 151 | + <div class="panel-head"><span class="name">blog</span><span class="agents">claude</span></div> | |
| 152 | + <ul class="files"> | |
| 153 | + <li style="--i:7">.claude/skills/seo-audit/SKILL.md</li> | |
| 154 | + <li style="--i:8">.claude/rules/writing.md</li> | |
| 155 | + </ul> | |
| 156 | + </div> | |
| 157 | + </div> | |
| 158 | + </div> | |
| 159 | + | |
| 160 | + <dl class="facts"> | |
| 161 | + <div> | |
| 162 | + <dt>Skills on demand</dt> | |
| 163 | + <dd>Installed, but invisible until you call them. No context wasted.</dd> | |
| 164 | + </div> | |
| 165 | + <div> | |
| 166 | + <dt>Synced with git</dt> | |
| 167 | + <dd>New machine? Clone your catalog and you are back.</dd> | |
| 168 | + </div> | |
| 169 | + <div> | |
| 170 | + <dt>Your team needs nothing</dt> | |
| 171 | + <dd>The files are committed with the project.</dd> | |
| 172 | + </div> | |
| 173 | + </dl> | |
| 174 | + </section> | |
| 175 | + | |
| 176 | + <section class="try" id="try" aria-labelledby="try-title"> | |
| 177 | + <h2 id="try-title">Try it</h2> | |
| 178 | + <div class="code"> | |
| 179 | + <pre><code id="install-cmd">git clone https://rickub.com/hlassiege/skilly.git | |
| 180 | +cd skilly | |
| 181 | +pnpm install && pnpm build && pnpm start</code></pre> | |
| 182 | + <button class="btn btn-line btn-sm copy" type="button" data-copy="install-cmd">Copy</button> | |
| 183 | + </div> | |
| 184 | + <p class="note"><a href="https://rickub.com/hlassiege/skilly">Read the README</a> for everything else.</p> | |
| 185 | + </section> | |
| 186 | + </main> | |
| 187 | + | |
| 188 | + <footer class="foot"> | |
| 189 | + <p>Open source, MIT. Made by <a href="https://eventuallycoding.com">Hugo Lassiège</a>.</p> | |
| 190 | + <a href="https://rickub.com/hlassiege/skilly">Source code</a> | |
| 191 | + </footer> | |
| 192 | +</body> | |
| 193 | +</html> | |
| new file mode 100644 | |||
| @@ -0,0 +1,193 @@ | |||
| 1 | +<!doctype html> | ||
| 2 | +<html lang="en"> | ||
| 3 | +<head> | ||
| 4 | + <meta charset="utf-8"> | ||
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | + <title>skilly: stop copying agent skills between projects</title> | ||
| 7 | + <meta name="description" content="One catalog for your Claude Code and Codex skills, rules and MCP servers. Each project gets only what it needs. Open source."> | ||
| 8 | + <link rel="canonical" href="https://skilly.eventuallycoding.com/"> | ||
| 9 | + <meta name="color-scheme" content="light dark"> | ||
| 10 | + <meta name="theme-color" content="#f5f4f1" media="(prefers-color-scheme: light)"> | ||
| 11 | + <meta name="theme-color" content="#121110" media="(prefers-color-scheme: dark)"> | ||
| 12 | + | ||
| 13 | + <meta property="og:type" content="website"> | ||
| 14 | + <meta property="og:url" content="https://skilly.eventuallycoding.com/"> | ||
| 15 | + <meta property="og:title" content="skilly: stop copying agent skills between projects"> | ||
| 16 | + <meta property="og:description" content="One catalog for your Claude Code and Codex skills, rules and MCP servers. Each project gets only what it needs."> | ||
| 17 | + <meta property="og:image" content="https://skilly.eventuallycoding.com/screenshots/project.png"> | ||
| 18 | + <meta name="twitter:card" content="summary_large_image"> | ||
| 19 | + | ||
| 20 | + <link rel="icon" type="image/svg+xml" href="favicon.svg"> | ||
| 21 | + <link rel="preload" href="fonts/ibm-plex-mono-latin-400-normal.woff2" as="font" type="font/woff2" crossorigin> | ||
| 22 | + <link rel="preload" href="fonts/instrument-sans-latin-400-normal.woff2" as="font" type="font/woff2" crossorigin> | ||
| 23 | + <link rel="stylesheet" href="styles.css"> | ||
| 24 | + <script>document.documentElement.classList.add('js')</script> | ||
| 25 | + <script src="main.js" defer></script> | ||
| 26 | +</head> | ||
| 27 | +<body> | ||
| 28 | + <a class="skip" href="#main">Skip to content</a> | ||
| 29 | + | ||
| 30 | + <header class="top"> | ||
| 31 | + <a class="brand" href="./" aria-label="skilly, home"> | ||
| 32 | + <span class="mark" aria-hidden="true"><i></i><i></i><i></i><i></i></span> | ||
| 33 | + <span>skilly</span> | ||
| 34 | + </a> | ||
| 35 | + <nav class="top-nav" aria-label="Sections"> | ||
| 36 | + <a href="#tour">Tour</a> | ||
| 37 | + <a href="#how">How it works</a> | ||
| 38 | + <a href="#try">Try it</a> | ||
| 39 | + </nav> | ||
| 40 | + <a class="btn btn-ink btn-sm" href="https://rickub.com/hlassiege/skilly">Source code</a> | ||
| 41 | + </header> | ||
| 42 | + | ||
| 43 | + <main id="main"> | ||
| 44 | + <section class="hero"> | ||
| 45 | + <h1>Stop copying skills from project to project.</h1> | ||
| 46 | + <p class="lead">One catalog for your skills, rules and MCP servers.</p> | ||
| 47 | + <div class="actions"> | ||
| 48 | + <a class="btn btn-ink" href="#try">Try it</a> | ||
| 49 | + <a class="btn btn-line" href="https://rickub.com/hlassiege/skilly">Source code</a> | ||
| 50 | + </div> | ||
| 51 | + </section> | ||
| 52 | + | ||
| 53 | + <section class="tour" id="tour" aria-labelledby="tour-title"> | ||
| 54 | + <h2 class="sr-only" id="tour-title">A tour of skilly</h2> | ||
| 55 | + <div class="tour-tabs" role="tablist" aria-label="Screens" aria-orientation="vertical"> | ||
| 56 | + <button role="tab" id="tab-project" aria-controls="shot-project" aria-selected="true"> | ||
| 57 | + <span class="tab-title">Pick what each project uses</span> | ||
| 58 | + <span class="tab-desc">Add a preset or a single skill. skilly writes the files.</span> | ||
| 59 | + </button> | ||
| 60 | + <button role="tab" id="tab-review" aria-controls="shot-review" aria-selected="false" tabindex="-1"> | ||
| 61 | + <span class="tab-title">See every change first</span> | ||
| 62 | + <span class="tab-desc">Nothing is written until you apply. Edited a file by hand? skilly asks first.</span> | ||
| 63 | + </button> | ||
| 64 | + <button role="tab" id="tab-global" aria-controls="shot-global" aria-selected="false" tabindex="-1"> | ||
| 65 | + <span class="tab-title">Slim down your global config</span> | ||
| 66 | + <span class="tab-desc">Move the skills you rarely use out of <code>~/.claude</code>.</span> | ||
| 67 | + </button> | ||
| 68 | + <button role="tab" id="tab-catalog" aria-controls="shot-catalog" aria-selected="false" tabindex="-1"> | ||
| 69 | + <span class="tab-title">All your items in one place</span> | ||
| 70 | + <span class="tab-desc">Import what you already have, or write new ones.</span> | ||
| 71 | + </button> | ||
| 72 | + <button role="tab" id="tab-presets" aria-controls="shot-presets" aria-selected="false" tabindex="-1"> | ||
| 73 | + <span class="tab-title">Bundle what goes together</span> | ||
| 74 | + <span class="tab-desc">A Nuxt preset, an SEO preset. One click per project.</span> | ||
| 75 | + </button> | ||
| 76 | + <button role="tab" id="tab-marketplaces" aria-controls="shot-marketplaces" aria-selected="false" tabindex="-1"> | ||
| 77 | + <span class="tab-title">Grab skills from others</span> | ||
| 78 | + <span class="tab-desc">Anthropic, Vercel, or any git repo.</span> | ||
| 79 | + </button> | ||
| 80 | + </div> | ||
| 81 | + <div class="tour-shots"> | ||
| 82 | + <div class="frame" id="shot-project" role="tabpanel" aria-labelledby="tab-project"><img src="screenshots/project.png" width="2880" height="1800" alt="A project in skilly with its presets and items" fetchpriority="high"></div> | ||
| 83 | + <div class="frame" id="shot-review" role="tabpanel" aria-labelledby="tab-review"><img src="screenshots/review.png" width="2880" height="1800" alt="The review screen listing the files that will change" loading="lazy"></div> | ||
| 84 | + <div class="frame" id="shot-global" role="tabpanel" aria-labelledby="tab-global"><img src="screenshots/global.png" width="2880" height="1800" alt="Global items with suggestions to move them to projects" loading="lazy"></div> | ||
| 85 | + <div class="frame" id="shot-catalog" role="tabpanel" aria-labelledby="tab-catalog"><img src="screenshots/catalog.png" width="2880" height="1800" alt="The catalog with a skill opened in the editor" loading="lazy"></div> | ||
| 86 | + <div class="frame" id="shot-presets" role="tabpanel" aria-labelledby="tab-presets"><img src="screenshots/presets.png" width="2880" height="1120" alt="Presets and the projects that use them" loading="lazy"></div> | ||
| 87 | + <div class="frame" id="shot-marketplaces" role="tabpanel" aria-labelledby="tab-marketplaces"><img src="screenshots/marketplaces.png" width="2880" height="1800" alt="Browsing the skills of a marketplace" loading="lazy"></div> | ||
| 88 | + </div> | ||
| 89 | + </section> | ||
| 90 | + | ||
| 91 | + <section class="why" aria-labelledby="why-title"> | ||
| 92 | + <h2 id="why-title">Agent setups get messy fast</h2> | ||
| 93 | + <div class="why-list"> | ||
| 94 | + <div> | ||
| 95 | + <h3>Copies everywhere</h3> | ||
| 96 | + <p>You copy a skill to a new project. Now you have two versions.</p> | ||
| 97 | + </div> | ||
| 98 | + <div> | ||
| 99 | + <h3>Everything in global</h3> | ||
| 100 | + <p>Every skill loads in every session, even where it is useless.</p> | ||
| 101 | + </div> | ||
| 102 | + <div> | ||
| 103 | + <h3>No overview</h3> | ||
| 104 | + <p>Which skills do you have? Which project uses them? No idea.</p> | ||
| 105 | + </div> | ||
| 106 | + </div> | ||
| 107 | + </section> | ||
| 108 | + | ||
| 109 | + <section class="how" id="how" aria-labelledby="how-title"> | ||
| 110 | + <h2 id="how-title">One catalog, plain files in each project</h2> | ||
| 111 | + <p class="section-lead">Your projects keep working without skilly.</p> | ||
| 112 | + | ||
| 113 | + <div class="flow" data-flow> | ||
| 114 | + <div class="panel cat"> | ||
| 115 | + <div class="panel-head"> | ||
| 116 | + <span class="name">catalog</span> | ||
| 117 | + <span class="path">git repo</span> | ||
| 118 | + </div> | ||
| 119 | + <ul class="items"> | ||
| 120 | + <li><span class="tag">skill</span>nuxt</li> | ||
| 121 | + <li><span class="tag">skill</span>code-review</li> | ||
| 122 | + <li><span class="tag">skill</span>seo-audit</li> | ||
| 123 | + <li><span class="tag">rule</span>typescript</li> | ||
| 124 | + <li><span class="tag">rule</span>writing</li> | ||
| 125 | + <li><span class="tag">mcp</span>nuxt-docs</li> | ||
| 126 | + <li><span class="tag">preset</span>nuxt-saas</li> | ||
| 127 | + </ul> | ||
| 128 | + </div> | ||
| 129 | + | ||
| 130 | + <div class="apply" aria-hidden="true"><span>apply</span></div> | ||
| 131 | + | ||
| 132 | + <div class="projects"> | ||
| 133 | + <div class="panel proj"> | ||
| 134 | + <div class="panel-head"><span class="name">acme-shop</span><span class="agents">claude, codex</span></div> | ||
| 135 | + <ul class="files"> | ||
| 136 | + <li style="--i:0">.claude/skills/nuxt/SKILL.md</li> | ||
| 137 | + <li style="--i:1">.agents/skills/nuxt/SKILL.md</li> | ||
| 138 | + <li style="--i:2">.claude/rules/typescript.md</li> | ||
| 139 | + <li style="--i:3">.mcp.json</li> | ||
| 140 | + </ul> | ||
| 141 | + </div> | ||
| 142 | + <div class="panel proj"> | ||
| 143 | + <div class="panel-head"><span class="name">billing-api</span><span class="agents">codex</span></div> | ||
| 144 | + <ul class="files"> | ||
| 145 | + <li style="--i:4">.agents/skills/code-review/SKILL.md</li> | ||
| 146 | + <li style="--i:5">AGENTS.md</li> | ||
| 147 | + <li style="--i:6">.codex/config.toml</li> | ||
| 148 | + </ul> | ||
| 149 | + </div> | ||
| 150 | + <div class="panel proj"> | ||
| 151 | + <div class="panel-head"><span class="name">blog</span><span class="agents">claude</span></div> | ||
| 152 | + <ul class="files"> | ||
| 153 | + <li style="--i:7">.claude/skills/seo-audit/SKILL.md</li> | ||
| 154 | + <li style="--i:8">.claude/rules/writing.md</li> | ||
| 155 | + </ul> | ||
| 156 | + </div> | ||
| 157 | + </div> | ||
| 158 | + </div> | ||
| 159 | + | ||
| 160 | + <dl class="facts"> | ||
| 161 | + <div> | ||
| 162 | + <dt>Skills on demand</dt> | ||
| 163 | + <dd>Installed, but invisible until you call them. No context wasted.</dd> | ||
| 164 | + </div> | ||
| 165 | + <div> | ||
| 166 | + <dt>Synced with git</dt> | ||
| 167 | + <dd>New machine? Clone your catalog and you are back.</dd> | ||
| 168 | + </div> | ||
| 169 | + <div> | ||
| 170 | + <dt>Your team needs nothing</dt> | ||
| 171 | + <dd>The files are committed with the project.</dd> | ||
| 172 | + </div> | ||
| 173 | + </dl> | ||
| 174 | + </section> | ||
| 175 | + | ||
| 176 | + <section class="try" id="try" aria-labelledby="try-title"> | ||
| 177 | + <h2 id="try-title">Try it</h2> | ||
| 178 | + <div class="code"> | ||
| 179 | + <pre><code id="install-cmd">git clone https://rickub.com/hlassiege/skilly.git | ||
| 180 | +cd skilly | ||
| 181 | +pnpm install && pnpm build && pnpm start</code></pre> | ||
| 182 | + <button class="btn btn-line btn-sm copy" type="button" data-copy="install-cmd">Copy</button> | ||
| 183 | + </div> | ||
| 184 | + <p class="note"><a href="https://rickub.com/hlassiege/skilly">Read the README</a> for everything else.</p> | ||
| 185 | + </section> | ||
| 186 | + </main> | ||
| 187 | + | ||
| 188 | + <footer class="foot"> | ||
| 189 | + <p>Open source, MIT. Made by <a href="https://eventuallycoding.com">Hugo Lassiège</a>.</p> | ||
| 190 | + <a href="https://rickub.com/hlassiege/skilly">Source code</a> | ||
| 191 | + </footer> | ||
| 192 | +</body> | ||
| 193 | +</html> | ||
added
site/public/main.js +59 -0 | new file mode 100644 | ||
| @@ -0,0 +1,59 @@ | ||
| 1 | +// Progressive enhancement only: without this file every screenshot is listed and the diagram shows its final state. | |
| 2 | + | |
| 3 | +// Tour: screenshots as tabs (WAI-ARIA tabs pattern; up/down on the vertical list, left/right on mobile) | |
| 4 | +const tabs = [...document.querySelectorAll('.tour-tabs [role="tab"]')] | |
| 5 | + | |
| 6 | +function select(tab, focus) { | |
| 7 | + for (const t of tabs) { | |
| 8 | + const on = t === tab | |
| 9 | + t.setAttribute('aria-selected', String(on)) | |
| 10 | + t.tabIndex = on ? 0 : -1 | |
| 11 | + document.getElementById(t.getAttribute('aria-controls')).hidden = !on | |
| 12 | + } | |
| 13 | + if (focus) { | |
| 14 | + tab.focus() | |
| 15 | + tab.scrollIntoView({ block: 'nearest', inline: 'nearest' }) | |
| 16 | + } | |
| 17 | +} | |
| 18 | + | |
| 19 | +for (const tab of tabs) { | |
| 20 | + tab.addEventListener('click', () => select(tab, false)) | |
| 21 | + tab.addEventListener('keydown', (e) => { | |
| 22 | + const i = tabs.indexOf(tab) | |
| 23 | + const next = { ArrowDown: i + 1, ArrowRight: i + 1, ArrowUp: i - 1, ArrowLeft: i - 1, Home: 0, End: tabs.length - 1 }[e.key] | |
| 24 | + if (next === undefined) return | |
| 25 | + e.preventDefault() | |
| 26 | + select(tabs[(next + tabs.length) % tabs.length], true) | |
| 27 | + }) | |
| 28 | +} | |
| 29 | +if (tabs.length) select(tabs[0], false) | |
| 30 | + | |
| 31 | +// Copy buttons | |
| 32 | +for (const button of document.querySelectorAll('[data-copy]')) { | |
| 33 | + button.addEventListener('click', async () => { | |
| 34 | + const text = document.getElementById(button.dataset.copy).textContent | |
| 35 | + try { | |
| 36 | + await navigator.clipboard.writeText(text) | |
| 37 | + button.textContent = 'Copied' | |
| 38 | + } catch { | |
| 39 | + button.textContent = 'Copy failed' | |
| 40 | + } | |
| 41 | + setTimeout(() => { button.textContent = 'Copy' }, 1600) | |
| 42 | + }) | |
| 43 | +} | |
| 44 | + | |
| 45 | +// Diagram: files get "written" once, the first time it comes into view | |
| 46 | +const flow = document.querySelector('[data-flow]') | |
| 47 | +if (flow) { | |
| 48 | + if (!('IntersectionObserver' in window)) { | |
| 49 | + flow.classList.add('done') | |
| 50 | + } else { | |
| 51 | + const observer = new IntersectionObserver((entries) => { | |
| 52 | + if (entries.some((e) => e.isIntersecting)) { | |
| 53 | + flow.classList.add('done') | |
| 54 | + observer.disconnect() | |
| 55 | + } | |
| 56 | + }, { threshold: 0.4 }) | |
| 57 | + observer.observe(flow) | |
| 58 | + } | |
| 59 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | +// Progressive enhancement only: without this file every screenshot is listed and the diagram shows its final state. | ||
| 2 | + | ||
| 3 | +// Tour: screenshots as tabs (WAI-ARIA tabs pattern; up/down on the vertical list, left/right on mobile) | ||
| 4 | +const tabs = [...document.querySelectorAll('.tour-tabs [role="tab"]')] | ||
| 5 | + | ||
| 6 | +function select(tab, focus) { | ||
| 7 | + for (const t of tabs) { | ||
| 8 | + const on = t === tab | ||
| 9 | + t.setAttribute('aria-selected', String(on)) | ||
| 10 | + t.tabIndex = on ? 0 : -1 | ||
| 11 | + document.getElementById(t.getAttribute('aria-controls')).hidden = !on | ||
| 12 | + } | ||
| 13 | + if (focus) { | ||
| 14 | + tab.focus() | ||
| 15 | + tab.scrollIntoView({ block: 'nearest', inline: 'nearest' }) | ||
| 16 | + } | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +for (const tab of tabs) { | ||
| 20 | + tab.addEventListener('click', () => select(tab, false)) | ||
| 21 | + tab.addEventListener('keydown', (e) => { | ||
| 22 | + const i = tabs.indexOf(tab) | ||
| 23 | + const next = { ArrowDown: i + 1, ArrowRight: i + 1, ArrowUp: i - 1, ArrowLeft: i - 1, Home: 0, End: tabs.length - 1 }[e.key] | ||
| 24 | + if (next === undefined) return | ||
| 25 | + e.preventDefault() | ||
| 26 | + select(tabs[(next + tabs.length) % tabs.length], true) | ||
| 27 | + }) | ||
| 28 | +} | ||
| 29 | +if (tabs.length) select(tabs[0], false) | ||
| 30 | + | ||
| 31 | +// Copy buttons | ||
| 32 | +for (const button of document.querySelectorAll('[data-copy]')) { | ||
| 33 | + button.addEventListener('click', async () => { | ||
| 34 | + const text = document.getElementById(button.dataset.copy).textContent | ||
| 35 | + try { | ||
| 36 | + await navigator.clipboard.writeText(text) | ||
| 37 | + button.textContent = 'Copied' | ||
| 38 | + } catch { | ||
| 39 | + button.textContent = 'Copy failed' | ||
| 40 | + } | ||
| 41 | + setTimeout(() => { button.textContent = 'Copy' }, 1600) | ||
| 42 | + }) | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +// Diagram: files get "written" once, the first time it comes into view | ||
| 46 | +const flow = document.querySelector('[data-flow]') | ||
| 47 | +if (flow) { | ||
| 48 | + if (!('IntersectionObserver' in window)) { | ||
| 49 | + flow.classList.add('done') | ||
| 50 | + } else { | ||
| 51 | + const observer = new IntersectionObserver((entries) => { | ||
| 52 | + if (entries.some((e) => e.isIntersecting)) { | ||
| 53 | + flow.classList.add('done') | ||
| 54 | + observer.disconnect() | ||
| 55 | + } | ||
| 56 | + }, { threshold: 0.4 }) | ||
| 57 | + observer.observe(flow) | ||
| 58 | + } | ||
| 59 | +} | ||
added
site/public/styles.css +382 -0 | new file mode 100644 | ||
| @@ -0,0 +1,382 @@ | ||
| 1 | +/* skilly site. Tokens mirror the app (packages/ui/app/assets/css/main.css) so screenshots sit at home. */ | |
| 2 | + | |
| 3 | +@font-face { font-family: 'Instrument Sans'; font-weight: 400; font-display: swap; src: url(fonts/instrument-sans-latin-400-normal.woff2) format('woff2'); } | |
| 4 | +@font-face { font-family: 'Instrument Sans'; font-weight: 500; font-display: swap; src: url(fonts/instrument-sans-latin-500-normal.woff2) format('woff2'); } | |
| 5 | +@font-face { font-family: 'Instrument Sans'; font-weight: 600; font-display: swap; src: url(fonts/instrument-sans-latin-600-normal.woff2) format('woff2'); } | |
| 6 | +@font-face { font-family: 'IBM Plex Mono'; font-weight: 400; font-display: swap; src: url(fonts/ibm-plex-mono-latin-400-normal.woff2) format('woff2'); } | |
| 7 | +@font-face { font-family: 'IBM Plex Mono'; font-weight: 500; font-display: swap; src: url(fonts/ibm-plex-mono-latin-500-normal.woff2) format('woff2'); } | |
| 8 | + | |
| 9 | +:root { | |
| 10 | + --bg: #f5f4f1; | |
| 11 | + --dot: #dcd8d0; | |
| 12 | + --panel: #ffffff; | |
| 13 | + --line: #e3e0da; | |
| 14 | + --line2: #eeebe6; | |
| 15 | + --hover: #f6f5f2; | |
| 16 | + --text: #1c1b19; | |
| 17 | + --muted: #6b6862; | |
| 18 | + --faint: #8a867e; | |
| 19 | + --ink: #1c1b19; | |
| 20 | + --onink: #ffffff; | |
| 21 | + --accent: #e2774c; | |
| 22 | + --accent-text: #b4471d; | |
| 23 | + --ok: oklch(0.6 0.13 155); | |
| 24 | + --info: oklch(0.58 0.13 250); | |
| 25 | + --ring: #ebe8e2; | |
| 26 | + | |
| 27 | + --sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif; | |
| 28 | + --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; | |
| 29 | + --page: 1120px; | |
| 30 | + --gutter: clamp(20px, 4vw, 32px); | |
| 31 | +} | |
| 32 | + | |
| 33 | +@media (prefers-color-scheme: dark) { | |
| 34 | + :root { | |
| 35 | + --bg: #121110; | |
| 36 | + --dot: #272521; | |
| 37 | + --panel: #1a1917; | |
| 38 | + --line: #2e2c28; | |
| 39 | + --line2: #24221f; | |
| 40 | + --hover: #22201d; | |
| 41 | + --text: #eeece8; | |
| 42 | + --muted: #a7a39b; | |
| 43 | + --faint: #7d7970; | |
| 44 | + --ink: #eeece8; | |
| 45 | + --onink: #121110; | |
| 46 | + --accent: #ec8a5f; | |
| 47 | + --accent-text: #f1a07b; | |
| 48 | + --ok: oklch(0.72 0.13 155); | |
| 49 | + --info: oklch(0.72 0.12 250); | |
| 50 | + --ring: #2e2c28; | |
| 51 | + } | |
| 52 | +} | |
| 53 | + | |
| 54 | +/* ------------------------------------------------------------------ base */ | |
| 55 | +*, *::before, *::after { box-sizing: border-box; } | |
| 56 | + | |
| 57 | +html { scroll-behavior: smooth; scroll-padding-top: 24px; } | |
| 58 | + | |
| 59 | +body { | |
| 60 | + margin: 0; | |
| 61 | + font-family: var(--sans); | |
| 62 | + font-size: 1rem; | |
| 63 | + line-height: 1.55; | |
| 64 | + color: var(--text); | |
| 65 | + background-color: var(--bg); | |
| 66 | + background-image: radial-gradient(var(--dot) 1px, transparent 1.2px); | |
| 67 | + background-size: 12px 12px; | |
| 68 | + -webkit-font-smoothing: antialiased; | |
| 69 | + text-rendering: optimizeLegibility; | |
| 70 | +} | |
| 71 | + | |
| 72 | +::selection { background: color-mix(in oklch, var(--accent) 28%, transparent); } | |
| 73 | + | |
| 74 | +h1, h2, h3, p, dl, dd, ul, figure { margin: 0; } | |
| 75 | +ul { padding: 0; list-style: none; } | |
| 76 | + | |
| 77 | +a { color: inherit; text-underline-offset: 3px; text-decoration-thickness: 1px; } | |
| 78 | +a:hover { color: var(--accent-text); } | |
| 79 | + | |
| 80 | +code { | |
| 81 | + font-family: var(--mono); | |
| 82 | + font-size: 0.875em; | |
| 83 | + padding: 0.05em 0.35em; | |
| 84 | + border-radius: 5px; | |
| 85 | + background: var(--line2); | |
| 86 | +} | |
| 87 | + | |
| 88 | +:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; } | |
| 89 | + | |
| 90 | +.sr-only { | |
| 91 | + position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; | |
| 92 | + overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; | |
| 93 | +} | |
| 94 | + | |
| 95 | +.skip { | |
| 96 | + position: absolute; left: 12px; top: -48px; z-index: 10; | |
| 97 | + padding: 8px 14px; border-radius: 7px; background: var(--ink); color: var(--onink); | |
| 98 | +} | |
| 99 | +.skip:focus { top: 12px; } | |
| 100 | + | |
| 101 | +main > section, | |
| 102 | +.top, | |
| 103 | +.foot { | |
| 104 | + width: min(100% - 2 * var(--gutter), var(--page)); | |
| 105 | + margin-inline: auto; | |
| 106 | +} | |
| 107 | + | |
| 108 | +main > section + section { margin-top: clamp(88px, 12vw, 144px); } | |
| 109 | + | |
| 110 | +h2 { | |
| 111 | + font-family: var(--mono); | |
| 112 | + font-weight: 400; | |
| 113 | + font-size: clamp(1.5rem, 3vw, 2.125rem); | |
| 114 | + line-height: 1.2; | |
| 115 | + letter-spacing: -0.02em; | |
| 116 | + max-width: 28ch; | |
| 117 | + text-wrap: balance; | |
| 118 | +} | |
| 119 | + | |
| 120 | +h3 { font-size: 1.0625rem; font-weight: 600; line-height: 1.35; } | |
| 121 | + | |
| 122 | +.section-lead { margin-top: 16px; max-width: 62ch; color: var(--muted); font-size: 1.0625rem; } | |
| 123 | + | |
| 124 | +.note { margin-top: 16px; color: var(--muted); font-size: 0.9375rem; max-width: 70ch; } | |
| 125 | + | |
| 126 | +/* ------------------------------------------------------------------ buttons */ | |
| 127 | +.btn { | |
| 128 | + display: inline-flex; align-items: center; justify-content: center; | |
| 129 | + height: 44px; padding: 0 20px; | |
| 130 | + border-radius: 999px; | |
| 131 | + font: 500 0.9375rem/1 var(--sans); | |
| 132 | + text-decoration: none; | |
| 133 | + white-space: nowrap; | |
| 134 | + cursor: pointer; | |
| 135 | + border: 0; | |
| 136 | +} | |
| 137 | +.btn-sm { height: 34px; padding: 0 14px; font-size: 0.875rem; } | |
| 138 | +.btn-ink { background: var(--ink); color: var(--onink); } | |
| 139 | +.btn-ink:hover { color: var(--onink); background: color-mix(in oklch, var(--ink) 86%, var(--bg)); } | |
| 140 | +.btn-line { background: var(--panel); color: var(--text); box-shadow: 0 0 0 1px var(--line); } | |
| 141 | +.btn-line:hover { color: var(--text); box-shadow: 0 0 0 1px var(--faint); } | |
| 142 | + | |
| 143 | +/* ------------------------------------------------------------------ header */ | |
| 144 | +.top { | |
| 145 | + display: flex; align-items: center; gap: 24px; | |
| 146 | + padding-block: 20px; | |
| 147 | +} | |
| 148 | + | |
| 149 | +.brand { | |
| 150 | + display: inline-flex; align-items: center; gap: 10px; | |
| 151 | + font-weight: 600; font-size: 1.125rem; text-decoration: none; | |
| 152 | +} | |
| 153 | +.brand:hover { color: inherit; } | |
| 154 | + | |
| 155 | +/* The in-app logo mark: a 2x2 grid, one clay square */ | |
| 156 | +.mark { | |
| 157 | + display: grid; grid-template-columns: 1fr 1fr; gap: 3px; | |
| 158 | + width: 28px; height: 28px; padding: 6px; | |
| 159 | + border-radius: 8px; background: var(--ink); | |
| 160 | +} | |
| 161 | +.mark i { border-radius: 1px; } | |
| 162 | +.mark i:nth-child(1), .mark i:nth-child(4) { background: var(--onink); } | |
| 163 | +.mark i:nth-child(3) { background: var(--accent); } | |
| 164 | + | |
| 165 | +.top-nav { display: flex; gap: 4px; margin-left: auto; } | |
| 166 | +.top-nav a { | |
| 167 | + padding: 6px 12px; border-radius: 999px; | |
| 168 | + color: var(--muted); text-decoration: none; font-size: 0.9375rem; | |
| 169 | +} | |
| 170 | +.top-nav a:hover { color: var(--text); background: var(--line2); } | |
| 171 | + | |
| 172 | +@media (max-width: 720px) { | |
| 173 | + .top-nav { display: none; } | |
| 174 | + .top .btn { margin-left: auto; } | |
| 175 | +} | |
| 176 | + | |
| 177 | +/* ------------------------------------------------------------------ hero */ | |
| 178 | +.hero { padding-top: clamp(56px, 10vw, 120px); } | |
| 179 | + | |
| 180 | +.hero h1 { | |
| 181 | + font-family: var(--mono); | |
| 182 | + font-weight: 400; | |
| 183 | + font-size: clamp(2.125rem, 5.6vw, 4rem); | |
| 184 | + line-height: 1.06; | |
| 185 | + letter-spacing: -0.035em; | |
| 186 | + max-width: 19ch; | |
| 187 | + text-wrap: balance; | |
| 188 | +} | |
| 189 | + | |
| 190 | +.lead { | |
| 191 | + margin-top: 28px; | |
| 192 | + max-width: 58ch; | |
| 193 | + font-size: clamp(1.0625rem, 1.6vw, 1.1875rem); | |
| 194 | + color: var(--muted); | |
| 195 | +} | |
| 196 | +.lead strong { color: var(--text); font-weight: 500; } | |
| 197 | + | |
| 198 | +.actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 36px; } | |
| 199 | + | |
| 200 | +/* ------------------------------------------------------------------ tour: tabs on the left, screenshot on the right */ | |
| 201 | +.hero + .tour { margin-top: clamp(56px, 8vw, 88px); } | |
| 202 | + | |
| 203 | +[hidden] { display: none !important; } | |
| 204 | + | |
| 205 | +.tour { | |
| 206 | + display: grid; | |
| 207 | + grid-template-columns: minmax(0, 300px) minmax(0, 1fr); | |
| 208 | + gap: 40px; | |
| 209 | + align-items: start; | |
| 210 | +} | |
| 211 | + | |
| 212 | +.tour-tabs { display: flex; flex-direction: column; gap: 4px; } | |
| 213 | + | |
| 214 | +.tour-tabs button { | |
| 215 | + display: block; | |
| 216 | + width: 100%; | |
| 217 | + padding: 14px 16px; | |
| 218 | + border: 0; border-radius: 14px; | |
| 219 | + background: transparent; | |
| 220 | + color: var(--text); | |
| 221 | + font: inherit; | |
| 222 | + text-align: left; | |
| 223 | + cursor: pointer; | |
| 224 | +} | |
| 225 | +.tab-title { display: block; font-weight: 600; line-height: 1.35; color: var(--muted); } | |
| 226 | +.tab-desc { display: block; margin-top: 4px; font-size: 0.9375rem; line-height: 1.45; color: var(--faint); } | |
| 227 | + | |
| 228 | +.tour-tabs button:hover .tab-title { color: var(--text); } | |
| 229 | +.tour-tabs button[aria-selected="true"] { background: var(--panel); box-shadow: 0 0 0 1px var(--ring); } | |
| 230 | +.tour-tabs button[aria-selected="true"] .tab-title { color: var(--text); } | |
| 231 | +.tour-tabs button[aria-selected="true"] .tab-desc { color: var(--muted); } | |
| 232 | + | |
| 233 | +.tour-shots { display: grid; gap: 24px; } | |
| 234 | + | |
| 235 | +.frame { | |
| 236 | + aspect-ratio: 2880 / 1800; | |
| 237 | + border-radius: 20px; | |
| 238 | + overflow: hidden; | |
| 239 | + background: #f5f4f1; /* screenshots are light: keep the frame light in dark mode too */ | |
| 240 | + box-shadow: 0 0 0 1px var(--ring), 0 24px 60px -24px rgba(28, 27, 25, 0.25); | |
| 241 | +} | |
| 242 | +.frame img { display: block; width: 100%; height: 100%; object-fit: contain; object-position: top; } | |
| 243 | + | |
| 244 | +/* Small screens: titles become a scrolling row above the screenshot */ | |
| 245 | +@media (max-width: 900px) { | |
| 246 | + .tour { grid-template-columns: 1fr; gap: 16px; } | |
| 247 | + .tour-tabs { flex-direction: row; overflow-x: auto; scrollbar-width: none; margin-inline: calc(-1 * var(--gutter)); padding-inline: var(--gutter); } | |
| 248 | + .tour-tabs::-webkit-scrollbar { display: none; } | |
| 249 | + .tour-tabs button { flex: none; width: auto; padding: 9px 14px; border-radius: 999px; } | |
| 250 | + .tab-desc { display: none; } | |
| 251 | + .frame { border-radius: 12px; } | |
| 252 | +} | |
| 253 | + | |
| 254 | +/* ------------------------------------------------------------------ why */ | |
| 255 | +.why-list { | |
| 256 | + display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 32px; | |
| 257 | + margin-top: 40px; | |
| 258 | +} | |
| 259 | +.why-list > div { padding-top: 20px; border-top: 1px solid var(--line); } | |
| 260 | +.why-list h3 { display: flex; align-items: center; gap: 10px; } | |
| 261 | +.why-list h3::before { | |
| 262 | + content: ""; flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); | |
| 263 | +} | |
| 264 | +.why-list p { margin-top: 8px; color: var(--muted); } | |
| 265 | + | |
| 266 | +@media (max-width: 820px) { .why-list { grid-template-columns: 1fr; gap: 24px; } } | |
| 267 | + | |
| 268 | +/* ------------------------------------------------------------------ how: the flow diagram */ | |
| 269 | +.flow { | |
| 270 | + display: grid; | |
| 271 | + grid-template-columns: minmax(0, 0.9fr) 72px minmax(0, 1.4fr); | |
| 272 | + align-items: center; | |
| 273 | + margin-top: 48px; | |
| 274 | +} | |
| 275 | + | |
| 276 | +.panel { | |
| 277 | + background: var(--panel); | |
| 278 | + border-radius: 20px; | |
| 279 | + box-shadow: 0 0 0 1px var(--ring); | |
| 280 | + padding: 20px 22px; | |
| 281 | +} | |
| 282 | + | |
| 283 | +.panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; } | |
| 284 | +.panel-head .name { font-family: var(--mono); font-size: 1.125rem; letter-spacing: -0.01em; } | |
| 285 | +.panel-head .path, | |
| 286 | +.panel-head .agents { font-family: var(--mono); font-size: 0.8125rem; color: var(--faint); } | |
| 287 | + | |
| 288 | +.items { margin-top: 16px; display: grid; gap: 2px; } | |
| 289 | +.items li { | |
| 290 | + display: flex; align-items: baseline; gap: 10px; | |
| 291 | + padding: 7px 10px; border-radius: 9px; | |
| 292 | + font-size: 0.9375rem; | |
| 293 | +} | |
| 294 | +.items li:nth-child(odd) { background: var(--hover); } | |
| 295 | + | |
| 296 | +.tag { | |
| 297 | + flex: none; min-width: 52px; | |
| 298 | + font-family: var(--mono); font-size: 0.75rem; color: var(--faint); | |
| 299 | +} | |
| 300 | + | |
| 301 | +.apply { position: relative; align-self: stretch; display: grid; place-items: center; } | |
| 302 | +.apply::before { | |
| 303 | + content: ""; position: absolute; top: 12%; bottom: 12%; left: 50%; | |
| 304 | + border-left: 1px dashed var(--line); | |
| 305 | +} | |
| 306 | +.apply span { | |
| 307 | + position: relative; | |
| 308 | + padding: 4px 10px; border-radius: 999px; | |
| 309 | + font-family: var(--mono); font-size: 0.75rem; | |
| 310 | + background: var(--ink); color: var(--onink); | |
| 311 | +} | |
| 312 | + | |
| 313 | +.projects { display: grid; gap: 14px; } | |
| 314 | +.proj { padding-block: 16px; } | |
| 315 | + | |
| 316 | +.files { margin-top: 10px; display: grid; gap: 4px; } | |
| 317 | +.files li { | |
| 318 | + display: flex; align-items: center; gap: 10px; | |
| 319 | + font-family: var(--mono); font-size: 0.8125rem; | |
| 320 | + overflow-wrap: anywhere; | |
| 321 | +} | |
| 322 | +.files li::before { | |
| 323 | + content: ""; flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--ok); | |
| 324 | +} | |
| 325 | + | |
| 326 | +/* The one orchestrated moment: files go from "will add" (blue, faded) to written (green), in order */ | |
| 327 | +.js .flow .files li, | |
| 328 | +.js .flow .files li::before { | |
| 329 | + transition-property: opacity, background-color; | |
| 330 | + transition-duration: 320ms; | |
| 331 | + transition-timing-function: ease-out; | |
| 332 | + transition-delay: calc(500ms + var(--i) * 140ms); | |
| 333 | +} | |
| 334 | +.js .flow:not(.done) .files li { opacity: 0.4; } | |
| 335 | +.js .flow:not(.done) .files li::before { background: var(--info); } | |
| 336 | + | |
| 337 | +@media (max-width: 820px) { | |
| 338 | + .flow { grid-template-columns: 1fr; } | |
| 339 | + .apply { height: 64px; } | |
| 340 | + .apply::before { top: 0; bottom: 0; } | |
| 341 | +} | |
| 342 | + | |
| 343 | +.facts { | |
| 344 | + display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 32px; | |
| 345 | + margin-top: 64px; | |
| 346 | +} | |
| 347 | +.facts > div { padding-top: 20px; border-top: 1px solid var(--line); } | |
| 348 | +.facts dt { font-weight: 600; font-size: 1.0625rem; } | |
| 349 | +.facts dd { margin-top: 6px; color: var(--muted); } | |
| 350 | + | |
| 351 | +@media (max-width: 820px) { .facts { grid-template-columns: 1fr; gap: 24px; } } | |
| 352 | + | |
| 353 | +/* ------------------------------------------------------------------ try it */ | |
| 354 | +.code { | |
| 355 | + position: relative; | |
| 356 | + margin-top: 28px; | |
| 357 | + max-width: 720px; | |
| 358 | + border-radius: 16px; | |
| 359 | + background: var(--ink); | |
| 360 | + color: var(--onink); | |
| 361 | +} | |
| 362 | +.code pre { margin: 0; padding: 22px 24px; overflow-x: auto; } | |
| 363 | +.code code { | |
| 364 | + background: none; padding: 0; | |
| 365 | + font-size: 0.9375rem; line-height: 1.7; | |
| 366 | +} | |
| 367 | +.code .copy { position: absolute; top: 14px; right: 14px; } | |
| 368 | + | |
| 369 | +/* ------------------------------------------------------------------ footer */ | |
| 370 | +.foot { | |
| 371 | + display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px 32px; | |
| 372 | + margin-top: clamp(88px, 12vw, 144px); | |
| 373 | + padding-block: 28px 40px; | |
| 374 | + border-top: 1px solid var(--line); | |
| 375 | + color: var(--muted); font-size: 0.9375rem; | |
| 376 | +} | |
| 377 | + | |
| 378 | +/* ------------------------------------------------------------------ motion */ | |
| 379 | +@media (prefers-reduced-motion: reduce) { | |
| 380 | + html { scroll-behavior: auto; } | |
| 381 | + *, *::before, *::after { transition: none !important; } | |
| 382 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,382 @@ | |||
| 1 | +/* skilly site. Tokens mirror the app (packages/ui/app/assets/css/main.css) so screenshots sit at home. */ | ||
| 2 | + | ||
| 3 | +@font-face { font-family: 'Instrument Sans'; font-weight: 400; font-display: swap; src: url(fonts/instrument-sans-latin-400-normal.woff2) format('woff2'); } | ||
| 4 | +@font-face { font-family: 'Instrument Sans'; font-weight: 500; font-display: swap; src: url(fonts/instrument-sans-latin-500-normal.woff2) format('woff2'); } | ||
| 5 | +@font-face { font-family: 'Instrument Sans'; font-weight: 600; font-display: swap; src: url(fonts/instrument-sans-latin-600-normal.woff2) format('woff2'); } | ||
| 6 | +@font-face { font-family: 'IBM Plex Mono'; font-weight: 400; font-display: swap; src: url(fonts/ibm-plex-mono-latin-400-normal.woff2) format('woff2'); } | ||
| 7 | +@font-face { font-family: 'IBM Plex Mono'; font-weight: 500; font-display: swap; src: url(fonts/ibm-plex-mono-latin-500-normal.woff2) format('woff2'); } | ||
| 8 | + | ||
| 9 | +:root { | ||
| 10 | + --bg: #f5f4f1; | ||
| 11 | + --dot: #dcd8d0; | ||
| 12 | + --panel: #ffffff; | ||
| 13 | + --line: #e3e0da; | ||
| 14 | + --line2: #eeebe6; | ||
| 15 | + --hover: #f6f5f2; | ||
| 16 | + --text: #1c1b19; | ||
| 17 | + --muted: #6b6862; | ||
| 18 | + --faint: #8a867e; | ||
| 19 | + --ink: #1c1b19; | ||
| 20 | + --onink: #ffffff; | ||
| 21 | + --accent: #e2774c; | ||
| 22 | + --accent-text: #b4471d; | ||
| 23 | + --ok: oklch(0.6 0.13 155); | ||
| 24 | + --info: oklch(0.58 0.13 250); | ||
| 25 | + --ring: #ebe8e2; | ||
| 26 | + | ||
| 27 | + --sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif; | ||
| 28 | + --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; | ||
| 29 | + --page: 1120px; | ||
| 30 | + --gutter: clamp(20px, 4vw, 32px); | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +@media (prefers-color-scheme: dark) { | ||
| 34 | + :root { | ||
| 35 | + --bg: #121110; | ||
| 36 | + --dot: #272521; | ||
| 37 | + --panel: #1a1917; | ||
| 38 | + --line: #2e2c28; | ||
| 39 | + --line2: #24221f; | ||
| 40 | + --hover: #22201d; | ||
| 41 | + --text: #eeece8; | ||
| 42 | + --muted: #a7a39b; | ||
| 43 | + --faint: #7d7970; | ||
| 44 | + --ink: #eeece8; | ||
| 45 | + --onink: #121110; | ||
| 46 | + --accent: #ec8a5f; | ||
| 47 | + --accent-text: #f1a07b; | ||
| 48 | + --ok: oklch(0.72 0.13 155); | ||
| 49 | + --info: oklch(0.72 0.12 250); | ||
| 50 | + --ring: #2e2c28; | ||
| 51 | + } | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +/* ------------------------------------------------------------------ base */ | ||
| 55 | +*, *::before, *::after { box-sizing: border-box; } | ||
| 56 | + | ||
| 57 | +html { scroll-behavior: smooth; scroll-padding-top: 24px; } | ||
| 58 | + | ||
| 59 | +body { | ||
| 60 | + margin: 0; | ||
| 61 | + font-family: var(--sans); | ||
| 62 | + font-size: 1rem; | ||
| 63 | + line-height: 1.55; | ||
| 64 | + color: var(--text); | ||
| 65 | + background-color: var(--bg); | ||
| 66 | + background-image: radial-gradient(var(--dot) 1px, transparent 1.2px); | ||
| 67 | + background-size: 12px 12px; | ||
| 68 | + -webkit-font-smoothing: antialiased; | ||
| 69 | + text-rendering: optimizeLegibility; | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +::selection { background: color-mix(in oklch, var(--accent) 28%, transparent); } | ||
| 73 | + | ||
| 74 | +h1, h2, h3, p, dl, dd, ul, figure { margin: 0; } | ||
| 75 | +ul { padding: 0; list-style: none; } | ||
| 76 | + | ||
| 77 | +a { color: inherit; text-underline-offset: 3px; text-decoration-thickness: 1px; } | ||
| 78 | +a:hover { color: var(--accent-text); } | ||
| 79 | + | ||
| 80 | +code { | ||
| 81 | + font-family: var(--mono); | ||
| 82 | + font-size: 0.875em; | ||
| 83 | + padding: 0.05em 0.35em; | ||
| 84 | + border-radius: 5px; | ||
| 85 | + background: var(--line2); | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; } | ||
| 89 | + | ||
| 90 | +.sr-only { | ||
| 91 | + position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; | ||
| 92 | + overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +.skip { | ||
| 96 | + position: absolute; left: 12px; top: -48px; z-index: 10; | ||
| 97 | + padding: 8px 14px; border-radius: 7px; background: var(--ink); color: var(--onink); | ||
| 98 | +} | ||
| 99 | +.skip:focus { top: 12px; } | ||
| 100 | + | ||
| 101 | +main > section, | ||
| 102 | +.top, | ||
| 103 | +.foot { | ||
| 104 | + width: min(100% - 2 * var(--gutter), var(--page)); | ||
| 105 | + margin-inline: auto; | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +main > section + section { margin-top: clamp(88px, 12vw, 144px); } | ||
| 109 | + | ||
| 110 | +h2 { | ||
| 111 | + font-family: var(--mono); | ||
| 112 | + font-weight: 400; | ||
| 113 | + font-size: clamp(1.5rem, 3vw, 2.125rem); | ||
| 114 | + line-height: 1.2; | ||
| 115 | + letter-spacing: -0.02em; | ||
| 116 | + max-width: 28ch; | ||
| 117 | + text-wrap: balance; | ||
| 118 | +} | ||
| 119 | + | ||
| 120 | +h3 { font-size: 1.0625rem; font-weight: 600; line-height: 1.35; } | ||
| 121 | + | ||
| 122 | +.section-lead { margin-top: 16px; max-width: 62ch; color: var(--muted); font-size: 1.0625rem; } | ||
| 123 | + | ||
| 124 | +.note { margin-top: 16px; color: var(--muted); font-size: 0.9375rem; max-width: 70ch; } | ||
| 125 | + | ||
| 126 | +/* ------------------------------------------------------------------ buttons */ | ||
| 127 | +.btn { | ||
| 128 | + display: inline-flex; align-items: center; justify-content: center; | ||
| 129 | + height: 44px; padding: 0 20px; | ||
| 130 | + border-radius: 999px; | ||
| 131 | + font: 500 0.9375rem/1 var(--sans); | ||
| 132 | + text-decoration: none; | ||
| 133 | + white-space: nowrap; | ||
| 134 | + cursor: pointer; | ||
| 135 | + border: 0; | ||
| 136 | +} | ||
| 137 | +.btn-sm { height: 34px; padding: 0 14px; font-size: 0.875rem; } | ||
| 138 | +.btn-ink { background: var(--ink); color: var(--onink); } | ||
| 139 | +.btn-ink:hover { color: var(--onink); background: color-mix(in oklch, var(--ink) 86%, var(--bg)); } | ||
| 140 | +.btn-line { background: var(--panel); color: var(--text); box-shadow: 0 0 0 1px var(--line); } | ||
| 141 | +.btn-line:hover { color: var(--text); box-shadow: 0 0 0 1px var(--faint); } | ||
| 142 | + | ||
| 143 | +/* ------------------------------------------------------------------ header */ | ||
| 144 | +.top { | ||
| 145 | + display: flex; align-items: center; gap: 24px; | ||
| 146 | + padding-block: 20px; | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +.brand { | ||
| 150 | + display: inline-flex; align-items: center; gap: 10px; | ||
| 151 | + font-weight: 600; font-size: 1.125rem; text-decoration: none; | ||
| 152 | +} | ||
| 153 | +.brand:hover { color: inherit; } | ||
| 154 | + | ||
| 155 | +/* The in-app logo mark: a 2x2 grid, one clay square */ | ||
| 156 | +.mark { | ||
| 157 | + display: grid; grid-template-columns: 1fr 1fr; gap: 3px; | ||
| 158 | + width: 28px; height: 28px; padding: 6px; | ||
| 159 | + border-radius: 8px; background: var(--ink); | ||
| 160 | +} | ||
| 161 | +.mark i { border-radius: 1px; } | ||
| 162 | +.mark i:nth-child(1), .mark i:nth-child(4) { background: var(--onink); } | ||
| 163 | +.mark i:nth-child(3) { background: var(--accent); } | ||
| 164 | + | ||
| 165 | +.top-nav { display: flex; gap: 4px; margin-left: auto; } | ||
| 166 | +.top-nav a { | ||
| 167 | + padding: 6px 12px; border-radius: 999px; | ||
| 168 | + color: var(--muted); text-decoration: none; font-size: 0.9375rem; | ||
| 169 | +} | ||
| 170 | +.top-nav a:hover { color: var(--text); background: var(--line2); } | ||
| 171 | + | ||
| 172 | +@media (max-width: 720px) { | ||
| 173 | + .top-nav { display: none; } | ||
| 174 | + .top .btn { margin-left: auto; } | ||
| 175 | +} | ||
| 176 | + | ||
| 177 | +/* ------------------------------------------------------------------ hero */ | ||
| 178 | +.hero { padding-top: clamp(56px, 10vw, 120px); } | ||
| 179 | + | ||
| 180 | +.hero h1 { | ||
| 181 | + font-family: var(--mono); | ||
| 182 | + font-weight: 400; | ||
| 183 | + font-size: clamp(2.125rem, 5.6vw, 4rem); | ||
| 184 | + line-height: 1.06; | ||
| 185 | + letter-spacing: -0.035em; | ||
| 186 | + max-width: 19ch; | ||
| 187 | + text-wrap: balance; | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +.lead { | ||
| 191 | + margin-top: 28px; | ||
| 192 | + max-width: 58ch; | ||
| 193 | + font-size: clamp(1.0625rem, 1.6vw, 1.1875rem); | ||
| 194 | + color: var(--muted); | ||
| 195 | +} | ||
| 196 | +.lead strong { color: var(--text); font-weight: 500; } | ||
| 197 | + | ||
| 198 | +.actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 36px; } | ||
| 199 | + | ||
| 200 | +/* ------------------------------------------------------------------ tour: tabs on the left, screenshot on the right */ | ||
| 201 | +.hero + .tour { margin-top: clamp(56px, 8vw, 88px); } | ||
| 202 | + | ||
| 203 | +[hidden] { display: none !important; } | ||
| 204 | + | ||
| 205 | +.tour { | ||
| 206 | + display: grid; | ||
| 207 | + grid-template-columns: minmax(0, 300px) minmax(0, 1fr); | ||
| 208 | + gap: 40px; | ||
| 209 | + align-items: start; | ||
| 210 | +} | ||
| 211 | + | ||
| 212 | +.tour-tabs { display: flex; flex-direction: column; gap: 4px; } | ||
| 213 | + | ||
| 214 | +.tour-tabs button { | ||
| 215 | + display: block; | ||
| 216 | + width: 100%; | ||
| 217 | + padding: 14px 16px; | ||
| 218 | + border: 0; border-radius: 14px; | ||
| 219 | + background: transparent; | ||
| 220 | + color: var(--text); | ||
| 221 | + font: inherit; | ||
| 222 | + text-align: left; | ||
| 223 | + cursor: pointer; | ||
| 224 | +} | ||
| 225 | +.tab-title { display: block; font-weight: 600; line-height: 1.35; color: var(--muted); } | ||
| 226 | +.tab-desc { display: block; margin-top: 4px; font-size: 0.9375rem; line-height: 1.45; color: var(--faint); } | ||
| 227 | + | ||
| 228 | +.tour-tabs button:hover .tab-title { color: var(--text); } | ||
| 229 | +.tour-tabs button[aria-selected="true"] { background: var(--panel); box-shadow: 0 0 0 1px var(--ring); } | ||
| 230 | +.tour-tabs button[aria-selected="true"] .tab-title { color: var(--text); } | ||
| 231 | +.tour-tabs button[aria-selected="true"] .tab-desc { color: var(--muted); } | ||
| 232 | + | ||
| 233 | +.tour-shots { display: grid; gap: 24px; } | ||
| 234 | + | ||
| 235 | +.frame { | ||
| 236 | + aspect-ratio: 2880 / 1800; | ||
| 237 | + border-radius: 20px; | ||
| 238 | + overflow: hidden; | ||
| 239 | + background: #f5f4f1; /* screenshots are light: keep the frame light in dark mode too */ | ||
| 240 | + box-shadow: 0 0 0 1px var(--ring), 0 24px 60px -24px rgba(28, 27, 25, 0.25); | ||
| 241 | +} | ||
| 242 | +.frame img { display: block; width: 100%; height: 100%; object-fit: contain; object-position: top; } | ||
| 243 | + | ||
| 244 | +/* Small screens: titles become a scrolling row above the screenshot */ | ||
| 245 | +@media (max-width: 900px) { | ||
| 246 | + .tour { grid-template-columns: 1fr; gap: 16px; } | ||
| 247 | + .tour-tabs { flex-direction: row; overflow-x: auto; scrollbar-width: none; margin-inline: calc(-1 * var(--gutter)); padding-inline: var(--gutter); } | ||
| 248 | + .tour-tabs::-webkit-scrollbar { display: none; } | ||
| 249 | + .tour-tabs button { flex: none; width: auto; padding: 9px 14px; border-radius: 999px; } | ||
| 250 | + .tab-desc { display: none; } | ||
| 251 | + .frame { border-radius: 12px; } | ||
| 252 | +} | ||
| 253 | + | ||
| 254 | +/* ------------------------------------------------------------------ why */ | ||
| 255 | +.why-list { | ||
| 256 | + display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 32px; | ||
| 257 | + margin-top: 40px; | ||
| 258 | +} | ||
| 259 | +.why-list > div { padding-top: 20px; border-top: 1px solid var(--line); } | ||
| 260 | +.why-list h3 { display: flex; align-items: center; gap: 10px; } | ||
| 261 | +.why-list h3::before { | ||
| 262 | + content: ""; flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); | ||
| 263 | +} | ||
| 264 | +.why-list p { margin-top: 8px; color: var(--muted); } | ||
| 265 | + | ||
| 266 | +@media (max-width: 820px) { .why-list { grid-template-columns: 1fr; gap: 24px; } } | ||
| 267 | + | ||
| 268 | +/* ------------------------------------------------------------------ how: the flow diagram */ | ||
| 269 | +.flow { | ||
| 270 | + display: grid; | ||
| 271 | + grid-template-columns: minmax(0, 0.9fr) 72px minmax(0, 1.4fr); | ||
| 272 | + align-items: center; | ||
| 273 | + margin-top: 48px; | ||
| 274 | +} | ||
| 275 | + | ||
| 276 | +.panel { | ||
| 277 | + background: var(--panel); | ||
| 278 | + border-radius: 20px; | ||
| 279 | + box-shadow: 0 0 0 1px var(--ring); | ||
| 280 | + padding: 20px 22px; | ||
| 281 | +} | ||
| 282 | + | ||
| 283 | +.panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; } | ||
| 284 | +.panel-head .name { font-family: var(--mono); font-size: 1.125rem; letter-spacing: -0.01em; } | ||
| 285 | +.panel-head .path, | ||
| 286 | +.panel-head .agents { font-family: var(--mono); font-size: 0.8125rem; color: var(--faint); } | ||
| 287 | + | ||
| 288 | +.items { margin-top: 16px; display: grid; gap: 2px; } | ||
| 289 | +.items li { | ||
| 290 | + display: flex; align-items: baseline; gap: 10px; | ||
| 291 | + padding: 7px 10px; border-radius: 9px; | ||
| 292 | + font-size: 0.9375rem; | ||
| 293 | +} | ||
| 294 | +.items li:nth-child(odd) { background: var(--hover); } | ||
| 295 | + | ||
| 296 | +.tag { | ||
| 297 | + flex: none; min-width: 52px; | ||
| 298 | + font-family: var(--mono); font-size: 0.75rem; color: var(--faint); | ||
| 299 | +} | ||
| 300 | + | ||
| 301 | +.apply { position: relative; align-self: stretch; display: grid; place-items: center; } | ||
| 302 | +.apply::before { | ||
| 303 | + content: ""; position: absolute; top: 12%; bottom: 12%; left: 50%; | ||
| 304 | + border-left: 1px dashed var(--line); | ||
| 305 | +} | ||
| 306 | +.apply span { | ||
| 307 | + position: relative; | ||
| 308 | + padding: 4px 10px; border-radius: 999px; | ||
| 309 | + font-family: var(--mono); font-size: 0.75rem; | ||
| 310 | + background: var(--ink); color: var(--onink); | ||
| 311 | +} | ||
| 312 | + | ||
| 313 | +.projects { display: grid; gap: 14px; } | ||
| 314 | +.proj { padding-block: 16px; } | ||
| 315 | + | ||
| 316 | +.files { margin-top: 10px; display: grid; gap: 4px; } | ||
| 317 | +.files li { | ||
| 318 | + display: flex; align-items: center; gap: 10px; | ||
| 319 | + font-family: var(--mono); font-size: 0.8125rem; | ||
| 320 | + overflow-wrap: anywhere; | ||
| 321 | +} | ||
| 322 | +.files li::before { | ||
| 323 | + content: ""; flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--ok); | ||
| 324 | +} | ||
| 325 | + | ||
| 326 | +/* The one orchestrated moment: files go from "will add" (blue, faded) to written (green), in order */ | ||
| 327 | +.js .flow .files li, | ||
| 328 | +.js .flow .files li::before { | ||
| 329 | + transition-property: opacity, background-color; | ||
| 330 | + transition-duration: 320ms; | ||
| 331 | + transition-timing-function: ease-out; | ||
| 332 | + transition-delay: calc(500ms + var(--i) * 140ms); | ||
| 333 | +} | ||
| 334 | +.js .flow:not(.done) .files li { opacity: 0.4; } | ||
| 335 | +.js .flow:not(.done) .files li::before { background: var(--info); } | ||
| 336 | + | ||
| 337 | +@media (max-width: 820px) { | ||
| 338 | + .flow { grid-template-columns: 1fr; } | ||
| 339 | + .apply { height: 64px; } | ||
| 340 | + .apply::before { top: 0; bottom: 0; } | ||
| 341 | +} | ||
| 342 | + | ||
| 343 | +.facts { | ||
| 344 | + display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 32px; | ||
| 345 | + margin-top: 64px; | ||
| 346 | +} | ||
| 347 | +.facts > div { padding-top: 20px; border-top: 1px solid var(--line); } | ||
| 348 | +.facts dt { font-weight: 600; font-size: 1.0625rem; } | ||
| 349 | +.facts dd { margin-top: 6px; color: var(--muted); } | ||
| 350 | + | ||
| 351 | +@media (max-width: 820px) { .facts { grid-template-columns: 1fr; gap: 24px; } } | ||
| 352 | + | ||
| 353 | +/* ------------------------------------------------------------------ try it */ | ||
| 354 | +.code { | ||
| 355 | + position: relative; | ||
| 356 | + margin-top: 28px; | ||
| 357 | + max-width: 720px; | ||
| 358 | + border-radius: 16px; | ||
| 359 | + background: var(--ink); | ||
| 360 | + color: var(--onink); | ||
| 361 | +} | ||
| 362 | +.code pre { margin: 0; padding: 22px 24px; overflow-x: auto; } | ||
| 363 | +.code code { | ||
| 364 | + background: none; padding: 0; | ||
| 365 | + font-size: 0.9375rem; line-height: 1.7; | ||
| 366 | +} | ||
| 367 | +.code .copy { position: absolute; top: 14px; right: 14px; } | ||
| 368 | + | ||
| 369 | +/* ------------------------------------------------------------------ footer */ | ||
| 370 | +.foot { | ||
| 371 | + display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px 32px; | ||
| 372 | + margin-top: clamp(88px, 12vw, 144px); | ||
| 373 | + padding-block: 28px 40px; | ||
| 374 | + border-top: 1px solid var(--line); | ||
| 375 | + color: var(--muted); font-size: 0.9375rem; | ||
| 376 | +} | ||
| 377 | + | ||
| 378 | +/* ------------------------------------------------------------------ motion */ | ||
| 379 | +@media (prefers-reduced-motion: reduce) { | ||
| 380 | + html { scroll-behavior: auto; } | ||
| 381 | + *, *::before, *::after { transition: none !important; } | ||
| 382 | +} | ||