On the calligraphy reveal
The studio site has one move on it that nothing else does. The Bismillah inscribes itself in saffron at the top of the home page, right-to-left, over about four and a half seconds. A small saffron dot, the “unseen pen,” traces the leading edge of the text and fades out after the last character settles. There are brief pauses between the four words of the phrase, the way a calligrapher pauses to reload ink.
We’re calling this the calligraphy reveal. It’s the first concrete proof of one of the studio’s most important visual rules: when the unseen speaks, it appears as inscription, not as voice. There’s a long-form note in the previous devlog about why that rule exists. This post is about what shipping it is actually like, in browser, in HTML and CSS.
It is also a post in three parts: what v1 got wrong, what v2 fixes, what v3 still needs. Studios usually publish the third part. We’re going to publish all three because the work is supposed to be visible.
v1: the single sweep
The first pass was a uniform clip-path inset, animated from inset(0 0 0 100%) to inset(0 0 0 0) over 3.2 seconds. The text was a single <span> in Amiri 700, RTL-direction, masked by the clip-path. The animation easing was a stock cubic-bezier: 0.65, 0, 0.35, 1.
It worked. The Arabic appeared right-to-left. It hit on page load. Reduced-motion users skipped the animation and saw the text immediately. The accessibility was clean: the actual Arabic was in the DOM, not painted into an image, and an aria-label carried the transliteration so screen readers heard “Bismillah ar-Rahman ar-Rahim, in the name of God, the Most Gracious, the Most Merciful.”
It also looked mechanical.
The reason it looked mechanical was that nothing about the animation engaged the structure of the script itself. A clip-path inset is a hard line moving across a fixed text. There is no edge character (the leading edge is a hard cut), no per-letter timing (the entire line moves in lockstep), no variable speed (a calligrapher does not draw at a constant rate; their hand pauses, lifts, accelerates over open strokes), and no sense that anything is being made. It was a blind being drawn across a poster, not ink being laid down.
The user feedback after I put it on the home page the first time was a single line: “it’s not as handwritten as I thought it would be.” That was correct.
v2: the soft edge, the pen tip, the lifts
Three changes turned v1 into something that reads handwritten. None of them are clever. All of them are obvious in retrospect.
Soft leading edge. The clip-path is replaced with a mask-image gradient that runs over a 24-pixel feather between fully transparent and fully opaque. The edge is no longer a knife cut; it’s an ink edge that fades in over a few pixels. This is one CSS line and changes the entire feel of the reveal. The mask animates by sliding mask-position-x from -111% (mask is shifted off the right edge entirely, text invisible) to 0% (mask covers the text fully, text visible).
Pen tip indicator. A small saffron circle, ten pixels across, with a soft glow, absolutely positioned and animated to trace the same right-to-left path as the mask edge. The user’s eye tracks it. Without the pen tip, the reveal happens and the eye doesn’t know where to land. With the pen tip, there is a thing making the inscription, even if that thing is a stylized abstraction.
The pen tip is removed entirely under prefers-reduced-motion. It’s decoration; the text is the content.
Non-uniform timing with word-boundary lifts. The Bismillah has four words: بِسْمِ, ٱللَّهِ, ٱلرَّحْمَٰنِ, ٱلرَّحِيمِ. A real calligrapher reaching the end of one word lifts the pen, reloads, and starts the next. v2 simulates this by introducing brief plateaus in the animation curve at each word boundary, about 150 milliseconds of near-zero velocity at the end of words 1, 2, and 3. The mask doesn’t advance during those plateaus, and neither does the pen tip. The eye reads them as little exhalations between words.
The full animation duration also increased from 3.2 seconds to 4.5 seconds. That sounds long for a CSS animation; on the page it actually reads natural. The shorter version felt like a transition; the longer version feels like writing.
The full implementation is about 80 lines of CSS plus a 25-line component. There is no JavaScript animation library, no canvas, no WebGL. The whole thing is two <span> elements and one <div> wrapper. The browser does the work.
What v2 still gets wrong
Three things, in increasing order of how much they bother me.
The mask edge is not the same as ink character. Real ink has weight variation along a stroke; pressure peaks at the start of a letter, releases through it, and the trailing edge has a different texture than the leading edge. v2’s soft edge is a single linear-gradient feather; it has no asymmetry between the front of the wave and the back. Real calligraphy ink does. You can see the difference if you look at any actual Thuluth piece next to v2. The studio’s reveal looks like it’s being uncovered, not like it’s being laid down.
The reveal is a horizontal sweep, not a stroke order. Arabic letterforms have a stroke order. The vertical of the ا is drawn before the curl. The diacriticals (the kasra under بِ, the shadda over the لّ in ٱللَّهِ) are added after the body of the letter, not as part of the same stroke. v2 reveals everything in left-to-right (well, right-to-left) horizontal order, ignoring the stroke order entirely. A literate viewer feels the difference even if they can’t name it.
The technique only works at one length. The Bismillah is 39 Arabic characters. The mask gradient and the pen-tip animation are tuned to that length. Try to use the same component on a longer ayah and the timing breaks. The current code has the timing values hard-coded in keyframes; making it work for arbitrary text would require recomputing the keyframes per string, which the CSS doesn’t do natively.
The first two of those are the “handwritten” gap that v3 has to close. The third is a usability gap that v3 also has to close.
v3: what it will take
I want to be honest about what v3 needs because I want it to actually ship and not be the kind of perpetual “coming soon” that studios put on roadmaps.
The technique is SVG path drawing. Each letter (or stroke, ideally) of the Bismillah is a separate SVG path with stroke instead of fill. The path’s stroke-dasharray is set to its length and stroke-dashoffset is animated from that length down to zero. This is the standard SVG handwriting effect; thousands of websites use it for English handwriting reveals.
For Arabic specifically, there are two implementations in the wild and both have problems.
Option A: runtime extraction with opentype.js. opentype.js can read a font file (we have Amiri loaded already), shape an Arabic string into glyph clusters, and output SVG paths. In theory, the studio’s build script runs once per locked-string and outputs static SVG path data, checked into the repo. The animation component reads the SVG and animates per-path stroke-dashoffset.
The problem is that opentype.js’s Arabic shaping is imperfect. The glyph clusters that come out for ٱللَّهِ are not always what a calligrapher would want. The diacriticals are positioned by the font’s GPOS table but may need manual tuning. We’d need a hand-cleanup pass per string before the animation looks right.
Option B: commission a calligrapher. A real calligrapher draws the Bismillah in Thuluth or Diwani, scans it as separate stroke paths, documents the stroke order, and we save it as a static SVG asset. The animation walks the strokes in the documented order with hand-tuned timing.
The trade is reproducibility versus authenticity. Option A scales: any string can be revealed if we’re willing to do per-string cleanup. Option B doesn’t scale: we’d commission per phrase. But Option B is unambiguously the right rendering for a phrase that matters.
For the Bismillah specifically, I think Option B is right. The Bismillah is the studio’s opening signature; it shows up on the home page and probably in the patron site footer and maybe in every game’s splash. It earns one calligrapher commission. For arbitrary in-game text (an ayah revealed when something happens in the world, the Prophet’s ﷺ reported words appearing as a sahabi recalls them) we use Option A with cleanup, because per-phrase commissioning won’t scale to a game-length script.
So the plan is: commission Option B for the Bismillah this sprint or next; build Option A as the studio’s deenod-calligraphy-reveal package this year, with per-string review by the scholar advisor before any phrase ships.
Where this lives in the studio
The CSS for v2 currently lives in src/app/globals.css at the studio site. The component is at src/components/CalligraphyReveal.tsx. Both are public; you can look at the source on GitHub if that’s the kind of reading you do.
The Unity package (shared/unity-packages/deenod-calligraphy/ per CLAUDE.md §7) is empty so far. When the slice begins and we have an actual scene where calligraphy needs to render in-game, that’s where the technique migrates. The web reveal is the proof of concept; the Unity package is the production system.
Note
Most of what I wrote above is the engineering. The harder version of the problem is the theology, and the previous devlog covered some of that. I’m going to keep alternating between engineering posts like this one and writing posts like the last one because both halves of the studio matter and a devlog that’s only one or the other is not honest.
If anything in this post made you think of a calligrapher we should commission, an opentype.js trick we missed, or a way to handle stroke order that’s more elegant than what I described, reach out via the contact form. The studio is genuinely small enough that one good email finds the right hands.
Moody
Get devlog posts when they ship.
No marketing, no spam. Every other Monday.