Skip to content

Commit 48a8e55

Browse files
committed
feat(apps): add Ebru — a Turkish marbling tray
A hands-on su ebrusu (paper-marbling) studio under an Ottoman illuminated-manuscript frame. Float pigments with the Damlalık, then draw the Biz (needle) and Tarak (comb) through the paint and lift the sheet. Engine is raster semi-Lagrangian advection: ink is a pixel field warped as a fluid, so combing yields smooth cusps and feathering with no polygon shards, and cost is fixed by resolution (no slowdown however long you comb). Drops use the exact area-preserving outward push. Includes engine unit tests, route wiring, and a Whimsical Tools registry entry.
1 parent dd1c613 commit 48a8e55

5 files changed

Lines changed: 834 additions & 0 deletions

File tree

public/apps/apps.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,14 @@
181181
"icon": "SparkleIcon",
182182
"order": 2,
183183
"apps": [
184+
{
185+
"slug": "ebru",
186+
"to": "/apps/ebru",
187+
"title": "Ebru",
188+
"description": "A hands-on Turkish paper-marbling tray. Float pigments on water, comb them with the biz and tarak, and lift your marbled sheet.",
189+
"icon": "DropIcon",
190+
"created_at": "2026-06-08T12:00:00+03:00"
191+
},
184192
{
185193
"slug": "souls-banner-generator",
186194
"to": "/apps/souls-banner-generator",

src/components/AnimatedRoutes.jsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ const NotepadPage = lazy(() => import('../pages/apps/NotepadPage'));
168168
const CozyAppPage = lazy(() => import('../pages/apps/CozyAppPage'));
169169
const SpirographPage = lazy(() => import('../pages/apps/SpirographPage'));
170170
const FractalFloraPage = lazy(() => import('../pages/apps/FractalFloraPage'));
171+
const EbruPage = lazy(() => import('../pages/apps/EbruPage'));
171172
const AlchemyLabPage = lazy(() => import('../pages/apps/AlchemyLabPage'));
172173
const CloudMusicPlayerPage = lazy(
173174
() => import('../pages/apps/CloudMusicPlayerPage'),
@@ -3140,6 +3141,22 @@ const AnimatedRoutes = ({
31403141
</motion.div>
31413142
}
31423143
/>
3144+
<Route
3145+
path="/apps/ebru"
3146+
element={
3147+
<motion.div
3148+
initial="initial"
3149+
animate="in"
3150+
exit="out"
3151+
variants={pageVariants}
3152+
transition={pageTransition}
3153+
>
3154+
<Suspense fallback={<Loading />}>
3155+
<EbruPage />
3156+
</Suspense>
3157+
</motion.div>
3158+
}
3159+
/>
31433160
<Route
31443161
path="/apps/alchemy-lab"
31453162
element={

0 commit comments

Comments
 (0)