Skip to content

FreeIn reviewv1.0.0

Glass Player

A now-playing card on glass with art, a progress bar and play, back and forward controls.
npx shadcn@latest add @vibebox/glass-player

Controls

Tone
214

About

A media card that shows what glass can do over artwork. It carries no audio: connect onPlayingChange to your player.

Use it when

  • Music, podcast or radio sites.
  • A widget over a hero or a 3D background.

Skip it when

  • As a full audio player with a queue: build that on top.

Install

  1. 1Add the VibeBox registry to components.json:
    {
      "registries": {
        "@vibebox": "https://motion.vibeboxph.com/r/{name}.json"
      }
    }
  2. 2Add the component with the shadcn CLI:
    npx shadcn@latest add @vibebox/glass-player

AI prompt

Everything an AI coding tool needs to rebuild this component the way it looks here: colours, fonts, the art it draws, props and rules.

Read the prompt
Build the VibeBox Motion component "Glass Player" (Liquid glass) in a Next.js App Router project with Tailwind CSS v4.

What it is
- A now-playing card on glass with art, a progress bar and play, back and forward controls.
- A music player card: album art (an image or a token gradient), title and artist, a progress bar that rides one scaleX, timecodes and transport buttons. Audio is yours to wire.

Install
- `npx shadcn@latest add @vibebox/glass-player`
- Registry: add `"@vibebox": "https://motion.vibeboxph.com/r/{name}.json"` to components.json, then run the command. The source lands in `components/vibebox/glass-player` and is yours to edit.

Visual spec
- Colours (the VibeBox spectrum, the same in light and dark):
    - --spectrum-1: #00c5fc (cyan)
    - --spectrum-2: #1d6ef0 (blue)
    - --spectrum-3: #5225ed (indigo)
    - --spectrum-4: #e434dc (magenta)
    - --spectrum-5: #fd3aa9 (pink)
    - --spectrum-6: #f98472 (coral)
    - --spectrum-7: #fcbe72 (amber)
- Ink surface (dark): background #05070c, card #0a0d15, text #f5f6fa, muted text #a3abbd, border rgba(255,255,255,0.08).
- Paper surface (light): background #f3f0ea, card #fbfaf7, text #0c0e14, muted text #4a4f5c, border rgba(12,14,20,0.12).
- Fonts:
    - Display and body: Figtree (400, 500, 600, 700), falling back to ui-sans-serif, system-ui.
    - Emphasis: Fraunces italic, for one or two words of a heading.
    - Labels and code: JetBrains Mono (400, 500), uppercase labels with 0.14em tracking.
- Shape: radius 8 to 12px on cards and panels; pills may be fully round. Hairline borders, soft glow instead of heavy shadow.
- Motion: animate transform and opacity only; fast ease-out (cubic-bezier(0.16, 1, 0.3, 1)), 140ms for micro-interactions, 320 to 520ms for reveals.

Art and media
- Built from HTML, CSS and inline SVG only; no image files or video.

Props
- `title`: `string` (default "Midnight Reverie"). Track title.
- `artist`: `string` (default "Luna Park"). Artist.
- `art`: `string`. Album art URL; a token gradient is used without it.
- `artAlt`: `string` (default ""). Alt text for the art.
- `duration`: `number` (default 214). Track length in seconds.
- `playing`: `boolean`. Controlled playing state.
- `onPlayingChange`: `(playing: boolean) => void`. Called when play or pause is pressed.
- `tone`: `"auto" | "dark" | "light"` (default "auto"). Tints the glass and sets the text colour for the page behind it.
- `className`: `string`. Merged with cn(); sets the width and margins.

Behaviour and access
- Reduced motion: The pointer sheen and sliding are off; state changes are instant.
- Keyboard: Buttons are native and focusable; Space or Enter toggles play.
- Screen readers: A section named Now playing; the progress is a progressbar with its value; play is a pressed toggle.
- Touch: Buttons are 40 to 48 px.
- Budget: about 3 KB of JavaScript, WebGL none, GPU low. It pauses while off screen.

Theming
The surface reads `--g-tint`, `--g-edge`, `--g-ink`, `--g-blur` and `--g-radius` from its root; the demo backdrop uses `--spectrum-*`.

Notes
Add the VibeBox Motion Glass Player with `npx shadcn@latest add @vibebox/glass-player`. Pass the track `title`, `artist`, an `art` image URL with alt text and the `duration`, and wire `onPlayingChange` to your audio element. Keep the progress bar on a single scaleX and the 15-second skip buttons.

Rules
- Keep the licence header on every installed file.
- Original code only: do not copy components from other libraries, and do not add GSAP.
- Use tokens for colour, type and radius; no hard-coded brand values.
- Server Components by default; `"use client"` only for the island.

Code

2 files
components/vibebox/glass-player/glass-player.client.tsx
/**
 * VibeBox Motion · Glass Player
 * Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
 * https://motion.vibeboxph.com/license
 * Use it in unlimited personal, commercial and client projects.
 * Do not sell or redistribute it as a component, kit, template or builder.
 */
"use client";

import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
import { Glass, GlassGlyphIcon } from "@/lib/vibebox/glass";

export type GlassPlayerProps = {
  title?: string;
  artist?: string;
  /** Album art: an image URL. Without it the art is a gradient from the spectrum tokens. */
  art?: string;
  /** Alt text for the art. */
  artAlt?: string;
  /** Track length in seconds. */
  duration?: number;
  /** Starts playing (the progress runs; no audio is played, you wire that). */
  playing?: boolean;
  onPlayingChange?: (playing: boolean) => void;
  tone?: "auto" | "dark" | "light";
  className?: string;
};

const time = (seconds: number) => `${Math.floor(seconds / 60)}:${String(Math.floor(seconds % 60)).padStart(2, "0")}`;

/**
 * A now-playing card. The progress runs on a timer while playing and rides one `scaleX`; the audio
 * itself is yours to wire to `onPlayingChange`. Art is an image or a gradient from the tokens.
 */
export function GlassPlayer({ title = "Midnight Reverie", artist = "Luna Park", art, artAlt = "", duration = 214, playing, onPlayingChange, tone, className }: GlassPlayerProps) {
  const [own, setOwn] = useState(false);
  const [elapsed, setElapsed] = useState(62);
  const on = playing ?? own;

  useEffect(() => {
    if (!on) return;
    const id = window.setInterval(() => setElapsed((value) => (value + 0.5 >= duration ? 0 : value + 0.5)), 500);
    return () => window.clearInterval(id);
  }, [on, duration]);

  const toggle = () => {
    setOwn(!on);
    onPlayingChange?.(!on);
  };
  const step = (delta: number) => setElapsed((value) => Math.min(duration, Math.max(0, value + delta)));

  return (
    <Glass as="section" data-vb="glass-player" aria-label="Now playing" tone={tone} className={cn("w-full max-w-[22rem] p-4", className)}>
      <div className="flex items-center gap-3.5">
        <div className="relative size-[4.5rem] flex-none overflow-hidden rounded-lg border border-white/25 shadow-[0_12px_24px_-12px_rgb(0_0_0/0.6)]">
          {art ? (
            // eslint-disable-next-line @next/next/no-img-element
            <img src={art} alt={artAlt} className="size-full object-cover" />
          ) : (
            <span aria-hidden="true" className="absolute inset-0 bg-[radial-gradient(circle_at_30%_25%,var(--spectrum-3),transparent_62%),radial-gradient(circle_at_75%_80%,var(--spectrum-5),transparent_60%),linear-gradient(135deg,var(--spectrum-1),var(--spectrum-6))]" />
          )}
        </div>
        <div className="min-w-0">
          <p className="truncate text-[1rem] leading-tight font-semibold">{title}</p>
          <p className="mt-0.5 truncate text-[0.84rem] opacity-70">{artist}</p>
        </div>
      </div>
      <div className="mt-4">
        <div role="progressbar" aria-label="Track progress" aria-valuemin={0} aria-valuemax={duration} aria-valuenow={Math.round(elapsed)} className="h-1 overflow-hidden rounded-full bg-white/20">
          <span className="block h-full origin-left rounded-full bg-white/90 transition-transform duration-500 ease-linear motion-reduce:transition-none" style={{ transform: `scaleX(${elapsed / duration})` }} />
        </div>
        <div className="mt-1.5 flex justify-between font-mono text-[0.68rem] opacity-65">
          <span>{time(elapsed)}</span>
          <span>-{time(duration - elapsed)}</span>
        </div>
      </div>
      <div className="mt-2 flex items-center justify-center gap-3">
        <button type="button" aria-label="Back 15 seconds" onClick={() => step(-15)} className="grid size-10 place-items-center rounded-full transition-colors hover:bg-white/15 focus-visible:outline-2 focus-visible:outline-ring">
          <GlassGlyphIcon name="prev" />
        </button>
        <button
          type="button"
          aria-label={on ? "Pause" : "Play"}
          aria-pressed={on}
          onClick={toggle}
          className="grid size-12 place-items-center rounded-full border border-white/35 bg-gradient-to-b from-white/45 to-white/15 shadow-[inset_0_1px_0_rgb(255_255_255/0.6)] transition-transform active:scale-95 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
        >
          <GlassGlyphIcon name={on ? "pause" : "play"} className="size-6" />
        </button>
        <button type="button" aria-label="Forward 15 seconds" onClick={() => step(15)} className="grid size-10 place-items-center rounded-full transition-colors hover:bg-white/15 focus-visible:outline-2 focus-visible:outline-ring">
          <GlassGlyphIcon name="next" />
        </button>
      </div>
    </Glass>
  );
}
components/vibebox/glass-player/index.tsx
/**
 * VibeBox Motion · Glass Player
 * Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
 * https://motion.vibeboxph.com/license
 * Use it in unlimited personal, commercial and client projects.
 * Do not sell or redistribute it as a component, kit, template or builder.
 */
export * from "./glass-player.client";

Props

PropTypeDefaultDescription
titlestring"Midnight Reverie"Track title.
artiststring"Luna Park"Artist.
artstringAlbum art URL; a token gradient is used without it.
artAltstring""Alt text for the art.
durationnumber214Track length in seconds.
playingbooleanControlled playing state.
onPlayingChange(playing: boolean) => voidCalled when play or pause is pressed.
tone"auto" | "dark" | "light""auto"Tints the glass and sets the text colour for the page behind it.
classNamestring—Merged with cn(); sets the width and margins.

Accessibility and motion

Keyboard
Buttons are native and focusable; Space or Enter toggles play.
Screen readers
A section named Now playing; the progress is a progressbar with its value; play is a pressed toggle.
Touch
Buttons are 40 to 48 px.
Reduced motion
The pointer sheen and sliding are off; state changes are instant.
  • A frosted glass card over a colourful gradient, with a bright rim and a soft sheen

    The base liquid glass surface: blurred, rimmed and sheened, as a titled card.

    Liquid glass · CSS

  • A glass control centre with four toggle tiles and two sliders

    A control-centre panel: glass toggle tiles and sliders that are real range inputs.

    Liquid glass · CSS

  • A glass confirmation dialog with two buttons over a dimmed colourful page

    A confirmation dialog on glass over a dimmed scrim, with focus kept inside and Escape to cancel.

    Liquid glass · CSS

↑ ↓ to move↵ to openesc to close