From 1222c8f4d6ae1111699ed1bc8612dc1f5fbb4b72 Mon Sep 17 00:00:00 2001 From: Baltsar Date: Sat, 22 Aug 2026 21:55:39 +0200 Subject: [PATCH] fix(Shuffle): mask clips descenders on any font with them The clip wrapper is sized from the char box (Shuffle.jsx: `ch.getBoundingClientRect().height`), and `.shuffle-char` pins that box to `line-height: 1`. Every typeface has a content area taller than 1em, so the mask is shorter than the glyph and the bottom of g/j/p/q/y is cut off. Measured at font-size 64px, clip box vs content: Outfit 64px vs 72px -> 8px cut Playfair Display 64px vs 75px -> 11px cut Poppins 64px vs 77px -> 13px cut Invisible in the demo because the default face is Press Start 2P with text-transform: uppercase, and uppercase Latin has no descenders. Setting the char's own line-height to `normal` resolves it to the font's ascent + descent, so the wrapper is sized correctly per face. Line spacing stays with .shuffle-parent. --- src/content/TextAnimations/Shuffle/Shuffle.css | 6 +++++- src/tailwind/TextAnimations/Shuffle/Shuffle.jsx | 5 ++++- src/ts-default/TextAnimations/Shuffle/Shuffle.css | 6 +++++- src/ts-tailwind/TextAnimations/Shuffle/Shuffle.tsx | 5 ++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/content/TextAnimations/Shuffle/Shuffle.css b/src/content/TextAnimations/Shuffle/Shuffle.css index e498f57c..c693228a 100644 --- a/src/content/TextAnimations/Shuffle/Shuffle.css +++ b/src/content/TextAnimations/Shuffle/Shuffle.css @@ -27,7 +27,11 @@ } .shuffle-char { - line-height: 1; + /* `normal` resolves to the font's own ascent + descent. The clip wrapper is + sized from this box, so a fixed 1 makes the mask shorter than the glyph + and clips descenders on every face that has them. Line spacing is still + controlled by .shuffle-parent. */ + line-height: normal; display: inline-block; text-align: center; } diff --git a/src/tailwind/TextAnimations/Shuffle/Shuffle.jsx b/src/tailwind/TextAnimations/Shuffle/Shuffle.jsx index af1c7593..ef53e1d3 100644 --- a/src/tailwind/TextAnimations/Shuffle/Shuffle.jsx +++ b/src/tailwind/TextAnimations/Shuffle/Shuffle.jsx @@ -116,7 +116,10 @@ const Shuffle = ({ splitRef.current = new GSAPSplitText(el, { type: 'chars', - charsClass: 'shuffle-char', + // `leading-normal` overrides the parent's `leading-none` on the char + // itself: the clip wrapper is sized from this box, and a line-height + // of 1 makes the mask shorter than the glyph, clipping descenders. + charsClass: 'shuffle-char leading-normal', wordsClass: 'shuffle-word', linesClass: 'shuffle-line', smartWrap: true, diff --git a/src/ts-default/TextAnimations/Shuffle/Shuffle.css b/src/ts-default/TextAnimations/Shuffle/Shuffle.css index e498f57c..c693228a 100644 --- a/src/ts-default/TextAnimations/Shuffle/Shuffle.css +++ b/src/ts-default/TextAnimations/Shuffle/Shuffle.css @@ -27,7 +27,11 @@ } .shuffle-char { - line-height: 1; + /* `normal` resolves to the font's own ascent + descent. The clip wrapper is + sized from this box, so a fixed 1 makes the mask shorter than the glyph + and clips descenders on every face that has them. Line spacing is still + controlled by .shuffle-parent. */ + line-height: normal; display: inline-block; text-align: center; } diff --git a/src/ts-tailwind/TextAnimations/Shuffle/Shuffle.tsx b/src/ts-tailwind/TextAnimations/Shuffle/Shuffle.tsx index 77481b55..8fd16386 100644 --- a/src/ts-tailwind/TextAnimations/Shuffle/Shuffle.tsx +++ b/src/ts-tailwind/TextAnimations/Shuffle/Shuffle.tsx @@ -129,7 +129,10 @@ const Shuffle: React.FC = ({ splitRef.current = new GSAPSplitText(el, { type: 'chars', - charsClass: 'shuffle-char', + // `leading-normal` overrides the parent's `leading-none` on the char + // itself: the clip wrapper is sized from this box, and a line-height + // of 1 makes the mask shorter than the glyph, clipping descenders. + charsClass: 'shuffle-char leading-normal', wordsClass: 'shuffle-word', linesClass: 'shuffle-line', smartWrap: true,