fix(nav): recenter the logo mark's L and add pointer cursor to nav buttons - #37
Merged
Conversation
…buttons The rotated square looked like it clipped its left side. It didn't — the "." next to "L" faded via `opacity` only, so once invisible it still reserved its full layout width as an inline sibling inside the square's `justify-content: center` box. The visible "L" sat left of true center the entire time the dot was fading (60-130px of scroll), which on a rotated square reads as an asymmetric clip rather than off-center text. The dot's span now also collapses `width` to 0 over the same scroll range (matching the pattern the wordmark's own "L" already uses when it collapses into the icon), so once the dot is gone the "L" recenters. Same scroll-driven motion values regardless of viewport, so this holds identically on mobile. Separately, the three <button> elements in the nav (the Lernen/Praxis/Wissen dropdown triggers, and the mobile menu's open/close toggles) had no `cursor-pointer`. Tailwind v4's preflight removed the pointer-cursor default it used to add for buttons, and a native <button> is `cursor: default` in the browser's own UA styles (unlike `<a href>`, which is `pointer` natively) — so hovering them showed the plain arrow cursor. `cursor-pointer` is already the established convention elsewhere in this codebase; these three had just never picked it up.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
content-lint's TSX comment-skip heuristic only recognizes single-line comments and JSDoc-style `* `-prefixed continuation lines; a plain multi-line `/* ...\n continuation\n */` block, where a continuation line does not itself start with `*`, reads as ordinary prose. My previous commit added exactly that shape with an em-dash on a continuation line, which the gate correctly caught. Rephrased around it rather than touching the shared, canonical content-lint.mjs.
Mavengence
added a commit
that referenced
this pull request
Aug 12, 2026
…on (#38) Added after investigating a report that the icon mark's "L" and "." looked misaligned on scroll: production, dev, and both Chromium and desktop WebKit all measured correctly centred and on the same line at every checkpoint across 0-200px of scroll, on this build and live. No product bug reproduced anywhere; the likely explanation is catching the ~2-minute window between the #37 merge and Vercel's deploy finishing, or a cached page. Given the fix is already shipped and verified rather than in question, the useful "last PR" here is a permanent regression test locking the invariant in for whoever touches this component next, so a revert of the width-collapse fix (or a change that makes the dot a block-level sibling of "L") fails immediately instead of shipping unnoticed. Building it surfaced a real Playwright+WebKit gap along the way: neither a programmatic `scrollTo` + a manually dispatched `scroll` event, nor a real `mouse.wheel` gesture on a desktop WebKit context, ever produced a change in this mark's rendered transform/opacity in Playwright's bundled WebKit browser - headless or headed - while `window.scrollY` itself updated correctly every time. That implicates framer-motion's `useScroll()` reporting to Playwright's WebKit specifically, not this component: every other scroll-driven nav property (icon size, border, wordmark tracking) rides the same MotionValue and would be equally frozen. No existing spec in this suite currently exercises that mechanism (a11y-reduced-motion.spec.ts covers IntersectionObserver-driven whileInView reveals and explicitly excludes this same hero/nav scroll-linked motion from its scan). Skipped on WebKit with a documented reason rather than asserting against geometry that silently never leaves its resting state; Chromium and mobile-chromium exercise the real mechanism reliably and are the coverage that matters here. Stress-tested 8+ consecutive runs across chromium/mobile-chromium/mobile-webkit with zero flakes before landing.
Mavengence
added a commit
that referenced
this pull request
Aug 12, 2026
…aseline (#39) Real bug, reported after #37 shipped: the "." visibly floated above "L"'s bottom edge instead of resting on it. My own earlier Playwright measurements had already caught this (dot bottom 6px above L's bottom at rest) but I mis-scoped the check to "do these two elements' lines merely overlap", which a 4-6px gap still satisfies, and dismissed it as imperceptible without looking at the actual number. Root cause: `inline-block` defaults to `vertical-align: baseline`, which aligns the dot SPAN's own baseline to L's text baseline. Per spec, once `overflow-hidden` is set (needed for the width-collapse animation from #37), an inline-block's own baseline becomes its bottom margin edge rather than its content's text baseline - so the dot's BOX bottom was landing at L's baseline, not at L's own visible bottom. Measured directly (not eyeballed): 4-6px of gap depending on the icon's current font size (13-20px across the scroll range). `align-text-bottom` aligns to the bottom of the parent's font instead of the dot's own computed baseline, landing exactly on L's bottom - measured 0.00px of gap at every font size the icon uses, with a small (<1.3px) residual only under full rotation, which is an axis-aligned-bounding-box measurement artifact of rotating the box, not a real misalignment: it appears in the screen-space rect of both L and the dot equally. The test added in #38 could not have caught this: its overlap check is a much weaker property than "sits on the same baseline". Replaced it with a strict sub-1.5px baseline check (`sitsOnBaseline`), which would have failed against the pre-fix code and now passes against this fix - measured directly on the built output before committing, not just on the reasoning above.
Mavengence
added a commit
that referenced
this pull request
Aug 12, 2026
The mark paired "L" with a "." that faded and collapsed on scroll. That pairing went through two rounds of alignment bugs in a row: 1. (#37) The dot faded via `opacity` alone. Opacity doesn't remove layout width, so the invisible dot kept reserving space inside the square's `justify-content: center` box, pushing "L" off centre. 2. Even after that fix, the dot's `inline-block` defaulted to `vertical-align: baseline`, which (per spec, once `overflow-hidden` is set) aligns the dot's own bottom edge to L's text baseline rather than to L's visible bottom - measured 4-6px of gap depending on the icon's font size, the dot visibly floating above where it should rest. Two fixable bugs on the same pairing is a sign the pairing itself is the wrong shape for this animation, not that the next fix will be the last one. Dropped the dot: "L" alone, centred, rotating, has no second element to misalign against, and no bug class from either regression above can recur. Verified via Playwright measurement (not just visual review): "L" sits within <1px of the square's true centre at every scroll checkpoint from 0-200px, and the square's transform is non-identity once fully scrolled and returns to "none" at rest. Stress-tested 8 consecutive runs across chromium/mobile-chromium/mobile-webkit with zero flakes. The e2e test is rewritten to match: it no longer asserts anything about a dot, since there isn't one, only that "L" stays centred through the full transition and that the square actually rotates.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The "L" wasn't centered — it read as a clip, but was a layout bug
The rotated square looked like it clipped its left side on scroll. It didn't — the
.next toLfaded viaopacityalone, and opacity doesn't remove an element from layout. As an inline sibling ofLinside the square'sjustify-content: centerbox, the invisible dot kept reserving its full width for the entire 60-130px scroll range it takes to fade out. The visibleLsat left of true center that whole time, which on a rotated square reads as an asymmetric clip rather than off-center text.Fix: the dot's span now collapses
widthto0over the same scroll range as its opacity fade — the same pattern the wordmark's ownLalready uses when it collapses into the icon (lWidth/lOpacitya few lines above). Once the dot is gone,Lrecenters.Verified against an isolated reproduction of the exact box model (green line = square's true center):
Lsits left of centerLstraddles center correctlyuseTransform(scrollY, [60,130], ...)range drives both opacity and width, so they're inherently synchronizedThis is a scroll-position-driven motion value with no viewport-conditional logic, so it holds identically on mobile — confirmed the top-of-scroll render is unaffected/unclipped at a 390px viewport.
Nav buttons had no pointer cursor on hover
The three
<button>elements in the nav — the Lernen/Praxis/Wissen dropdown triggers, and the mobile menu's open/close toggles — showed the plain arrow cursor on hover instead of a pointer.Root cause: Tailwind v4's preflight removed the
cursor: pointerdefault it used to add for buttons (a deliberate upstream change), and a native<button>iscursor: defaultin the browser's own UA stylesheet — unlike<a href>, which getspointernatively.cursor-pointeris already the established convention elsewhere in this codebase (14 other usages); these three had just never picked it up.Verification
typecheckclean,lintclean,vitest537 files / 4630 tests passorigin/main(already has the previous logo fix, fix(nav): stop clipping the logo mark and give the lockup a real black cut #35) to avoid touching a concurrent session's checkout in the shared working directoryHeads up on CI
main'sverifygate has a pre-existing, unrelated sitewide WCAG touch-target regression (documented on #35) that fails every PR regardless of diff. If that's still unresolved when this PR's CI runs, the same situation applies — a bypass-list merge may be needed, same as #35.