1/**
2 * Forge — code review & CI landing page (dark, hairline, one accent).
3 *
4 * A complete, self-contained single page: React is the only import and every
5 * style lives in the <style> block at the bottom of this file (classes are
6 * prefixed `fd-frg-`). Drop it into any React 18+ project and render <Page />.
7 *
8 * Motion: terminal typing hero, a PR timeline whose line draws with scroll,
9 * an annotated diff where review comments pop in (and a suggestion can be
10 * committed), a sticky "90-second merge" scene scrubbed by scroll, a bento of
11 * live micro-visuals, an integrations grid with a cursor-follow spotlight on
12 * its hairlines, a per-seat pricing calculator and staggered reveals.
13 *
14 * Browser support: evergreen browsers (sticky, IntersectionObserver, CSS
15 * custom properties, mask-image). Accessibility: landmarks, real buttons and
16 * range input, aria-live terminal summary, focus rings, reduced-motion and
17 * prefers-contrast fallbacks. SSR-safe (no window access during render).
18 */
19import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
20
21/* ─────────────────────────── helpers ─────────────────────────── */
22
23const reducedMotion = () =>
24 typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
25const clamp01 = (n: number) => (n < 0 ? 0 : n > 1 ? 1 : n);
26
27function useReveal(root: React.RefObject<HTMLElement | null>) {
28 useEffect(() => {
29 const el = root.current;
30 if (!el) return;
31 el.setAttribute("data-js", "");
32 const items = Array.from(el.querySelectorAll<HTMLElement>("[data-reveal]"));
33 if (typeof IntersectionObserver === "undefined" || reducedMotion()) {
34 items.forEach((i) => i.setAttribute("data-in", ""));
35 return;
36 }
37 const io = new IntersectionObserver(
38 (entries) => {
39 for (const e of entries)
40 if (e.isIntersecting) {
41 e.target.setAttribute("data-in", "");
42 io.unobserve(e.target);
43 }
44 },
45 { rootMargin: "0px 0px -8% 0px" },
46 );
47 items.forEach((i) => io.observe(i));
48 return () => io.disconnect();
49 }, [root]);
50}
51
52/** rAF-throttled scroll/resize callback, active only while `ref` is near the viewport. */
53function useScrollFrame(ref: React.RefObject<HTMLElement | null>, fn: () => void) {
54 const fnRef = useRef(fn);
55 fnRef.current = fn;
56 useEffect(() => {
57 const el = ref.current;
58 if (!el) return;
59 let raf = 0;
60 let active = true;
61 const run = () => {
62 raf = 0;
63 if (active) fnRef.current();
64 };
65 const queue = () => {
66 if (!raf) raf = requestAnimationFrame(run);
67 };
68 const io =
69 typeof IntersectionObserver !== "undefined"
70 ? new IntersectionObserver(
71 ([e]) => {
72 active = e.isIntersecting;
73 if (active) queue();
74 },
75 { rootMargin: "200px 0px" },
76 )
77 : undefined;
78 io?.observe(el);
79 queue();
80 window.addEventListener("scroll", queue, { passive: true });
81 window.addEventListener("resize", queue);
82 return () => {
83 io?.disconnect();
84 window.removeEventListener("scroll", queue);
85 window.removeEventListener("resize", queue);
86 if (raf) cancelAnimationFrame(raf);
87 };
88 }, [ref]);
89}
90
91/** Fires once when `ref` enters the viewport (immediately under reduced motion). */
92function useInView(ref: React.RefObject<HTMLElement | null>, threshold = 0.35) {
93 const [seen, setSeen] = useState(false);
94 useEffect(() => {
95 const el = ref.current;
96 if (!el) return;
97 if (typeof IntersectionObserver === "undefined" || reducedMotion()) {
98 setSeen(true);
99 return;
100 }
101 const io = new IntersectionObserver(
102 ([e]) => {
103 if (e.isIntersecting) {
104 setSeen(true);
105 io.disconnect();
106 }
107 },
108 { threshold },
109 );
110 io.observe(el);
111 return () => io.disconnect();
112 }, [ref, threshold]);
113 return seen;
114}
115
116/* ─────────────────────────── icons ─────────────────────────── */
117
118function Logo({ size = 20 }: { size?: number }) {
119 return (
120 <svg className="fd-frg-logo" viewBox="0 0 24 24" width={size} height={size} aria-hidden="true">
121 <path d="M12 2.5l8.5 4.9v9.2L12 21.5l-8.5-4.9V7.4z" fill="none" stroke="currentColor" strokeWidth="1.5" />
122 <path d="M12 7.5v9M8 10l4-2.5 4 2.5" fill="none" stroke="var(--fd-frg-accent)" strokeWidth="1.5" />
123 </svg>
124 );
125}
126function Arrow() {
127 return (
128 <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" className="fd-frg-arrow">
129 <path d="M3 8h9.5M8.5 3.5L13 8l-4.5 4.5" fill="none" stroke="currentColor" strokeWidth="1.5" />
130 </svg>
131 );
132}
133function Tick() {
134 return (
135 <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
136 <path d="M3.5 8.5l3 3 6-7" fill="none" stroke="currentColor" strokeWidth="1.6" />
137 </svg>
138 );
139}
140function Branch() {
141 return (
142 <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
143 <circle cx="4.5" cy="3.5" r="1.6" fill="none" stroke="currentColor" />
144 <circle cx="4.5" cy="12.5" r="1.6" fill="none" stroke="currentColor" />
145 <circle cx="11.5" cy="5.5" r="1.6" fill="none" stroke="currentColor" />
146 <path d="M4.5 5.1v5.8M11.5 7.1c0 2.4-2 3-7 3.8" fill="none" stroke="currentColor" />
147 </svg>
148 );
149}
150
151/* ─────────────────────────── nav ─────────────────────────── */
152
153const NAV = [
154 ["#how", "How it works"],
155 ["#review", "Review"],
156 ["#speed", "Speed"],
157 ["#integrations", "Integrations"],
158 ["#pricing", "Pricing"],
159];
160
161function Nav() {
162 const [scrolled, setScrolled] = useState(false);
163 const [open, setOpen] = useState(false);
164 useEffect(() => {
165 const on = () => setScrolled(window.scrollY > 12);
166 on();
167 window.addEventListener("scroll", on, { passive: true });
168 return () => window.removeEventListener("scroll", on);
169 }, []);
170 return (
171 <header className="fd-frg-nav" data-scrolled={scrolled || undefined} data-open={open || undefined}>
172 <div className="fd-frg-nav-in">
173 <a href="#top" className="fd-frg-brand" aria-label="Forge home">
174 <Logo />
175 <span>Forge</span>
176 </a>
177 <nav className="fd-frg-nav-links" aria-label="Primary">
178 {NAV.map(([h, l]) => (
179 <a key={h} href={h}>
180 {l}
181 </a>
182 ))}
183 </nav>
184 <div className="fd-frg-nav-right">
185 <a className="fd-frg-nav-ghost" href="#top">
186 Docs
187 </a>
188 <a className="fd-frg-btn fd-frg-btn-sm" href="#pricing">
189 Start free
190 </a>
191 <button
192 type="button"
193 className="fd-frg-burger"
194 aria-expanded={open}
195 aria-label={open ? "Close menu" : "Open menu"}
196 onClick={() => setOpen((o) => !o)}
197 >
198 <span />
199 <span />
200 </button>
201 </div>
202 </div>
203 <div className="fd-frg-sheet" hidden={!open}>
204 {NAV.map(([h, l]) => (
205 <a key={h} href={h} onClick={() => setOpen(false)}>
206 {l}
207 </a>
208 ))}
209 </div>
210 </header>
211 );
212}
213
214/* ─────────────────────────── hero terminal ─────────────────────────── */
215
216type TLine = { kind: "cmd" | "out" | "ok" | "warn" | "dim" | "sum"; text: string; pause?: number };
217
218const SCRIPT: TLine[] = [
219 { kind: "cmd", text: "git push origin feat/session-rotation" },
220 { kind: "dim", text: "→ forge: PR #4182 opened · 7 files · +214 −38", pause: 380 },
221 { kind: "out", text: " selecting tests 214 of 9,812 touch this diff", pause: 420 },
222 { kind: "ok", text: " ✓ build cached 0:11", pause: 260 },
223 { kind: "ok", text: " ✓ unit 214 passed 0:38", pause: 260 },
224 { kind: "ok", text: " ✓ integration 41 passed 0:52", pause: 260 },
225 { kind: "warn", text: " ◆ review 2 suggestions on src/auth/session.ts", pause: 520 },
226 { kind: "ok", text: " ✓ approved by jane (staff eng)", pause: 360 },
227 { kind: "sum", text: "merged to main in 1m 28s · deployed to prod-us-east", pause: 0 },
228];
229
230function Terminal() {
231 const [shown, setShown] = useState<{ line: number; chars: number }>({ line: 0, chars: 0 });
232 const [run, setRun] = useState(0);
233 const boxRef = useRef<HTMLDivElement>(null);
234 const done = shown.line >= SCRIPT.length;
235
236 useEffect(() => {
237 if (reducedMotion()) {
238 setShown({ line: SCRIPT.length, chars: 0 });
239 return;
240 }
241 let line = 0;
242 let chars = 0;
243 let t = 0;
244 let cancelled = false;
245 setShown({ line: 0, chars: 0 });
246 const tick = () => {
247 if (cancelled) return;
248 const cur = SCRIPT[line];
249 if (!cur) return;
250 if (cur.kind === "cmd" && chars < cur.text.length) {
251 chars += 1;
252 setShown({ line, chars });
253 t = window.setTimeout(tick, 28 + Math.random() * 46);
254 return;
255 }
256 line += 1;
257 chars = 0;
258 setShown({ line, chars: 0 });
259 t = window.setTimeout(tick, cur.pause ?? 300);
260 };
261 t = window.setTimeout(tick, 700);
262 return () => {
263 cancelled = true;
264 window.clearTimeout(t);
265 };
266 }, [run]);
267
268 return (
269 <div className="fd-frg-term" ref={boxRef}>
270 <div className="fd-frg-term-bar">
271 <span className="fd-frg-term-dots" aria-hidden="true">
272 <i />
273 <i />
274 <i />
275 </span>
276 <span className="fd-frg-term-title">~/acme/api — zsh</span>
277 <button type="button" className="fd-frg-term-replay" onClick={() => setRun((r) => r + 1)} disabled={!done}>
278 Replay
279 </button>
280 </div>
281 <div className="fd-frg-term-body" aria-hidden="true">
282 {SCRIPT.map((l, i) => {
283 if (i > shown.line) return null;
284 const typing = l.kind === "cmd" && i === shown.line;
285 const text = typing ? l.text.slice(0, shown.chars) : l.text;
286 if (i === shown.line && !typing) return null;
287 return (
288 <div key={`${run}-${i}`} className={`fd-frg-tl fd-frg-tl-${l.kind}`}>
289 {l.kind === "cmd" && <span className="fd-frg-prompt">$</span>}
290 {l.kind === "sum" && <span className="fd-frg-sum-dot" />}
291 <span>{text}</span>
292 {typing && <span className="fd-frg-caret" />}
293 </div>
294 );
295 })}
296 {done && (
297 <div className="fd-frg-tl fd-frg-tl-cmd">
298 <span className="fd-frg-prompt">$</span>
299 <span className="fd-frg-caret" />
300 </div>
301 )}
302 </div>
303 <p className="fd-frg-sr" aria-live="polite">
304 {done ? "Example run: pull request 4182 merged to main in 1 minute 28 seconds." : ""}
305 </p>
306 </div>
307 );
308}
309
310function Hero() {
311 return (
312 <section className="fd-frg-hero" id="top">
313 <div className="fd-frg-hero-grid" aria-hidden="true" />
314 <div className="fd-frg-wrap fd-frg-hero-in">
315 <a className="fd-frg-pill fd-frg-in" href="#speed" style={{ "--d": "0ms" } as CSSProperties}>
316 <span className="fd-frg-pill-new">v4.2</span> <span className="fd-frg-pill-txt">Merge queues now rebase stacked PRs</span> <Arrow />
317 </a>
318 <h1 className="fd-frg-h1">
319 <span className="fd-frg-in" style={{ "--d": "80ms" } as CSSProperties}>
320 Ship the PR.
321 </span>
322 <span className="fd-frg-in fd-frg-h1-mute" style={{ "--d": "180ms" } as CSSProperties}>
323 Skip the wait.
324 </span>
325 </h1>
326 <p className="fd-frg-lede fd-frg-in" style={{ "--d": "280ms" } as CSSProperties}>
327 Forge runs only the tests your diff touches, reviews every line before a human does, and lands the merge in
328 about ninety seconds. Works with the GitHub repo you already have.
329 </p>
330 <div className="fd-frg-ctas fd-frg-in" style={{ "--d": "360ms" } as CSSProperties}>
331 <a className="fd-frg-btn" href="#pricing">
332 Start free <Arrow />
333 </a>
334 <CopyCmd cmd="npx forge init" />
335 </div>
336 <div className="fd-frg-hero-term fd-frg-in" style={{ "--d": "460ms" } as CSSProperties}>
337 <Terminal />
338 </div>
339 </div>
340 </section>
341 );
342}
343
344function CopyCmd({ cmd }: { cmd: string }) {
345 const [copied, setCopied] = useState(false);
346 return (
347 <button
348 type="button"
349 className="fd-frg-cmd"
350 onClick={async () => {
351 try {
352 await navigator.clipboard.writeText(cmd);
353 setCopied(true);
354 window.setTimeout(() => setCopied(false), 1600);
355 } catch {
356 /* clipboard unavailable */
357 }
358 }}
359 aria-label={`Copy command: ${cmd}`}
360 >
361 <span className="fd-frg-cmd-p">$</span>
362 <span>{cmd}</span>
363 <span className="fd-frg-cmd-c">{copied ? "Copied" : "Copy"}</span>
364 </button>
365 );
366}
367
368/* ─────────────────────────── logos ─────────────────────────── */
369
370function Logos() {
371 const names = ["Linear Labs", "northwind", "PARCEL", "Halcyon", "tessellate", "Orbital", "kiln.dev", "Quanta"];
372 return (
373 <section className="fd-frg-logos" aria-label="Customers">
374 <div className="fd-frg-wrap">
375 <p className="fd-frg-mini" data-reveal="">
376 Merging 2.1 million PRs a month for 3,400 engineering teams
377 </p>
378 <ul>
379 {names.map((n, i) => (
380 <li key={n} data-reveal="" style={{ "--i": i } as CSSProperties} className={`fd-frg-wm fd-frg-wm-${i % 4}`}>
381 {n}
382 </li>
383 ))}
384 </ul>
385 </div>
386 </section>
387 );
388}
389
390/* ─────────────────────────── PR timeline (draws on scroll) ─────────────────────────── */
391
392const STEPS: { t: string; time: string; title: string; body: string; meta: ReactNode }[] = [
393 {
394 t: "00:00",
395 time: "Push",
396 title: "PR opened from your branch",
397 body: "Forge reads the diff, the dependency graph and the CODEOWNERS file before CI even starts.",
398 meta: (
399 <span className="fd-frg-chip">
400 <Branch /> feat/session-rotation → main
401 </span>
402 ),
403 },
404 {
405 t: "00:04",
406 time: "Select",
407 title: "214 of 9,812 tests selected",
408 body: "Test impact analysis maps every changed symbol to the tests that exercise it. The other 9,598 stay cached.",
409 meta: (
410 <span className="fd-frg-meter" aria-label="2.2 percent of the suite">
411 <i />
412 <b>2.2% of suite</b>
413 </span>
414 ),
415 },
416 {
417 t: "00:11",
418 time: "Build",
419 title: "Remote cache hit, 11 seconds",
420 body: "Content-addressed build artifacts are shared across every branch, so the second build is almost free.",
421 meta: <span className="fd-frg-chip">cache · 96.4% hit rate</span>,
422 },
423 {
424 t: "00:52",
425 time: "Review",
426 title: "Auto-review leaves two suggestions",
427 body: "Forge comments like a careful staff engineer: idempotency, missing indexes, unhandled errors. Never style nits.",
428 meta: <span className="fd-frg-chip fd-frg-chip-acc">2 suggestions · 0 nits</span>,
429 },
430 {
431 t: "01:14",
432 time: "Approve",
433 title: "A human signs off",
434 body: "Reviewers are routed by ownership and load. Hot files with incident history pull in a second reviewer automatically.",
435 meta: (
436 <span className="fd-frg-avs" aria-label="Approved by Jane and Nikhil">
437 <img className="" src="/img/people/p9.webp" alt="Jane Lindqvist, staff engineer, a woman with natural curly hair in a black sweater" width={28} height={28} loading="lazy" decoding="async" />
438 <img className="" src="/img/people/p7.webp" alt="Nikhil Sharma, reviewer, a bearded man in round glasses and a black sweater" width={28} height={28} loading="lazy" decoding="async" />
439 </span>
440 ),
441 },
442 {
443 t: "01:28",
444 time: "Merge",
445 title: "Queued, rebased, merged, deployed",
446 body: "The merge queue batches compatible PRs, re-runs only what changed after rebase and promotes to production.",
447 meta: <span className="fd-frg-chip">prod-us-east · v2026.10.29-4182</span>,
448 },
449];
450
451function Timeline() {
452 const ref = useRef<HTMLElement>(null);
453 const list = useRef<HTMLOListElement>(null);
454 useScrollFrame(ref, () => {
455 const l = list.current;
456 if (!l) return;
457 const r = l.getBoundingClientRect();
458 const vh = window.innerHeight;
459 const p = reducedMotion() ? 1 : clamp01((vh * 0.62 - r.top) / r.height);
460 l.style.setProperty("--draw", p.toFixed(4));
461 const lineY = p * r.height;
462 l.querySelectorAll<HTMLElement>("[data-step]").forEach((li) => {
463 if (li.offsetTop + 14 <= lineY + 1) li.setAttribute("data-on", "");
464 else li.removeAttribute("data-on");
465 });
466 });
467 return (
468 <section className="fd-frg-sec" id="how" ref={ref}>
469 <div className="fd-frg-wrap fd-frg-tl-grid">
470 <div className="fd-frg-sticky-head" data-reveal="">
471 <p className="fd-frg-eyebrow">01 · Anatomy of a merge</p>
472 <h2 className="fd-frg-h2">
473 From push to prod,
474 <br />
475 <span className="fd-frg-h2-mute">one minute twenty-eight.</span>
476 </h2>
477 <p className="fd-frg-p">
478 Every step below is a real span from PR #4182 in the acme/api monorepo — 1.4M lines, 61 services, 180
479 engineers.
480 </p>
481 </div>
482 <ol className="fd-frg-steps" ref={list}>
483 <span className="fd-frg-rail" aria-hidden="true">
484 <i />
485 </span>
486 {STEPS.map((s) => (
487 <li key={s.t} data-step="">
488 <span className="fd-frg-node" aria-hidden="true" />
489 <div className="fd-frg-step-head">
490 <span className="fd-frg-mono fd-frg-step-t">{s.t}</span>
491 <span className="fd-frg-mono fd-frg-step-k">{s.time}</span>
492 </div>
493 <h3>{s.title}</h3>
494 <p>{s.body}</p>
495 <div className="fd-frg-step-meta">{s.meta}</div>
496 </li>
497 ))}
498 </ol>
499 </div>
500 </section>
501 );
502}
503
504/* ─────────────────────────── diff with auto-review ─────────────────────────── */
505
506type DL = { n: number | null; o: number | null; k: " " | "+" | "-"; code: string };
507const DIFF: DL[] = [
508 { o: 41, n: 41, k: " ", code: "export async function rotateSession(userId: string) {" },
509 { o: 42, n: 42, k: " ", code: " const current = await db.session.findActive(userId);" },
510 { o: 43, n: null, k: "-", code: " await db.session.revoke(current.id);" },
511 { o: 44, n: null, k: "-", code: " return db.session.create({ userId });" },
512 { o: null, n: 43, k: "+", code: " return db.transaction(async (tx) => {" },
513 { o: null, n: 44, k: "+", code: " await tx.session.revoke(current.id);" },
514 { o: null, n: 45, k: "+", code: " const next = await tx.session.create({ userId });" },
515 { o: null, n: 46, k: "+", code: " audit.emit(\"session.rotated\", { userId });" },
516 { o: null, n: 47, k: "+", code: " return next;" },
517 { o: null, n: 48, k: "+", code: " });" },
518 { o: 45, n: 49, k: " ", code: "}" },
519];
520
521function Diff() {
522 const ref = useRef<HTMLDivElement>(null);
523 const seen = useInView(ref, 0.3);
524 const [stage, setStage] = useState(0);
525 const [applied, setApplied] = useState(false);
526
527 useEffect(() => {
528 if (!seen) return;
529 if (reducedMotion()) {
530 setStage(4);
531 return;
532 }
533 const ts = [400, 1500, 2600, 3500].map((ms, i) => window.setTimeout(() => setStage(i + 1), ms));
534 return () => ts.forEach((t) => window.clearTimeout(t));
535 }, [seen]);
536
537 return (
538 <section className="fd-frg-sec fd-frg-sec-alt" id="review">
539 <div className="fd-frg-wrap">
540 <div className="fd-frg-head-row" data-reveal="">
541 <div>
542 <p className="fd-frg-eyebrow">02 · Auto-review</p>
543 <h2 className="fd-frg-h2">
544 Reviews that read the code,
545 <br />
546 <span className="fd-frg-h2-mute">not the linter output.</span>
547 </h2>
548 </div>
549 <p className="fd-frg-p fd-frg-head-p">
550 Forge checks for the bugs that page people at 3 a.m. — race conditions, missing idempotency, unbounded
551 queries — and stays silent about semicolons.
552 </p>
553 </div>
554
555 <div className="fd-frg-diff" ref={ref} data-stage={stage} data-reveal="">
556 <div className="fd-frg-diff-bar">
557 <span className="fd-frg-mono">src/auth/session.ts</span>
558 <span className="fd-frg-diff-stat">
559 <b className="fd-frg-add">+6</b> <b className="fd-frg-del">−2</b>
560 </span>
561 <span className="fd-frg-diff-status" data-on={stage >= 1 || undefined}>
562 <span className="fd-frg-spin" aria-hidden="true" />
563 {stage >= 4 ? "Review complete · 2 suggestions" : stage >= 1 ? "Forge is reviewing…" : "Queued"}
564 </span>
565 </div>
566 <div className="fd-frg-diff-body">
567 <div className="fd-frg-code" role="table" aria-label="Diff of src/auth/session.ts">
568 {DIFF.map((l, i) => (
569 <div key={i} role="row" className={`fd-frg-dl fd-frg-dl-${l.k === "+" ? "add" : l.k === "-" ? "del" : "ctx"}`} data-scan={stage >= 1 || undefined} style={{ "--i": i } as CSSProperties}>
570 <span role="cell" className="fd-frg-ln">{l.o ?? ""}</span>
571 <span role="cell" className="fd-frg-ln">{l.n ?? ""}</span>
572 <span role="cell" className="fd-frg-sign">{l.k}</span>
573 <span role="cell" className="fd-frg-src">
574 {applied && i === 7 ? " audit.emit(\"session.rotated\", { userId, prev: current.id });" : l.code}
575 </span>
576 </div>
577 ))}
578 </div>
579 <div className="fd-frg-comments">
580 <article className="fd-frg-cmt" data-show={stage >= 2 || undefined}>
581 <header>
582 <span className="fd-frg-bot">
583 <Logo size={14} />
584 </span>
585 <b>forge-review</b>
586 <span className="fd-frg-mono">L43 · correctness</span>
587 </header>
588 <p>
589 Good call wrapping revoke + create in a transaction. Retries from the mobile client will still mint
590 two sessions though — key the transaction on <code>request.idempotencyKey</code>.
591 </p>
592 </article>
593 <article className="fd-frg-cmt" data-show={stage >= 3 || undefined}>
594 <header>
595 <span className="fd-frg-bot">
596 <Logo size={14} />
597 </span>
598 <b>forge-review</b>
599 <span className="fd-frg-mono">L46 · observability</span>
600 </header>
601 <p>
602 Include the previous session id so dashboards can split rotations from logouts.
603 </p>
604 <div className="fd-frg-sugg">
605 <span className="fd-frg-mono">Suggested change</span>
606 <code>- audit.emit("session.rotated", {"{ userId }"});</code>
607 <code>+ audit.emit("session.rotated", {"{ userId, prev: current.id }"});</code>
608 </div>
609 <button
610 type="button"
611 className="fd-frg-btn fd-frg-btn-sm fd-frg-btn-ghost"
612 onClick={() => setApplied(true)}
613 disabled={applied}
614 >
615 {applied ? (
616 <>
617 <Tick /> Committed · 9c41f2a
618 </>
619 ) : (
620 "Commit suggestion"
621 )}
622 </button>
623 </article>
624 <article className="fd-frg-cmt fd-frg-cmt-human" data-show={stage >= 4 || undefined}>
625 <header>
626 <img className="fd-frg-av" src="/img/people/p9.webp" alt="Jane Lindqvist, staff engineer, a woman with natural curly hair in a black sweater" width={22} height={22} loading="lazy" decoding="async" />
627 <b>jane</b>
628 <span className="fd-frg-mono">staff eng</span>
629 </header>
630 <p>This file had 14 incidents last quarter — glad it pulled me in. Approving once the key lands.</p>
631 </article>
632 </div>
633 </div>
634 </div>
635 </div>
636 </section>
637 );
638}
639
640/* ─────────────────────────── 90-second merge (scroll-scrubbed) ─────────────────────────── */
641
642const JOBS = [
643 { name: "Checkout + diff", start: 0, dur: 4 },
644 { name: "Build (cached)", start: 4, dur: 11 },
645 { name: "Unit · 214 tests", start: 15, dur: 38 },
646 { name: "Integration · 41", start: 15, dur: 52 },
647 { name: "Auto-review", start: 15, dur: 37 },
648 { name: "Human approval", start: 52, dur: 22 },
649 { name: "Merge queue + deploy", start: 74, dur: 14 },
650];
651const TOTAL = 88;
652
653function Speed() {
654 const ref = useRef<HTMLElement>(null);
655 const clock = useRef<HTMLSpanElement>(null);
656 const legacy = useRef<HTMLSpanElement>(null);
657 useScrollFrame(ref, () => {
658 const el = ref.current;
659 if (!el) return;
660 const r = el.getBoundingClientRect();
661 const vh = window.innerHeight;
662 const travel = Math.max(1, r.height - vh);
663 const p = reducedMotion() ? 1 : clamp01((-r.top) / travel);
664 const t = p * TOTAL;
665 el.style.setProperty("--p", p.toFixed(4));
666 el.querySelectorAll<HTMLElement>("[data-job]").forEach((row) => {
667 const s = Number(row.dataset.start);
668 const d = Number(row.dataset.dur);
669 const f = clamp01((t - s) / d);
670 row.style.setProperty("--f", f.toFixed(4));
671 row.dataset.state = f >= 1 ? "done" : f > 0 ? "run" : "wait";
672 });
673 if (clock.current) {
674 const sec = Math.round(t);
675 clock.current.textContent = `${Math.floor(sec / 60)}:${String(sec % 60).padStart(2, "0")}`;
676 }
677 if (legacy.current) {
678 const lt = Math.round(p * 852);
679 legacy.current.textContent = `${Math.floor(lt / 60)}:${String(lt % 60).padStart(2, "0")}`;
680 }
681 });
682 return (
683 <section className="fd-frg-speed" id="speed" ref={ref}>
684 <div className="fd-frg-speed-stick">
685 <div className="fd-frg-wrap fd-frg-speed-in">
686 <div className="fd-frg-speed-head">
687 <div>
688 <p className="fd-frg-eyebrow">03 · The ninety-second merge</p>
689 <h2 className="fd-frg-h2">
690 Same PR. <span className="fd-frg-h2-mute">Ten times sooner.</span>
691 </h2>
692 </div>
693 <p className="fd-frg-mono fd-frg-speed-hint" aria-hidden="true">Scroll to run the pipeline ↓</p>
694 </div>
695 <div className="fd-frg-clock-row">
696 <div className="fd-frg-clock">
697 <span className="fd-frg-mono">Forge</span>
698 <span ref={clock} className="fd-frg-clock-n">
699 0:00
700 </span>
701 </div>
702 <div className="fd-frg-clock fd-frg-clock-legacy">
703 <span className="fd-frg-mono">Same PR on full-suite CI</span>
704 <span ref={legacy} className="fd-frg-clock-n">
705 0:00
706 </span>
707 </div>
708 </div>
709 <div className="fd-frg-gantt" role="img" aria-label="Pipeline: seven jobs complete in 88 seconds, versus 14 minutes 12 seconds on full-suite CI">
710 <div className="fd-frg-gantt-axis" aria-hidden="true">
711 {[0, 15, 30, 45, 60, 75, 88].map((s) => (
712 <span key={s} style={{ left: `${(s / TOTAL) * 100}%` }}>
713 {s}s
714 </span>
715 ))}
716 </div>
717 {JOBS.map((j) => (
718 <div key={j.name} className="fd-frg-job" data-job="" data-start={j.start} data-dur={j.dur} data-state="wait">
719 <span className="fd-frg-job-name">
720 <i aria-hidden="true" />
721 {j.name}
722 </span>
723 <span className="fd-frg-job-track">
724 <span
725 className="fd-frg-job-bar"
726 style={{ left: `${(j.start / TOTAL) * 100}%`, width: `${(j.dur / TOTAL) * 100}%` }}
727 >
728 <i />
729 </span>
730 </span>
731 <span className="fd-frg-job-dur fd-frg-mono">{j.dur}s</span>
732 </div>
733 ))}
734 <span className="fd-frg-playhead" aria-hidden="true" />
735 </div>
736 </div>
737 </div>
738 </section>
739 );
740}
741
742/* ─────────────────────────── bento ─────────────────────────── */
743
744function Bento() {
745 return (
746 <section className="fd-frg-sec" aria-label="Features">
747 <div className="fd-frg-wrap">
748 <div className="fd-frg-head-row" data-reveal="">
749 <div>
750 <p className="fd-frg-eyebrow">04 · Built for the merge queue</p>
751 <h2 className="fd-frg-h2">
752 Less waiting.
753 <br />
754 <span className="fd-frg-h2-mute">More shipping.</span>
755 </h2>
756 </div>
757 </div>
758 <div className="fd-frg-bento">
759 <article className="fd-frg-tile fd-frg-tile-wide" data-reveal="">
760 <div className="fd-frg-tile-vis fd-frg-vis-stack" aria-hidden="true">
761 {["#4180 schema: add sessions.prev_id", "#4181 api: rotate on refresh", "#4182 auth: idempotent rotation"].map((t, i) => (
762 <div key={t} style={{ "--i": i } as CSSProperties}>
763 <Branch />
764 <span>{t}</span>
765 <em>{i === 2 ? "rebasing" : "ready"}</em>
766 </div>
767 ))}
768 </div>
769 <h3>Stacked PRs that stay stacked</h3>
770 <p>Forge rebases, re-checks and re-routes reviewers as the stack changes. Land the bottom, the rest follows.</p>
771 </article>
772 <article className="fd-frg-tile" data-reveal="" style={{ "--i": 1 } as CSSProperties}>
773 <div className="fd-frg-tile-vis fd-frg-vis-flake" aria-hidden="true">
774 {Array.from({ length: 48 }, (_, i) => (
775 <i key={i} data-f={i === 13 || i === 29 || i === 38 ? "" : undefined} />
776 ))}
777 </div>
778 <h3>Flake quarantine</h3>
779 <p>Tests that fail then pass on retry are isolated, owned and ticketed — never blocking your merge.</p>
780 </article>
781 <article className="fd-frg-tile" data-reveal="" style={{ "--i": 2 } as CSSProperties}>
782 <div className="fd-frg-tile-vis fd-frg-vis-cache" aria-hidden="true">
783 <span>96.4%</span>
784 <small>remote cache hit rate</small>
785 </div>
786 <h3>Shared build cache</h3>
787 <p>Content-addressed artifacts across every branch and laptop. Cold builds become a rounding error.</p>
788 </article>
789 <article className="fd-frg-tile fd-frg-tile-wide" data-reveal="" style={{ "--i": 3 } as CSSProperties}>
790 <div className="fd-frg-tile-vis fd-frg-vis-chart" aria-hidden="true">
791 <svg aria-hidden="true" focusable="false" viewBox="0 0 400 110" preserveAspectRatio="none">
792 <path d="M0 18 L40 22 L80 20 L120 30 L160 46 L200 62 L240 76 L280 84 L320 88 L360 92 L400 93" fill="none" stroke="var(--fd-frg-accent)" strokeWidth="2" />
793 <path d="M0 18 L40 22 L80 20 L120 30 L160 46 L200 62 L240 76 L280 84 L320 88 L360 92 L400 93 L400 110 L0 110Z" fill="var(--fd-frg-accent)" opacity="0.08" />
794 <path d="M160 0V110" stroke="rgba(255,255,255,0.2)" strokeDasharray="3 4" />
795 </svg>
796 <span className="fd-frg-chart-tag">Forge enabled · Jun 3</span>
797 </div>
798 <h3>Cycle time you’ll actually open on Monday</h3>
799 <p>PR cycle time, review latency and flake rate per team — measured from the same spans that run your CI.</p>
800 </article>
801 </div>
802 </div>
803 </section>
804 );
805}
806
807/* ─────────────────────────── integrations (spotlight) ─────────────────────────── */
808
809const INTEGRATIONS: [string, string, string][] = [
810 ["GitHub", "Checks, merge queue, CODEOWNERS", "M8 1.5a6.5 6.5 0 00-2 12.7c.3 0 .4-.2.4-.4v-1.3c-1.8.4-2.2-.8-2.2-.8-.3-.8-.7-1-.7-1-.6-.4 0-.4 0-.4.7 0 1 .7 1 .7.6 1 1.6.7 2 .5 0-.4.2-.7.4-.9-1.4-.2-3-.7-3-3.2 0-.7.3-1.3.7-1.8 0-.2-.3-.8.1-1.7 0 0 .5-.2 1.8.7a6 6 0 013.2 0c1.3-.9 1.8-.7 1.8-.7.4.9.1 1.5.1 1.7.4.5.7 1.1.7 1.8 0 2.5-1.5 3-3 3.2.3.2.5.6.5 1.2v1.8c0 .2.1.4.4.4A6.5 6.5 0 008 1.5z"],
811 ["GitLab", "MR pipelines and approvals", "M8 14.5L2 9.8l1.6-6.3 1.5 4.3h5.8l1.5-4.3L14 9.8z"],
812 ["Slack", "Review requests that respect focus time", "M4 9.5a1.3 1.3 0 11-1.3-1.3H4zm.7 0a1.3 1.3 0 012.6 0v3.3a1.3 1.3 0 11-2.6 0zM6 4a1.3 1.3 0 111.3-1.3V4zm0 .7a1.3 1.3 0 010 2.6H2.7a1.3 1.3 0 110-2.6zM11.5 6a1.3 1.3 0 111.3 1.3h-1.3zm-.7 0a1.3 1.3 0 01-2.6 0V2.7a1.3 1.3 0 112.6 0zM9.5 11.5a1.3 1.3 0 11-1.3 1.3v-1.3zm0-.7a1.3 1.3 0 010-2.6h3.3a1.3 1.3 0 110 2.6z"],
813 ["Linear", "Issue status follows the PR", "M2 9.5l4.5 4.5M2 6.8l7.2 7.2M3 4.4l8.6 8.6M5 2.6l8.4 8.4M8 1.6l6.4 6.4"],
814 ["Jira", "Transitions on merge and deploy", "M8 1.5L14.5 8 8 14.5 1.5 8zM8 5L11 8 8 11 5 8z"],
815 ["PagerDuty", "Incident history weights reviews", "M4 14.5v-13h4.5a3.5 3.5 0 010 7H4"],
816 ["Datadog", "Deploy markers and test traces", "M3 12l2-7 3 3 3-5 2 9z"],
817 ["Sentry", "Regressions linked to the PR", "M8 2l6 11H10a5 5 0 00-5-5M8 2L2 13h3"],
818 ["Vercel", "Preview URLs on every commit", "M8 2.5l6.5 11h-13z"],
819 ["AWS", "OIDC deploys to ECS and Lambda", "M2 10c3 2.5 9 2.5 12 0M11.5 10.8l2.5-.8-.6 2.5"],
820 ["Bazel", "Remote execution and cache", "M8 1.5l3 3-3 3-3-3zM4.5 5l3 3-3 3-3-3zM11.5 5l3 3-3 3-3-3zM8 8.5l3 3-3 3-3-3z"],
821 ["Terraform", "Plan diffs as review comments", "M2 2.5l3.8 2.2v4.4L2 6.9zM6.5 5.1l3.8 2.2v4.4L6.5 9.5zM11 7.3l3-1.7v4.4l-3 1.7zM6.5 10.3l3.8 2.2v1.9"],
822];
823
824function Integrations() {
825 const grid = useRef<HTMLDivElement>(null);
826 useEffect(() => {
827 const g = grid.current;
828 if (!g) return;
829 let raf = 0;
830 let ex = 0;
831 let ey = 0;
832 const apply = () => {
833 raf = 0;
834 const gr = g.getBoundingClientRect();
835 g.style.setProperty("--mx", `${ex - gr.left}px`);
836 g.style.setProperty("--my", `${ey - gr.top}px`);
837 g.querySelectorAll<HTMLElement>("[data-cell]").forEach((c) => {
838 const r = c.getBoundingClientRect();
839 c.style.setProperty("--mx", `${ex - r.left}px`);
840 c.style.setProperty("--my", `${ey - r.top}px`);
841 });
842 };
843 const move = (e: PointerEvent) => {
844 ex = e.clientX;
845 ey = e.clientY;
846 g.setAttribute("data-hot", "");
847 if (!raf) raf = requestAnimationFrame(apply);
848 };
849 const leave = () => g.removeAttribute("data-hot");
850 g.addEventListener("pointermove", move);
851 g.addEventListener("pointerleave", leave);
852 return () => {
853 g.removeEventListener("pointermove", move);
854 g.removeEventListener("pointerleave", leave);
855 if (raf) cancelAnimationFrame(raf);
856 };
857 }, []);
858 return (
859 <section className="fd-frg-sec fd-frg-sec-alt" id="integrations">
860 <div className="fd-frg-wrap">
861 <div className="fd-frg-head-row" data-reveal="">
862 <div>
863 <p className="fd-frg-eyebrow">05 · Integrations</p>
864 <h2 className="fd-frg-h2">
865 Fits the stack
866 <br />
867 <span className="fd-frg-h2-mute">you won’t migrate.</span>
868 </h2>
869 </div>
870 <p className="fd-frg-p fd-frg-head-p">
871 Forge is a GitHub App and a single YAML file. No runners to babysit, no agents in your VPC unless you want
872 them.
873 </p>
874 </div>
875 <div className="fd-frg-int" ref={grid} data-reveal="">
876 {INTEGRATIONS.map(([name, desc, d]) => (
877 <a key={name} href="#integrations" className="fd-frg-int-cell" data-cell="">
878 <svg viewBox="0 0 16 16" width="22" height="22" aria-hidden="true">
879 <path d={d} fill="none" stroke="currentColor" strokeWidth="1.1" strokeLinejoin="round" />
880 </svg>
881 <b>{name}</b>
882 <span>{desc}</span>
883 </a>
884 ))}
885 </div>
886 </div>
887 </section>
888 );
889}
890
891/* ─────────────────────────── stats + quote ─────────────────────────── */
892
893function Proof() {
894 return (
895 <section className="fd-frg-sec" aria-label="Results">
896 <div className="fd-frg-wrap">
897 <dl className="fd-frg-stats">
898 {[
899 ["14m → 90s", "median pipeline after enabling test selection"],
900 ["−41%", "PR cycle time in the first 30 days"],
901 ["0.3%", "of auto-review comments dismissed as noise"],
902 ["SOC 2", "Type II · code never used for training"],
903 ].map(([k, v], i) => (
904 <div key={k} data-reveal="" style={{ "--i": i } as CSSProperties}>
905 <dt>{k}</dt>
906 <dd>{v}</dd>
907 </div>
908 ))}
909 </dl>
910 <figure className="fd-frg-quote" data-reveal="">
911 <blockquote>
912 “We used to batch merges at 4 p.m. because CI took fifteen minutes. Now people merge whenever they’re done,
913 and the queue just… keeps up.”
914 </blockquote>
915 <figcaption>
916 <img className="fd-frg-av fd-frg-av-lg" src="/img/people/p6.webp" alt="Priya Anand, customer, a woman with long wavy dark hair in a black sweater" width={40} height={40} loading="lazy" decoding="async" />
917 <span>
918 <b>Priya Anand</b>
919 <small>Head of Platform, Tessellate · 240 engineers</small>
920 </span>
921 </figcaption>
922 </figure>
923 </div>
924 </section>
925 );
926}
927
928/* ─────────────────────────── pricing (seat calculator) ─────────────────────────── */
929
930function Pricing() {
931 const [seats, setSeats] = useState(24);
932 const [annual, setAnnual] = useState(true);
933 const per = annual ? 19 : 24;
934 const total = seats * per;
935 const pct = ((seats - 1) / 199) * 100;
936 return (
937 <section className="fd-frg-sec" id="pricing">
938 <div className="fd-frg-wrap">
939 <div className="fd-frg-head-row" data-reveal="">
940 <div>
941 <p className="fd-frg-eyebrow">06 · Pricing</p>
942 <h2 className="fd-frg-h2">
943 Per seat.
944 <br />
945 <span className="fd-frg-h2-mute">CI minutes included.</span>
946 </h2>
947 </div>
948 <div className="fd-frg-toggle" role="group" aria-label="Billing period">
949 <button type="button" aria-pressed={!annual} onClick={() => setAnnual(false)}>
950 Monthly
951 </button>
952 <button type="button" aria-pressed={annual} onClick={() => setAnnual(true)}>
953 Yearly <em>−20%</em>
954 </button>
955 <i aria-hidden="true" data-right={annual || undefined} />
956 </div>
957 </div>
958 <div className="fd-frg-plans">
959 <article className="fd-frg-plan" data-reveal="">
960 <h3>Hobby</h3>
961 <p className="fd-frg-plan-p">Open source and side projects.</p>
962 <div className="fd-frg-price">
963 <span>$0</span>
964 </div>
965 <ul>
966 {["Public repos, unlimited", "3 private repos", "2,000 CI minutes / mo", "Auto-review on 50 PRs / mo"].map((f) => (
967 <li key={f}>
968 <Tick />
969 {f}
970 </li>
971 ))}
972 </ul>
973 <a href="#top" className="fd-frg-btn fd-frg-btn-ghost">
974 Start free
975 </a>
976 </article>
977 <article className="fd-frg-plan fd-frg-plan-hot" data-reveal="" style={{ "--i": 1 } as CSSProperties}>
978 <div className="fd-frg-plan-top">
979 <h3>Team</h3>
980 <span className="fd-frg-chip fd-frg-chip-acc">Most teams</span>
981 </div>
982 <p className="fd-frg-plan-p">Merge queues, test selection, unlimited review.</p>
983 <div className="fd-frg-price">
984 <span>${total.toLocaleString("en-US")}</span>
985 <small>/ mo</small>
986 </div>
987 <p className="fd-frg-plan-calc fd-frg-mono">
988 {seats} seats × ${per} {annual ? "billed yearly" : "billed monthly"}
989 </p>
990 <label className="fd-frg-range" style={{ "--pct": `${pct}%` } as CSSProperties}>
991 <span className="fd-frg-sr">Number of seats</span>
992 <input type="range" min={1} max={200} value={seats} onChange={(e) => setSeats(Number(e.target.value))} />
993 </label>
994 <ul>
995 {["Unlimited private repos", "25,000 CI minutes / seat pool", "Smart test selection", "Merge queue + stacked PRs", "SAML SSO"].map((f) => (
996 <li key={f}>
997 <Tick />
998 {f}
999 </li>
1000 ))}
1001 </ul>
1002 <a href="#top" className="fd-frg-btn">
1003 Start 14-day trial <Arrow />
1004 </a>
1005 </article>
1006 <article className="fd-frg-plan" data-reveal="" style={{ "--i": 2 } as CSSProperties}>
1007 <h3>Enterprise</h3>
1008 <p className="fd-frg-plan-p">Self-hosted runners and custom retention.</p>
1009 <div className="fd-frg-price">
1010 <span>Custom</span>
1011 </div>
1012 <ul>
1013 {["Runners in your VPC", "SCIM, audit log streaming", "99.95% uptime SLA", "Dedicated solutions engineer"].map((f) => (
1014 <li key={f}>
1015 <Tick />
1016 {f}
1017 </li>
1018 ))}
1019 </ul>
1020 <a href="#top" className="fd-frg-btn fd-frg-btn-ghost">
1021 Talk to sales
1022 </a>
1023 </article>
1024 </div>
1025 </div>
1026 </section>
1027 );
1028}
1029
1030/* ─────────────────────────── CTA + footer ─────────────────────────── */
1031
1032function Cta() {
1033 return (
1034 <section className="fd-frg-cta">
1035 <div className="fd-frg-hero-grid" aria-hidden="true" />
1036 <div className="fd-frg-wrap fd-frg-cta-in" data-reveal="">
1037 <Logo size={40} />
1038 <h2 className="fd-frg-cta-h">Your next PR could already be merged.</h2>
1039 <p className="fd-frg-p">Install the GitHub App, add one YAML file, push. Most teams see their first sub-two-minute merge the same afternoon.</p>
1040 <div className="fd-frg-ctas">
1041 <a className="fd-frg-btn" href="#pricing">
1042 Start free <Arrow />
1043 </a>
1044 <CopyCmd cmd="npx forge init" />
1045 </div>
1046 </div>
1047 </section>
1048 );
1049}
1050
1051function Footer() {
1052 const cols: [string, string[]][] = [
1053 ["Product", ["Review", "CI", "Merge queue", "Insights", "Changelog"]],
1054 ["Developers", ["Docs", "CLI", "API", "Status", "forge.yml reference"]],
1055 ["Company", ["About", "Blog", "Careers", "Security", "Contact"]],
1056 ];
1057 return (
1058 <footer className="fd-frg-foot">
1059 <div className="fd-frg-wrap">
1060 <div className="fd-frg-foot-top">
1061 <div>
1062 <a href="#top" className="fd-frg-brand" aria-label="Forge home">
1063 <Logo />
1064 <span>Forge</span>
1065 </a>
1066 <p className="fd-frg-foot-p">Code review and CI for teams that merge on Fridays.</p>
1067 <span className="fd-frg-status">
1068 <i aria-hidden="true" /> All systems operational
1069 </span>
1070 </div>
1071 {cols.map(([h, items]) => (
1072 <div key={h} className="fd-frg-foot-col">
1073 <h4>{h}</h4>
1074 <ul>
1075 {items.map((it) => (
1076 <li key={it}>
1077 <a href="#top">{it}</a>
1078 </li>
1079 ))}
1080 </ul>
1081 </div>
1082 ))}
1083 </div>
1084 <div className="fd-frg-foot-bot">
1085 <span>© 2026 Forge Labs, Inc.</span>
1086 <span className="fd-frg-mono">build 2026.10.29 · 4f1c9e2</span>
1087 </div>
1088 </div>
1089 </footer>
1090 );
1091}
1092
1093/* ─────────────────────────── page ─────────────────────────── */
1094
1095export function Page() {
1096 const root = useRef<HTMLDivElement>(null);
1097 useReveal(root);
1098 return (
1099 <div className="fd-frg" ref={root}>
1100 <style>{css}</style>
1101 <Nav />
1102 <main>
1103 <Hero />
1104 <Logos />
1105 <Timeline />
1106 <Diff />
1107 <Speed />
1108 <Bento />
1109 <Integrations />
1110 <Proof />
1111 <Pricing />
1112 <Cta />
1113 </main>
1114 <Footer />
1115 </div>
1116 );
1117}
1118
1119export default Page;
1120
1121/* ─────────────────────────── styles ─────────────────────────── */
1122
1123const css = `
1124.fd-frg {
1125 --fd-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
1126 --fd-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
1127 --fd-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
1128 --fd-dur-1: 120ms;
1129 --fd-dur-2: 240ms;
1130 --fd-dur-3: 420ms;
1131 --fd-dur-4: 700ms;
1132
1133 --fd-frg-bg: #0a0a0a;
1134 --fd-frg-bg-2: #0f0f10;
1135 --fd-frg-surface: #141415;
1136 --fd-frg-surface-2: #1b1b1d;
1137 --fd-frg-fg: #ededed;
1138 --fd-frg-mute: #a1a1a1;
1139 --fd-frg-dim: #8a8a8a;
1140 --fd-frg-line: rgba(255, 255, 255, 0.08);
1141 --fd-frg-line-2: rgba(255, 255, 255, 0.14);
1142 --fd-frg-accent: #ff6b2c;
1143 --fd-frg-accent-soft: rgba(255, 107, 44, 0.12);
1144 --fd-frg-add: #3fb950;
1145 --fd-frg-del: #f2555a;
1146 --fd-frg-sans: "Geist", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter Variable", "Inter", "Segoe UI", Roboto, sans-serif;
1147 --fd-frg-mono-f: "Geist Mono", ui-monospace, "SF Mono", "JetBrains Mono Variable", "JetBrains Mono", Menlo, Consolas, monospace;
1148
1149 position: relative; display: block; margin: 0; padding: 0;
1150 background: var(--fd-frg-bg); color: var(--fd-frg-fg);
1151 font: 400 1rem/1.5 var(--fd-frg-sans); letter-spacing: -0.006em; text-align: left;
1152 -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
1153 overflow-x: clip; color-scheme: dark;
1154}
1155.fd-frg, .fd-frg *, .fd-frg *::before, .fd-frg *::after { box-sizing: border-box; }
1156.fd-frg :where(h1, h2, h3, h4, p, ul, ol, li, dl, dt, dd, figure, figcaption, blockquote, main, header, footer, section, nav, article, label) {
1157 margin: 0; padding: 0; border: 0; font: inherit; color: inherit; letter-spacing: inherit; background: none;
1158}
1159.fd-frg :where(ul, ol) { list-style: none; }
1160.fd-frg :where(a) { color: inherit; text-decoration: none; }
1161.fd-frg :where(button) { appearance: none; -webkit-appearance: none; margin: 0; padding: 0; border: 0; background: none; font: inherit; color: inherit; letter-spacing: inherit; cursor: pointer; text-align: inherit; line-height: inherit; }
1162.fd-frg :where(button:disabled) { cursor: default; }
1163.fd-frg :where(svg) { display: block; overflow: visible; }
1164.fd-frg :where(code) { font: 400 0.92em/1.5 var(--fd-frg-mono-f); background: none; padding: 0; color: inherit; }
1165.fd-frg :where(b, strong) { font-weight: 600; }
1166.fd-frg :where(em) { font-style: normal; }
1167.fd-frg :where(small) { font-size: inherit; }
1168.fd-frg :where(a, button, input, [tabindex]):focus-visible { outline: 2px solid rgba(255, 107, 44, 0.7); outline-offset: 2px; border-radius: 6px; }
1169.fd-frg-sr { position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
1170
1171.fd-frg-wrap { width: 100%; max-width: 1200px; margin: 0 auto; padding: 0 32px; }
1172.fd-frg-mono { font: 400 0.75rem/1.4 var(--fd-frg-mono-f); letter-spacing: 0; }
1173.fd-frg-mini { font: 400 0.8125rem/1.4 var(--fd-frg-sans); color: var(--fd-frg-mute); text-align: center; }
1174.fd-frg-eyebrow { font: 500 0.8125rem/1.4 var(--fd-frg-mono-f); color: var(--fd-frg-accent); letter-spacing: 0; }
1175.fd-frg-h2 { margin-top: 14px; font: 600 clamp(2rem, 4.2vw, 3.25rem)/1.04 var(--fd-frg-sans); letter-spacing: -0.04em; }
1176.fd-frg-h2-mute { color: var(--fd-frg-dim); }
1177.fd-frg-p { margin-top: 18px; max-width: 34em; font: 400 1.0625rem/1.55 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1178.fd-frg-head-row { display: flex; justify-content: space-between; align-items: flex-end; gap: 32px; flex-wrap: wrap; }
1179.fd-frg-head-p { max-width: 26em; margin: 0; }
1180
1181.fd-frg-btn {
1182 display: inline-flex; align-items: center; justify-content: center; gap: 8px; height: 44px; padding: 0 18px;
1183 border: 1px solid var(--fd-frg-fg); border-radius: 9999px; background: var(--fd-frg-fg); color: var(--fd-frg-bg);
1184 font: 500 0.9375rem/1 var(--fd-frg-sans); letter-spacing: -0.01em; white-space: nowrap;
1185 transition: transform var(--fd-dur-1) var(--fd-ease-out), background-color var(--fd-dur-2) var(--fd-ease-out), border-color var(--fd-dur-2) var(--fd-ease-out), color var(--fd-dur-2) var(--fd-ease-out);
1186}
1187.fd-frg-btn:hover { background: #fff; }
1188.fd-frg-btn:active { transform: scale(0.96); }
1189.fd-frg-btn .fd-frg-arrow { transition: transform var(--fd-dur-2) var(--fd-spring); }
1190.fd-frg-btn:hover .fd-frg-arrow { transform: translateX(3px); }
1191.fd-frg-btn-sm { height: 34px; padding: 0 14px; font-size: 0.8125rem; }
1192.fd-frg-btn-ghost { background: transparent; color: var(--fd-frg-fg); border-color: var(--fd-frg-line-2); }
1193.fd-frg-btn-ghost:hover { background: var(--fd-frg-surface-2); border-color: rgba(255,255,255,0.3); }
1194.fd-frg-btn-ghost:disabled { color: var(--fd-frg-mute); }
1195.fd-frg-cmd {
1196 display: inline-flex; align-items: center; gap: 12px; height: 44px; padding: 0 8px 0 16px; border: 1px solid var(--fd-frg-line-2); border-radius: 9999px;
1197 background: var(--fd-frg-bg-2); font: 400 0.875rem/1 var(--fd-frg-mono-f); color: var(--fd-frg-fg);
1198 transition: border-color var(--fd-dur-2) var(--fd-ease-out), transform var(--fd-dur-1) var(--fd-ease-out);
1199}
1200.fd-frg-cmd:hover { border-color: rgba(255,255,255,0.3); }
1201.fd-frg-cmd:active { transform: scale(0.97); }
1202.fd-frg-cmd-p { color: var(--fd-frg-accent); }
1203.fd-frg-cmd-c { display: inline-flex; align-items: center; height: 28px; padding: 0 10px; border-radius: 9999px; background: var(--fd-frg-surface-2); font: 500 0.75rem/1 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1204.fd-frg-chip { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; border: 1px solid var(--fd-frg-line-2); border-radius: 9999px; font: 400 0.75rem/1 var(--fd-frg-mono-f); color: var(--fd-frg-mute); white-space: nowrap; }
1205.fd-frg-chip-acc { color: var(--fd-frg-accent); border-color: rgba(255,107,44,0.35); background: var(--fd-frg-accent-soft); }
1206
1207/* nav */
1208.fd-frg-nav { position: sticky; top: 0; z-index: 50; display: block; border-bottom: 1px solid transparent; transition: background-color var(--fd-dur-3) var(--fd-ease-out), border-color var(--fd-dur-3) var(--fd-ease-out); }
1209.fd-frg-nav[data-scrolled], .fd-frg-nav[data-open] { background: rgba(10,10,10,0.78); border-bottom-color: var(--fd-frg-line); -webkit-backdrop-filter: saturate(160%) blur(14px); backdrop-filter: saturate(160%) blur(14px); }
1210.fd-frg-nav-in { display: flex; align-items: center; gap: 28px; max-width: 1200px; height: 64px; margin: 0 auto; padding: 0 32px; transition: height var(--fd-dur-3) var(--fd-ease-out); }
1211.fd-frg-nav[data-scrolled] .fd-frg-nav-in { height: 54px; }
1212.fd-frg-brand { display: inline-flex; align-items: center; gap: 9px; font: 600 1rem/1 var(--fd-frg-sans); letter-spacing: -0.02em; color: var(--fd-frg-fg); }
1213.fd-frg-nav-links { display: flex; gap: 4px; }
1214.fd-frg-nav-links a, .fd-frg-nav-ghost { display: inline-flex; align-items: center; height: 32px; padding: 0 12px; border-radius: 9999px; font: 400 0.875rem/1 var(--fd-frg-sans); color: var(--fd-frg-mute); transition: color var(--fd-dur-1) linear, background-color var(--fd-dur-1) linear; }
1215.fd-frg-nav-links a:hover, .fd-frg-nav-ghost:hover { color: var(--fd-frg-fg); background: var(--fd-frg-surface-2); }
1216.fd-frg-nav-right { display: flex; align-items: center; gap: 8px; margin-left: auto; }
1217.fd-frg-burger { display: none; position: relative; width: 40px; height: 40px; }
1218.fd-frg-burger span { position: absolute; left: 11px; right: 11px; height: 1.5px; background: var(--fd-frg-fg); transition: transform var(--fd-dur-3) var(--fd-spring); }
1219.fd-frg-burger span:first-child { top: 16px; }
1220.fd-frg-burger span:last-child { top: 23px; }
1221.fd-frg-nav[data-open] .fd-frg-burger span:first-child { transform: translateY(3.5px) rotate(45deg); }
1222.fd-frg-nav[data-open] .fd-frg-burger span:last-child { transform: translateY(-3.5px) rotate(-45deg); }
1223.fd-frg-sheet { display: grid; padding: 4px 20px 16px; }
1224.fd-frg-sheet[hidden] { display: none; }
1225.fd-frg-sheet a { padding: 14px 0; border-bottom: 1px solid var(--fd-frg-line); font: 500 1.125rem/1.2 var(--fd-frg-sans); }
1226
1227/* hero */
1228.fd-frg-hero { position: relative; padding: 96px 0 120px; margin-top: -64px; padding-top: 160px; overflow: hidden; isolation: isolate; }
1229.fd-frg-hero-grid {
1230 position: absolute; inset: 0; z-index: -1; pointer-events: none;
1231 background-image: linear-gradient(to right, var(--fd-frg-line) 1px, transparent 1px), linear-gradient(to bottom, var(--fd-frg-line) 1px, transparent 1px);
1232 background-size: 64px 64px; background-position: center -1px;
1233 -webkit-mask-image: radial-gradient(ellipse 60% 55% at 50% 30%, #000 0%, transparent 75%); mask-image: radial-gradient(ellipse 60% 55% at 50% 30%, #000 0%, transparent 75%);
1234}
1235.fd-frg-hero::after { content: ""; position: absolute; left: 50%; top: 220px; width: 900px; height: 500px; transform: translateX(-50%); z-index: -1; background: radial-gradient(closest-side, rgba(255,107,44,0.14), transparent); pointer-events: none; }
1236.fd-frg-hero-in { display: flex; flex-direction: column; align-items: center; text-align: center; }
1237.fd-frg-pill { display: inline-flex; align-items: center; gap: 10px; height: 32px; padding: 0 12px 0 4px; border: 1px solid var(--fd-frg-line-2); border-radius: 9999px; background: rgba(20,20,21,0.8); font: 400 0.8125rem/1 var(--fd-frg-sans); color: var(--fd-frg-mute); transition: border-color var(--fd-dur-2) var(--fd-ease-out), color var(--fd-dur-2); }
1238.fd-frg-pill:hover { border-color: rgba(255,255,255,0.3); color: var(--fd-frg-fg); }
1239.fd-frg-pill-new { display: inline-flex; align-items: center; height: 24px; padding: 0 9px; border-radius: 9999px; background: var(--fd-frg-accent-soft); color: var(--fd-frg-accent); font: 500 0.75rem/1 var(--fd-frg-mono-f); }
1240.fd-frg-h1 { display: flex; flex-direction: column; margin-top: 28px; font: 600 clamp(3rem, 8vw, 6.5rem)/0.96 var(--fd-frg-sans); letter-spacing: -0.055em; }
1241.fd-frg-h1-mute { color: var(--fd-frg-dim); }
1242.fd-frg-in { animation: fd-frg-in 1000ms var(--fd-ease-out) both; animation-delay: var(--d, 0ms); }
1243@keyframes fd-frg-in { from { opacity: 0; transform: translateY(16px); filter: blur(6px); } to { opacity: 1; transform: none; filter: none; } }
1244.fd-frg-lede { max-width: 36em; margin-top: 26px; font: 400 clamp(1.0625rem, 1.5vw, 1.25rem)/1.5 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1245.fd-frg-ctas { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 34px; }
1246.fd-frg-hero-term { width: 100%; max-width: 820px; margin-top: 64px; text-align: left; }
1247
1248/* terminal */
1249.fd-frg-term { position: relative; border: 1px solid var(--fd-frg-line-2); border-radius: 14px; background: linear-gradient(180deg, #121213, #0c0c0d); box-shadow: 0 0 0 1px rgba(0,0,0,0.6), 0 40px 80px -30px rgba(0,0,0,0.9), 0 0 120px -40px rgba(255,107,44,0.25); overflow: hidden; }
1250.fd-frg-term-bar { display: flex; align-items: center; gap: 12px; height: 40px; padding: 0 12px 0 14px; border-bottom: 1px solid var(--fd-frg-line); }
1251.fd-frg-term-dots { display: flex; gap: 6px; }
1252.fd-frg-term-dots i { width: 10px; height: 10px; border-radius: 50%; background: var(--fd-frg-surface-2); border: 1px solid var(--fd-frg-line-2); }
1253.fd-frg-term-title { flex: 1; font: 400 0.75rem/1 var(--fd-frg-mono-f); color: var(--fd-frg-dim); text-align: center; }
1254.fd-frg-term-replay { height: 24px; padding: 0 10px; border-radius: 9999px; border: 1px solid var(--fd-frg-line-2); font: 500 0.6875rem/1 var(--fd-frg-sans); color: var(--fd-frg-mute); transition: opacity var(--fd-dur-2); }
1255.fd-frg-term-replay:disabled { opacity: 0.35; }
1256.fd-frg-term-body { height: 320px; padding: 18px 20px; font: 400 0.8125rem/1.85 var(--fd-frg-mono-f); color: var(--fd-frg-fg); overflow: hidden; }
1257.fd-frg-tl { display: flex; gap: 10px; align-items: center; white-space: pre; animation: fd-frg-line 280ms var(--fd-ease-out) both; }
1258@keyframes fd-frg-line { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
1259.fd-frg-tl-cmd { animation: none; }
1260.fd-frg-tl-dim { color: var(--fd-frg-dim); }
1261.fd-frg-tl-out { color: var(--fd-frg-mute); }
1262.fd-frg-tl-ok { color: var(--fd-frg-fg); }
1263.fd-frg-tl-warn { color: var(--fd-frg-accent); }
1264.fd-frg-tl-sum { margin-top: 6px; color: var(--fd-frg-fg); font-weight: 500; }
1265.fd-frg-sum-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--fd-frg-add); box-shadow: 0 0 0 4px rgba(63,185,80,0.15); }
1266.fd-frg-prompt { color: var(--fd-frg-accent); }
1267.fd-frg-caret { display: inline-block; width: 8px; height: 1.1em; margin-left: -6px; background: var(--fd-frg-fg); animation: fd-frg-blink 1s steps(2, start) infinite; }
1268@keyframes fd-frg-blink { to { visibility: hidden; } }
1269
1270/* logos */
1271.fd-frg-logos { padding: 0 0 96px; }
1272.fd-frg-logos ul { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); margin-top: 28px; border-top: 1px solid var(--fd-frg-line); border-bottom: 1px solid var(--fd-frg-line); }
1273.fd-frg-logos li { display: grid; place-items: center; height: 84px; color: var(--fd-frg-mute); border-right: 1px solid var(--fd-frg-line); white-space: nowrap; transition: color var(--fd-dur-2) var(--fd-ease-out); }
1274.fd-frg-logos li:last-child { border-right: 0; }
1275.fd-frg-logos li:hover { color: var(--fd-frg-fg); }
1276.fd-frg-wm-0 { font: 600 1.0625rem/1 var(--fd-frg-sans); letter-spacing: -0.03em; }
1277.fd-frg-wm-1 { font: 400 1.125rem/1 var(--fd-frg-sans); letter-spacing: -0.04em; }
1278.fd-frg-wm-2 { font: 600 0.875rem/1 var(--fd-frg-mono-f); letter-spacing: 0.08em; }
1279.fd-frg-wm-3 { font: italic 500 1.125rem/1 Georgia, serif; }
1280
1281/* sections */
1282.fd-frg-sec { padding: 128px 0; border-top: 1px solid var(--fd-frg-line); }
1283.fd-frg-sec-alt { background: var(--fd-frg-bg-2); }
1284
1285/* timeline */
1286.fd-frg-tl-grid { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: 72px; align-items: start; }
1287.fd-frg-sticky-head { position: sticky; top: 120px; }
1288.fd-frg-steps { --draw: 0; position: relative; padding-left: 44px; }
1289.fd-frg-rail { position: absolute; left: 11px; top: 14px; bottom: 14px; width: 1px; background: var(--fd-frg-line-2); }
1290.fd-frg-rail i { position: absolute; inset: 0; background: linear-gradient(to bottom, var(--fd-frg-accent), #ffb08a); transform-origin: top; transform: scaleY(var(--draw)); box-shadow: 0 0 12px rgba(255,107,44,0.6); }
1291.fd-frg-steps li { position: relative; padding: 0 0 56px; opacity: 0.4; transition: opacity var(--fd-dur-3) var(--fd-ease-out); }
1292.fd-frg-steps li:last-child { padding-bottom: 0; }
1293.fd-frg-steps li[data-on] { opacity: 1; }
1294.fd-frg-node { position: absolute; left: -38px; top: 8px; width: 11px; height: 11px; border-radius: 50%; background: var(--fd-frg-bg); border: 1px solid var(--fd-frg-line-2); transition: background-color var(--fd-dur-2) var(--fd-ease-out), border-color var(--fd-dur-2), box-shadow var(--fd-dur-3) var(--fd-ease-out), transform var(--fd-dur-3) var(--fd-spring); }
1295.fd-frg-steps li[data-on] .fd-frg-node { background: var(--fd-frg-accent); border-color: var(--fd-frg-accent); box-shadow: 0 0 0 5px var(--fd-frg-accent-soft); transform: scale(1.1); }
1296.fd-frg-step-head { display: flex; gap: 12px; align-items: center; }
1297.fd-frg-step-t { color: var(--fd-frg-fg); font-variant-numeric: tabular-nums; }
1298.fd-frg-step-k { color: var(--fd-frg-dim); text-transform: uppercase; letter-spacing: 0.06em; }
1299.fd-frg-steps h3 { margin-top: 8px; font: 600 1.375rem/1.25 var(--fd-frg-sans); letter-spacing: -0.025em; }
1300.fd-frg-steps p { margin-top: 8px; max-width: 32em; font: 400 0.9688rem/1.55 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1301.fd-frg-step-meta { margin-top: 14px; }
1302.fd-frg-meter { display: inline-flex; align-items: center; gap: 10px; }
1303.fd-frg-meter > i { display: grid; grid-template-columns: repeat(46, 1fr); gap: 2px; width: 230px; height: 14px; background: repeating-linear-gradient(to right, var(--fd-frg-surface-2) 0 3px, transparent 3px 5px); }
1304.fd-frg-meter > i::before { content: ""; width: 3px; height: 100%; background: var(--fd-frg-accent); box-shadow: 0 0 8px var(--fd-frg-accent); }
1305.fd-frg-meter b { font: 400 0.75rem/1 var(--fd-frg-mono-f); color: var(--fd-frg-mute); white-space: nowrap; }
1306.fd-frg-avs { display: inline-flex; }
1307.fd-frg-avs img, .fd-frg-av { display: inline-block; flex: none; width: 28px; height: 28px; border-radius: 50%; object-fit: cover; object-position: center top; background: var(--fd-frg-surface-2); border: 1px solid var(--fd-frg-line-2); }
1308.fd-frg-avs img + img { margin-left: -8px; }
1309
1310/* diff */
1311.fd-frg-diff { margin-top: 56px; border: 1px solid var(--fd-frg-line-2); border-radius: 14px; background: var(--fd-frg-bg); overflow: hidden; }
1312.fd-frg-diff-bar { display: flex; align-items: center; gap: 16px; height: 46px; padding: 0 16px; border-bottom: 1px solid var(--fd-frg-line); color: var(--fd-frg-fg); }
1313.fd-frg-diff-stat { font: 500 0.75rem/1 var(--fd-frg-mono-f); }
1314.fd-frg-add { color: var(--fd-frg-add); }
1315.fd-frg-del { color: var(--fd-frg-del); }
1316.fd-frg-diff-status { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; font: 400 0.8125rem/1 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1317.fd-frg-spin { width: 12px; height: 12px; border-radius: 50%; border: 1.5px solid var(--fd-frg-line-2); border-top-color: var(--fd-frg-accent); }
1318.fd-frg-diff-status[data-on] .fd-frg-spin { animation: fd-frg-spin 0.8s linear infinite; }
1319.fd-frg-diff[data-stage="4"] .fd-frg-spin { animation: none; border-color: var(--fd-frg-accent); background: var(--fd-frg-accent); }
1320@keyframes fd-frg-spin { to { transform: rotate(360deg); } }
1321.fd-frg-diff-body { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr); }
1322.fd-frg-code { padding: 12px 0; font: 400 0.8125rem/1.9 var(--fd-frg-mono-f); overflow-x: auto; border-right: 1px solid var(--fd-frg-line); }
1323.fd-frg-dl { position: relative; display: grid; grid-template-columns: 40px 40px 22px max-content; min-width: 100%; white-space: pre; }
1324.fd-frg-dl::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, transparent, rgba(255,107,44,0.18), transparent); opacity: 0; pointer-events: none; }
1325.fd-frg-dl[data-scan]::after { animation: fd-frg-scan 900ms var(--fd-ease-out) both; animation-delay: calc(var(--i) * 70ms); }
1326@keyframes fd-frg-scan { 0% { opacity: 0; transform: translateX(-30%); } 40% { opacity: 1; } 100% { opacity: 0; transform: translateX(30%); } }
1327.fd-frg-ln { padding-right: 12px; color: #5f5f63; text-align: right; user-select: none; font-variant-numeric: tabular-nums; }
1328.fd-frg-sign { color: var(--fd-frg-dim); user-select: none; }
1329.fd-frg-src { padding-right: 20px; color: #d6d6d6; }
1330.fd-frg-dl-add { background: rgba(63,185,80,0.1); }
1331.fd-frg-dl-add .fd-frg-sign, .fd-frg-dl-add .fd-frg-ln { color: var(--fd-frg-add); }
1332.fd-frg-dl-del { background: rgba(242,85,90,0.1); }
1333.fd-frg-dl-del .fd-frg-sign, .fd-frg-dl-del .fd-frg-ln { color: var(--fd-frg-del); }
1334.fd-frg-dl-del .fd-frg-src { color: #b9b9b9; }
1335.fd-frg-comments { display: flex; flex-direction: column; gap: 12px; padding: 16px; background: var(--fd-frg-bg-2); }
1336.fd-frg-cmt { padding: 14px; border: 1px solid var(--fd-frg-line-2); border-radius: 12px; background: var(--fd-frg-surface); opacity: 0; transform: translateY(10px) scale(0.96); transform-origin: top left; transition: opacity var(--fd-dur-3) var(--fd-ease-out), transform var(--fd-dur-3) var(--fd-spring); }
1337.fd-frg-cmt[data-show] { opacity: 1; transform: none; }
1338.fd-frg-cmt header { display: flex; align-items: center; gap: 8px; font: 400 0.8125rem/1 var(--fd-frg-sans); }
1339.fd-frg-cmt header .fd-frg-mono { margin-left: auto; color: var(--fd-frg-dim); font-size: 0.6875rem; }
1340.fd-frg-bot { display: grid; place-items: center; width: 22px; height: 22px; border-radius: 6px; background: var(--fd-frg-accent-soft); color: var(--fd-frg-accent); }
1341.fd-frg-cmt .fd-frg-av { width: 22px; height: 22px; font-size: 0.5625rem; }
1342.fd-frg-cmt p { margin-top: 10px; font: 400 0.875rem/1.5 var(--fd-frg-sans); color: #c9c9c9; }
1343.fd-frg-cmt p code { color: var(--fd-frg-accent); }
1344.fd-frg-sugg { display: grid; gap: 2px; margin: 12px 0; padding: 10px; border: 1px solid var(--fd-frg-line); border-radius: 8px; background: var(--fd-frg-bg); overflow-x: auto; }
1345.fd-frg-sugg .fd-frg-mono { color: var(--fd-frg-dim); margin-bottom: 4px; font-size: 0.6875rem; }
1346.fd-frg-sugg code { white-space: pre-wrap; word-break: break-word; font-size: 0.75rem; }
1347.fd-frg-sugg code:nth-of-type(1) { color: var(--fd-frg-del); }
1348.fd-frg-sugg code:nth-of-type(2) { color: var(--fd-frg-add); }
1349.fd-frg-cmt-human { border-style: dashed; }
1350
1351/* speed */
1352.fd-frg-speed { --p: 0; position: relative; height: 320vh; border-top: 1px solid var(--fd-frg-line); background: var(--fd-frg-bg); }
1353.fd-frg-speed-stick { position: sticky; top: 0; height: 100vh; display: flex; align-items: center; overflow: hidden; }
1354.fd-frg-speed-in { display: grid; gap: 36px; }
1355.fd-frg-speed-head { display: flex; justify-content: space-between; align-items: flex-end; flex-wrap: wrap; gap: 16px; }
1356.fd-frg-speed-hint { color: var(--fd-frg-dim); }
1357.fd-frg-clock-row { display: grid; grid-template-columns: 1.3fr 1fr; border: 1px solid var(--fd-frg-line-2); border-radius: 14px; overflow: hidden; }
1358.fd-frg-clock { display: flex; flex-direction: column; gap: 8px; padding: 20px 24px; }
1359.fd-frg-clock .fd-frg-mono { color: var(--fd-frg-accent); text-transform: uppercase; letter-spacing: 0.06em; }
1360.fd-frg-clock-n { font: 600 clamp(3.5rem, 9vw, 7.5rem)/0.9 var(--fd-frg-sans); letter-spacing: -0.06em; font-variant-numeric: tabular-nums; }
1361.fd-frg-clock-legacy { border-left: 1px solid var(--fd-frg-line); background: repeating-linear-gradient(135deg, transparent 0 8px, rgba(255,255,255,0.025) 8px 9px); }
1362.fd-frg-clock-legacy .fd-frg-mono { color: var(--fd-frg-dim); }
1363.fd-frg-clock-legacy .fd-frg-clock-n { color: #5f5f63; }
1364.fd-frg-gantt { position: relative; display: grid; gap: 8px; padding: 30px 0 4px; }
1365.fd-frg-gantt-axis { position: absolute; left: 190px; right: 56px; top: 0; height: 18px; }
1366.fd-frg-gantt-axis span { position: absolute; transform: translateX(-50%); font: 400 0.6875rem/1 var(--fd-frg-mono-f); color: var(--fd-frg-dim); }
1367.fd-frg-job { --f: 0; display: grid; grid-template-columns: 190px 1fr 56px; align-items: center; height: 30px; }
1368.fd-frg-job-name { display: flex; align-items: center; gap: 10px; font: 400 0.875rem/1 var(--fd-frg-sans); color: var(--fd-frg-mute); transition: color var(--fd-dur-2); }
1369.fd-frg-job-name i { width: 8px; height: 8px; border-radius: 50%; border: 1px solid var(--fd-frg-line-2); transition: background-color var(--fd-dur-2), border-color var(--fd-dur-2), box-shadow var(--fd-dur-2); }
1370.fd-frg-job[data-state="run"] .fd-frg-job-name { color: var(--fd-frg-fg); }
1371.fd-frg-job[data-state="run"] .fd-frg-job-name i { border-color: var(--fd-frg-accent); box-shadow: 0 0 0 3px var(--fd-frg-accent-soft); }
1372.fd-frg-job[data-state="done"] .fd-frg-job-name { color: var(--fd-frg-fg); }
1373.fd-frg-job[data-state="done"] .fd-frg-job-name i { background: var(--fd-frg-fg); border-color: var(--fd-frg-fg); }
1374.fd-frg-job-track { position: relative; height: 100%; border-left: 1px solid var(--fd-frg-line); background-image: linear-gradient(to right, var(--fd-frg-line) 1px, transparent 1px); background-size: calc(100% / 88 * 15) 100%; }
1375.fd-frg-job-bar { position: absolute; top: 6px; bottom: 6px; border-radius: 5px; background: var(--fd-frg-surface-2); border: 1px solid var(--fd-frg-line-2); overflow: hidden; }
1376.fd-frg-job-bar i { position: absolute; inset: 0; background: linear-gradient(90deg, rgba(255,107,44,0.7), var(--fd-frg-accent)); transform-origin: left; transform: scaleX(var(--f)); }
1377.fd-frg-job[data-state="done"] .fd-frg-job-bar i { background: #e6e6e6; }
1378.fd-frg-job-dur { text-align: right; color: var(--fd-frg-dim); }
1379.fd-frg-playhead { position: absolute; top: 22px; bottom: 0; left: calc(190px + (100% - 190px - 56px) * var(--p)); width: 1px; background: var(--fd-frg-accent); box-shadow: 0 0 10px var(--fd-frg-accent); }
1380
1381/* bento */
1382.fd-frg-bento { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; margin-top: 56px; border: 1px solid var(--fd-frg-line); border-radius: 16px; background: var(--fd-frg-line); overflow: hidden; }
1383.fd-frg-tile { display: flex; flex-direction: column; padding: 28px; background: var(--fd-frg-bg); min-height: 360px; }
1384.fd-frg-tile-wide { grid-column: span 2; }
1385.fd-frg-tile h3 { margin-top: auto; padding-top: 24px; font: 600 1.1875rem/1.3 var(--fd-frg-sans); letter-spacing: -0.02em; }
1386.fd-frg-tile p { margin-top: 8px; max-width: 34em; font: 400 0.9375rem/1.55 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1387.fd-frg-tile-vis { flex: 1; min-height: 160px; }
1388.fd-frg-vis-stack { display: flex; flex-direction: column; justify-content: center; gap: 8px; }
1389.fd-frg-vis-stack div { min-width: 0; display: flex; align-items: center; gap: 10px; height: 44px; padding: 0 14px; border: 1px solid var(--fd-frg-line-2); border-radius: 10px; background: var(--fd-frg-surface); margin-left: calc(var(--i) * 28px); font: 400 0.8125rem/1 var(--fd-frg-mono-f); color: var(--fd-frg-fg); transition: transform var(--fd-dur-3) var(--fd-spring); }
1390.fd-frg-tile:hover .fd-frg-vis-stack div { transform: translateX(calc(var(--i) * 6px)); }
1391.fd-frg-vis-stack svg { color: var(--fd-frg-dim); flex: none; }
1392.fd-frg-vis-stack span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1393.fd-frg-vis-stack em { margin-left: auto; font-size: 0.6875rem; color: var(--fd-frg-dim); }
1394.fd-frg-vis-stack div:last-child em { color: var(--fd-frg-accent); }
1395.fd-frg-vis-flake { display: grid; grid-template-columns: repeat(12, 1fr); gap: 6px; align-content: center; }
1396.fd-frg-vis-flake i { aspect-ratio: 1; border-radius: 3px; background: var(--fd-frg-surface-2); }
1397.fd-frg-vis-flake i[data-f] { background: var(--fd-frg-accent); animation: fd-frg-flake 2.4s var(--fd-ease-in-out) infinite; }
1398@keyframes fd-frg-flake { 50% { opacity: 0.35; } }
1399.fd-frg-vis-cache { display: flex; flex-direction: column; justify-content: center; }
1400.fd-frg-vis-cache span { font: 600 4.5rem/1 var(--fd-frg-sans); letter-spacing: -0.06em; background: linear-gradient(180deg, #fff, #6b6b6b); -webkit-background-clip: text; background-clip: text; color: transparent; }
1401.fd-frg-vis-cache small { margin-top: 8px; font: 400 0.75rem/1 var(--fd-frg-mono-f); color: var(--fd-frg-dim); }
1402.fd-frg-vis-chart { position: relative; }
1403.fd-frg-vis-chart svg { width: 100%; height: 100%; min-height: 160px; }
1404.fd-frg-chart-tag { position: absolute; left: 41%; top: 6px; font: 400 0.6875rem/1 var(--fd-frg-mono-f); color: var(--fd-frg-mute); }
1405
1406/* integrations */
1407.fd-frg-int {
1408 --mx: -999px; --my: -999px; position: relative; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; margin-top: 56px;
1409 border-radius: 16px; overflow: hidden; padding: 1px;
1410 background: radial-gradient(360px circle at var(--mx) var(--my), rgba(255,107,44,0.75), transparent 60%), var(--fd-frg-line);
1411}
1412.fd-frg-int-cell { --mx: -999px; --my: -999px; position: relative; display: flex; flex-direction: column; gap: 6px; min-height: 148px; padding: 22px; background: var(--fd-frg-bg-2); color: var(--fd-frg-fg); transition: background-color var(--fd-dur-2) var(--fd-ease-out); }
1413.fd-frg-int-cell::before { content: ""; position: absolute; inset: 0; background: radial-gradient(260px circle at var(--mx) var(--my), rgba(255,107,44,0.09), transparent 65%); opacity: 0; transition: opacity var(--fd-dur-3) var(--fd-ease-out); pointer-events: none; }
1414.fd-frg-int[data-hot] .fd-frg-int-cell::before { opacity: 1; }
1415.fd-frg-int-cell svg { margin-bottom: auto; color: var(--fd-frg-mute); transition: color var(--fd-dur-2), transform var(--fd-dur-3) var(--fd-spring); }
1416.fd-frg-int-cell:hover svg { color: var(--fd-frg-accent); transform: scale(1.08); }
1417.fd-frg-int-cell b { position: relative; margin-top: 22px; font: 500 0.9688rem/1.2 var(--fd-frg-sans); letter-spacing: -0.01em; }
1418.fd-frg-int-cell span { position: relative; font: 400 0.8125rem/1.4 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1419
1420/* proof */
1421.fd-frg-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border-top: 1px solid var(--fd-frg-line-2); border-bottom: 1px solid var(--fd-frg-line); }
1422.fd-frg-stats div { padding: 28px 24px 28px 0; }
1423.fd-frg-stats div + div { padding-left: 24px; border-left: 1px solid var(--fd-frg-line); }
1424.fd-frg-stats dt { font: 600 clamp(1.75rem, 3vw, 2.5rem)/1 var(--fd-frg-sans); letter-spacing: -0.045em; white-space: nowrap; }
1425.fd-frg-stats dd { margin-top: 10px; font: 400 0.875rem/1.45 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1426.fd-frg-quote { max-width: 900px; margin: 96px auto 0; text-align: center; }
1427.fd-frg-quote blockquote { font: 500 clamp(1.5rem, 2.8vw, 2.25rem)/1.25 var(--fd-frg-sans); letter-spacing: -0.03em; }
1428.fd-frg-quote figcaption { display: inline-flex; align-items: center; gap: 12px; margin-top: 32px; text-align: left; }
1429.fd-frg-av-lg { width: 40px; height: 40px; font-size: 0.75rem; }
1430.fd-frg-quote b { display: block; font: 500 0.9375rem/1.3 var(--fd-frg-sans); }
1431.fd-frg-quote small { display: block; font: 400 0.8125rem/1.3 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1432
1433/* pricing */
1434.fd-frg-toggle { position: relative; display: inline-grid; grid-template-columns: 1fr 1fr; padding: 3px; border: 1px solid var(--fd-frg-line-2); border-radius: 9999px; background: var(--fd-frg-bg-2); }
1435.fd-frg-toggle button { position: relative; z-index: 1; height: 34px; padding: 0 16px; border-radius: 9999px; font: 500 0.8125rem/1 var(--fd-frg-sans); color: var(--fd-frg-mute); transition: color var(--fd-dur-2); }
1436.fd-frg-toggle button[aria-pressed="true"] { color: var(--fd-frg-bg); }
1437.fd-frg-toggle em { color: var(--fd-frg-accent); font-size: 0.75rem; }
1438.fd-frg-toggle button[aria-pressed="true"] em { color: #b5420f; }
1439.fd-frg-toggle i { position: absolute; top: 3px; bottom: 3px; left: 3px; width: calc(50% - 3px); border-radius: 9999px; background: var(--fd-frg-fg); transition: transform var(--fd-dur-3) var(--fd-spring); }
1440.fd-frg-toggle i[data-right] { transform: translateX(100%); }
1441.fd-frg-plans { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-top: 56px; align-items: stretch; }
1442.fd-frg-plan { display: flex; flex-direction: column; padding: 28px; border: 1px solid var(--fd-frg-line-2); border-radius: 16px; background: var(--fd-frg-bg-2); }
1443.fd-frg-plan-hot { border-color: rgba(255,107,44,0.45); background: linear-gradient(180deg, rgba(255,107,44,0.07), transparent 40%), var(--fd-frg-bg-2); box-shadow: 0 0 0 1px rgba(255,107,44,0.15), 0 30px 80px -40px rgba(255,107,44,0.4); }
1444.fd-frg-plan-top { display: flex; justify-content: space-between; align-items: center; }
1445.fd-frg-plan h3 { font: 600 1.125rem/1.2 var(--fd-frg-sans); letter-spacing: -0.02em; }
1446.fd-frg-plan-p { margin-top: 6px; font: 400 0.875rem/1.45 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1447.fd-frg-price { display: flex; align-items: baseline; gap: 6px; margin-top: 28px; }
1448.fd-frg-price span { font: 600 2.75rem/1 var(--fd-frg-sans); letter-spacing: -0.05em; font-variant-numeric: tabular-nums; }
1449.fd-frg-price small { font: 400 0.9375rem/1 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1450.fd-frg-plan-calc { margin-top: 8px; color: var(--fd-frg-dim); }
1451.fd-frg-range { --pct: 50%; display: block; margin-top: 18px; }
1452.fd-frg-range input { -webkit-appearance: none; appearance: none; display: block; width: 100%; height: 22px; margin: 0; background: transparent; cursor: pointer; }
1453.fd-frg-range input::-webkit-slider-runnable-track { height: 4px; border-radius: 9999px; background: linear-gradient(to right, var(--fd-frg-accent) var(--pct), var(--fd-frg-surface-2) var(--pct)); }
1454.fd-frg-range input::-moz-range-track { height: 4px; border-radius: 9999px; background: linear-gradient(to right, var(--fd-frg-accent) var(--pct), var(--fd-frg-surface-2) var(--pct)); }
1455.fd-frg-range input::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; margin-top: -7px; border-radius: 50%; background: #fff; border: 0; box-shadow: 0 0 0 4px var(--fd-frg-accent-soft), 0 2px 6px rgba(0,0,0,0.5); transition: transform var(--fd-dur-2) var(--fd-spring); }
1456.fd-frg-range input:active::-webkit-slider-thumb { transform: scale(1.15); }
1457.fd-frg-range input::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: #fff; border: 0; box-shadow: 0 0 0 4px var(--fd-frg-accent-soft); }
1458.fd-frg-plan ul { display: grid; gap: 10px; margin: 24px 0 28px; }
1459.fd-frg-plan li { display: flex; gap: 10px; align-items: center; font: 400 0.875rem/1.4 var(--fd-frg-sans); color: #d0d0d0; }
1460.fd-frg-plan li svg { color: var(--fd-frg-accent); flex: none; }
1461.fd-frg-plan .fd-frg-btn { margin-top: auto; width: 100%; }
1462
1463/* cta */
1464.fd-frg-cta { position: relative; padding: 160px 0; border-top: 1px solid var(--fd-frg-line); overflow: hidden; isolation: isolate; }
1465.fd-frg-cta::after { content: ""; position: absolute; left: 50%; top: 50%; width: 800px; height: 420px; transform: translate(-50%, -50%); background: radial-gradient(closest-side, rgba(255,107,44,0.16), transparent); z-index: -1; }
1466.fd-frg-cta-in { display: flex; flex-direction: column; align-items: center; text-align: center; }
1467.fd-frg-cta-in .fd-frg-p { margin-left: auto; margin-right: auto; }
1468.fd-frg-cta-h { max-width: 14em; margin-top: 28px; font: 600 clamp(2.25rem, 5.5vw, 4.5rem)/1 var(--fd-frg-sans); letter-spacing: -0.05em; }
1469
1470/* footer */
1471.fd-frg-foot { padding: 72px 0 32px; border-top: 1px solid var(--fd-frg-line); background: var(--fd-frg-bg); }
1472.fd-frg-foot-top { display: grid; grid-template-columns: 1.6fr repeat(3, minmax(0, 1fr)); gap: 32px; }
1473.fd-frg-foot-p { margin-top: 14px; max-width: 22em; font: 400 0.875rem/1.5 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1474.fd-frg-status { display: inline-flex; align-items: center; gap: 8px; margin-top: 18px; height: 28px; padding: 0 12px; border: 1px solid var(--fd-frg-line-2); border-radius: 9999px; font: 400 0.75rem/1 var(--fd-frg-sans); color: var(--fd-frg-mute); }
1475.fd-frg-status i { width: 7px; height: 7px; border-radius: 50%; background: var(--fd-frg-add); }
1476.fd-frg-foot-col h4 { font: 500 0.8125rem/1 var(--fd-frg-sans); color: var(--fd-frg-fg); }
1477.fd-frg-foot-col ul { display: grid; gap: 10px; margin-top: 16px; }
1478.fd-frg-foot-col a { font: 400 0.875rem/1.3 var(--fd-frg-sans); color: var(--fd-frg-mute); transition: color var(--fd-dur-1) linear; }
1479.fd-frg-foot-col a:hover { color: var(--fd-frg-fg); }
1480.fd-frg-foot-bot { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-top: 64px; padding-top: 20px; border-top: 1px solid var(--fd-frg-line); font: 400 0.8125rem/1.3 var(--fd-frg-sans); color: var(--fd-frg-dim); }
1481
1482/* reveal */
1483.fd-frg[data-js] [data-reveal] { opacity: 0; transform: translateY(18px); transition: opacity var(--fd-dur-4) var(--fd-ease-out), transform 900ms var(--fd-ease-out); transition-delay: calc(var(--i, 0) * 70ms); }
1484.fd-frg[data-js] [data-reveal][data-in] { opacity: 1; transform: none; }
1485
1486/* responsive */
1487@media (max-width: 1000px) {
1488 .fd-frg-nav-links { display: none; }
1489 .fd-frg-burger { display: block; }
1490 .fd-frg-logos ul { grid-template-columns: repeat(4, minmax(0, 1fr)); }
1491 .fd-frg-logos li:nth-child(4) { border-right: 0; }
1492 .fd-frg-logos li:nth-child(-n + 4) { border-bottom: 1px solid var(--fd-frg-line); }
1493 .fd-frg-tl-grid { grid-template-columns: 1fr; gap: 48px; }
1494 .fd-frg-sticky-head { position: static; }
1495 .fd-frg-diff-body { grid-template-columns: 1fr; }
1496 .fd-frg-code { border-right: 0; border-bottom: 1px solid var(--fd-frg-line); }
1497 .fd-frg-int { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1498 .fd-frg-plans { grid-template-columns: 1fr; max-width: 520px; }
1499 .fd-frg-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1500 .fd-frg-stats div:nth-child(3) { padding-left: 0; border-left: 0; }
1501 .fd-frg-stats div:nth-child(n + 3) { border-top: 1px solid var(--fd-frg-line); }
1502}
1503@media (max-width: 720px) {
1504 .fd-frg-wrap, .fd-frg-nav-in { padding: 0 20px; }
1505 .fd-frg-nav-ghost { display: none; }
1506 .fd-frg-hero { padding-top: 120px; padding-bottom: 88px; }
1507 .fd-frg-hero-term { margin-top: 44px; }
1508 .fd-frg-term-body { height: 300px; padding: 14px; font-size: 0.6875rem; line-height: 1.8; }
1509 .fd-frg-term-title { display: none; }
1510 .fd-frg-term-bar { justify-content: space-between; }
1511 .fd-frg-sec { padding: 88px 0; }
1512 .fd-frg-logos ul { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1513 .fd-frg-logos li { height: 64px; border-bottom: 1px solid var(--fd-frg-line); }
1514 .fd-frg-logos li:nth-child(2n) { border-right: 0; }
1515 .fd-frg-logos li:nth-child(4) { border-right: 0; }
1516 .fd-frg-logos li:nth-child(n + 7) { border-bottom: 0; }
1517 .fd-frg-steps { padding-left: 36px; }
1518 .fd-frg-node { left: -30px; }
1519 .fd-frg-rail { left: 10px; }
1520 .fd-frg-meter > i { width: 150px; }
1521 .fd-frg-diff-bar { flex-wrap: wrap; height: auto; padding: 12px 14px; row-gap: 8px; }
1522 .fd-frg-diff-status { margin-left: 0; width: 100%; }
1523 .fd-frg-code { font-size: 0.6875rem; }
1524 .fd-frg-dl { grid-template-columns: 28px 28px 16px max-content; }
1525 .fd-frg-speed { height: 300vh; }
1526 .fd-frg-speed-in { gap: 24px; }
1527 .fd-frg-speed-hint { color: var(--fd-frg-dim); }
1528.fd-frg-clock-row { grid-template-columns: 1fr 1fr; }
1529 .fd-frg-clock { padding: 14px 16px; }
1530 .fd-frg-clock-n { font-size: 2.75rem; }
1531 .fd-frg-clock .fd-frg-mono { font-size: 0.625rem; }
1532 .fd-frg-gantt-axis { left: 104px; right: 0; }
1533 .fd-frg-job { grid-template-columns: 104px 1fr 0; height: 28px; }
1534 .fd-frg-job-name { font-size: 0.6875rem; gap: 6px; }
1535 .fd-frg-job-dur { display: none; }
1536 .fd-frg-playhead { left: calc(104px + (100% - 104px) * var(--p)); }
1537 .fd-frg-bento { grid-template-columns: 1fr; }
1538 .fd-frg-tile-wide { grid-column: auto; }
1539 .fd-frg-tile { min-height: 300px; padding: 22px; }
1540 .fd-frg-vis-stack div { margin-left: calc(var(--i) * 12px); padding: 0 10px; gap: 8px; font-size: 0.6875rem; min-width: 0; }
1541 .fd-frg-vis-stack em { display: none; }
1542 .fd-frg-vis-flake { grid-template-columns: repeat(8, 1fr); }
1543 .fd-frg-vis-flake i:nth-child(n + 33) { display: none; }
1544 .fd-frg-pill { max-width: 100%; }
1545 .fd-frg-pill-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1546 .fd-frg-int { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1547 .fd-frg-int-cell { min-height: 132px; padding: 16px; }
1548 .fd-frg-stats { grid-template-columns: 1fr; }
1549 .fd-frg-stats div, .fd-frg-stats div + div { padding: 20px 0; border-left: 0; }
1550 .fd-frg-stats div + div { border-top: 1px solid var(--fd-frg-line); }
1551 .fd-frg-quote { margin-top: 64px; }
1552 .fd-frg-cta { padding: 104px 0; }
1553 .fd-frg-foot-top { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1554 .fd-frg-foot-top > div:first-child { grid-column: 1 / -1; }
1555}
1556@media (prefers-reduced-motion: reduce) {
1557 .fd-frg *, .fd-frg *::before, .fd-frg *::after { animation: none !important; }
1558 .fd-frg[data-js] [data-reveal] { transform: none; transition: opacity 120ms linear; transition-delay: 0ms; }
1559 .fd-frg-cmt { transform: none; transition: opacity 120ms linear; }
1560 .fd-frg-speed { height: auto; padding: 96px 0; }
1561 .fd-frg-speed-stick { position: relative; height: auto; }
1562 .fd-frg-caret { display: none; }
1563}
1564@media (prefers-contrast: more) {
1565 .fd-frg { --fd-frg-mute: #cfcfcf; --fd-frg-dim: #b0b0b0; --fd-frg-line: rgba(255,255,255,0.3); --fd-frg-line-2: rgba(255,255,255,0.45); }
1566}
1567@media (prefers-reduced-transparency: reduce) {
1568 .fd-frg-nav[data-scrolled], .fd-frg-nav[data-open] { background: var(--fd-frg-bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
1569}
1570`;