Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/luigitemu/pikante-landing/llms.txt

Use this file to discover all available pages before exploring further.

PrepVideo.jsx is the only React component in the Pikanté landing. It renders a looping preparation video alongside a branded aside panel, and ships full play/pause and mute/unmute controls. Because it is a React island, it must be used with an Astro client:* directive.

Partial Hydration — client:visible

In Hero.astro the component is consumed as:
<PrepVideo client:visible />
client:visible defers hydration until the component scrolls into the browser viewport, rather than hydrating immediately on page load like client:load would. This means:
  • The initial HTML/CSS paint is unblocked — the hero section is interactive before the video island loads.
  • The video JavaScript bundle (React + component) is only downloaded and executed when the user actually reaches that part of the page, reducing the initial JS payload for visitors who never scroll.
  • The IntersectionObserver inside the component then takes over to further pause/play the video based on continued visibility.
Use client:load only if the video is above the fold and must autoplay immediately on first render.

Video source & attributes

The video file is served as a static asset from public/assets/:
<video
  ref={ref}
  src="/assets/prep-video.mp4"
  autoPlay
  muted
  loop
  playsInline
  preload="metadata"
/>
AttributePurpose
autoPlayStarts playback as soon as the component hydrates
mutedRequired by browsers to allow autoplay without a user gesture
loopRestarts automatically when the clip ends
playsInlinePrevents iOS Safari from opening fullscreen on play
preload="metadata"Fetches only duration/dimensions before play, keeping initial load light
The video file must be placed at public/assets/prep-video.mp4 in the project root. Astro copies everything inside public/ verbatim to the build output, making the file available at the URL path /assets/prep-video.mp4.

IntersectionObserver

A useEffect hook wires up an IntersectionObserver that pauses the video when it leaves the viewport (threshold: 25 % visibility) and resumes it when it re-enters — but only if the user has not manually paused via the play button:
useEffect(() => {
  const v = ref.current;
  if (!v) return;

  const io = new IntersectionObserver(
    (entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting && playing) {
          v.play().catch(() => {});
        } else {
          v.pause();
        }
      });
    },
    { threshold: 0.25 }
  );

  io.observe(v);
  return () => io.disconnect();
}, [playing]);
The playing state variable is a dependency: re-registering the observer whenever playing changes ensures the visibility callback always has the latest user intent.

State & controls

The component tracks two boolean state values:
StateInitialMeaning
playingtrueWhether the video should be playing
mutedtrueWhether the video is muted

Play / Pause

Clicking the play button toggles video.play() / video.pause() and flips playing:
const toggle = () => {
  const v = ref.current;
  if (!v) return;
  if (v.paused) {
    v.play().catch(() => {});
    setPlaying(true);
  } else {
    v.pause();
    setPlaying(false);
  }
};
The button renders a pause icon (two rectangles) when playing is true, and a play icon (triangle) when false.

Mute / Unmute

Clicking the volume button directly toggles video.muted on the DOM element and syncs the muted state:
const toggleMute = () => {
  const v = ref.current;
  if (!v) return;
  v.muted = !v.muted;
  setMuted(v.muted);
};
The button renders a volume-x (Lucide) SVG when muted, and a volume-2 SVG when unmuted.

Layout

.prep-video is a two-column CSS grid:
.prep-video {
  display: grid;
  grid-template-columns: minmax(0, .85fr) 1fr;
  gap: 28px;
  margin-bottom: 48px;
  align-items: stretch;
  width: 100%;
}
ColumnElementContents
Left (.85fr).prep-video-frame<video> + overlay with tag pill + controls
Right (1fr).prep-video-side (aside)Mono label, <h3>, description paragraph
The video frame is aspect-ratio: 9/16 with a max-height: 720px cap, giving it a portrait/phone-reel feel. At viewports below 880 px the grid collapses to a single column.

Aside content

<aside className="prep-video-side">
  <span className="mono accent">Party · Friends · Vibes</span>
  <h3>
    Mírala
    <br />
    <em>en acción.</em>
  </h3>
  <p>
    15 segundos son suficientes para que te prepares la mejor michelada , Pikanté.
  </p>
</aside>
To replace the video, update the src attribute on the <video> element in PrepVideo.jsx — e.g. src="/assets/new-video.mp4" — and drop the new file into public/assets/. No other code changes are required; the controls and IntersectionObserver will work with any video source.

Complete JSX source

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

export default function PrepVideo() {
  const ref = useRef(null);
  const [playing, setPlaying] = useState(true);
  const [muted, setMuted] = useState(true);

  useEffect(() => {
    const v = ref.current;
    if (!v) return;
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting && playing) {
            v.play().catch(() => {});
          } else {
            v.pause();
          }
        });
      },
      { threshold: 0.25 }
    );
    io.observe(v);
    return () => io.disconnect();
  }, [playing]);

  const toggle = () => {
    const v = ref.current;
    if (!v) return;
    if (v.paused) {
      v.play().catch(() => {});
      setPlaying(true);
    } else {
      v.pause();
      setPlaying(false);
    }
  };

  const toggleMute = () => {
    const v = ref.current;
    if (!v) return;
    v.muted = !v.muted;
    setMuted(v.muted);
  };

  return (
    <div className="prep-video reveal">
      <div className="prep-video-frame">
        <video
          ref={ref}
          src="/assets/prep-video.mp4"
          autoPlay
          muted
          loop
          playsInline
          preload="metadata"
        />
        <div className="prep-video-overlay">
          <span className="prep-video-tag">Reel · 00:08 al primer trago</span>
          <div className="prep-video-controls">
            <button
              onClick={toggle}
              aria-label={playing ? 'Pausar' : 'Reproducir'}
              className="pv-btn"
              type="button"
            >
              {playing ? (
                <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
                  <rect x="6" y="5" width="4" height="14" rx="1" />
                  <rect x="14" y="5" width="4" height="14" rx="1" />
                </svg>
              ) : (
                <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
                  <path d="M7 4l14 8-14 8z" />
                </svg>
              )}
            </button>
            <button
              onClick={toggleMute}
              aria-label={muted ? 'Activar sonido' : 'Silenciar'}
              className="pv-btn"
              type="button"
            >
              {muted ? (
                /* volume-x icon */
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
                  viewBox="0 0 24 24" fill="none" stroke="currentColor"
                  strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"/>
                  <line x1="22" x2="16" y1="9" y2="15"/>
                  <line x1="16" x2="22" y1="9" y2="15"/>
                </svg>
              ) : (
                /* volume-2 icon */
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
                  viewBox="0 0 24 24" fill="none" stroke="currentColor"
                  strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"/>
                  <path d="M16 9a5 5 0 0 1 0 6"/>
                  <path d="M19.364 18.364a9 9 0 0 0 0-12.728"/>
                </svg>
              )}
            </button>
          </div>
        </div>
      </div>
      <aside className="prep-video-side">
        <span className="mono accent">Party · Friends · Vibes</span>
        <h3>
          Mírala
          <br />
          <em>en acción.</em>
        </h3>
        <p>
          15 segundos son suficientes para que te prepares la mejor michelada , Pikanté.
        </p>
      </aside>
    </div>
  );
}

Build docs developers (and LLMs) love