Skip to main content

Overview

The Shorts feature on Jefftube provides a mobile-optimized, vertical video viewing experience similar to TikTok, Instagram Reels, or YouTube Shorts. Videos marked with the is_shorts flag are displayed in a full-screen, swipeable format.

What Are Shorts?

Shorts are videos specifically marked for vertical viewing:
export const videos = pgTable("videos", {
  id: varchar("id", { length: 50 }).primaryKey(),
  title: varchar("title", { length: 255 }),
  is_shorts: boolean("is_shorts").notNull().default(false), // Marks video as a short
  // ... other fields
});
The is_shorts flag determines whether a video appears on the Shorts page. Videos are not automatically classified—they must be explicitly marked in the database.

Accessing Shorts

Navigate to the Shorts page by:
1

Click Shorts in Navigation

Select “Shorts” from the sidebar menu (desktop) or navigation drawer (mobile).
2

Direct URL

Navigate to /shorts in your browser.

Shorts Interface

The Shorts page provides a unique full-screen viewing experience:

Layout

Full-Screen Video

Each short takes up the full viewport height (minus header), optimized for vertical 9:16 aspect ratio videos.

Snap Scrolling

Swipe or scroll to navigate between shorts. The interface snaps to each video for a clean viewing experience.

Auto-Play

The currently visible short automatically starts playing. Scrolling to a new video pauses the previous one and plays the new one.

Video Player

<div className="h-[calc(100vh-56px)] snap-start flex items-center justify-center">
  <div className="relative h-full aspect-9/16 bg-black rounded-xl overflow-hidden">
    {/* Poster image when not active */}
    {showPoster && (
      <img src={thumbnailUrl} className="absolute inset-0 object-contain" />
    )}
    
    {/* Video element */}
    <video
      ref={videoRef}
      src={getVideoUrl(video.filename)}
      className="w-full h-full object-contain"
      loop
      playsInline
      onClick={togglePlay}
    />
  </div>
</div>

Mobile Navigation

Swipe up to see the next short, swipe down to return to the previous one. The interface uses snap scrolling for smooth transitions.

Desktop Navigation

On larger screens (≥ 1024px), dedicated navigation buttons appear:
<div className="fixed right-6 top-1/2 -translate-y-1/2 flex-col gap-3 z-50">
  <button
    onClick={goPrev}
    disabled={activeIndex === 0}
    className="w-12 h-12 rounded-full bg-secondary"
  >
    <NavUpIcon />
  </button>
  <button
    onClick={goNext}
    disabled={activeIndex === videos.length - 1}
    className="w-12 h-12 rounded-full bg-secondary"
  >
    <NavDownIcon />
  </button>
</div>
Navigation buttons are hidden on mobile (< 1024px) to avoid cluttering the interface. Mobile users navigate via touch gestures.

Video Controls

Play/Pause Overlay

When you tap to play or pause, a temporary overlay appears:
<div className={`transition-opacity ${showPlayPauseOverlay ? 'opacity-100' : 'opacity-0'}`}>
  <div className="w-20 h-20 rounded-full bg-black/60">
    {overlayIcon === 'play' ? (
      <svg viewBox="0 0 24 24" fill="currentColor">
        <path d="M8 5v14l11-7z" />
      </svg>
    ) : (
      <svg viewBox="0 0 24 24" fill="currentColor">
        <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
      </svg>
    )}
  </div>
</div>
The overlay automatically fades after 1 second.

Top Controls

The top of the video displays:

Play/Pause Button

Toggle video playback

Volume/Mute Button

Toggle audio on/off
<div className="absolute top-0 left-0 right-0 p-3 bg-gradient-to-b from-black/50">
  <button onClick={togglePlay}>
    {isPlaying ? <PauseIcon /> : <PlayIcon />}
  </button>
  <button onClick={toggleMute}>
    {isMuted ? <VolumeMutedIcon /> : <VolumeIcon />}
  </button>
</div>

Video Information Overlay

The bottom of each short displays video and channel information:
<div className="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/70">
  {/* Channel info */}
  <div className="flex items-center gap-3 mb-3">
    <Link to="/">
      <Avatar src={channelAvatar} size="md" />
    </Link>
    <Link to="/" className="font-semibold text-white">
      @jefferyepstein
    </Link>
  </div>
  
  {/* Video title */}
  <p className="text-white line-clamp-2">{video.title}</p>
</div>
The overlay uses a gradient background to ensure text remains readable over any video content.

Action Buttons

Action buttons are positioned on the right side of the screen:

Button Layout

1

Like Button

Shows a heart icon (filled when liked) and the total like count.
<ActionButton
  icon={<LikeIcon filled={userLike === true} />}
  label={formatCount(video.likes)}
  onClick={handleLike}
  active={userLike === true}
/>
2

Comment Button

Shows a comment icon and comment count. Opens a slide-out comment modal.
<ActionButton
  icon={<CommentIcon />}
  label={formatCount(video.commentCount ?? 0)}
  onClick={() => setShowComments(true)}
/>
3

Share Button

Copies the current video URL to clipboard. Changes to “Copied” for 1 second.
<ActionButton
  icon={shareCopied ? <CheckIcon /> : <ShareIcon />}
  label={shareCopied ? "Copied" : "Share"}
  onClick={handleShare}
/>
4

More Options

Displays additional options (three vertical dots).

Count Formatting

Large numbers are abbreviated for clean display:
function formatCount(count: number): string {
  if (count >= 1_000_000) {
    return `${(count / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`;
  }
  if (count >= 1_000) {
    return `${(count / 1_000).toFixed(1).replace(/\.0$/, '')}K`;
  }
  return count.toString();
}

// Examples:
// 1234 → "1.2K"
// 1234567 → "1.2M"
// 1000000 → "1M" (trailing .0 removed)

Comments Modal

Clicking the comment button opens a full-height slide-out modal:
<div className={`fixed top-0 right-0 h-full w-full sm:w-[400px] bg-primary z-50 transform ${isOpen ? 'translate-x-0' : 'translate-x-full'}`}>
  {/* Header */}
  <div className="flex items-center justify-between p-4 border-b">
    <h2>Comments {formatCount(commentCount)}</h2>
    <button onClick={onClose}>
      <CloseIcon />
    </button>
  </div>
  
  {/* Comments content */}
  <div className="h-[calc(100%-64px)] overflow-y-auto px-4">
    <CommentSection videoId={videoId} />
  </div>
</div>
On mobile, the modal takes full width. On larger screens (≥ 640px), it’s constrained to 400px width.
  • Slide Animation: Smoothly slides in from the right
  • Backdrop: Semi-transparent black overlay
  • Body Scroll Lock: Prevents background scrolling when open
  • Auto-Close: Closes automatically when navigating to a different short

Progress Bar

A thin progress bar at the bottom shows playback position:
<div className="absolute bottom-0 left-0 right-0 h-1 bg-white/30">
  <div
    className="h-full bg-red-600"
    style={{ width: `${progress}%` }}
  />
</div>
Progress updates in real-time as the video plays:
const handleTimeUpdate = () => {
  const prog = (videoEl.currentTime / videoEl.duration) * 100;
  setProgress(isNaN(prog) ? 0 : prog);
};

Auto-Play Behavior

Shorts implement smart auto-play:
1

Detect Active Video

The scroll position determines which video is currently visible.
const handleScroll = () => {
  const scrollTop = container.scrollTop;
  const itemHeight = container.clientHeight;
  const newIndex = Math.round(scrollTop / itemHeight);
  setActiveIndex(newIndex);
};
2

Play Active Video

When a video becomes active, it automatically plays and shows the actual video (hiding the poster).
useEffect(() => {
  if (isActive) {
    setShowPoster(false);
    videoRef.current?.play();
  } else {
    videoRef.current?.pause();
    videoRef.current.currentTime = 0;
    setShowPoster(true);
  }
}, [isActive]);
3

Loop Playback

Videos automatically loop when they reach the end.
<video loop playsInline />
Videos are set to preload="none" for inactive shorts to conserve bandwidth. Only the active video preloads and plays.

Performance Optimization

Virtualization

To handle many shorts efficiently, the page uses virtualization:
// Only render videos within range of current position
const BUFFER_SIZE = 2;
const virtualizedVideos = useMemo(() => {
  const start = Math.max(0, activeIndex - BUFFER_SIZE);
  const end = Math.min(videos.length, activeIndex + BUFFER_SIZE + 1);
  
  return videos.slice(start, end).map((video, i) => ({
    video,
    originalIndex: start + i,
  }));
}, [videos, activeIndex]);
This renders:
  • The current video
  • 2 videos before
  • 2 videos after
  • Spacer divs for unmounted videos
Loading all video elements at once would consume massive amounts of memory and bandwidth. Virtualization renders only the videos near the current position, dramatically improving performance, especially with large catalogs.

Random Sorting

Shorts are displayed in random order to provide variety:
const { randomSortedShorts } = useData();
The randomization happens server-side or in the data context, ensuring a consistent but unpredictable viewing order each session.

Responsive Design

Mobile-First Approach

The Shorts interface is optimized for mobile:
  • Touch Gestures: Swipe to navigate
  • Full-Screen: Maximizes video visibility
  • Large Tap Targets: Buttons sized for touch (48px minimum)
  • Overlaid Controls: Maximize video real estate

Desktop Adaptations

Centered Video

Videos center on screen with black letterboxing

Navigation Buttons

Circular up/down buttons for scrolling

Side Actions

Action buttons positioned beside video, not overlaid

Hover States

Interactive elements respond to mouse hover
{/* Action buttons - mobile: overlaid, desktop: beside */}
<div className="absolute right-2 bottom-24 flex flex-col gap-3 lg:static lg:self-end lg:pb-24">
  {/* Buttons */}
</div>

Keyboard Shortcuts

While not explicitly implemented in the current codebase, standard HTML5 video keyboard shortcuts work when the video element has focus (spacebar for play/pause, arrow keys for seek, etc.).

Differences from Regular Videos

FeatureRegular VideosShorts
Page LayoutHorizontal with sidebarFull-screen vertical
NavigationClick suggested videosSwipe/scroll up/down
CommentsBelow videoSlide-out modal
Aspect Ratio16:9 (horizontal)9:16 (vertical)
Auto-PlayOn page load onlyOn scroll to video
Video ControlsStandard HTML5 controlsCustom overlaid controls
Playlist SupportYesNo

Build docs developers (and LLMs) love