Detail 05 · Signature
A signature written by hand
Handwriting generated from a font, the pen that writes it, how it flies to the navbar and becomes a logo that rewrites itself for each page. 7 minutes, 2 demos.
01 · The idea
A name, written while you watch
A signature is the most personal mark there is, and a sketchbook is signed. So the site opens by signing my name in the middle of an empty page, then keeps that signature as its logo on every page after.
It had to look written, not typed and not animated in from the side: one hand, moving at a human pace, speeding up through the easy strokes and slowing into the ends. And it had to come from a real font, so any page could have a signature of its own without anyone drawing it by hand.
02 · From font to ink
Turning a font into paths
A font on its own can only show text, not write it. So a small script reads the signature font, Tropical Country, with opentype.js, lays out the text at a size of 100, and writes every glyph out as an SVG path, along with roughly how long its outline is and where each word starts and ends.
// Generated from src/assets/fonts/Tropical_Country.ttf for the text "Sujoy Kr. Haldar".
// Paths are at a font size of 100, with the baseline at y = 0.
// Each glyph is drawn in order; `d: null` marks a space (a short pen lift).
// `length` is the approximate outline length, used to pace the drawing.
export const SIGNATURE_VIEWBOX = "-17.1 -77.3 567.1 145.8";
// Font line metrics, used to line SVG words up with real text
export const SIGNATURE_ASCENT = 98.2;
export const SIGNATURE_DESCENT = 70.3;The lengths pace the writing: a long glyph takes longer to write than a short one. The word ranges let the logo shorten to one word, and let the giant name behind the home page line up with real text. Every logo on the site (“Sujoy Kr. Haldar”, “Sujoy’s Resume”, “Sujoy’s Craft”) is one of these files.
03 · The pen
Ink, uncovered by a moving pen
There’s a catch. A font’s glyphs are shapes to fill, not lines to trace: their outline runs around the outside of each stroke, both sides. Animating that outline would draw a hollow letter, not ink.
So the ink is drawn all at once and hidden, and a thick pen moves along each glyph’s outline inside a mask, uncovering the ink beneath it. Show the pen below to see it at work.
14 glyphs, 2 pen lifts, one hand
Three details make it read as handwriting. The pen’s edge is blurred, so the ink seems to flow on rather than being wiped into view. The spaces become short pauses, the pen lifting between words. And the easing runs across the whole name, not glyph by glyph: each glyph gets its share of the time by length, then one ease-in-out curve is laid over all of them, so the name speeds up and slows down as one hand would.
// Width of the "pen" that uncovers each glyph; wide enough to cover the thickest stroke
export const PEN_WIDTH = 18;
// Softens the pen's leading edge so the ink appears to flow on
export const INK_BLUR = 2;
// Pen lift between words, as a fraction of the total ink length
const PEN_LIFT = 0.05;
// Each glyph starts before the previous one finishes, for one continuous motion
const GLYPH_OVERLAP = 2;
// Inverse of an ease-in-out sine curve: maps how far the pen has travelled
// (0-1) to when it gets there, so the whole signature speeds up and slows down
// naturally instead of each glyph moving at its own pace
export const timeAt = (progress) => Math.acos(1 - 2 * progress) / Math.PI;{/* A thick pen stroke travels along each glyph's outline, uncovering the ink beneath */}
<mask
id={`${id}-mask`}
maskUnits="userSpaceOnUse"
x={x - maskMargin}
y={y - maskMargin}
width={w + maskMargin * 2}
height={h + maskMargin * 2}
>
<g filter={`url(#${id}-ink)`}>
{strokes.map((stroke) => {
const start = delay + duration * timeAt(stroke.from);
const length = duration * (timeAt(stroke.to) - timeAt(stroke.from));
return (
<motion.path
key={stroke.glyphIndex}
d={stroke.d}
fill="none"
stroke="#fff"
strokeWidth={PEN_WIDTH}
strokeLinecap="round"
strokeLinejoin="round"
initial={{ pathLength: 0, opacity: 0 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{
delay: start,
duration: length,
ease: "linear",
// Hide the round cap's dot until this glyph's turn
opacity: { delay: start, duration: 0 },
}}
onAnimationComplete={
stroke === myLastStroke ? handleDrawComplete : undefined
}
/>
);
})}
</g>
</mask>Once the last glyph is written the mask is dropped, so the finished signature is plain, crisp paths.
04 · The logo
A logo that knows where it is
When the intro ends, the signature flies up into the navbar and stays there as the logo. From then on it follows you: on the résumé it reads “Sujoy’s Resume”, across Crafts “Sujoy’s Craft”. Changing page, the old logo lifts away in a blur and the pen writes the new one, while the page itself transitions underneath.
Scroll down and it gets out of the way, shortening to one word. The letters leave from the far end, a few hundredths of a second apart, drifting left as they fade; on “Sujoy’s Resume” and “Sujoy’s Craft” the last word is the one kept, so it slides left into the first word’s place.
Which logo a page gets is one small function. The navbar asks it for the page you’re going to, not the page you’re on, so the logo is already rewriting itself while the page transition plays.
/** Which signature serves as the logo on a page */
export const logoSignatureFor = (pathname) => {
if (pathname?.startsWith("/resume")) return SIGNATURES.resume;
if (pathname?.startsWith("/crafts")) return SIGNATURES.craft;
return SIGNATURES.main;
};On a desktop, hovering the logo swaps it for a greeting written in the same font: a hello on the home page, a different one each time, and “Back to home” everywhere else, since that’s where the logo leads.
05 · Decisions
Why it’s built this way
Generated, not traced
Every signature comes from the font by script, so a new section of the site gets its own handwritten logo in minutes.
One hand, not fourteen
The easing spans the whole name. Eased glyph by glyph, each letter would start and stop on its own, like a typewriter.
The intro becomes the logo
The signature from the first seconds is the one in the navbar, flown into place, not a second copy that appears.
Quiet once it's done
Past the top of the page it shortens to one word. The header shouldn’t compete with what you scrolled down to read.
One gap: the script that turns the font into paths lives outside the repository for now, so making a new logo depends on having it to hand. It belongs next to the code it generates.
06 · Takeaways
What to keep
- To make a font write, turn its glyphs into paths and uncover filled ink with a moving mask: outlines are shapes, not strokes.
- Pace by length and ease across the whole word, so it moves like one hand.
- Let the logo answer “where am I”: a different word per section, rewritten as you move between them.
Go to the résumé: the logo lifts away and the pen writes “Sujoy’s Resume” while the page changes.