diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a798256
--- /dev/null
+++ b/README.md
@@ -0,0 +1,96 @@
+# @ilia/carousel
+
+Tiny, framework-agnostic scroll-snap carousel. Zero runtime deps. Native scroll
+handles touch/momentum, CSS handles snap + motion, JS is a thin layer.
+
+- **core** ≤1 KB gzip · **dots** ≤0.6 KB · **autoplay** ≤0.6 KB
+- Ships **unminified** ESM — your bundler minifies.
+
+## Install
+
+This package lives on a private Gitea registry. Point the `@ilia` scope at it:
+
+```
+# .npmrc
+@ilia:registry=https://git.allmy.work/api/packages/ilia/npm/
+```
+
+```bash
+yarn add @ilia/carousel
+```
+
+## Entry points
+
+| Import | What |
+|---|---|
+| `@ilia/carousel` | `createCarousel(track)` core |
+| `@ilia/carousel/dots` | `dots()` — Firefox/legacy dot fallback (self-gates) |
+| `@ilia/carousel/autoplay` | `autoplay()` — reduce-motion-aware auto-advance |
+| `@ilia/carousel/carousel.css` | snap track, native markers, opt-in `.fx` effect |
+
+## Usage
+
+```html
+
+
+
+
+
+
+
+
+
+```
+
+```ts
+import { createCarousel } from '@ilia/carousel';
+import { dots } from '@ilia/carousel/dots';
+import { autoplay } from '@ilia/carousel/autoplay';
+import '@ilia/carousel/carousel.css';
+
+const track = document.getElementById('carousel');
+const c = createCarousel(track);
+
+// Arrows are just your own handlers — no module needed.
+document.getElementById('next').onclick = () => c.next();
+document.getElementById('prev').onclick = () => c.prev();
+
+// Dots: no-ops where native ::scroll-marker exists (no double dots).
+dots(c, document.getElementById('dots'));
+
+// Opt-in auto-advance. Provide a visible pause control (see a11y note).
+const stop = autoplay(c, { interval: 4000, root: track });
+```
+
+Add the opt-in scroll-driven effect with `class="track fx"` — it degrades to plain
+snap where `animation-timeline: view()` is unsupported.
+
+## Accessibility
+
+- `autoplay()` **never starts** under `prefers-reduced-motion: reduce`, and the `.fx`
+ effect + smooth scrolling are gated behind `@media (prefers-reduced-motion: no-preference)`.
+- If you use `autoplay()`, you **must** provide a visible pause/stop control
+ (WCAG 2.2.2) — wire it to the returned `stop()`.
+- Give dot buttons accessible labels (`aria-label`).
+
+## Browser support
+
+| Feature | Chrome | Safari | Firefox |
+|---|---|---|---|
+| `scroll-snap` + core path | ✅ | ✅ | ✅ |
+| `::scroll-marker` native dots | ✅ 135+ | ✅ 18.2+ | ⚠️ → JS `dots()` fallback |
+| scroll-driven `.fx` | ✅ | ⚠️ partial → plain snap | ✅ |
+
+Index tracking uses IntersectionObserver (universal today); a `scrollsnapchange`
+upgrade is deferred until Firefox ships it.
+
+## Release
+
+```bash
+npm version
+git push --follow-tags # CI verifies, runs e2e, publishes on the v* tag
+```