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 Web

21 outils pour finir ton site : typo, couleurs, performance

Un visiteur juge un site en moins d'une seconde, et à cet instant il n'a lu aucun texte. Il a vu une typographie, une palette et une vitesse de chargement. C'est la partie que presque personne ne travaille, et c'est exactement pour ça qu'elle rapporte. Voici les 21 outils que nous utilisons pour la traiter.

Voir la fiche
Site Web

19 galeries pour voler les meilleurs designs du web

Tu ouvres Figma devant une page blanche, ou tu lances un prompt sans savoir à quoi tu veux arriver. Le problème n'est pas le manque d'idées, c'est le manque de références. Voici les 19 sites où nous allons chercher les nôtres.

Voir la fiche
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
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