Films and Photos Carousel Hero

Ostria in motion

Sixmomentsfromanordinarydayhere.

Films and photographs from one week in June. Let it play, or step through at your own pace.

Book a stay
01 / 06, FilmThe house as the sun drops
About this block

Films and Photos Carousel HeroPRO

A hotel hero built around a wide mixed-media carousel: silent films play through and photographs drift slowly for a set time before the next slide crossfades in, with a numbered caption that says whether it is a film or a photo, previous, pause and next controls, and a strip of thumbnails marked for films whose hairlines fill with each slide.

A serif headline and book pill sit above a wide carousel that mixes silent films, which play through to their end, with photographs that drift slowly for a set time, crossfading slide to slide under a numbered caption, previous, pause and next controls, and a thumbnail strip whose hairlines fill as each slide plays.

Upgrade to Pro

Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.

Upgrade Now

Installation

Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.

Radix flavor

bash
npx shadcn add "https://beste.dev/r/hotel38?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

bash
npx shadcn add "https://beste.dev/r-base/hotel38?email=YOUR_EMAIL&license_key=YOUR_KEY"

This installs the block to components/beste/block/hotel38.tsx plus the button22 sliding-marker pill used for the book action and the text1 word stagger used for the headline.

Quick start

The installed file exports hotel38Demo alongside the block: the exact props behind the preview above. Spread it to get a working section in one line.

tsx
import { Hotel38, hotel38Demo } from "@/components/beste/block/hotel38";

export default function Page() {
  return <Hotel38 {...hotel38Demo} />;
}

Then replace the demo with your own props. Written out, a trimmed setup looks like this:

tsx
import { Hotel38 } from "@/components/beste/block/hotel38";

export default function Page() {
  return (
    <Hotel38
      eyebrow="Ostria in motion"
      heading="Moments from an ordinary day on Paros."
      description="Films and photographs from one week in June."
      button={{ label: "Book a stay", href: "/book", tone: "dark" }}
      slides={[
        {
          type: "video",
          src: "https://oud.pics/b/hotel/ostria-video-exterior.mp4",
          poster: "https://oud.pics/b/hotel/ostria-exterior-golden.webp",
          alt: "The hotel on its hillside above the cove at golden hour",
          caption: "The house as the sun drops",
        },
        {
          type: "image",
          src: "https://oud.pics/b/hotel/ostria-breakfast-spread.webp",
          alt: "A breakfast table of yogurt, figs, bread and coffee",
          caption: "Breakfast in the dappled shade",
        },
        {
          type: "video",
          src: "https://oud.pics/b/hotel/ostria-video-boat-cove.mp4",
          poster: "https://oud.pics/b/hotel/ostria-boat-cove.webp",
          alt: "A small wooden boat in a clear turquoise cove",
          caption: "Anchored in the third cove",
        },
      ]}
      interval={6}
      labels={{ film: "Film", photo: "Photo", previous: "Previous", next: "Next", play: "Play", pause: "Pause", show: "Show" }}
    />
  );
}

Props

PropTypeDefaultDescription
eyebrowstring–Small line above the headline
headingstring–Serif <h1>, revealed word by word through Text1
descriptionstring–Short paragraph in the right-hand column
buttonActionButton–Pill action through Button22; tone defaults to dark
slidesSlide[][]Films and photographs in carousel order
intervalnumber6Seconds a photo stays on screen before advancing
labelsLabels{}Caption words and accessible names for the controls and thumbnails
classNamestring–Extra classes for the outer <section>
ts
type ActionButton = {
  label: string;
  href: string;
  tone?: "primary" | "light" | "dark" | "outline";
};

type Slide = {
  type: "video" | "image";
  src: string;
  poster?: string;
  alt: string;
  caption: string;
};

type Labels = {
  film?: string;
  photo?: string;
  previous?: string;
  next?: string;
  play?: string;
  pause?: string;
  show?: string;
};

Behavior notes

  • One requestAnimationFrame clock drives the strip: a photo counts its own elapsed time against interval, while a film reports currentTime / duration and advances on its ended event, so a film slide lasts exactly as long as the file.
  • Slides crossfade over 1.2 seconds from a 104% scale, and each photo slowly zooms from 108% to 100% across interval plus 1.2 seconds. The carousel wraps from the last slide back to the first.
  • The caption reads like 02 / 06, Photo, using labels.film or labels.photo by slide type, and swaps with a short slide up.
  • The pause button freezes the photo clock and pauses the current film; previous, next and the thumbnails jump to a slide and reset its clock. Past slides show full hairlines, upcoming ones empty.
  • Thumbnails use a film's poster or a photo's src and carry a small play badge for films. Below sm the thumbnail images and the previous button are hidden, leaving the hairlines as the progress strip.
  • The thumbnail strip is a fixed six-column grid, so six slides fill it exactly; fewer leave empty cells and more wrap onto a second row.
  • With reduced motion the carousel starts stopped on the first slide with films paused; the play button still starts it, and from then on the button alone decides. The frame is 4:5 on a phone, 16:9 from sm and 21:9 from lg.

More Hotel blocks

View all Hotel
PRO

hotel35

Watch the Film Hero

PRO

hotel37

Round Window Film Hero

PRO

hotel21

Postcard Pile Hero

Get Pro