← Case study

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.

Try it: the signature, written by the site

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.

What the script writes: one file per signature
// 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.

The pen, uncovered

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.

The pen, and one curve for the whole name
// 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;
The mask: a thick stroke along each outline
{/* 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.

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.
Watch the logo rewrite itself→

Go to the résumé: the logo lifts away and the pen writes “Sujoy’s Resume” while the page changes.