From 9789969ecf4ef6b5a56f9c1a4480fa3a6f83740d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Wed, 22 Jul 2026 04:24:59 +0200 Subject: [PATCH] Improve docs landing page --- packages/docs/src/pages/index.module.css | 650 +++++++++++++++++++++-- packages/docs/src/pages/index.tsx | 221 ++++++-- 2 files changed, 793 insertions(+), 78 deletions(-) diff --git a/packages/docs/src/pages/index.module.css b/packages/docs/src/pages/index.module.css index 8d4db16..1104372 100644 --- a/packages/docs/src/pages/index.module.css +++ b/packages/docs/src/pages/index.module.css @@ -1,80 +1,652 @@ .page { + --home-ink: #102f31; + --home-muted: #506a6c; + --home-teal: #08777b; + --home-teal-bright: #16a3a1; + --home-amber: #e99526; + --home-paper: #fffdf8; + --home-card: rgba(255, 255, 255, 0.76); + --home-line: rgba(16, 47, 49, 0.12); min-height: calc(100vh - var(--ifm-navbar-height)); + overflow: hidden; + position: relative; background: - radial-gradient(circle at top left, rgba(11, 111, 116, 0.22), transparent 32rem), - radial-gradient(circle at 85% 20%, rgba(244, 166, 54, 0.18), transparent 28rem), - linear-gradient(135deg, #f8f4ea 0%, #eef8f7 48%, #fffaf1 100%); + radial-gradient(circle at 8% 5%, rgba(14, 145, 148, 0.2), transparent 28rem), + radial-gradient(circle at 92% 14%, rgba(240, 159, 48, 0.2), transparent 27rem), + linear-gradient(145deg, #f7f2e7 0%, #edf8f6 48%, #fff8ed 100%); + color: var(--home-ink); +} + +.gridBackdrop { + background-image: + linear-gradient(rgba(16, 47, 49, 0.045) 1px, transparent 1px), + linear-gradient(90deg, rgba(16, 47, 49, 0.045) 1px, transparent 1px); + background-size: 48px 48px; + inset: 0; + mask-image: linear-gradient(to bottom, black, transparent 62%); + pointer-events: none; + position: absolute; } .hero { - padding: 8rem 1rem 5rem; + align-items: center; + display: grid; + gap: clamp(3rem, 6vw, 6rem); + grid-template-columns: minmax(0, 0.88fr) minmax(560px, 1.12fr); + min-height: min(820px, calc(100vh - var(--ifm-navbar-height))); + padding-bottom: 6rem; + padding-top: 6rem; + position: relative; + z-index: 1; } -.eyebrow { - color: #0b6f74; - font-size: 0.85rem; - font-weight: 800; - letter-spacing: 0.16em; - margin-bottom: 1.25rem; +.heroCopy { + max-width: 650px; +} + +.eyebrow, +.sectionKicker { + align-items: center; + color: var(--home-teal); + display: flex; + font-size: 0.76rem; + font-weight: 850; + gap: 0.65rem; + letter-spacing: 0.18em; + margin: 0 0 1.35rem; text-transform: uppercase; } +.pulse { + background: var(--home-teal-bright); + border-radius: 50%; + box-shadow: 0 0 0 5px rgba(22, 163, 161, 0.14); + height: 0.55rem; + width: 0.55rem; +} + .hero h1 { - color: #132c2f; - font-size: clamp(3rem, 9vw, 6.75rem); - letter-spacing: -0.08em; - line-height: 0.9; + color: var(--home-ink); + font-size: clamp(3.4rem, 6.4vw, 6.4rem); + font-weight: 780; + letter-spacing: -0.075em; + line-height: 0.92; margin: 0; - max-width: 880px; +} + +.hero h1 span { + background: linear-gradient(105deg, var(--home-teal) 5%, #1a9793 48%, var(--home-amber)); + background-clip: text; + color: transparent; + display: inline; + -webkit-background-clip: text; } .lede { - color: #385256; - font-size: clamp(1.15rem, 2vw, 1.45rem); + color: var(--home-muted); + font-size: clamp(1.08rem, 1.55vw, 1.32rem); line-height: 1.65; margin: 2rem 0 0; - max-width: 720px; + max-width: 630px; } .actions { display: flex; flex-wrap: wrap; - gap: 1rem; - margin-top: 2.5rem; + gap: 0.8rem; + margin-top: 2.3rem; } -.cards { +.primaryAction, +.secondaryAction, +.ctaAction { + align-items: center; + border-radius: 999px; + display: inline-flex; + font-weight: 750; + gap: 0.8rem; + justify-content: center; + padding: 0.88rem 1.35rem; + text-decoration: none !important; + transition: transform 160ms ease, box-shadow 160ms ease, background 160ms ease; +} + +.primaryAction { + background: var(--home-ink); + box-shadow: 0 12px 28px rgba(16, 47, 49, 0.2); + color: #f6fffd; +} + +.primaryAction:hover { + box-shadow: 0 16px 36px rgba(16, 47, 49, 0.28); + color: #fff; + transform: translateY(-2px); +} + +.secondaryAction { + background: rgba(255, 255, 255, 0.58); + border: 1px solid var(--home-line); + color: var(--home-ink); +} + +.secondaryAction:hover { + background: rgba(255, 255, 255, 0.9); + color: var(--home-teal); + transform: translateY(-2px); +} + +.install { + align-items: center; + color: var(--home-muted); + display: flex; + font-size: 0.84rem; + gap: 0.7rem; + margin-top: 1.4rem; +} + +.install span { + color: var(--home-amber); + font-family: var(--ifm-font-family-monospace); + font-weight: 800; +} + +.install code { + background: transparent; + border: 0; + color: inherit; + padding: 0; +} + +.codeStage { + min-width: 0; + perspective: 1200px; + position: relative; +} + +.codeGlow { + background: linear-gradient(115deg, rgba(11, 133, 136, 0.48), rgba(238, 156, 40, 0.38)); + filter: blur(55px); + inset: 8% 5%; + opacity: 0.55; + position: absolute; +} + +.codeWindow { + background: #101b1e; + border: 1px solid rgba(166, 225, 220, 0.2); + border-radius: 1.25rem; + box-shadow: + 0 34px 90px rgba(18, 49, 52, 0.3), + 0 2px 0 rgba(255, 255, 255, 0.06) inset; + overflow: hidden; + position: relative; + transform: rotateY(-2deg) rotateX(1deg); +} + +.windowBar { + align-items: center; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + color: #b6c9c9; display: grid; - gap: 1.25rem; - grid-template-columns: repeat(3, minmax(0, 1fr)); - padding: 0 1rem 6rem; + font-family: var(--ifm-font-family-monospace); + font-size: 0.73rem; + grid-template-columns: 1fr auto 1fr; + padding: 0.78rem 1rem; } -.cards article { - background: rgba(255, 255, 255, 0.74); - border: 1px solid rgba(19, 44, 47, 0.12); - border-radius: 1.5rem; - box-shadow: 0 24px 80px rgba(19, 44, 47, 0.08); - padding: 1.5rem; +.windowDots { + display: flex; + gap: 0.38rem; } -.cards h2 { - color: #132c2f; - font-size: 1.2rem; - margin-bottom: 0.75rem; +.windowDots span { + background: #4f6d6e; + border-radius: 50%; + height: 0.55rem; + width: 0.55rem; } -.cards p { - color: #486267; +.windowDots span:first-child { + background: #ef8d5b; +} + +.windowDots span:nth-child(2) { + background: #e9bc50; +} + +.windowDots span:last-child { + background: #65bd92; +} + +.windowStatus { + color: #74d2cd; + justify-self: end; +} + +.codeWindow :global(.theme-code-block) { + border-radius: 0; + box-shadow: none; margin: 0; } +.codeWindow :global(.theme-code-block pre) { + background: #101b1e !important; + color: #d7e5e4 !important; + font-size: 0.78rem; + line-height: 1.62; + max-height: none; + padding: 1.45rem 1.6rem !important; +} + +.codeWindow :global(.token-line), +.codeWindow :global(.token.plain), +.codeWindow :global(.token.imports), +.codeWindow :global(.token.maybe-class-name), +.codeWindow :global(.token.punctuation), +.codeWindow :global(.token.operator), +.codeWindow :global(.token.plain-text), +.codeWindow :global(.token.property-access) { + color: #d7e5e4 !important; +} + +.codeWindow :global(.token.keyword) { + color: #c792ea !important; +} + +.codeWindow :global(.token.string) { + color: #c3e88d !important; +} + +.codeWindow :global(.token.function), +.codeWindow :global(.token.method) { + color: #82aaff !important; +} + +.codeWindow :global(.token.tag) { + color: #f07178 !important; +} + +.codeWindow :global(.token.builtin) { + color: #ffcb6b !important; +} + +.codeLegend { + border-top: 1px solid rgba(255, 255, 255, 0.08); + color: #a9bcbc; + display: grid; + font-size: 0.7rem; + grid-template-columns: repeat(3, 1fr); +} + +.codeLegend span { + padding: 0.8rem 0.9rem; + text-align: center; +} + +.codeLegend span + span { + border-left: 1px solid rgba(255, 255, 255, 0.08); +} + +.codeLegend b { + color: #e8a94b; + font-family: var(--ifm-font-family-monospace); + margin-right: 0.25rem; +} + +.featuresSection { + padding-bottom: 7rem; + padding-top: 4rem; + position: relative; + z-index: 1; +} + +.sectionHeading { + display: grid; + gap: 0 3rem; + grid-template-columns: minmax(0, 1.25fr) minmax(280px, 0.75fr); + margin-bottom: 2.8rem; +} + +.sectionHeading .sectionKicker { + grid-column: 1 / -1; +} + +.sectionHeading h2, +.cta h2 { + color: var(--home-ink); + font-size: clamp(2.25rem, 4vw, 4rem); + letter-spacing: -0.055em; + line-height: 1; + margin: 0; +} + +.sectionHeading > p:last-child { + align-self: end; + color: var(--home-muted); + font-size: 1.03rem; + line-height: 1.65; + margin: 0; +} + +.featureGrid { + display: grid; + gap: 1rem; + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.featureCard { + background: var(--home-card); + border: 1px solid var(--home-line); + border-radius: 1.35rem; + color: inherit; + display: flex; + flex-direction: column; + min-height: 310px; + overflow: hidden; + padding: 1.45rem; + position: relative; + text-decoration: none !important; + transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease; +} + +.featureCard::before { + background: linear-gradient(90deg, var(--home-teal-bright), var(--home-amber)); + content: ""; + height: 3px; + left: 1.45rem; + opacity: 0; + position: absolute; + right: 1.45rem; + top: 0; + transform: scaleX(0.5); + transition: opacity 180ms ease, transform 180ms ease; +} + +.featureCard:hover { + border-color: rgba(8, 119, 123, 0.35); + box-shadow: 0 24px 60px rgba(28, 65, 66, 0.12); + color: inherit; + transform: translateY(-5px); +} + +.featureCard:hover::before { + opacity: 1; + transform: scaleX(1); +} + +.cardTopline { + align-items: center; + display: flex; + justify-content: space-between; +} + +.featureIcon { + align-items: center; + background: rgba(8, 119, 123, 0.1); + border: 1px solid rgba(8, 119, 123, 0.12); + border-radius: 0.8rem; + color: var(--home-teal); + display: flex; + font-family: var(--ifm-font-family-monospace); + font-size: 1rem; + font-weight: 850; + height: 2.7rem; + justify-content: center; + width: 2.7rem; +} + +.cardIndex { + color: rgba(16, 47, 49, 0.32); + font-family: var(--ifm-font-family-monospace); + font-size: 0.72rem; +} + +.featureCard h3 { + color: var(--home-ink); + font-size: 1.25rem; + letter-spacing: -0.025em; + margin: 2rem 0 0.7rem; +} + +.featureCard > p { + color: var(--home-muted); + font-size: 0.94rem; + line-height: 1.65; + margin: 0; +} + +.cardFooter { + align-items: center; + display: flex; + justify-content: space-between; + margin-top: auto; + padding-top: 1.5rem; +} + +.cardFooter code { + background: rgba(8, 119, 123, 0.08); + border: 0; + color: var(--home-teal); + font-size: 0.7rem; + padding: 0.35rem 0.5rem; +} + +.cardFooter > span { + color: var(--home-amber); + font-size: 1.1rem; + transition: transform 180ms ease; +} + +.featureCard:hover .cardFooter > span { + transform: translate(3px, -3px); +} + +.ctaWrap { + padding-bottom: 7rem; + position: relative; + z-index: 1; +} + +.cta { + align-items: center; + background: + radial-gradient(circle at 90% 20%, rgba(233, 149, 38, 0.22), transparent 20rem), + linear-gradient(125deg, #12383a, #10292c); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 1.75rem; + box-shadow: 0 28px 80px rgba(16, 47, 49, 0.2); + display: flex; + gap: 3rem; + justify-content: space-between; + overflow: hidden; + padding: clamp(2rem, 5vw, 4rem); +} + +.cta .sectionKicker { + color: #79d5d1; +} + +.cta h2 { + color: #f7fffd; +} + +.cta p:last-child { + color: #b9cccc; + line-height: 1.65; + margin: 1rem 0 0; + max-width: 650px; +} + +.ctaAction { + background: #f0a13b; + color: #162d2e; + flex: 0 0 auto; +} + +.ctaAction:hover { + box-shadow: 0 14px 35px rgba(0, 0, 0, 0.24); + color: #102526; + transform: translateY(-2px); +} + +:global([data-theme="dark"]) .page { + --home-ink: #e9f7f5; + --home-muted: #a9bfbd; + --home-teal: #79d5d1; + --home-teal-bright: #5bc8c5; + --home-amber: #f1ad4e; + --home-card: rgba(20, 42, 45, 0.76); + --home-line: rgba(174, 221, 218, 0.14); + background: + radial-gradient(circle at 8% 5%, rgba(33, 154, 154, 0.17), transparent 28rem), + radial-gradient(circle at 92% 14%, rgba(209, 130, 31, 0.13), transparent 27rem), + linear-gradient(145deg, #0f1b1e 0%, #102426 48%, #161e1e 100%); +} + +:global([data-theme="dark"]) .gridBackdrop { + background-image: + linear-gradient(rgba(174, 221, 218, 0.04) 1px, transparent 1px), + linear-gradient(90deg, rgba(174, 221, 218, 0.04) 1px, transparent 1px); +} + +:global([data-theme="dark"]) .secondaryAction { + background: rgba(19, 43, 46, 0.75); +} + +:global([data-theme="dark"]) .secondaryAction:hover { + background: rgba(27, 57, 60, 0.95); +} + +:global([data-theme="dark"]) .primaryAction { + background: #dff8f5; + color: #123638; +} + +:global([data-theme="dark"]) .primaryAction:hover { + color: #0b282a; +} + +:global([data-theme="dark"]) .cardIndex { + color: rgba(220, 242, 239, 0.35); +} + +@media screen and (max-width: 1180px) { + .hero { + gap: 3rem; + grid-template-columns: minmax(0, 0.85fr) minmax(500px, 1.15fr); + } + + .hero h1 { + font-size: clamp(3.2rem, 6vw, 5rem); + } +} + @media screen and (max-width: 996px) { .hero { - padding-top: 5rem; + grid-template-columns: minmax(0, 1fr); + padding-bottom: 4rem; + padding-top: 4.5rem; } - .cards { - grid-template-columns: 1fr; + .heroCopy, + .codeStage, + .codeWindow, + .codeWindow :global(.theme-code-block) { + min-width: 0; + width: 100%; + } + + .heroCopy { + max-width: 760px; + } + + .codeStage { + margin: 0 auto; + max-width: 760px; + width: 100%; + } + + .codeWindow { + transform: none; + } + + .featureGrid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media screen and (max-width: 700px) { + .hero { + box-sizing: border-box; + margin-left: 0; + margin-right: 0; + max-width: 100vw; + padding-left: 1.15rem; + padding-right: 1.15rem; + width: 100vw; + } + + .heroCopy, + .codeStage { + max-width: calc(100vw - 2.3rem); + } + + .hero h1 { + font-size: clamp(3rem, 15vw, 4.3rem); + } + + .sectionHeading { + grid-template-columns: 1fr; + } + + .sectionHeading > p:last-child { + margin-top: 1.25rem; + } + + .featureGrid { + grid-template-columns: 1fr; + } + + .featureCard { + min-height: 275px; + } + + .codeLegend { + grid-template-columns: 1fr; + } + + .codeLegend span + span { + border-left: 0; + border-top: 1px solid rgba(255, 255, 255, 0.08); + } + + .windowStatus { + display: none; + } + + .windowBar { + grid-template-columns: 1fr auto; + } + + .codeWindow :global(.theme-code-block pre) { + font-size: 0.69rem; + overflow-x: auto; + padding: 1.1rem !important; + } + + .cta { + align-items: flex-start; + flex-direction: column; + } +} + +@media (prefers-reduced-motion: reduce) { + .featureCard, + .featureCard::before, + .cardFooter > span, + .primaryAction, + .secondaryAction, + .ctaAction { + transition: none; } } diff --git a/packages/docs/src/pages/index.tsx b/packages/docs/src/pages/index.tsx index 90d003f..129f8e7 100644 --- a/packages/docs/src/pages/index.tsx +++ b/packages/docs/src/pages/index.tsx @@ -1,60 +1,203 @@ import Link from "@docusaurus/Link" +import CodeBlock from "@theme/CodeBlock" import Layout from "@theme/Layout" import clsx from "clsx" import type { ReactNode } from "react" import styles from "./index.module.css" +const componentExample = `import { Effect } from "effect" +import { Async, Component } from "effect-view" +import { Users } from "./services" + +export const UserCard = Component.make("UserCard")( + function* ({ userId }: { userId: string }) { + const users = yield* Users + + const user = yield* Component.useOnChange( + () => users.find(userId), + [userId], + ) + + yield* Component.useReactEffect( + () => Effect.forkScoped(users.watch(userId)), + [userId], + ) + + return ( +
+

{user.name}

+

{user.email}

+
+ ) + }, +).pipe(Async.async)` + +const features = [ + { + icon: "fx", + title: "Components are Effect programs", + description: + "Yield Effects and services directly from a component body, then return ordinary JSX. Types track every dependency all the way to the runtime boundary.", + tag: "Component.make", + to: "/docs/getting-started#write-your-first-component", + }, + { + icon: "R", + title: "One typed runtime", + description: + "Build your application Layer once. Components access HTTP clients, repositories, configuration, tracing, and your own services without React context plumbing.", + tag: "ReactRuntime", + to: "/docs/getting-started#create-the-runtime", + }, + { + icon: "S", + title: "Scopes follow React", + description: + "Every component owns an Effect Scope. Resources, subscriptions, and fibers are finalized when their component or dependency lifecycle ends.", + tag: "Scope.Scope", + to: "/docs/getting-started#the-component-root-scope", + }, + { + icon: "↔", + title: "Reactive state without a new universe", + description: + "Lens and View connect Effect state to React. Focus large models into small writable values and subscribe only where rendering needs them.", + tag: "Lens + View", + to: "/docs/state-management", + }, + { + icon: "Q", + title: "TanStack-shaped server state", + description: + "Reactive keys, caching, stale times, background refresh, mutations, and invalidation—with typed Effect services, Causes, and scoped fibers underneath.", + tag: "Query + Mutation", + to: "/docs/query", + }, + { + icon: "Σ", + title: "Forms driven by Schema", + description: + "Keep input-friendly encoded values while application code receives validated, decoded types. Focus one root into reusable field-sized subforms.", + tag: "MutationForm + LensForm", + to: "/docs/forms", + }, +] as const + export default function Home(): ReactNode { return (
+ + +
+ {features.map((feature, index) => ( + +
+ {feature.icon} + + {String(index + 1).padStart(2, "0")} + +
+

{feature.title}

+

{feature.description}

+
+ {feature.tag} + +
+ + ))} +
+ + +
+
+
+

Ready when React is

+

Start with one Effect component.

+

+ Add a runtime, cross the React boundary once, and grow into the + rest of the toolkit only when you need it. +

+
+ + Read the guide + +