KOMPOSE.
Toutes les ressources
Site WebClaudeGemini

Volez cet effet vidéo boomerang (le prompt inclus)

Le problème résolu

Les vidéos de fond en boucle qui ont toujours cette coupure gênante au redémarrage, en voici une qui n'en a pas.

Le prompt à copier

Optimisé avecClaudeGemini
### Stack

- **Vite** + **React 18** + **TypeScript**
- **Tailwind CSS 3.4**
- **lucide-react** for icons (`LogIn`, `UserPlus`, `Play`, `Sparkles`, `Menu`, `X`)
- No Framer Motion -- all animations are CSS `transition-*` classes

---

### Fonts (loaded in `index.html`)

```html





```

Body/root font stack (in `index.css`):

```css
html, body, #root {
  height: 100%;
  margin: 0;
  font-family: 'Neue Haas Grotesk Display Pro 55 Roman', 'Neue Haas Grotesk Text Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}
```

---

### Video URL (CloudFront)

```
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260511_131941_d136af49-e243-493a-be14-6ff3f24e09e6.mp4
```

---

### Color Palette

| Token | Hex |
|-------|-----|
| Dark green (text, buttons) | `#1f2a1d` |
| Medium dark green | `#2d3a2a` |
| Button hover | `#2a3827` |
| Body text green | `#4b5b47` |
| Heading primary | `#336443` |
| Heading accent | `#85AB8B` |
| Bottom-left text | `#3d5638` |
| Bottom-left button bg | `#3d5638`, hover `#2d4228` |

---

### Architecture

Two files:

1. **`BoomerangVideoBg.tsx`** -- captures video frames into canvas, then plays them forward/backward in a seamless boomerang loop at 30fps (960px max capture width).
2. **`App.tsx`** -- the full hero section.

---

### `BoomerangVideoBg.tsx` (exact)

```tsx
import { useEffect, useRef, useState } from 'react';

type Props = {
  src: string;
  className?: string;
};

export default function BoomerangVideoBg({ src, className }: Props) {
  const videoRef = useRef(null);
  const displayCanvasRef = useRef(null);
  const [framesReady, setFramesReady] = useState(false);
  const framesRef = useRef([]);

  useEffect(() => {
    const video = videoRef.current;
    if (!video) return;

    const frames: HTMLCanvasElement[] = [];
    let capturing = true;
    let lastTime = -1;
    const MAX_WIDTH = 960;

    const captureFrame = () => {
      if (!capturing || video.readyState < 2) return;
      if (video.currentTime === lastTime) return;
      lastTime = video.currentTime;

      const vw = video.videoWidth;
      const vh = video.videoHeight;
      if (!vw || !vh) return;

      const scale = Math.min(1, MAX_WIDTH / vw);
      const w = Math.round(vw * scale);
      const h = Math.round(vh * scale);

      const canvas = document.createElement('canvas');
      canvas.width = w;
      canvas.height = h;
      const ctx = canvas.getContext('2d');
      if (!ctx) return;
      ctx.drawImage(video, 0, 0, w, h);
      frames.push(canvas);
    };

    type VFCVideo = HTMLVideoElement & {
      requestVideoFrameCallback?: (cb: () => void) => number;
    };
    const vfcVideo = video as VFCVideo;
    const hasVFC = typeof vfcVideo.requestVideoFrameCallback === 'function';

    let rafId = 0;
    const rafLoop = () => {
      captureFrame();
      if (capturing) rafId = requestAnimationFrame(rafLoop);
    };

    const vfcLoop = () => {
      captureFrame();
      if (capturing && vfcVideo.requestVideoFrameCallback) {
        vfcVideo.requestVideoFrameCallback(vfcLoop);
      }
    };

    const onEnded = () => {
      capturing = false;
      if (frames.length > 0) {
        framesRef.current = frames;
        setFramesReady(true);
      }
    };

    const onLoaded = () => {
      video.play().catch(() => {});
      if (hasVFC) {
        vfcVideo.requestVideoFrameCallback!(vfcLoop);
      } else {
        rafId = requestAnimationFrame(rafLoop);
      }
    };

    video.addEventListener('loadedmetadata', onLoaded);
    video.addEventListener('ended', onEnded);
    if (video.readyState >= 1) onLoaded();

    return () => {
      capturing = false;
      cancelAnimationFrame(rafId);
      video.removeEventListener('loadedmetadata', onLoaded);
      video.removeEventListener('ended', onEnded);
    };
  }, [src]);

  useEffect(() => {
    if (!framesReady) return;
    const canvas = displayCanvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    if (!ctx) return;
    const frames = framesRef.current;
    if (frames.length === 0) return;

    const first = frames[0];
    canvas.width = first.width;
    canvas.height = first.height;

    let index = 0;
    let direction = 1;
    let last = performance.now();
    const interval = 1000 / 30;
    let rafId = 0;

    const render = (now: number) => {
      if (now - last >= interval) {
        last = now;
        ctx.drawImage(frames[index], 0, 0);
        index += direction;
        if (index >= frames.length - 1) {
          index = frames.length - 1;
          direction = -1;
        } else if (index  cancelAnimationFrame(rafId);
  }, [framesReady]);

  return (
    
      
      
    
  );
}
```

---

### `App.tsx` (exact)

```tsx
import { useState, useEffect } from 'react';
import { LogIn, UserPlus, Play, Sparkles, Menu, X } from 'lucide-react';
import BoomerangVideoBg from './BoomerangVideoBg';

const BG_VIDEO =
  'https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260511_131941_d136af49-e243-493a-be14-6ff3f24e09e6.mp4';

function App() {
  const [menuOpen, setMenuOpen] = useState(false);

  useEffect(() => {
    if (menuOpen) {
      document.body.style.overflow = 'hidden';
    } else {
      document.body.style.overflow = '';
    }
    return () => {
      document.body.style.overflow = '';
    };
  }, [menuOpen]);

  const navLinks = [
    { href: '#mission', label: 'Purpose' },
    { href: '#how', label: 'The Process' },
    { href: '#pricing', label: 'Tariffs' },
  ];

  return (
    
      
      
        
          
            LinkFlowTM
          
        

        
          {navLinks.map((link, i) => (
            
              {link.label}
            
          ))}
          
            Try it Live
          
        

        
          
            
            Sign Me Up!
          
          
            
            Enter
          
           setMenuOpen((v) => !v)}
            className="lg:hidden relative flex items-center justify-center w-10 h-10 rounded-full bg-white/70 backdrop-blur-md border border-white/60 text-[#1f2a1d] transition-all duration-300 hover:bg-white/90"
            aria-label={menuOpen ? 'Close menu' : 'Open menu'}
            aria-expanded={menuOpen}
          >
            
            
          
        
      

      {/* Mobile menu overlay */}
       setMenuOpen(false)}
      >
        
      

      {/* Mobile menu drawer */}
      
        
          
            {navLinks.map((link, i) => (
               setMenuOpen(false)}
                className={`text-2xl font-semibold text-[#1f2a1d] py-4 border-b border-[#1f2a1d]/10 transition-all duration-500 ${
                  menuOpen ? 'translate-x-0 opacity-100' : 'translate-x-8 opacity-0'
                }`}
                style={{ transitionDelay: menuOpen ? `${150 + i * 70}ms` : '0ms' }}
              >
                {link.label}
              
            ))}
          

          
            
              
              Sign Me Up!
            
            
              
              Enter
            
            
              Try it Live
            
          
        
      

      {/* Hero copy */}
      
        
          Close the rift{' '}
          
            linking
             signals and action
          
        
        
          Shape scattered signals into meaningful outcomes via AI-driven workflows.
        
      

      {/* Bottom-left CTA block */}
      
        
          
          
            FluxEngineTM
          
        
        
          LinkFlow smoothly unites your company systems, streamlining data paths between services without having to write custom scripts.
        
        
          
            Try it Live
          
          
            Know More.
          
        
      

      {/* Bottom-right video link */}
      
        
          
        
        How we build?
        1:35
      
    
  );
}

export default App;
```

---

### Animation Details (all CSS, no Framer Motion)

| Element | Property | Values |
|---------|----------|--------|
| Hamburger Menu/X icon swap | `transition-all duration-300` | Open: Menu gets `opacity-0 rotate-90 scale-50`, X gets `opacity-100 rotate-0 scale-100`. Closed: reverse. |
| Mobile overlay backdrop | `transition-opacity duration-300` | Open: `opacity-100 pointer-events-auto`. Closed: `opacity-0 pointer-events-none`. |
| Mobile drawer slide | `transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]` | Open: `translate-x-0`. Closed: `translate-x-full`. |
| Mobile nav links stagger | `transition-all duration-500` | Open: `translate-x-0 opacity-100`, delay per item: `150ms + i * 70ms`. Closed: `translate-x-8 opacity-0`, delay `0ms`. |
| Mobile CTA group | `transition-all duration-500` | Open: `translate-x-0 opacity-100`, delay `400ms`. Closed: `translate-x-8 opacity-0`, delay `0ms`. |
| Nav buttons | `transition-colors` | Default Tailwind duration (150ms). |
| Opacity links | `transition-opacity` | `hover:opacity-80`. |

---

### Key Layout/Spacing Notes

- Root section: `relative w-full min-h-screen sm:h-screen overflow-hidden`
- Navbar padding: `px-4 sm:px-6 md:px-10 py-4 sm:py-6`
- Desktop pill nav: `bg-white/70 backdrop-blur-md rounded-full pl-6 pr-1 py-1 shadow-sm border border-white/60`
- Hero heading: `pt-24 sm:pt-28 md:pt-32`, font sizes `text-[2rem] sm:text-4xl md:text-5xl lg:text-[4.75rem] xl:text-[5.25rem]`, `leading-[0.95]`, `letterSpacing: '-0.035em'`
- Bottom-left block: `absolute left-4 right-4 sm:right-auto sm:left-6 md:left-10 bottom-6 sm:bottom-8 md:bottom-10`
- Bottom-right video: `absolute right-6 md:right-10 bottom-8 md:bottom-10`

---

### Dependencies (package.json)

```json
{
  "dependencies": {
    "lucide-react": "^0.344.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.3.1",
    "autoprefixer": "^10.4.18",
    "postcss": "^8.4.35",
    "tailwindcss": "^3.4.1",
    "typescript": "^5.5.3",
    "vite": "^5.4.2"
  }
}
```

La plupart des vidéos de fond en boucle ont une jointure évidente : le clip redémarre et on voit une coupure nette. Cette technique évite complètement ça en capturant la vidéo image par image et en rejouant ces images en avant, puis en arrière, puis en avant à nouveau, si bien que le point de boucle devient invisible. La nav vitrée flottante et le tiroir mobile coulissant avec des liens qui apparaissent un par un complètent un hero qui a vraiment l’air premium, pas juste un énième template vidéo statique avec overlay.

Ce qui vaut le coup d’être volé

  • La technique de capture boomerang elle-même, fonctionne avec n’importe quelle vidéo, même celles qui n’étaient jamais prévues pour boucler.
  • Une nav en pilule liquid-glass qui se transforme en vrai tiroir coulissant sur mobile, pas juste un menu accordéon basique.
  • Faire apparaître les liens de nav mobile un par un à l’ouverture donne une impression bien plus soignée qu’un menu qui apparaît d’un coup.

Le prompt ci-dessous est volontairement générique : remplace le nom du produit, le texte et la vidéo par les tiens. Le code du composant est une pure technique, sans branding intégré.

Ressources liées

Toutes les ressources
Site WebClaude

Comment sont construits les sites de Lando Norris, Charles Leclerc et Oscar Piastri

Les sites de pilotes F1 (landonorris.com, charlesleclerc.com, oscarpiastri.com) donnent l'impression d'un budget inatteignable, alors qu'aucun des trois n'utilise de 3D : tout repose sur Lenis, GSAP ScrollTrigger, des révélations de texte masquées et de la vidéo plein cadre. Le vrai blocage, c'est qu'un seul prompt ne peut pas produire ce type de site, parce que les systèmes dépendent les uns des autres (le moteur de scroll avant les animations, le préloader avant le hero, les épinglages dans une seule séquence). Ce guide détaille ce qui tourne réellement sous le capot des trois sites, puis découpe la construction en 10 étapes avec un prompt prêt à coller pour chacune.

›_Tu es mon partenaire de build pour un site one-page cinématique de pilote ou d'athlète, da
Voir la fiche
Site WebClaude

Reproduire une page produit Apple de A à Z (le démontage du code)

Les pages produit d'Apple passent pour hors de portée, alors qu'elles reposent sur cinq couches empilables et toutes reproductibles. Cette ressource ouvre les bundles JavaScript livrés en production sur iPhone 17 Pro, AirPods Pro et MacBook Pro, montre ce que chacune fait vraiment (shaders GLSL maison, scrub vidéo en HLS, moteur de keyframes piloté en CSS), puis donne l'ordre de construction et le prompt complet.

›_You are building an Apple-style product page: a long scroll where the visuals are driven b
Voir la fiche
Site WebClaude

Réussir des animations au scroll de très haute qualité

Le smooth scroll installé en trois lignes donne un site mou, qui tremble sur les sections épinglées et qui devient pénible sur mobile. Un scroll de niveau studio, ce sont quatre couches distinctes à régler séparément : inertie, révélation, épinglage, scrub. Ce guide donne les quatre, avec le code, les valeurs qui marchent et le démontage de trois sites qui les réussissent.

›_# Scroll motion layer: build spec # Portable: save it as CLAUDE.md (Claude Code) or AGENTS
Voir la fiche
Passer à l'action

On vous construit le site ?

Ces ressources donnent le ton. On peut livrer le site complet, rapide et pensé pour convertir.

Réserver un appelÀ propos de nous