Skip to content

Refactor acronym hero hover interaction to use absolute positioning - #569

Merged
jackgranatowski merged 3 commits into
mainfrom
claude/framework-github-page-fixes-okjkan
Jul 8, 2026
Merged

jackgranatowski merged 3 commits into
mainfrom
claude/framework-github-page-fixes-okjkan

Conversation

@jackgranatowski

Copy link
Copy Markdown
Contributor

Summary

Replaces the acronym hero's width-based hover animation with an absolute-positioning approach. The new implementation uses clip-path to reveal unfolded words without affecting layout, eliminating the cursor-tracking bug where hovered words would slide and re-trigger hover on neighbours.

Key changes

  • Acronym hero hover mechanism: Changed from animating width on .rest elements with per-word hardcoded --w values to absolute positioning with clip-path reveal

    • Removed all 7 per-word width declarations (--w: 8.2ch, etc.)
    • Removed container-level translate rules that attempted to counter-shift the centered row
    • Added position: relative to .al container to establish positioning context
    • Positioned .rest absolutely at inset-inline-start: 100% (immediately after bold letter)
    • Used clip-path: inset(0 100% 0 0) for hidden state, inset(0 0 0 0) for revealed state
    • Added z-index: 1 on hover to layer unfolded words above dimmed neighbours
  • Font sizing: Reduced font-size floor from 2.75rem to 2.4rem to ensure the longest unfolded word (Deterministic) fits without clipping on narrow hover-capable viewports

  • Transition timing: Adjusted transition delays (60ms instead of 120ms) and removed container-level transition delay that was coupled to the old translate mechanism

  • Header padding: Added compact block padding (var(--sf-space-s)) to header with .site-nav to override the default section rhythm scale, creating a symmetric 49px nav bar

  • Navigation & documentation links:

    • Replaced /docs/demo.html references with /docs/test-coverage.html (Coverage)
    • Updated demo page links to point to specific coverage pages (test-coverage-3-layout.html, test-coverage-4-macros-states.html)
    • Removed redundant "Demo" link from footer navigation

Implementation details

The old approach animated real width values but couldn't match actual glyph widths, causing siblings to leap and re-trigger hover. The new approach takes unfolded text completely out of flow via absolute positioning and clip-path, so no sibling ever moves under the cursor. This is more robust and eliminates the need for per-word width calculations.

https://claude.ai/code/session_017iAYFfHmHUnbDTgA7LHBmx

…ml links

The homepage hero, header, and demo links all needed work.

Hero acronym re-trigger: the SLASHED letter-row animated each word's real
width on hover and tried to keep the hovered letter under the cursor by
counter-translating the centered row by a hardcoded per-word amount. Those
amounts couldn't match the real glyph widths, so siblings leapt (measured
283px) and slid under the cursor, re-firing the hover on a neighbour. Rework
the reveal to be out of flow: the unfolded text is absolutely positioned and
revealed with clip-path, so unfolding a word changes the size and position of
nothing else — no letter ever moves under the cursor (verified 0px shift), so
re-triggering is structurally impossible. Floor the hover-mode font size so
the longest word (Deterministic) never clips on narrow hover-capable widths.

Header padding: the header used .sf-section--xs, but that still resolves to
the section vertical-rhythm scale (--sf-space-xl, ~52px a side), stacking a
~150px band around a 49px nav. Pin the header to a compact bar (94px).

demo.html retirement (site links only): point every user-facing link — the
homepage nav, hero CTA, docs list, and the shared _layouts nav — to the
test-coverage suite / Full API Demo instead. The doc-prose references in
layout/states/motion now point to the matching coverage pages. demo.html
stays on disk as the internal test fixture the Playwright specs and the
class-coverage gate depend on.

Copy verified against source: 729 tokens, 15 layers, 6-token rebrand,
exactly two components, and the Chrome 125+/Safari 18+/Firefox 129+ floor
all check out and are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017iAYFfHmHUnbDTgA7LHBmx
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jackgranatowski, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0f00d20f-79b9-4d3d-a12f-ff38691c62f0

📥 Commits

Reviewing files that changed from the base of the PR and between d8a0b6f and 08b6c07.

📒 Files selected for processing (8)
  • _layouts/default.html
  • demos/full-api-demo-with-overrides.html
  • demos/full-api-demo.html
  • demos/ultimate-override.css
  • docs/layout.md
  • docs/motion.md
  • docs/states.md
  • index.html
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/framework-github-page-fixes-okjkan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix acronym hero hover by revealing unfolded words out of flow via clip-path

🐞 Bug fix ✨ Enhancement 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Replace acronym hero hover expansion from width/translate hacks to absolute + clip-path reveal
• Compact the header’s vertical padding to match the nav bar’s intended height
• Retire demo.html references by linking docs/nav to the test-coverage suite
Diagram

graph TD
  U([User hover/click]) --> H["index.html (homepage)"] --> HH["Hero hover CSS"] --> R["Absolute '.rest'"] --> C["clip-path reveal"]
  H --> HP["Header padding CSS"]
  H --> L["Nav/footer links"] --> D["docs/*.md links"] --> Cov["Test coverage pages"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep width animation but measure real text widths (JS)
  • ➕ Exact pixel widths avoid glyph mismatch and neighbor re-hover
  • ➕ Keeps text in flow if desired for accessibility/selection
  • ➖ Adds JavaScript to an otherwise zero-JS interaction
  • ➖ More complexity (measurement timing, font-load, resize handling)
2. Use transform-based reveal (scaleX) with overflow masking
  • ➕ Avoids layout reflow compared to animating width
  • ➕ Can be simpler than clip-path in some cases
  • ➖ Scaling text can look distorted during animation
  • ➖ Still needs careful layering/positioning to guarantee no hover retrigger

Recommendation: The chosen absolute-positioning + clip-path approach is the best fit for the stated goal (structurally eliminating cursor-triggered neighbor hover) while preserving the project’s zero-JS ethos. Alternatives either reintroduce complexity (JS measurement) or risk visual artifacts and still require non-trivial hover stability work.

Files changed (5) +43 / -43

Bug fix (1) +39 / -39
index.htmlRefactor acronym hover to absolute + clip-path; compact header; refresh links +39/-39

Refactor acronym hover to absolute + clip-path; compact header; refresh links

• Reworks the acronym hero hover interaction from width-based expansion (with per-word hardcoded widths and container counter-translation) to an out-of-flow absolute-positioned reveal using clip-path, preventing cursor-driven neighbor retriggers. Lowers the hover-mode font-size floor to avoid clipping of the longest unfolded word on narrow hover-capable viewports, and adjusts transition timing/z-index for clean overlay behavior. Also compacts header padding for a true nav-bar feel and replaces demo.html references across nav/CTA/footer with coverage suite links.

index.html

Documentation (4) +4 / -4
default.htmlSwap global nav “Demo” link to “Coverage” +1/-1

Swap global nav “Demo” link to “Coverage”

• Replaces the header navigation link from /docs/demo.html to /docs/test-coverage.html and renames it to “Coverage”. This aligns the shared layout navigation with the new preferred entry point for demos/verification.

_layouts/default.html

layout.mdPoint layout docs to layout coverage page +1/-1

Point layout docs to layout coverage page

• Updates the layout documentation to reference the dedicated layout coverage page instead of demo.html. This keeps docs links accurate after demo.html retirement.

docs/layout.md

motion.mdPoint motion docs to macros & states coverage page +1/-1

Point motion docs to macros & states coverage page

• Replaces the demo.html reference with a link to the macros & states coverage page. Ensures users land on the current, maintained demo surface.

docs/motion.md

states.mdPoint states docs to macros & states coverage page +1/-1

Point states docs to macros & states coverage page

• Updates the states documentation to link to the macros & states coverage page rather than demo.html. Keeps documentation consistent with the coverage suite structure.

docs/states.md

The generated full-api demos were still stamped v0.7.3 — the v0.7.5 version
bump synced the version artifacts but didn't rebuild demos/, so the artifact
drift gate (check-artifacts.js) failed. Regenerating via demos/generate.mjs
updates only the version stamp in each file (no content change), which is
what the gate prescribes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017iAYFfHmHUnbDTgA7LHBmx
@qodo-code-review

qodo-code-review Bot commented Jul 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 12 rules

Grey Divider


Action required

1. Hover query hides words ✓ Resolved 🐞 Bug ☼ Reliability
Description
The acronym hero’s collapsed/hidden state is gated by @media (any-hover: hover), which activates
whenever any input can hover; on hybrid devices, touch interaction may never trigger hover, leaving
only the acronym letters visible with no way to reveal the full words.
Code

index.html[R138-167]

      @media (any-hover: hover) {
-        /* Per-word widths (ch). Each word expands to exactly --w, and the
-           centered row is counter-shifted by --w/2, so the hovered letter
-           stays put under the cursor instead of sliding onto a neighbour
-           and re-firing the hover. */
-        .al:nth-of-type(1) { --w: 8.2ch; }
-        .al:nth-of-type(2) { --w: 3.1ch; }
-        .al:nth-of-type(3) { --w: 6ch; }
-        .al:nth-of-type(4) { --w: 7.8ch; }
-        .al:nth-of-type(5) { --w: 4.3ch; }
-        .al:nth-of-type(6) { --w: 5ch; }
-        .al:nth-of-type(7) { --w: 9.8ch; }
+        /* Collapsed to the tight letter-row; each word unfolds on hover.
+           The unfolded text is taken OUT OF FLOW — absolutely positioned and
+           revealed with clip-path — so unfolding a word changes the size and
+           position of nothing else. No letter ever moves under the cursor, so
+           a hovered word can't slide onto a neighbour and re-fire the hover.
+           (The old approach animated real width and tried to counter-shift the
+           centered row by a hardcoded per-word amount; the amounts couldn't
+           match the real glyph widths, so siblings leapt and re-triggered.) */
        .acronym-hero {
-          /* Never wrap mid-interaction, and cap the size — relative to the
-             hero's own container, with a floor so a hover-capable device
-             at a narrow width (a resized browser, a laptop touchscreen)
-             never shrinks below a readable size — so the widest unfolded
-             word still fits the row on one line. */
-          flex-wrap:  nowrap;
-          row-gap:    0;
-          font-size:  max(2.75rem, min(var(--sf-text-4xl), 4.4cqi));
+          /* Never wrap mid-interaction; cap the size relative to the hero's
+             own container, with a readable floor for narrow hover-capable
+             viewports (resized browser, laptop touchscreen). */
+          flex-wrap:   nowrap;
+          row-gap:     0;
+          /* Floor sized so the longest unfolded word (Deterministic) still
+             fits the hero without clipping on narrow hover-capable widths. */
+          font-size:   max(2.4rem, min(var(--sf-text-4xl), 4.4cqi));
          line-height: var(--sf-display-l-line-height);
-          transition: translate var(--sf-duration-slow) var(--sf-ease-out);
        }
        .al { white-space: nowrap; }
        .al .rest {
-          display:     inline-block;
-          width:       0;
-          opacity:     0;
-          overflow:    hidden;
-          transition: width   var(--sf-duration-slow) var(--sf-ease-out),
-                      opacity var(--sf-duration-normal) var(--sf-ease-out);
+          position:           absolute;
+          inset-block-start:  0;
+          inset-inline-start: 100%;              /* immediately after the bold letter */
+          width:              max-content;
+          opacity:            0;
+          clip-path:          inset(0 100% 0 0); /* hidden by clip, so it occupies no layout space */
+          transition: clip-path var(--sf-duration-slow)   var(--sf-ease-out),
+                      opacity   var(--sf-duration-normal) var(--sf-ease-out);
Relevance

⭐⭐⭐ High

Team previously fixed hover-only UI to work on touch; landing PR #499 explicitly changed acronym to
show words on touch.

PR-#499
PR-#460
PR-#434

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The CSS comment explicitly says touch/no-hover devices should show words, but the implementation
hides .rest inside an any-hover media query. The codebase already uses pointer media queries to
adjust behavior for touch, indicating the intended approach is to differentiate coarse vs fine
inputs.

index.html[104-109]
index.html[138-175]
index.html[368-372]
core/accessibility.css[93-110]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The acronym hero hover interaction is enabled under `@media (any-hover: hover)`, but the stated intent is “touch / no-hover shows words straight away.” `any-hover` can be true on hybrid devices even when the current interaction is touch, resulting in the `.rest` text staying clipped/hidden.

## Issue Context
This is user-visible, impacts discoverability of the manifesto words, and also affects the “Psst — hover the name.” hint which is similarly gated.

## Fix Focus Areas
- index.html[104-177]
- index.html[368-372]

## Suggested fix
- Replace `@media (any-hover: hover)` with a stricter gate such as `@media (hover: hover) and (pointer: fine)` for the collapsed/clip-path behavior.
- Add an explicit touch/coarse-pointer fallback to force the expanded/in-flow presentation when `@media (pointer: coarse)` (or when hover isn’t reliably available).
- Apply the same gating to `.hover-hint` so the hint and the interaction stay consistent.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread index.html Outdated
The acronym-hero collapse/unfold interaction (and the "hover the name" hint)
were gated on @media (any-hover: hover), which is true on hybrid devices —
a touchscreen laptop, or a phone with a stylus/mouse — even while the user
is touching. That stranded the acronym in its collapsed, clipped state with
no touch way to reveal the words, contradicting the page's own stated intent
that touch shows the full words.

Gate both the interaction and the hint on the PRIMARY input actually being a
fine pointer that can hover: @media (hover: hover) and (pointer: fine). Touch
/ coarse-pointer devices now reliably fall through to the default in-flow
state where every word is already shown. Verified: desktop mouse gets the
interactive collapse + hint; a coarse-pointer phone gets full words, no hint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017iAYFfHmHUnbDTgA7LHBmx
@jackgranatowski
jackgranatowski merged commit facb8a1 into main Jul 8, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants