Skip to content
← All posts

Thirteen typography decisions for a bilingual marketing site

We just shipped brand v2 across the ByteSpike marketing site — new wordmark, new gradient, new sparkle motif. The visible part took a week. The typography micro-work took longer than that, and you're supposed to never notice it. Here's what actually changed, and why.

KL7 min read

A bilingual marketing site has dozens of typography decisions you don't notice until one of them is wrong. The leading is fine, until you switch to Chinese and the body looks suffocating. The tracking is fine, until you cross 1440px and the headline starts feeling loose. The sparkle pulse is fine, until you read the page twice and realize it's competing with the text for your attention.

We just finished a full brand-v2 migration on bytespike.ai — new wordmark, new gradient (mint → teal-bright → cobalt instead of the old deep-blue / cyan / teal), new sparkle motif borrowed from the K letter of the wordmark itself. The visible work — swapping logos and the favicon — took a few hours. The typography work that has to ride alongside it took the rest of the week. Most of these are micro-decisions; collectively they're what separates a site that reads professional from a site that reads almost-but-not-quite.

The leading problem nobody talks about

Geist's body line-height of 1.5 is fine for Latin script. It is not fine for Chinese. Han glyphs fill more of the em-box than Latin glyphs do, so the same numerical line-height feels visibly tighter — body copy starts to look airless. We default the marketing site to 1.5 and override `:lang(zh) p, :lang(zh) li` to 1.7. That's the leading Apple, Notion, and Linear all use for localized CJK marketing copy. Headlines stay tight via their explicit leading-* classes; only flowing copy gets the bump.

Why headlines need looser tracking on small screens and tighter on large

Default headline tracking of -0.025em reads correct at 1024–1280px. Past 1440px, the same value starts feeling loose: more pixels per em means the negative tracking covers proportionally less ground. We add `lg:tracking-[-0.028em] xl:tracking-[-0.03em]` so the headline tightens as the viewport widens — the optical rhythm stays consistent even when the typographic scale ramps up. The clamp on font-size already handles fluid sizing; this handles fluid spacing.

When motion has to step out of the reader's way

The hero sparkle was originally on an infinite pulse. After watching the page in three different sessions, the same answer kept coming up: it competed with the headline for attention every 3.6 seconds. So we rewrote it as a one-shot entry pulse — 1100ms, ease-out, single iteration, backwards fill — that fires at mount and then settles into a stationary glow. We also delay the sparkle by 500ms so it lands after the bs-reveal cascade on the headline + subline + CTA finishes at 300ms. The eye reaches the sparkle after the words have already parsed, instead of being pulled toward it.

The 13 decisions, listed

  • 1. CJK body line-height = 1.7 via `:lang(zh)` override; Latin stays at 1.5.
  • 2. Headline tracking ramp: -0.025em base, lg:-0.028em, xl:-0.03em.
  • 3. Hero font-size = `clamp(2.4rem, 5.6vw, 4.5rem)` — fluid across viewports without media-query stairs.
  • 4. Headline leading = 1.04 (tight, editorial), body leading = relaxed (1.5 / 1.7) — never the same value.
  • 5. Sparkle pulse: one iteration, 1100ms, ease-out, backwards fill — never an infinite loop on a marketing surface.
  • 6. Sparkle entry delayMs = 500ms — lands after the bs-reveal text cascade settles.
  • 7. Sparkle motif split by size: K-glyph (brand SVG) at ≥48px decorative; lucide 4-point Sparkles at 12–16px inline accents (the K is unreadable at small sizes).
  • 8. ::selection background = brand teal-bright, not cobalt — cobalt against white text creates too much contrast for a comfortable selection state.
  • 9. Hover transitions on price tables ride the brand cubic-bezier(0.32, 0.72, 0.4, 1), not Tailwind's default cubic-bezier(0, 0, 0.2, 1). Same 200ms, gentler curve start.
  • 10. Wordmark height: 22px in Nav, 20px in Footer — 2px difference is invisible to the eye but quietly says "this row is the primary navigation."
  • 11. Font stack chains by script: Geist for Latin (sans + mono), Noto Sans SC fronting `var(--font-zh)` with PingFang SC / Microsoft YaHei fallbacks for Chinese — never a single fallback chain that has to handle both scripts.
  • 12. Radii are a four-point scale (6 / 8 / 12 / 16 / 20px). No 7px, no 10px, no "design hunch" radii. The discipline matters more than the specific values.
  • 13. Easing palette is exactly two curves: `ease-out` for entries / hovers, `ease-in-out` for state cycles. No spring overshoot, no bounce. Restraint is the brand voice.

What we deliberately don't tune

There's a temptation to round off every rough edge — match selection color exactly to the headline gradient, soften every shadow until everything reads "refined." We didn't. The body radial-gradient backgrounds keep their tealish overtone instead of matching the new cobalt; the K sparkle keeps its slightly higher saturation than the wordmark text. These small dissonances are what stop the page from looking dead — they leave a fingerprint.

Typography is the slow part of brand work. You can't generate it from a Figma export, you can't ship it in a PR review, and most of it is invisible if you do it right. But it's the part the reader feels every single time they land on the page. So you grind on it.