Drop the Cloud / Desktop / Self-hosted tabs from the hero. One Start on Cloud button, a ghost Copy setup prompt beside it, and a fine-print link to the Run it section for the local options. The rail stops selling: the Get started, star, and YC boxes become two quiet text lines. Extract the intro steps and the copy button into components.
Unified · Split
| @@ -23,3 +23,4 @@ pnpm-debug.log* · 22 unmodified lines | |||
| 23 | # jetbrains setting folder | 23 | # jetbrains setting folder |
| 24 | .idea/ | 24 | .idea/ |
| 25 | .vercel | 25 | .vercel |
| 26 | .preview/ | ||
| @@ -0,0 +1,36 @@ | |||
| 1 | --- | ||
| 2 | // The "Copy setup prompt" button: a dashed ghost button beside the primary, | ||
| 3 | // carrying the three agent marks. Copies `setupPrompt`; the click handler | ||
| 4 | // lives in pages/index.astro. | ||
| 5 | import { setupPrompt } from "../../content/site-copy"; | ||
| 6 | |||
| 7 | const logos = import.meta.glob<string>("../../assets/logos/*.svg", { | ||
| 8 | eager: true, | ||
| 9 | query: "?url", | ||
| 10 | import: "default", | ||
| 11 | }); | ||
| 12 | const logo = (name: string): string => { | ||
| 13 | const hit = Object.entries(logos).find(([k]) => k.endsWith(`/${name}.svg`)); | ||
| 14 | if (!hit) throw new Error(`missing logo ${name}`); | ||
| 15 | return hit[1]; | ||
| 16 | }; | ||
| 17 | const marks = ["claude", "codex", "cursor"]; | ||
| 18 | |||
| 19 | |||
| 20 | --- | ||
| 21 | |||
| 22 | <button | ||
| 23 | type="button" | ||
| 24 | class="btn-ghost copy-btn doc__copy" | ||
| 25 | data-copy={setupPrompt} | ||
| 26 | data-event="marketing_setup_prompt_copied" | ||
| 27 | > | ||
| 28 | <span class="doc__copy-logos" aria-hidden="true"> | ||
| 29 | {marks.map((n) => <img src={logo(n)} alt="" width="14" height="14" />)} | ||
| 30 | </span> | ||
| 31 | <span>Copy setup prompt</span> | ||
| 32 | <span class="copy-icons" aria-hidden="true"> | ||
| 33 | <svg class="copy-icon-copy" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg> | ||
| 34 | <svg class="copy-icon-check" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"></path></svg> | ||
| 35 | </span> | ||
| 36 | </button> | ||
| @@ -1,32 +1,19 @@ | |||
| 1 | --- | 1 | --- |
| 2 | // Document-style homepage body. Reads like a short paper about the product: | 2 | // Document-style homepage body. Reads like a short paper about the product: |
| 3 | // one modest headline, plain first-person prose, six beats separated by | 3 | // one modest headline, plain first-person prose, six beats separated by |
| 4 | // hairlines, the two demos kept as numbered figures, and one way to start. | 4 | // hairlines, the two demos kept as numbered figures, and one door in: Cloud, |
| 5 | // with a setup prompt for visitors who already have an agent open. | ||
| 5 | // Sits inside the rail layout; the rail's "On this page" list points at the | 6 | // Sits inside the rail layout; the rail's "On this page" list points at the |
| 6 | // section ids below. | 7 | // section ids below. |
| 7 | import { AnimatedBeamDemo } from "../animated-beam-demo"; | 8 | import { AnimatedBeamDemo } from "../animated-beam-demo"; |
| 8 | import { ContextBloatDemo } from "../context-bloat-demo"; | 9 | import { ContextBloatDemo } from "../context-bloat-demo"; |
| 9 | import { setupPrompt } from "../../content/site-copy"; | ||
| 10 | import Testimonials from "./testimonials.astro"; | 10 | import Testimonials from "./testimonials.astro"; |
| 11 | import IntroSteps from "./intro-steps.astro"; | ||
| 12 | import CopyPrompt from "./copy-prompt.astro"; | ||
| 11 | import "../../styles/variants/doc.css"; | 13 | import "../../styles/variants/doc.css"; |
| 12 | // Avatar goes through Vite so it ships as a hashed asset; new /public files | 14 | // Avatar goes through Vite so it ships as a hashed asset; new /public files |
| 13 | // are not picked up by the deploy pipeline. | 15 | // are not picked up by the deploy pipeline. |
| 14 | import rhysAvatar from "../../assets/rhys-sullivan.jpg"; | 16 | import rhysAvatar from "../../assets/rhys-sullivan.jpg"; |
| 15 | // Brand marks for the hover reveals in the intro steps. | ||
| 16 | const logos = import.meta.glob<string>("../../assets/logos/*.svg", { | ||
| 17 | eager: true, | ||
| 18 | query: "?url", | ||
| 19 | import: "default", | ||
| 20 | }); | ||
| 21 | const logo = (name: string): string => { | ||
| 22 | const hit = Object.entries(logos).find(([k]) => k.endsWith(`/${name}.svg`)); | ||
| 23 | if (!hit) throw new Error(`missing logo ${name}`); | ||
| 24 | return hit[1]; | ||
| 25 | }; | ||
| 26 | const toolLogos = ["github", "linear", "sentry", "stripe", "notion", "slack", "jira", "gmail"]; | ||
| 27 | const agentLogos = ["claude", "codex", "cursor", "chatgpt", "gemini", "hermes", "copilot", "windsurf", "opencode", "zed", "grok"]; | ||
| 28 | // The three marks on the copy button; the full list would crowd it. | ||
| 29 | const agentLogosShort = ["claude", "codex", "cursor"]; | ||
| 30 | const X_URL = "https://x.com/RhysSullivan"; | 17 | const X_URL = "https://x.com/RhysSullivan"; |
| 31 | 18 | ||
| 32 | interface Props { | 19 | interface Props { |
| @@ -39,91 +26,15 @@ const { gh } = Astro.props; · 6 unmodified lines | |||
| 39 | {/* ── Intro ── */} | 26 | {/* ── Intro ── */} |
| 40 | <header class="doc__intro"> | 27 | <header class="doc__intro"> |
| 41 | <h1 class="doc__title">Executor is an MCP gateway.</h1> | 28 | <h1 class="doc__title">Executor is an MCP gateway.</h1> |
| 42 | <ol class="doc__steps"> | 29 | <IntroSteps /> |
| 43 | <li> | 30 | <p class="doc__subhead">One MCP. All your accounts. Free for up to three people.</p> |
| 44 | <span class="doc__num">01</span> | 31 | <div class="doc__actions"> |
| 45 | <span> | 32 | <a href="/cloud" class="btn-primary">Start on Cloud</a> |
| 46 | Connect{" "} | 33 | <CopyPrompt /> |
| 47 | <span class="doc__pop" tabindex="0"> | ||
| 48 | <span class="doc__pop-word">everything</span> | ||
| 49 | <span class="doc__pop-panel" aria-hidden="true" inert> | ||
| 50 | {toolLogos.map((n, i) => ( | ||
| 51 | <span class="doc__pop-tile" style={`--i:${i}`}><img src={logo(n)} alt="" width="18" height="18" /></span> | ||
| 52 | ))} | ||
| 53 | <span class="doc__pop-pill" style={`--i:${toolLogos.length}`}>any OpenAPI, GraphQL, or MCP</span> | ||
| 54 | </span> | ||
| 55 | </span> | ||
| 56 | {" "}to Executor. | ||
| 57 | </span> | ||
| 58 | </li> | ||
| 59 | <li> | ||
| 60 | <span class="doc__num">02</span> | ||
| 61 | <span> | ||
| 62 | Give{" "} | ||
| 63 | <span class="doc__pop" tabindex="0"> | ||
| 64 | <span class="doc__pop-word">your agent</span> | ||
| 65 | <span class="doc__pop-panel" aria-hidden="true" inert> | ||
| 66 | {agentLogos.map((n, i) => ( | ||
| 67 | <span class="doc__pop-tile" style={`--i:${i}`}><img src={logo(n)} alt="" width="18" height="18" /></span> | ||
| 68 | ))} | ||
| 69 | <span class="doc__pop-pill" style={`--i:${agentLogos.length}`}>any MCP client</span> | ||
| 70 | </span> | ||
| 71 | </span> | ||
| 72 | {" "}the Executor tool. | ||
| 73 | </span> | ||
| 74 | </li> | ||
| 75 | <li> | ||
| 76 | <span class="doc__num">03</span> | ||
| 77 | <span>That's it.{" "}<a href="#install">Get started.</a></span> | ||
| 78 | </li> | ||
| 79 | </ol> | ||
| 80 | <p class="doc__subhead">One MCP. All your accounts.</p> | ||
| 81 | <div class="doc__tabs" role="tablist" aria-label="How to run Executor"> | ||
| 82 | <button type="button" role="tab" aria-selected="true" data-doc-tab="cloud">Cloud</button> | ||
| 83 | <button type="button" role="tab" aria-selected="false" data-doc-tab="local">Desktop / CLI</button> | ||
| 84 | <button type="button" role="tab" aria-selected="false" data-doc-tab="self">Self-hosted</button> | ||
| 85 | </div> | ||
| 86 | <div class="doc__tabpanes"> | ||
| 87 | <div role="tabpanel" data-doc-pane="cloud"> | ||
| 88 | <p>Sign in, point your agent at one URL, done. Free for up to three people and 100,000 executions a month.</p> | ||
| 89 | <div class="doc__actions"><a href="/cloud" class="btn-primary">Start on Cloud</a><button type="button" class="btn-secondary copy-btn doc__copy" data-copy={setupPrompt} data-event="marketing_setup_prompt_copied"> | ||
| 90 | <span class="doc__copy-logos" aria-hidden="true"> | ||
| 91 | {agentLogosShort.map((n) => <img src={logo(n)} alt="" width="14" height="14" />)} | ||
| 92 | </span> | ||
| 93 | <span>Copy setup prompt</span> | ||
| 94 | <span class="copy-icons" aria-hidden="true"> | ||
| 95 | <svg class="copy-icon-copy" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg> | ||
| 96 | <svg class="copy-icon-check" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"></path></svg> | ||
| 97 | </span> | ||
| 98 | </button></div> | ||
| 99 | </div> | ||
| 100 | <div role="tabpanel" data-doc-pane="local" data-inactive aria-hidden="true" inert> | ||
| 101 | <p>Nothing leaves your machine. A desktop app for Mac, Windows, and Linux, or a CLI that runs in the background. Same local endpoint either way.</p> | ||
| 102 | <div class="doc__actions"><span class="doc__dl" data-download> | ||
| 103 | <a data-os="mac-arm64" href={`${gh}/releases/latest/download/executor-desktop-mac-arm64.dmg`} class="btn-primary">Download for Mac</a> | ||
| 104 | <a data-os="mac-x64" href={`${gh}/releases/latest/download/executor-desktop-mac-x64.dmg`} class="btn-primary">Download for Mac (Intel)</a> | ||
| 105 | <a data-os="win-x64" href={`${gh}/releases/latest/download/executor-desktop-win-x64.exe`} class="btn-primary">Download for Windows</a> | ||
| 106 | <a data-os="linux-x64" href={`${gh}/releases/latest/download/executor-desktop-linux-x86_64.AppImage`} class="btn-primary">Download for Linux</a> | ||
| 107 | <details class="download-other doc__dl-other hidden" data-download-other> | ||
| 108 | <summary>Other platforms ▾</summary> | ||
| 109 | <div class="download-other-list" data-download-other-list></div> | ||
| 110 | </details> | ||
| 111 | </span><code class="doc__cmd">npm i -g executor</code><button type="button" class="btn-secondary copy-btn doc__copy" data-copy={setupPrompt} data-event="marketing_setup_prompt_copied"> | ||
| 112 | <span class="doc__copy-logos" aria-hidden="true"> | ||
| 113 | {agentLogosShort.map((n) => <img src={logo(n)} alt="" width="14" height="14" />)} | ||
| 114 | </span> | ||
| 115 | <span>Copy setup prompt</span> | ||
| 116 | <span class="copy-icons" aria-hidden="true"> | ||
| 117 | <svg class="copy-icon-copy" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg> | ||
| 118 | <svg class="copy-icon-check" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"></path></svg> | ||
| 119 | </span> | ||
| 120 | </button></div> | ||
| 121 | </div> | ||
| 122 | <div role="tabpanel" data-doc-pane="self" data-inactive aria-hidden="true" inert> | ||
| 123 | <p>One Docker image on your own box. It is the same code Cloud runs.</p> | ||
| 124 | <div class="doc__actions"><a href="https://executor.sh/docs/hosted/docker" class="btn-primary">Self-hosting docs</a><a href={gh} class="btn-secondary">Source on GitHub</a></div> | ||
| 125 | </div> | ||
| 126 | </div> | 34 | </div> |
| 35 | <p class="doc__subhead doc__subhead--fine"> | ||
| 36 | Rather keep it on your machine? <a href="#install">Desktop, CLI, or self-hosted ↓</a> | ||
| 37 | </p> | ||
| 127 | </header> | 38 | </header> |
| 128 | 39 | ||
| 129 | <figure class="doc__fig doc__fig--hero"> | 40 | <figure class="doc__fig doc__fig--hero"> |
| @@ -305,29 +216,6 @@ const { gh } = Astro.props; · 175 unmodified lines | |||
| 305 | </section> | 216 | </section> |
| 306 | </article> | 217 | </article> |
| 307 | 218 | ||
| 308 | <script> | ||
| 309 | for (const list of document.querySelectorAll<HTMLElement>(".doc__tabs")) { | ||
| 310 | const root = list.parentElement!; | ||
| 311 | list.addEventListener("click", (e) => { | ||
| 312 | const tab = (e.target as HTMLElement).closest<HTMLElement>("[data-doc-tab]"); | ||
| 313 | if (!tab) return; | ||
| 314 | const key = tab.dataset.docTab; | ||
| 315 | for (const t of list.querySelectorAll<HTMLElement>("[data-doc-tab]")) { | ||
| 316 | t.setAttribute("aria-selected", String(t === tab)); | ||
| 317 | } | ||
| 318 | for (const p of root.querySelectorAll<HTMLElement>("[data-doc-pane]")) { | ||
| 319 | const off = p.dataset.docPane !== key; | ||
| 320 | p.toggleAttribute("data-inactive", off); | ||
| 321 | p.toggleAttribute("inert", off); | ||
| 322 | p.setAttribute("aria-hidden", String(off)); | ||
| 323 | for (const el of p.querySelectorAll<HTMLElement>("a, button")) { | ||
| 324 | el.tabIndex = off ? -1 : 0; | ||
| 325 | } | ||
| 326 | } | ||
| 327 | }); | ||
| 328 | } | ||
| 329 | </script> | ||
| 330 | |||
| 331 | <script> | 219 | <script> |
| 332 | // Show one download button for the visitor's OS and tuck the rest behind | 220 | // Show one download button for the visitor's OS and tuck the rest behind |
| 333 | // "Other platforms". Without JS or on an unknown OS, all four stay visible. | 221 | // "Other platforms". Without JS or on an unknown OS, all four stay visible. |
| @@ -0,0 +1,54 @@ | |||
| 1 | --- | ||
| 2 | // The three numbered intro steps with the hover-reveal logo panels. | ||
| 3 | const logos = import.meta.glob<string>("../../assets/logos/*.svg", { | ||
| 4 | eager: true, | ||
| 5 | query: "?url", | ||
| 6 | import: "default", | ||
| 7 | }); | ||
| 8 | const logo = (name: string): string => { | ||
| 9 | const hit = Object.entries(logos).find(([k]) => k.endsWith(`/${name}.svg`)); | ||
| 10 | if (!hit) throw new Error(`missing logo ${name}`); | ||
| 11 | return hit[1]; | ||
| 12 | }; | ||
| 13 | const toolLogos = ["github", "linear", "sentry", "stripe", "notion", "slack", "jira", "gmail"]; | ||
| 14 | const agentLogos = ["claude", "codex", "cursor", "chatgpt", "gemini", "hermes", "copilot", "windsurf", "opencode", "zed", "grok"]; | ||
| 15 | --- | ||
| 16 | |||
| 17 | <ol class="doc__steps"> | ||
| 18 | <li> | ||
| 19 | <span class="doc__num">01</span> | ||
| 20 | <span> | ||
| 21 | Connect{" "} | ||
| 22 | <span class="doc__pop" tabindex="0"> | ||
| 23 | <span class="doc__pop-word">everything</span> | ||
| 24 | <span class="doc__pop-panel" aria-hidden="true" inert> | ||
| 25 | {toolLogos.map((n, i) => ( | ||
| 26 | <span class="doc__pop-tile" style={`--i:${i}`}><img src={logo(n)} alt="" width="18" height="18" /></span> | ||
| 27 | ))} | ||
| 28 | <span class="doc__pop-pill" style={`--i:${toolLogos.length}`}>any OpenAPI, GraphQL, or MCP</span> | ||
| 29 | </span> | ||
| 30 | </span> | ||
| 31 | {" "}to Executor. | ||
| 32 | </span> | ||
| 33 | </li> | ||
| 34 | <li> | ||
| 35 | <span class="doc__num">02</span> | ||
| 36 | <span> | ||
| 37 | Give{" "} | ||
| 38 | <span class="doc__pop" tabindex="0"> | ||
| 39 | <span class="doc__pop-word">your agent</span> | ||
| 40 | <span class="doc__pop-panel" aria-hidden="true" inert> | ||
| 41 | {agentLogos.map((n, i) => ( | ||
| 42 | <span class="doc__pop-tile" style={`--i:${i}`}><img src={logo(n)} alt="" width="18" height="18" /></span> | ||
| 43 | ))} | ||
| 44 | <span class="doc__pop-pill" style={`--i:${agentLogos.length}`}>any MCP client</span> | ||
| 45 | </span> | ||
| 46 | </span> | ||
| 47 | {" "}the Executor tool. | ||
| 48 | </span> | ||
| 49 | </li> | ||
| 50 | <li> | ||
| 51 | <span class="doc__num">03</span> | ||
| 52 | <span>That's it.</span> | ||
| 53 | </li> | ||
| 54 | </ol> | ||
| @@ -1,9 +1,10 @@ | |||
| 1 | --- | 1 | --- |
| 2 | // Rail, variant B: "Gridline rail". | 2 | // Rail, variant B: "Gridline rail". |
| 3 | // Compact and technical: mark-only wordmark, a tight link stack, an outlined | 3 | // Compact and technical: mark-only wordmark, a tight link stack, two quiet |
| 4 | // CTA, and a numbered mono index. A full-height hairline closes the right edge. | 4 | // meta lines (open source, YC), and a numbered mono index. The rail does not |
| 5 | // sell; the hero owns the call to action. A full-height hairline closes the | ||
| 6 | // right edge. | ||
| 5 | import "../../styles/variants/rail.css"; | 7 | import "../../styles/variants/rail.css"; |
| 6 | import ycBackedBy from "../../assets/yc-backed-by.svg?url"; | ||
| 7 | import rhysAvatar from "../../assets/rhys-sullivan.jpg"; | 8 | import rhysAvatar from "../../assets/rhys-sullivan.jpg"; |
| 8 | 9 | ||
| 9 | const X_URL = "https://x.com/RhysSullivan"; | 10 | const X_URL = "https://x.com/RhysSullivan"; |
| @@ -47,38 +48,13 @@ const pad = (index: number) => String(index + 1).padStart(2, "0"); · 37 unmodified lines | |||
| 47 | <a href={SIGN_IN} class="rail__link rail__link--keep">Sign in</a> | 48 | <a href={SIGN_IN} class="rail__link rail__link--keep">Sign in</a> |
| 48 | </nav> | 49 | </nav> |
| 49 | 50 | ||
| 50 | <div class="rail__cta"> | 51 | <div class="rail__meta"> |
| 51 | <a href="#install" class="rail__box rail__box--primary">Get started</a> | 52 | <a href={GH} target="_blank" rel="noopener" class="rail__meta-line"> |
| 52 | <a | 53 | <span>Open source</span> |
| 53 | href={GH} | 54 | {stars != null && <span class="rail__meta-n">★ {stars}</span>} |
| 54 | target="_blank" | ||
| 55 | rel="noopener" | ||
| 56 | class="rail__box gh-star" | ||
| 57 | aria-label="Star Executor on GitHub" | ||
| 58 | > | ||
| 59 | <svg | ||
| 60 | class="gh-star__ico" | ||
| 61 | width="13" | ||
| 62 | height="13" | ||
| 63 | viewBox="0 0 16 16" | ||
| 64 | aria-hidden="true" | ||
| 65 | > | ||
| 66 | <path | ||
| 67 | fill="#e3b341" | ||
| 68 | d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.75.75 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25z" | ||
| 69 | ></path> | ||
| 70 | </svg> | ||
| 71 | <span>Star on GitHub</span> | ||
| 72 | {stars != null && <span class="gh-star__n">{stars}</span>} | ||
| 73 | </a> | 55 | </a> |
| 74 | <a | 56 | <a href="https://www.ycombinator.com" target="_blank" rel="noopener noreferrer" class="rail__meta-line"> |
| 75 | href="https://www.ycombinator.com" | 57 | <span>Backed by Y Combinator</span> |
| 76 | target="_blank" | ||
| 77 | rel="noopener noreferrer" | ||
| 78 | class="rail__box rail__box--yc" | ||
| 79 | aria-label="Backed by Y Combinator" | ||
| 80 | > | ||
| 81 | <img src={ycBackedBy} alt="Backed by Y Combinator" width="350" height="125" /> | ||
| 82 | </a> | 58 | </a> |
| 83 | </div> | 59 | </div> |
| 84 | 60 | ||
| @@ -299,48 +299,6 @@ · 298 unmodified lines | |||
| 299 | text-decoration-color: var(--color-rule-strong); | 299 | text-decoration-color: var(--color-rule-strong); |
| 300 | } | 300 | } |
| 301 | 301 | ||
| 302 | .doc__tabs { | ||
| 303 | display: inline-flex; | ||
| 304 | gap: 0; | ||
| 305 | border: 1px solid var(--color-rule); | ||
| 306 | border-radius: 8px; | ||
| 307 | padding: 3px; | ||
| 308 | margin-bottom: 1rem; | ||
| 309 | background: var(--color-surface-2); | ||
| 310 | } | ||
| 311 | .doc__tabs button { | ||
| 312 | font: inherit; | ||
| 313 | font-size: 13.5px; | ||
| 314 | font-weight: 500; | ||
| 315 | padding: 0.4rem 0.85rem; | ||
| 316 | border: 0; | ||
| 317 | border-radius: 6px; | ||
| 318 | background: transparent; | ||
| 319 | color: var(--color-ink-2); | ||
| 320 | cursor: pointer; | ||
| 321 | } | ||
| 322 | .doc__tabs button[aria-selected="true"] { | ||
| 323 | background: var(--color-surface); | ||
| 324 | color: var(--color-ink); | ||
| 325 | box-shadow: | ||
| 326 | 0 1px 2px rgba(0, 0, 0, 0.06), | ||
| 327 | 0 0 0 1px var(--color-rule); | ||
| 328 | } | ||
| 329 | .doc__tabpanes p { | ||
| 330 | font-size: 15.5px; | ||
| 331 | line-height: 1.6; | ||
| 332 | color: var(--color-ink-2); | ||
| 333 | margin-bottom: 1rem; | ||
| 334 | max-width: 60ch; | ||
| 335 | /* Always two lines tall, so the buttons sit at the same height on every tab. */ | ||
| 336 | min-height: calc(2 * 1.6em); | ||
| 337 | } | ||
| 338 | .doc__tabpanes .doc__actions { | ||
| 339 | min-height: 46px; | ||
| 340 | } | ||
| 341 | .doc__tabpanes .doc__actions { | ||
| 342 | margin-bottom: 1.1rem; | ||
| 343 | } | ||
| 344 | .doc__cmd { | 302 | .doc__cmd { |
| 345 | font-family: var(--font-mono); | 303 | font-family: var(--font-mono); |
| 346 | font-size: 12.5px; | 304 | font-size: 12.5px; |
| @@ -737,20 +695,6 @@ a.doc__term-v:hover { · 390 unmodified lines | |||
| 737 | display: none; | 695 | display: none; |
| 738 | } | 696 | } |
| 739 | 697 | ||
| 740 | /* Tab panes share one grid cell, so the tallest one sets the height and | ||
| 741 | switching tabs never shifts the content below. Hidden panes stay in the | ||
| 742 | layout but are invisible and inert. */ | ||
| 743 | .doc__tabpanes { | ||
| 744 | display: grid; | ||
| 745 | } | ||
| 746 | .doc__tabpanes > [role="tabpanel"] { | ||
| 747 | grid-area: 1 / 1; | ||
| 748 | } | ||
| 749 | .doc__tabpanes > [role="tabpanel"][data-inactive] { | ||
| 750 | visibility: hidden; | ||
| 751 | pointer-events: none; | ||
| 752 | } | ||
| 753 | |||
| 754 | .doc__subhead { | 698 | .doc__subhead { |
| 755 | font-size: 17px; | 699 | font-size: 17px; |
| 756 | line-height: 1.5; | 700 | line-height: 1.5; |
| @@ -858,25 +802,12 @@ a.doc__term-v:hover { · 101 unmodified lines | |||
| 858 | width: auto; | 802 | width: auto; |
| 859 | max-width: none; | 803 | max-width: none; |
| 860 | } | 804 | } |
| 861 | .doc__tabs { | ||
| 862 | display: flex; | ||
| 863 | width: 100%; | ||
| 864 | } | ||
| 865 | .doc__tabs button { | ||
| 866 | flex: 1; | ||
| 867 | padding: 0.45rem 0.4rem; | ||
| 868 | font-size: 13px; | ||
| 869 | white-space: nowrap; | ||
| 870 | } | ||
| 871 | .doc__tabpanes p { | ||
| 872 | min-height: 0; | ||
| 873 | } | ||
| 874 | .doc__actions { | 805 | .doc__actions { |
| 875 | flex-direction: column; | 806 | flex-direction: column; |
| 876 | align-items: stretch; | 807 | align-items: stretch; |
| 877 | } | 808 | } |
| 878 | .doc__actions > .btn-primary, | 809 | .doc__actions > .btn-primary, |
| 879 | .doc__actions > .btn-secondary, | 810 | .doc__actions > .btn-ghost, |
| 880 | .doc__actions > .doc__cmd, | 811 | .doc__actions > .doc__cmd, |
| 881 | .doc__actions > .doc__dl { | 812 | .doc__actions > .doc__dl { |
| 882 | width: 100%; | 813 | width: 100%; |
| @@ -936,12 +867,6 @@ a.doc__term-v:hover { · 53 unmodified lines | |||
| 936 | justify-content: center; | 867 | justify-content: center; |
| 937 | gap: 0.6rem; | 868 | gap: 0.6rem; |
| 938 | } | 869 | } |
| 939 | .doc__tabs { | ||
| 940 | margin-inline: auto; | ||
| 941 | } | ||
| 942 | .doc__tabpanes p { | ||
| 943 | margin-inline: auto; | ||
| 944 | } | ||
| 945 | .doc__actions { | 870 | .doc__actions { |
| 946 | justify-content: center; | 871 | justify-content: center; |
| 947 | } | 872 | } |
| @@ -1015,3 +940,43 @@ a.doc__term-v:hover { · 67 unmodified lines | |||
| 1015 | max-width: 720px; | 940 | max-width: 720px; |
| 1016 | } | 941 | } |
| 1017 | } | 942 | } |
| 943 | |||
| 944 | /* ── Fine print under the hero buttons ── */ | ||
| 945 | .doc__subhead--fine { | ||
| 946 | font-size: 15px; | ||
| 947 | color: var(--color-ink-3); | ||
| 948 | max-width: 52ch; | ||
| 949 | } | ||
| 950 | .doc__subhead--fine a { | ||
| 951 | color: var(--color-ink-2); | ||
| 952 | text-decoration: underline; | ||
| 953 | text-underline-offset: 3px; | ||
| 954 | text-decoration-color: var(--color-rule-strong); | ||
| 955 | } | ||
| 956 | .doc__subhead--fine a:hover { | ||
| 957 | color: var(--color-ink); | ||
| 958 | } | ||
| 959 | |||
| 960 | /* ── Ghost copy button: text weight of a link, hit area of a button ── */ | ||
| 961 | .btn-ghost { | ||
| 962 | display: inline-flex; | ||
| 963 | align-items: center; | ||
| 964 | gap: 0.4rem; | ||
| 965 | background: transparent; | ||
| 966 | color: var(--color-ink-2); | ||
| 967 | border: 1px dashed var(--color-rule-strong); | ||
| 968 | border-radius: 8px; | ||
| 969 | padding: 0.7rem 1.1rem; | ||
| 970 | font-family: inherit; | ||
| 971 | font-weight: 500; | ||
| 972 | font-size: 14.5px; | ||
| 973 | letter-spacing: -0.005em; | ||
| 974 | cursor: pointer; | ||
| 975 | transition: | ||
| 976 | color 0.15s ease, | ||
| 977 | border-color 0.15s ease; | ||
| 978 | } | ||
| 979 | .btn-ghost:hover { | ||
| 980 | color: var(--color-ink); | ||
| 981 | border-color: var(--color-ink-3); | ||
| 982 | } | ||
| @@ -133,75 +133,31 @@ · 132 unmodified lines | |||
| 133 | .rail--b .rail__link[aria-current="page"]::before { | 133 | .rail--b .rail__link[aria-current="page"]::before { |
| 134 | opacity: 1; | 134 | opacity: 1; |
| 135 | } | 135 | } |
| 136 | .rail--b .rail__cta { | 136 | /* ── Quiet rail meta: star count and YC as text lines, not boxes ── */ |
| 137 | .rail--b .rail__meta { | ||
| 137 | display: grid; | 138 | display: grid; |
| 138 | gap: 8px; | 139 | gap: 4px; |
| 139 | margin-top: 16px; | 140 | margin-top: 16px; |
| 140 | --rail-box-h: 36px; | 141 | padding-top: 12px; |
| 142 | border-top: 1px solid var(--color-rule); | ||
| 141 | } | 143 | } |
| 142 | /* One stack, one button language: same height, width, radius, hairline | 144 | .rail--b .rail__meta-line { |
| 143 | border, and sans label. The primary is filled ink; the others are quiet. */ | ||
| 144 | .rail--b .rail__box { | ||
| 145 | display: flex; | 145 | display: flex; |
| 146 | align-items: center; | 146 | align-items: center; |
| 147 | justify-content: center; | 147 | justify-content: space-between; |
| 148 | gap: 7px; | 148 | gap: 8px; |
| 149 | height: var(--rail-box-h); | 149 | font-size: 12.5px; |
| 150 | width: 100%; | 150 | color: var(--color-ink-3); |
| 151 | box-sizing: border-box; | 151 | line-height: 1.6; |
| 152 | padding: 0 12px; | ||
| 153 | border: 1px solid var(--color-rule-strong); | ||
| 154 | border-radius: 6px; | ||
| 155 | background: var(--color-surface); | ||
| 156 | font-family: var(--font-sans); | ||
| 157 | font-size: 13px; | ||
| 158 | font-weight: 500; | ||
| 159 | color: var(--color-ink); | ||
| 160 | overflow: hidden; | ||
| 161 | transition: | ||
| 162 | background 0.15s ease, | ||
| 163 | color 0.15s ease, | ||
| 164 | border-color 0.15s ease; | ||
| 165 | } | ||
| 166 | .rail--b .rail__box:hover { | ||
| 167 | border-color: var(--color-ink); | ||
| 168 | } | ||
| 169 | .rail--b .rail__box--primary { | ||
| 170 | background: var(--color-ink); | ||
| 171 | border-color: var(--color-ink); | ||
| 172 | color: var(--color-surface); | ||
| 173 | } | ||
| 174 | .rail--b .rail__box--primary:hover { | ||
| 175 | background: #1f1f24; | ||
| 176 | } | 152 | } |
| 177 | /* Star pill: same box, sans label, count in mono like the app's metadata. */ | 153 | .rail--b .rail__meta-line:hover { |
| 178 | .rail--b .rail__box.gh-star { | ||
| 179 | color: var(--color-ink); | 154 | color: var(--color-ink); |
| 180 | } | 155 | } |
| 181 | .rail--b .rail__box.gh-star .gh-star__ico { | 156 | .rail--b .rail__meta-n { |
| 182 | width: 12px; | ||
| 183 | height: 12px; | ||
| 184 | } | ||
| 185 | .rail--b .rail__box.gh-star .gh-star__n { | ||
| 186 | font-family: var(--font-mono); | 157 | font-family: var(--font-mono); |
| 187 | font-weight: 400; | 158 | font-size: 11.5px; |
| 188 | font-size: 12px; | ||
| 189 | color: var(--color-ink-3); | ||
| 190 | border-left: 1px solid var(--color-rule); | ||
| 191 | padding-left: 8px; | ||
| 192 | margin-left: 1px; | ||
| 193 | } | ||
| 194 | /* The YC badge is a rounded cream card at 350x125; letting it fill the box | ||
| 195 | edge to edge keeps the badge's own corners hidden behind ours. */ | ||
| 196 | .rail--b .rail__box--yc { | ||
| 197 | padding: 0; | ||
| 198 | background: #f6f6ef; | ||
| 199 | } | ||
| 200 | .rail--b .rail__box--yc img { | ||
| 201 | height: 100%; | ||
| 202 | width: auto; | ||
| 203 | display: block; | ||
| 204 | } | 159 | } |
| 160 | |||
| 205 | .rail--b .rail__toc { | 161 | .rail--b .rail__toc { |
| 206 | gap: 8px; | 162 | gap: 8px; |
| 207 | } | 163 | } |
| @@ -314,8 +270,7 @@ · 106 unmodified lines | |||
| 314 | .rail .rail__foot, | 270 | .rail .rail__foot, |
| 315 | .rail .rail__contact, | 271 | .rail .rail__contact, |
| 316 | .rail .rail__secondary, | 272 | .rail .rail__secondary, |
| 317 | .rail .gh-star, | 273 | .rail .rail__meta { |
| 318 | .rail .rail__box--yc { | ||
| 319 | display: none; | 274 | display: none; |
| 320 | } | 275 | } |
| 321 | .rail .rail__stack { | 276 | .rail .rail__stack { |
| @@ -337,16 +292,6 @@ · 15 unmodified lines | |||
| 337 | .rail .rail__link::before { | 292 | .rail .rail__link::before { |
| 338 | display: none; | 293 | display: none; |
| 339 | } | 294 | } |
| 340 | .rail .rail__cta { | ||
| 341 | display: block; | ||
| 342 | margin-top: 0; | ||
| 343 | } | ||
| 344 | .rail--b .rail__box--primary { | ||
| 345 | width: auto; | ||
| 346 | height: 32px; | ||
| 347 | padding: 0 12px; | ||
| 348 | font-size: 13px; | ||
| 349 | } | ||
| 350 | } | 295 | } |
| 351 | @media (max-width: 479.98px) { | 296 | @media (max-width: 479.98px) { |
| 352 | .rail .rail__nav .rail__link:not(.rail__link--keep) { | 297 | .rail .rail__nav .rail__link:not(.rail__link--keep) { |