diff --git a/README.md b/README.md
index 54c9096..6a24be4 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,17 @@
-
+
-
+
-
+ react-native-canvas-kit
+
-# react-native-canvas-kit
+
+
+
+
+
+
@@ -21,6 +27,21 @@ experience from scratch.
> Full guides and API reference live in the
> [documentation site](https://adithyavis.github.io/react-native-canvas-kit/intro).
+## Showcase
+
+Popular app experiences rebuilt with Canvas Kit:
+
+
+
+
+
+
+
+
+
+ Canva · Instagram · Snapseed · DocuSign · Google Pay
+
+
## What you get
- **Shapes**: `Rect`, `Circle`, `Ellipse`, `Line`, `RegularPolygon`, `Star`,
diff --git a/assets/canva.mov b/assets/canva.mov
new file mode 100644
index 0000000..e82d46e
Binary files /dev/null and b/assets/canva.mov differ
diff --git a/assets/docusign.mov b/assets/docusign.mov
new file mode 100644
index 0000000..f26e359
Binary files /dev/null and b/assets/docusign.mov differ
diff --git a/assets/gpay_scratch_card.mov b/assets/gpay_scratch_card.mov
new file mode 100644
index 0000000..a40c659
Binary files /dev/null and b/assets/gpay_scratch_card.mov differ
diff --git a/assets/instagram_crop.mov b/assets/instagram_crop.mov
new file mode 100644
index 0000000..12f9c83
Binary files /dev/null and b/assets/instagram_crop.mov differ
diff --git a/assets/snapseed.mov b/assets/snapseed.mov
new file mode 100644
index 0000000..9843bbf
Binary files /dev/null and b/assets/snapseed.mov differ
diff --git a/docs/docs/core-concepts/infinite-canvas.md b/docs/docs/core-concepts/infinite-canvas.md
new file mode 100644
index 0000000..5e3b2a0
--- /dev/null
+++ b/docs/docs/core-concepts/infinite-canvas.md
@@ -0,0 +1,193 @@
+---
+sidebar_position: 5
+title: Infinite Canvas
+---
+
+# Infinite Canvas
+
+Set `infinite` on the [`Stage`](./stage.md) to turn it into a pannable,
+zoomable scene: one-finger drag pans the whole scene, two-finger pinch zooms it,
+and content can live anywhere in scene coordinates.
+
+```tsx
+
+ {/* shapes at any coordinates */}
+
+```
+
+Infinite mode is **not** a giant surface. The canvas stays the size of the
+viewport; a scene transform (a pan offset and a zoom factor) is applied at the
+root of the scene graph. Because every node's on-screen position is already
+resolved by walking up to that root, hit-testing, [Portals](../portal/overview.md),
+the [Transformer](../interactivity/transformer.md), and snapping all pan and
+zoom for free.
+
+## Gesture behavior
+
+With `infinite`, gestures route to a shape when one is under the fingers, and to
+the scene otherwise:
+
+| Gesture | On a matching node | On empty space |
+| ----------------- | --------------------------------- | --------------------------------- |
+| One-finger drag | Drags the node (if `draggable`) | Pans the scene |
+| Two-finger pinch | Scales the node (if `scalable`) | Zooms the scene |
+| Two-finger rotate | Rotates the node (if `rotatable`) | Nothing — the scene never rotates |
+
+So a note you can move and resize still declares `draggable scalable`; dragging
+it moves the note, while dragging the background pans the board. The scene itself
+only ever pans and zooms — it does not rotate.
+
+## Zoom limits
+
+Clamp how far the scene can zoom with `minZoom` / `maxZoom` (defaults `0.1` and
+`10`):
+
+```tsx
+
+```
+
+## Controlling the zoom
+
+Attach a `ref` to drive the scene imperatively through the
+[`StageHandle`](./stage.md):
+
+```tsx
+const stageRef = useRef(null);
+
+stageRef.current?.zoomIn(); // zoom in around the viewport center
+stageRef.current?.zoomOut();
+stageRef.current?.zoomTo(2, { x: 200, y: 300 }); // zoom to 2x around a focal point
+stageRef.current?.panTo(x, y); // set the scene offset
+stageRef.current?.centerOn(x, y, 0.7); // center a scene point at a given zoom
+stageRef.current?.resetView(); // back to offset 0, zoom 1
+const scene = stageRef.current?.getScene(); // { x, y, scale }
+```
+
+| Method | Description |
+| ---------------------------------- | ---------------------------------------------------------------- |
+| `getScene()` | Current scene as `{ x, y, scale }`. |
+| `zoomTo(scale, focal?)` | Zoom to `scale` (clamped) around `focal` or the viewport center. |
+| `zoomIn(step?)` / `zoomOut(step?)` | Multiply / divide the zoom by `step` (default `1.2`). |
+| `panTo(x, y)` | Set the scene offset directly. |
+| `centerOn(x, y, scale?)` | Center the scene point `(x, y)` in the viewport at `scale`. |
+| `resetView()` | Reset to offset `0`, zoom `1`. |
+
+Use `centerOn` on mount to open the view on your content instead of the
+top-left origin:
+
+```tsx
+useEffect(() => {
+ stageRef.current?.centerOn(boardCenterX, boardCenterY, 0.7);
+}, []);
+```
+
+### Observing zoom changes
+
+Pass `onSceneChange` to react to zoom on the JS thread — for example, to render a
+live zoom percentage. It fires on integer-percent zoom changes, so it does not
+flood React during a pinch:
+
+```tsx
+const [zoom, setZoom] = useState(1);
+
+ setZoom(scene.scale)}
+>
+ {/* ... */}
+ ;
+
+// elsewhere: {Math.round(zoom * 100)}%
+```
+
+## Reacting to the changes from children
+
+`useSceneTransform()` exposes the live scene offset and zoom as shared values to
+any component inside the `Stage`. It is the way to build scene-aware overlays —
+a dot grid, rulers, a minimap — that stay crisp at every zoom without
+re-rendering. Below, a grid keeps a constant on-screen dot spacing and size by
+choosing them from the current zoom, entirely on the UI thread:
+
+```tsx
+import { useSceneTransform } from 'react-native-canvas-kit';
+import { Path, Skia } from '@shopify/react-native-skia';
+import { useDerivedValue } from 'react-native-reanimated';
+
+function DotGrid() {
+ const scene = useSceneTransform();
+
+ const path = useDerivedValue(() => {
+ const p = Skia.Path.Make();
+ if (!scene) return p;
+ const scale = scene.sceneScaleSV.value.x || 1;
+ const offset = scene.sceneOffsetSV.value;
+
+ const left = -offset.x / scale;
+ const top = -offset.y / scale;
+ const right = (scene.width - offset.x) / scale;
+ const bottom = (scene.height - offset.y) / scale;
+
+ let spacing = 32;
+ while (spacing * scale < 24) spacing *= 2;
+ while (spacing * scale >= 48) spacing /= 2;
+
+ const startX = Math.floor(left / spacing) * spacing;
+ const startY = Math.floor(top / spacing) * spacing;
+ for (let x = startX; x <= right; x += spacing) {
+ for (let y = startY; y <= bottom; y += spacing) {
+ p.addCircle(x, y, radius);
+ }
+ }
+ return p;
+ }, [scene]);
+
+ return ;
+}
+```
+
+`useSceneTransform()` returns `{ sceneOffsetSV, sceneScaleSV, width, height }`,
+or `null` outside a `Stage`. The scene scale is a `Vector2d`; the zoom is
+uniform, so read `.x`.
+
+## Rendering large scenes
+
+The scene transform itself is cheap — it is one group transform on the GPU. What
+costs, at scale, is **the number of nodes**. Every canvas-kit node (a `Rect`,
+`Circle`, `Group`, …) is registered for hit-testing and carries its own
+transform, so a scene with thousands of them pays for all of them every frame,
+even the off-screen ones (Skia quick-rejects their _fill_, but the node tree is
+still walked).
+
+A few guidelines:
+
+- **Interactive objects** (things you tap, drag, or transform) are the right use
+ for canvas-kit nodes. Keep this to what the user actually manipulates.
+- **Bulk decoration** (grids, backgrounds, particle fields) should be a single
+ Skia primitive, like the `DotGrid` above — one node whose drawing is computed
+ in a worklet, not one node per dot.
+- **Large fields of static content** are cheapest drawn without per-item nodes:
+ render them as raw `@shopify/react-native-skia` components (`RoundedRect`,
+ `Text`, …) in a `React.memo`'d subtree so they mount once and are not
+ reconciled on pan/zoom, or record them into a single ``. Promote only
+ the item currently being edited to a live, interactive node.
+
+### Culling to the viewport
+
+For very large scenes, `useRenderBounds(margin)` gives you the visible scene
+rectangle (expanded by `margin`) as a **shared value**, updated on the UI thread
+with hysteresis so it does not fire every frame. Consume it in a worklet to draw
+only what is on screen, and use `rectIntersectsBounds` to test each item:
+
+```tsx
+import { useRenderBounds, rectIntersectsBounds } from 'react-native-canvas-kit';
+
+const boundsSV = useRenderBounds(300); // SharedValue
+```
+
+Because it is a shared value, `useRenderBounds` is for **worklet-driven**
+rendering (a `` or a Skia drawing). Mounting or unmounting canvas-kit
+_nodes_ based on visibility is a React operation and must be driven from state,
+so windowing a node list re-renders when the visible set changes — expected, and
+the same model as `FlatList`.
diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts
index c26209e..e213710 100644
--- a/docs/docusaurus.config.ts
+++ b/docs/docusaurus.config.ts
@@ -58,6 +58,12 @@ const config: Config = {
colorMode: {
respectPrefersColorScheme: true,
},
+ algolia: {
+ appId: 'U9PLQTMVZT',
+ apiKey: 'fb01f09f8f35cc449627ffcdfe315111',
+ indexName: 'React Native Canvas Kit',
+ contextualSearch: false,
+ },
navbar: {
title: 'Canvas Kit',
logo: {
@@ -72,6 +78,10 @@ const config: Config = {
position: 'left',
label: 'Docs',
},
+ {
+ type: 'search',
+ position: 'right',
+ },
{
type: 'docsVersionDropdown',
position: 'right',
diff --git a/docs/package.json b/docs/package.json
index fa3bbe6..02263d8 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -27,7 +27,11 @@
"typescript": "~5.6.2"
},
"browserslist": {
- "production": [">0.5%", "not dead", "not op_mini all"],
+ "production": [
+ ">0.5%",
+ "not dead",
+ "not op_mini all"
+ ],
"development": [
"last 3 chrome version",
"last 3 firefox version",
diff --git a/docs/src/components/HomepageFeatures/styles.module.css b/docs/src/components/HomepageFeatures/styles.module.css
index e99405e..eec05a2 100644
--- a/docs/src/components/HomepageFeatures/styles.module.css
+++ b/docs/src/components/HomepageFeatures/styles.module.css
@@ -1,13 +1,9 @@
-/**
- * Homepage feature-grid styling.
- */
-
.features {
- padding: 4.5rem 0 5rem;
+ padding: 5rem 0 5.5rem;
}
.sectionHead {
- max-width: 640px;
+ max-width: 660px;
margin: 0 auto 3rem;
text-align: center;
}
@@ -18,8 +14,9 @@
}
.sectionSubtitle {
- font-size: 1.1rem;
- color: var(--ifm-color-emphasis-700);
+ font-size: 1.12rem;
+ line-height: 1.6;
+ color: var(--ck-muted);
margin: 0;
}
@@ -28,42 +25,67 @@
}
.card {
+ position: relative;
height: 100%;
- padding: 1.75rem;
- border-radius: 14px;
+ padding: 1.85rem;
+ border-radius: 1rem;
background: var(--ifm-background-surface-color);
- border: 1px solid var(--ifm-color-emphasis-200);
+ border: 1px solid var(--ck-line);
+ overflow: hidden;
transition:
transform 0.18s ease,
box-shadow 0.18s ease,
border-color 0.18s ease;
}
+.card::before {
+ content: '';
+ position: absolute;
+ inset: 0 0 auto 0;
+ height: 3px;
+ background: var(--ck-gradient);
+ opacity: 0;
+ transition: opacity 0.18s ease;
+}
+
.card:hover {
- transform: translateY(-4px);
- border-color: var(--ifm-color-primary);
- box-shadow: 0 16px 40px rgba(138, 43, 226, 0.14);
+ transform: translateY(-5px);
+ border-color: color-mix(in srgb, var(--ck-indigo) 40%, transparent);
+ box-shadow: 0 20px 44px var(--ck-shadow);
+}
+
+.card:hover::before {
+ opacity: 1;
}
.cardIcon {
- font-size: 2rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 3rem;
+ height: 3rem;
+ border-radius: 0.75rem;
+ font-size: 1.6rem;
line-height: 1;
- margin-bottom: 1rem;
+ margin-bottom: 1.1rem;
+ background: color-mix(in srgb, var(--ck-indigo) 12%, transparent);
+ border: 1px solid var(--ck-line);
}
.cardTitle {
- font-size: 1.2rem;
+ font-size: 1.22rem;
+ letter-spacing: -0.015em;
margin-bottom: 0.5rem;
}
.cardText {
- color: var(--ifm-color-emphasis-700);
+ color: var(--ck-muted);
margin: 0;
- line-height: 1.6;
+ line-height: 1.62;
}
.ctaRow {
display: flex;
justify-content: center;
- margin-top: 1.5rem;
+ margin-top: 2rem;
}
diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css
index bced9d7..2a8dd77 100644
--- a/docs/src/css/custom.css
+++ b/docs/src/css/custom.css
@@ -1,26 +1,266 @@
-/**
- * Global styling for the documentation site.
- */
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');
:root {
- --ifm-color-primary: #8a2be2;
- --ifm-color-primary-dark: #7d1fd1;
- --ifm-color-primary-darker: #761dc6;
- --ifm-color-primary-darkest: #6118a3;
- --ifm-color-primary-light: #973ce6;
- --ifm-color-primary-lighter: #9d47e8;
- --ifm-color-primary-lightest: #b06ded;
- --ifm-code-font-size: 95%;
- --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
+ --ck-indigo: #4f46e5;
+ --ck-indigo-bright: #635bea;
+ --ck-cyan: #06b6d4;
+ --ck-cyan-bright: #22d3ee;
+ --ck-ink: #14121f;
+ --ck-muted: #dfd5ff;
+ --ck-gradient: linear-gradient(
+ 115deg,
+ var(--ck-indigo) 0%,
+ var(--ck-cyan) 100%
+ );
+ --ck-glow: rgba(79, 70, 229, 0.13);
+ --ck-dot: rgba(79, 70, 229, 0.13);
+ --ck-line: rgba(79, 70, 229, 0.14);
+ --ck-shadow: rgba(28, 22, 79, 0.13);
+ --ck-dot-size: 22px;
+
+ --ifm-color-primary: #4f46e5;
+ --ifm-color-primary-dark: #4038d1;
+ --ifm-color-primary-darker: #3c34c5;
+ --ifm-color-primary-darkest: #3129a2;
+ --ifm-color-primary-light: #635bea;
+ --ifm-color-primary-lighter: #7069ec;
+ --ifm-color-primary-lightest: #948ef2;
+
+ --ifm-background-color: #fbfaff;
+ --ifm-background-surface-color: #ffffff;
+ --ifm-heading-color: var(--ck-ink);
+ --ifm-font-color-base: #2a2638;
+
+ --ifm-font-family-base:
+ 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ --ifm-font-family-monospace:
+ 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+ --ifm-code-font-size: 90%;
+ --ifm-global-radius: 0.65rem;
+ --ifm-navbar-background-color: rgba(251, 250, 255, 0.7);
+ --ifm-navbar-shadow: none;
+ --ifm-toc-border-color: var(--ck-line);
+ --ifm-hr-border-color: var(--ck-line);
+ --ifm-color-emphasis-200: var(--ck-line);
+
+ --docusaurus-highlighted-code-line-bg: rgba(79, 70, 229, 0.1);
}
[data-theme='dark'] {
- --ifm-color-primary: #c79bff;
- --ifm-color-primary-dark: #b078ff;
- --ifm-color-primary-darker: #a566ff;
- --ifm-color-primary-darkest: #8630ff;
- --ifm-color-primary-light: #debeff;
- --ifm-color-primary-lighter: #e9d0ff;
- --ifm-color-primary-lightest: #ffffff;
- --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
+ --ck-indigo: #8b83ff;
+ --ck-indigo-bright: #a49dff;
+ --ck-cyan: #22d3ee;
+ --ck-cyan-bright: #67e8f9;
+ --ck-ink: #eceaf7;
+ --ck-muted: #9a93b0;
+ --ck-glow: rgba(120, 110, 255, 0.16);
+ --ck-dot: rgba(139, 131, 255, 0.15);
+ --ck-line: rgba(139, 131, 255, 0.14);
+ --ck-shadow: rgba(0, 0, 0, 0.5);
+
+ --ifm-color-primary: #8b83ff;
+ --ifm-color-primary-dark: #6f66ff;
+ --ifm-color-primary-darker: #635aff;
+ --ifm-color-primary-darkest: #3f34ff;
+ --ifm-color-primary-light: #a49dff;
+ --ifm-color-primary-lighter: #b1abff;
+ --ifm-color-primary-lightest: #dedaff;
+
+ --ifm-background-color: #0a0912;
+ --ifm-background-surface-color: #12111d;
+ --ifm-font-color-base: #d7d3e6;
+ --ifm-navbar-background-color: rgba(10, 9, 18, 0.7);
+ --ifm-color-emphasis-200: var(--ck-line);
+
+ --docusaurus-highlighted-code-line-bg: rgba(139, 131, 255, 0.13);
+}
+
+html {
+ font-feature-settings:
+ 'cv02' 1,
+ 'cv11' 1,
+ 'ss01' 1;
+ -webkit-font-smoothing: antialiased;
+}
+
+body {
+ background-color: var(--ifm-background-color);
+ background-image:
+ radial-gradient(65% 40% at 50% -6%, var(--ck-glow), transparent 70%),
+ radial-gradient(var(--ck-dot) 1px, transparent 1.4px);
+ background-size:
+ 100% 100%,
+ var(--ck-dot-size) var(--ck-dot-size);
+ background-attachment: fixed, fixed;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ letter-spacing: -0.02em;
+ font-weight: 700;
+}
+
+.markdown h1:first-child {
+ font-size: 2.6rem;
+ letter-spacing: -0.032em;
+}
+
+.markdown h2 {
+ padding-bottom: 0.3rem;
+ border-bottom: 1px solid var(--ck-line);
+}
+
+.navbar {
+ backdrop-filter: saturate(180%) blur(14px);
+ -webkit-backdrop-filter: saturate(180%) blur(14px);
+ border-bottom: 1px solid var(--ck-line);
+}
+
+.navbar__title {
+ font-weight: 800;
+ letter-spacing: -0.02em;
+ background: var(--ck-gradient);
+ -webkit-background-clip: text;
+ background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+
+.navbar__link {
+ font-weight: 500;
+}
+
+.menu {
+ padding: 0.75rem 0.5rem;
+ font-weight: 500;
+}
+
+.menu__link {
+ border-radius: 0.55rem;
+ transition:
+ background 0.15s ease,
+ color 0.15s ease;
+}
+
+.menu__link:hover,
+.menu__list-item-collapsible:hover {
+ background: color-mix(in srgb, var(--ck-indigo) 9%, transparent);
+ border-radius: 0.55rem;
+}
+
+.menu__link--active:not(.menu__link--sublist) {
+ background: color-mix(in srgb, var(--ck-indigo) 15%, transparent);
+ color: var(--ck-indigo);
+ font-weight: 600;
+}
+
+.table-of-contents__link--active {
+ color: var(--ck-indigo);
+ font-weight: 600;
+}
+
+.table-of-contents {
+ border-left-color: var(--ck-line);
+}
+
+a {
+ text-decoration-color: color-mix(
+ in srgb,
+ var(--ifm-color-primary) 45%,
+ transparent
+ );
+ text-underline-offset: 0.15em;
+}
+
+.button--primary {
+ --ifm-button-background-color: #4f46e5;
+ --ifm-button-border-color: #4f46e5;
+ color: #ffffff;
+ font-weight: 600;
+ box-shadow: 0 10px 24px color-mix(in srgb, var(--ck-indigo) 28%, transparent);
+ transition:
+ transform 0.16s ease,
+ box-shadow 0.16s ease,
+ filter 0.16s ease;
+}
+
+.button--primary:hover {
+ --ifm-button-background-color: #4338ca;
+ --ifm-button-border-color: #4338ca;
+ color: #ffffff;
+ transform: translateY(-2px);
+ box-shadow: 0 16px 32px color-mix(in srgb, var(--ck-indigo) 36%, transparent);
+}
+
+code {
+ background: color-mix(in srgb, var(--ck-indigo) 8%, transparent);
+ border: 1px solid var(--ck-line);
+ border-radius: 0.4rem;
+ font-weight: 500;
+}
+
+.theme-code-block,
+div[class*='codeBlockContainer'] {
+ border-radius: 0.75rem;
+ box-shadow: 0 14px 34px var(--ck-shadow);
+ border: 1px solid var(--ck-line);
+}
+
+.theme-doc-sidebar-container {
+ border-right: 1px solid var(--ck-line);
+}
+
+.pagination-nav__link {
+ border: 1px solid var(--ck-line);
+ border-radius: 0.75rem;
+ transition:
+ border-color 0.15s ease,
+ box-shadow 0.15s ease,
+ transform 0.15s ease;
+}
+
+.pagination-nav__link:hover {
+ border-color: var(--ck-indigo);
+ box-shadow: 0 12px 28px var(--ck-shadow);
+ transform: translateY(-2px);
+}
+
+.pagination-nav__label {
+ font-weight: 600;
+}
+
+.alert {
+ border: 1px solid var(--ck-line);
+ border-left-width: 3px;
+ border-radius: 0.6rem;
+}
+
+blockquote {
+ border-left: 3px solid var(--ck-indigo);
+ background: color-mix(in srgb, var(--ck-indigo) 7%, transparent);
+ border-radius: 0 0.6rem 0.6rem 0;
+}
+
+table thead tr {
+ background: color-mix(in srgb, var(--ck-indigo) 8%, transparent);
+}
+
+table th {
+ font-weight: 600;
+}
+
+.footer {
+ background: linear-gradient(180deg, #100e20 0%, #08060f 100%);
+ border-top: 1px solid rgba(139, 131, 255, 0.14);
+}
+
+.footer__title {
+ color: #dedaff;
+}
+
+::selection {
+ background: color-mix(in srgb, var(--ck-indigo) 28%, transparent);
}
diff --git a/docs/src/pages/index.module.css b/docs/src/pages/index.module.css
index 3fdd7bc..aa738fe 100644
--- a/docs/src/pages/index.module.css
+++ b/docs/src/pages/index.module.css
@@ -1,12 +1,53 @@
.hero {
- padding: 5rem 1rem 4rem;
- background: linear-gradient(150deg, #2a0a45 0%, #1b0030 45%, #10001f 100%);
- color: #f4ecff;
+ position: relative;
+ padding: 6rem 1rem 5rem;
+ background:
+ radial-gradient(
+ 75% 60% at 50% -10%,
+ rgba(99, 91, 234, 0.4),
+ transparent 68%
+ ),
+ linear-gradient(160deg, #171338 0%, #0f0d28 50%, #080714 100%);
+ color: #eef0ff;
overflow: hidden;
}
+.hero::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image:
+ linear-gradient(rgba(139, 131, 255, 0.09) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(139, 131, 255, 0.09) 1px, transparent 1px);
+ background-size: 48px 48px;
+ mask-image: radial-gradient(72% 72% at 50% 28%, #000 0%, transparent 82%);
+ -webkit-mask-image: radial-gradient(
+ 72% 72% at 50% 28%,
+ #000 0%,
+ transparent 82%
+ );
+ pointer-events: none;
+}
+
+.hero::after {
+ content: '';
+ position: absolute;
+ right: -140px;
+ bottom: -170px;
+ width: 440px;
+ height: 440px;
+ background: radial-gradient(
+ circle,
+ rgba(34, 211, 238, 0.22),
+ transparent 66%
+ );
+ pointer-events: none;
+}
+
.heroInner {
- max-width: 820px;
+ position: relative;
+ z-index: 1;
+ max-width: 840px;
margin: 0 auto;
text-align: center;
}
@@ -20,14 +61,15 @@
align-items: center;
gap: 0.5rem;
padding: 0.35rem 0.9rem 0.35rem 0.5rem;
- margin-bottom: 1.25rem;
+ margin-bottom: 1.5rem;
border-radius: 999px;
font-size: 0.8rem;
font-weight: 600;
letter-spacing: 0.02em;
- color: #e9d0ff;
- background: rgba(199, 155, 255, 0.14);
- border: 1px solid rgba(199, 155, 255, 0.35);
+ color: #d7d4ff;
+ background: rgba(139, 131, 255, 0.12);
+ border: 1px solid rgba(139, 131, 255, 0.34);
+ backdrop-filter: blur(6px);
}
.badgeIcon {
@@ -37,25 +79,27 @@
}
.heroTitle {
- font-size: 3.25rem;
- line-height: 1.05;
+ font-size: 3.6rem;
+ line-height: 1.03;
+ letter-spacing: -0.035em;
+ font-weight: 800;
margin-bottom: 1.25rem;
- background: linear-gradient(90deg, #ffffff 0%, #c79bff 100%);
+ background: linear-gradient(105deg, #ffffff 0%, #b9b4ff 50%, #67e8f9 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.heroTagline {
- font-size: 1.2rem;
+ font-size: 1.25rem;
line-height: 1.6;
- color: #d7c7ee;
- margin-bottom: 2rem;
+ color: #c7c4e6;
+ margin-bottom: 2.25rem;
}
.heroTagline code {
- background: rgba(199, 155, 255, 0.16);
- color: #e9d0ff;
+ background: rgba(139, 131, 255, 0.16);
+ color: #d7d4ff;
border: none;
}
@@ -66,17 +110,30 @@
gap: 0.85rem;
}
-.ghostButton {
- color: #e9d0ff;
- border-color: rgba(199, 155, 255, 0.5);
+.docsButton {
+ --ifm-button-color: #ffffff;
+ --ifm-button-background-color: rgba(255, 255, 255, 0.06);
+ --ifm-button-border-color: rgba(139, 131, 255, 0.5);
+ backdrop-filter: blur(6px);
+ transition:
+ background 0.16s ease,
+ border-color 0.16s ease,
+ transform 0.16s ease;
+}
+
+.docsButton:hover {
+ --ifm-button-color: #ffffff;
+ --ifm-button-background-color: rgba(255, 255, 255, 0.13);
+ --ifm-button-border-color: rgba(139, 131, 255, 0.85);
+ transform: translateY(-2px);
}
@media (max-width: 996px) {
.hero {
- padding: 3rem 1rem;
+ padding: 3.5rem 1rem;
}
.heroTitle {
- font-size: 2.5rem;
+ font-size: 2.6rem;
}
}
diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx
index bbb4232..6354983 100644
--- a/docs/src/pages/index.tsx
+++ b/docs/src/pages/index.tsx
@@ -37,7 +37,10 @@ function HomepageHero() {
>
Get Started
-
+
Read the Docs