Skip to content

feat(home): hover-play video tiles for Mission & Programs (+ ignore .claude/)#107

Open
BK5102 wants to merge 4 commits into
mainfrom
feat/hover-play-media
Open

feat(home): hover-play video tiles for Mission & Programs (+ ignore .claude/)#107
BK5102 wants to merge 4 commits into
mainfrom
feat/hover-play-media

Conversation

@BK5102

@BK5102 BK5102 commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New reusable HoverPlayMedia component renders a muted, looping video that plays on hover or keyboard focus and rewinds on leave
  • Wired into all 3 Mission pillar cards and all 4 Programs cards (including the wide Travel Reimbursement tile).
  • Adds a pan-and-zoom animation to the poster so it visibly moves on hover, until real videos are wired up.
  • Bundles the previously-staged chore: ignore local .claude/ commit so this PR also lands the gitignore rule for Claude Code's local-only state.

Why

Short looping clips give the page motion without auto-playing on load. And because real per-card videos haven't been produced yet, the poster image needed its own motion treatment so the tiles don't look broken in the meantime — hence the CSS pan/zoom fallback.

Implementation

  • src/components/HoverPlayMedia.tsx
    • Renders <img> only when no videoSrc is provided, or when the user has prefers-reduced-motion: reduce.
    • Otherwise renders <video muted loop playsInline preload="none"> with the image as poster, so the still frame shows until the video data is fetched.
    • Always appends a hover-pan-zoom class to whichever element it renders so the CSS pan/zoom animation kicks in on hover/focus.
    • Hover/focus listeners are attached to both the media element and (by default) the nearest .group ancestor, so hovering anywhere on the card triggers play.
    • play() rejections are swallowed silently — if a video file 404s, the user just keeps seeing the (now-animated) poster.
  • src/app.css
    • New @keyframes hover-pan-zoom (gentle scale 1 → 1.08 + offset translate + transform-origin shift).
    • .group:hover .hover-pan-zoom, .group:focus-within .hover-pan-zoom, .hover-pan-zoom:hover, and .hover-pan-zoom:focus-visible all activate the animation.
    • @media (prefers-reduced-motion: reduce) disables the animation entirely.
  • Mission.tsx and Blog.tsx (Programs): each card gets a videoURL paired with the existing poster image. The Mission card containers also gain overflow-hidden so the zoomed poster stays clipped inside the rounded-2xl corners. The no-link Weekly GBM card wrapper picks up group so hover-play works there too.
  • .gitignore: .claude/ added so Claude Code's local-only state (worktrees, launch.json, session data) cannot be accidentally committed via git add . or git add -A.

Reviewer notes

  • Video files at the referenced paths (/events/amazon-table.mp4, /pizza.mp4, etc.) do not exist yet. The page intentionally still ships cleanly without them — the poster image stays visible (and animates on hover) if the video request fails. Drop .mp4/.webm files at those paths to activate playback per card.
  • Recommended encode: H.264, ~1080p, 5–10 s loop, ≤ 2 MB.

Test plan

  • Visit / — Mission and Programs cards show their poster images as before; no console errors with video files missing
  • Hover a Mission or Programs tile — poster pans and zooms slowly, returns smoothly on leave
  • Keyboard-tab through Programs links — pan/zoom animation triggers on focus
  • When a real .mp4 is added at a referenced path, hover plays the video; leaving pauses and rewinds
  • Toggle OS "Reduce motion" — hovering shows the still poster only, no pan/zoom and no video playback
  • git check-ignore -v .claude matches the new gitignore rule
  • git status no longer lists .claude/ as untracked
image

BK5102 added 3 commits May 18, 2026 21:33
## Summary
Add `.claude/` to `.gitignore` so Claude Code's local-only state
(worktrees, launch.json, session data) cannot be accidentally committed
via `git add .` or `git add -A`.

## Why
The `.claude/` folder is created by Claude Code per checkout and contains
machine-local configuration only. It should never make it into the repo.

## Test plan
- [ ] `git check-ignore -v .claude` reports the new rule matched
- [ ] `git status` no longer lists `.claude/` as untracked
## Summary
Add a reusable `HoverPlayMedia` component and wire it into every photo
on the Mission section ("pillars") and the Programs section. Hovering
(or keyboard-focusing) a card now plays a muted, looping video; leaving
pauses and rewinds to the first frame. Inspired by Y Combinator's
homepage tiles.

## Why
Static photos on the homepage feel inert next to YC-style hover-play
tiles. Adding short looping clips gives the page motion without
auto-playing video on load (which would be jarring on a dark-themed
landing page and would burn bandwidth on every visit).

## Implementation
- `src/components/HoverPlayMedia.tsx`
  - Renders `<img>` only when no `videoSrc` is provided, or when the
    user has `prefers-reduced-motion: reduce`.
  - Otherwise renders `<video muted loop playsInline preload="none">`
    with the image as the `poster`, so the still frame is shown until
    the video data is fetched.
  - Wires hover/focus listeners directly on the element and (when
    `playOnGroupHover`, the default) also on the closest `.group`
    ancestor — so hovering anywhere on the card triggers play, matching
    YC's behavior.
  - `play()` rejections are swallowed; if the video file 404s the user
    just keeps seeing the poster, which is what we want while video
    assets are still being produced.
- `Mission.tsx`: each pillar card gets a `videoURL` (`/events/<name>.mp4`)
  paired with its existing poster image, and the card container picks
  up `group` so hover anywhere triggers play.
- `Blog.tsx`: same pattern for all three Programs cards plus the wide
  Travel Reimbursement tile. The no-link Weekly General Body Meetings
  wrapper also gains `group` so hover-play works without a `<Link>`.

## Bundled: ignore local `.claude/` directory
Carrying the previously-staged `chore: ignore local .claude/` commit
in this PR so the next merge into main also lands the gitignore rule
that prevents Claude Code's local-only state from being committed.

## Reviewer notes
- Video files at the referenced paths (`/events/amazon-table.mp4` etc.)
  do not exist yet. The page intentionally still ships cleanly without
  them — the poster image stays visible if the video request fails.
  Drop in `.mp4`/`.webm` files at those paths to activate playback per
  card.
- Recommended encode: H.264, ~1080p, 5–10 s loop, ≤ 2 MB. WebM/VP9 also
  works (browsers will pick whichever the `src` resolves to).

## Test plan
- [ ] Visit `/` — Mission and Programs cards each show their poster
      image as before; no console errors when video files are missing.
- [ ] Hover/focus a card with a real `.mp4` present — video plays muted,
      loops, and resets when you leave.
- [ ] Toggle "Reduce motion" in OS preferences — hovering shows the
      static poster only.
- [ ] `git check-ignore -v .claude` matches the new gitignore rule.
## Summary
Add a `hover-pan-zoom` CSS animation and apply it inside `HoverPlayMedia`
so every Mission and Programs tile visibly moves on hover, even while
the real `.mp4` video files don't exist yet.

## Why
Per task feedback: the hover-play tiles ship before any per-card videos
have been produced, which meant they looked static today. A slow Ken
Burns–style pan/zoom on the poster keeps the YC-style "tiles are alive"
feel until videos are dropped in.

## Implementation
- `src/app.css`: add `@keyframes hover-pan-zoom` (gentle scale 1 → 1.08
  + offset translate + transform-origin shift) and the `.hover-pan-zoom`
  trigger rule scoped to `.group:hover`, `.group:focus-within`,
  `.hover-pan-zoom:hover`, and `.hover-pan-zoom:focus-visible`.
- A `@media (prefers-reduced-motion: reduce)` block disables the
  animation entirely.
- `HoverPlayMedia.tsx`: always append `hover-pan-zoom` to the rendered
  element's className, whether it falls through to the `<img>` poster
  or renders the `<video>`. The CSS handles motion preferences.
- `Mission.tsx`: card containers gain `overflow-hidden` so the zoomed
  poster stays clipped inside the `rounded-2xl` corners. Programs cards
  in `Blog.tsx` already had `overflow-hidden`.

## Test plan
- [ ] Hover a Mission tile — poster image slowly pans and zooms, returns
      smoothly on leave.
- [ ] Hover a Programs tile — same animation; if/when an `.mp4` lands at
      the referenced path, the video plays beneath the same transform.
- [ ] Keyboard-tab through Programs links — animation triggers on focus.
- [ ] OS "Reduce motion" enabled — animation does not run on hover or
      focus; tile stays static.
@BK5102 BK5102 requested a review from a team as a code owner May 19, 2026 04:53
The second `useEffect` was placed after a conditional early return that
swapped between the `<video>` and `<img>` branches, which violates the
React "hooks must be called in the same order on every render" rule and
broke CI lint with `react-hooks/rules-of-hooks`.

- Move the `playOnGroupHover` effect above the early return and gate its
  body on the same `showFallback` flag (so the hook still runs every
  render but bails internally when there's nothing to play).
- Define `play`/`pause` inside the effect so they're scoped to the
  attached listeners and don't need to live in the dep array.
- Keep separate `playOnElement`/`pauseOnElement` closures for the inline
  React event handlers on the `<video>` element — those only run when
  the `<video>` branch is rendered.
- Drop the now-unneeded `eslint-disable-next-line` directive.
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.

1 participant