ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
Durar는 아름다운 아랍어 단어들의 몰입형 수중 세계로, 의미와 예문이 함께하는 어휘 진주를 스와이프해 탐색합니다
Durar is an immersive, underwater world of beautiful Arabic words. Swipe through pearls of vocabulary — each with its meaning and example sentences.
교육TypeScript · JavaScriptGitHub @la6ifbot온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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.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/).