ESP32-S3 Bench Guide
ESP32-S3 N16R8 bench guide: live pinout, lessons, simulators and the bench log.
Durar is an immersive, underwater world of beautiful Arabic words. Swipe through pearls of vocabulary — each with its meaning and example sentences.
EducationTypeScript · JavaScriptGitHub @la6ifbotOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Arabic words, like pearls. An immersive, single-screen 3D space for discovering beautiful Arabic
vocabulary. Word cards drift like pearls in deep water. You swipe through them, or search for one
and watch it rise out of the depths.
Status: Phase 0.4 — Sharing. Reports: 0.1, 0.2,
0.3, 0.4. Setup: accounts, daily email, domain day.Read the full README ↗Arabic words, like pearls. An immersive, single-screen 3D space for discovering beautiful Arabic vocabulary. Word cards drift like pearls in deep water. You swipe through them, or search for one and watch it rise out of the depths.
Status: Phase 0.4 — Sharing. Reports: 0.1, 0.2, 0.3, 0.4. Setup: accounts, daily email, domain day.
npm install
npm run dev # http://localhost:5173
npm run build # type-check, bundle, prerender /word/<slug> pages → dist/
npm run preview # serve dist/ at http://localhost:4173 (clean URLs like production)
npm run validate:data # schema check for words.json, topics.json and illustrations.json
npm run test:e2e # Playwright (builds are served via `npm run preview`)
npm run test:db # database tests (RLS, subscriptions); needs DATABASE_URL (PostgreSQL)
npm run test:unit # schedule, email rendering, server handlers (handlers need DATABASE_URL)
npm run cards # render card images into .cards/ for a look (CI uploads the real ones)
npm run illustrations # restyle illustrations into .illustrations/ (needs Wikimedia; CI uploads them)
npm run email:render # write today's emails to dist-email/ for a look
npm run check:functions # compile api/ like Vercel and load it in plain Node
Accounts need a Supabase project (see docs/ACCOUNTS-SETUP.md). Copy
.env.example to .env.local and fill in the values. Without them the site runs exactly as
before, just without sign-in. To try accounts with no backend at all, run
VITE_BACKEND=mock npm run dev: a browser-only demo that needs no email.
If Playwright can't download browsers in your environment, point it at an existing Chromium:
PW_CHROMIUM_PATH=/path/to/chrome npm run test:e2e.
| Action | Mouse / trackpad | Touch | Keyboard |
|---|---|---|---|
| I know this (card sinks into the deep) | drag right · two-finger swipe right | swipe right | → |
| Still learning (card drifts aside, comes back soon) | drag left · two-finger swipe left | swipe left | ← |
| Search | click the search box | tap it | / |
| Bring a background pearl forward | click it | tap it | — |
| Save to My Pearls | the pearl on the card's corner | tap it | S |
| Share | the share icon beside it: WhatsApp, Copy link, Download image | tap it: your phone's share sheet, with the image | Tab to it, Enter |
A Text-only view toggle (bottom-left) switches to a calm HTML version with the same data and controls. It's also what you see automatically if WebGL isn't available.
src/
data/words.json curated word set (140 words) — the single source of truth
lib/words.ts data access + search (diacritic/hamza-insensitive Arabic, translit, English)
lib/router.ts “/” and “/word/<slug>” routing; URL + <title> follow the focused card
lib/cardTexture.ts canvas renderer for card text (browser-shaped RTL Arabic)
lib/fonts.ts self-hosted webfonts, explicitly loaded before canvas drawing
state/store.ts Zustand store: rotation queue, swipes, search surfacing
scene/ react-three-fiber scene (lazy-loaded chunk)
Experience.tsx <Canvas> + scene graph
PearlCard.tsx one card: choreography (focus, sink, drift, rise) via critically-damped springs
cardMaterial.ts card shader: nacre body, iridescent rim, caustics, mip-bias depth-of-field, water fog
Backdrop/GodRays/Particles/Bubbles
useSwipeInput.ts one gesture model for mouse, touch, trackpad and arrow keys
ui/ 2D chrome: search, swipe controls, live regions, text-only view,
sign-in dialog, account menu, save control
account/ accounts: Supabase client (lazy chunk), browser-only mock, store
(optimistic saves, pending save across sign-in), friendly errors
pages/ /library (My Pearls), /privacy and /credits, light CSS-only pages
share/ sharing (lazy chunk): share text + links, the 9:16 story image, share sheet
shared/ code used by both site and server (Pearl of the Day schedule, flags,
image URLs, licences and credit lines)
api/ Vercel functions: subscribe, confirm, unsubscribe, cron/daily
server/ their logic: handlers, SQL-function store, email templates, SES adapter
scripts/cards.mjs card images (social previews + email): CI renders and uploads them to img.durar.space
scripts/illustrations.mjs illustrations: CI restyles public-domain plates and uploads them to img.durar.space
supabase/migrations/ versioned SQL: saved_pearls, subscribers, daily_sends, RLS, functions
supabase/setup/ one-off owner SQL (pg_cron schedule for the daily email)
scripts/prerender.mjs post-build: static HTML + OG/Twitter/JSON-LD for every word; noindex /library
tests/ Playwright e2e (desktop, mobile touch, no-WebGL, accounts, axe a11y)
tests/db/ node:test Row Level Security suite against real PostgreSQL
Design decisions:
direction: rtl). An aria-live HTML copy of the
focused card sits alongside it for screen readers and crawlers.prefers-reduced-motion calms the
camera, particles, light rays and bubbles.Evaluated Amiri, Noto Naskh Arabic, Scheherazade New, Markazi Text and Aref Ruqaa at display sizes with full diacritics:
All fonts are self-hosted via Fontsource (SIL Open Font License), with no third-party font CDN.
words.json plus prerendered pages). The database stores only
which word slugs each user saved, so SEO and load time are unaffected.tests/db/rls.test.mjs proves this against real PostgreSQL
(user A cannot read or write user B's pearls) and runs in CI.delete_my_account() is a security definer function, so people can delete their own
account without a server.shared/pearlOfTheDay.ts maps each Europe/Amsterdam calendar day to a word. Every word appears
once per cycle, and words added later (with an added date) join the next cycle, so past days
never change. The site (/ opens on today's pearl) and the email sender both use it.img.durar.space (a private S3 bucket in Frankfurt behind CloudFront). Each file name carries a hash
of the card's text (shared/cards.ts), so the site and the email work out a word's image URL from
its data. CI (npm run cards -- --upload) renders and uploads only the cards the host doesn't have
yet, on every pull request and on main. Old durar.space/cards/* links redirect to the host.docs/EMAIL-SETUP.md.src/share/image.ts) with the site's own fonts, in the
same look as the card images. Every exported image goes through renderStory, which always draws
the دُرَر · durar.space signature inside Instagram's safe area. layout.ts shrinks and wraps
long words until everything fits.https://durar.space/word/<slug> (from SITE_URL). Text:
سَرَاب (sarāb) — mirage · a pearl from Durar, or a “Today's pearl” version for the Pearl of the Day.src/data/words.json holds 140 hand-picked words: the sea, light, sky, longing, virtue, desert,
scent, language and poetry. Each has a fully vowelled headword, a transliteration, meanings and 1–3
example sentences. A few are attributed classical lines (Hafez Ibrahim, al-Mutanabbi, Imruʾ al-Qays,
all public domain). Examples were written for Durar. No third-party lexicon was imported, so no
licence is involved yet (that's Phase 0.6).
interface Word {
slug: string; // URL id → /word/<slug>
ar: string; // headword with diacritics
translit: string;
meanings: string[];
examples: { ar: string; en: string; source?: string }[]; // 1–3
root?: string; // reserved: root-family feature
topics: string[]; // ids from src/data/topics.json; [] = only in "The whole sea"
audio?: string; // reserved: pronunciation audio
added: string; // ISO date it joined (2026-09-24 for the original set; Pearl of the Day cycles)
image?: string; // an id from src/data/illustrations.json (source, credit, licence, alt)
etymology?: { text: string; source: string }; // required for the "borrowed" topic
}
src/data/topics.json is the topic registry: id (the /sea/<id> URL), name and description
in English and Arabic, order, and an optional cover (an illustration id). "The whole sea" is
every word and isn't listed there.
src/data/illustrations.json lists the illustrations: restyled public-domain plates from Wikimedia
Commons, each with its artist, work, date, licence and alt text. CI turns each entry into tinted
line art on img.durar.space, and /credits credits every one. How to add one:
docs/ILLUSTRATIONS.md.
Run npm run validate:data after editing. It checks the rules in scripts/lib/word-schema.mjs:
vowelled headwords, the transliteration format (ā ī ū, ḥ ṣ ḍ ṭ ẓ, ʿ and ʾ), 1–3 examples, known
topic ids, an added date, a sourced etymology for borrowed words, image ids that exist, and
unique slugs. Topics under 20 words are a warning. Illustrations are checked by
scripts/lib/illustration-schema.mjs (Commons file page, public domain or CC0, dates, crop).
The build output (dist/) is fully static:
vercel.json sets the build and clean URLs. There is no SPA
fallback: every app route is prerendered, and any other path gets the static 404.html (a lost
pearl with a word search) with a real 404 status. A new app route must be added to
scripts/prerender.mjs.netlify.toml does the same.npm run preview behaves the same way (vite.config.ts).Set SITE_URL (e.g. https://durar.example) at build time to emit absolute canonical/og:url
tags, sitemap.xml and a robots.txt sitemap entry. On Vercel and Netlify the production URL is
detected automatically. /library is always noindex and left out of the sitemap.
For accounts, set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY for Production and Preview
(full walkthrough, including Google sign-in and email delivery: docs/ACCOUNTS-SETUP.md).
Every word page carries its card image (og:image), so link previews in WhatsApp, Telegram and
iMessage show the card. vercel.json permanently redirects www.durar.space and
arabic-lang-project.vercel.app to durar.space (except /api/).