Skip to content

Extending @spindeck/vinyl-ui

@spindeck/vinyl-ui is SpinDeck's modular vinyl record player UI library. It separates structure (layout, animation, interaction) from decoration (colors, grooves, sheen) so new visual styles can be added with CSS alone.

Features

  • Interactive tonearm — Drag the arm onto the record to play; lift to pause.
  • Playback feedback — Spin animation and glow effects sync with playback state.
  • Adaptive layout — Size and position adjust to the viewport via CSS variables.
  • Extensible styling — Base + theme CSS architecture for new vinyl looks.

Installation

Add to your workspace package:

json
{
  "dependencies": {
    "@spindeck/vinyl-ui": "workspace:*"
  }
}

Or from the monorepo root:

bash
pnpm add @spindeck/vinyl-ui --workspace

Basic Usage

1. Import styles

Import base structure and at least one style theme in your global CSS:

css
@import "@spindeck/vinyl-ui/styles/base.css";
@import "@spindeck/vinyl-ui/styles/classic.css";
@import "@spindeck/vinyl-ui/styles/modern.css";

2. Render the component

tsx
import { SongVinylOverlay } from "@spindeck/vinyl-ui";

function PlayerPage() {
  return (
    <SongVinylOverlay
      song={currentSong}
      platform="QQMusic"
      visible={true}
      pageSessionId="unique-session-id"
      styleName="classic"
      theme="dark"
      tonearmTitle={t("vinyl.tonearm_title")}
      playLabel={t("vinyl.play")}
      pauseLabel={t("vinyl.pause")}
    />
  );
}

Shared packages do not depend on i18next. Pass translated strings from the app layer via props.

Component API

SongVinylOverlay

PropTypeDefaultDescription
songSongInfoRequiredSong metadata (name, artist, cover).
platformPlatformTypeRequiredMusic platform for playback control.
visiblebooleanRequiredVisibility and entry animations.
pageSessionIdstringRequiredUnique ID for the current page session.
theme"dark" | "light""dark"Used to derive glow colors.
styleNamestring"classic"Maps to .sd-vinyl-style-${styleName}.
tonearmPortalRefRefObjectOptional portal target for the tonearm layer.
tonearmTitlestringTooltip for the tonearm (pass translated text).
playLabel / pauseLabelstringAccessible labels for playback actions.
vinylColor / labelColorstringOverride colors derived from album art.

Other exports

  • VinylStylePreview — Mini preview for style pickers.
  • Tonearm — Standalone tonearm SVG component.
  • Layout and color utilities from ./lib/vinyl-layout and ./lib/colors.

CSS Architecture

Base layer (base.css)

Handles positioning, z-index, spin animations, tonearm interaction, and responsive sizing. Key classes:

ClassRole
.sd-vinyl-stageMain overlay container
.sd-vinyl-discVinyl disc element
.sd-vinyl-grooves / .sd-vinyl-grooves--fineGroove texture layers
.sd-vinyl-sheenHighlight / reflection layer
.sd-vinyl-centerLabel area (title, artist)
.sd-vinyl-arm-wrap / .sd-vinyl-armTonearm wrapper and SVG
.sd-vinyl-stage--playing / --pending / --interactiveState modifiers

Layout CSS variables (set by computeVinylLayout):

  • --vinyl-size — Disc diameter in pixels
  • --vinyl-offset-x — Horizontal offset from center
  • --tonearm-height-ratio — Tonearm height relative to disc

Style layer (classic.css, modern.css, …)

Each style uses the prefix .sd-vinyl-style-<name> and decorates base elements. Built-in styles:

StyleCharacter
classicRich grooves, warm gradients, detailed sheen
modernFlat, high-contrast, minimal texture

Create a New Vinyl Style

Step 1 — Add a style CSS file

Create packages/vinyl-ui/src/styles/retro.css:

css
.sd-vinyl-style-retro .sd-vinyl-disc {
  --vinyl-color: #c62828;
  --vinyl-label-color: #ffeb3b;
  background:
    radial-gradient(circle at 50% 50%, transparent 18%, rgba(255, 255, 255, 0.08) 19%, transparent 20%),
    radial-gradient(circle, color-mix(in srgb, var(--vinyl-color) 60%, #000) 0%, var(--vinyl-color) 100%);
  border: 4px solid color-mix(in srgb, var(--vinyl-color) 50%, white);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
}

.sd-vinyl-style-retro .sd-vinyl-grooves {
  background: repeating-radial-gradient(
    circle at 50% 50%,
    rgba(255, 255, 255, 0.12) 0 0.5px,
    transparent 0.5px 2px
  );
  opacity: 0.8;
}

.sd-vinyl-style-retro .sd-vinyl-center {
  border: 2px solid rgba(255, 255, 255, 0.2);
}

Use --vinyl-color and --vinyl-label-color so the component can inject colors from album artwork at runtime.

Step 2 — Import in the web app

Add to apps/web/app/app.css:

css
@import "@spindeck/vinyl-ui/styles/retro.css";

Step 3 — Expose in settings

Register the style ID wherever the picker lists options. In SpinDeck, update SettingsModal.tsx:

tsx
{["classic", "modern", "retro"].map((styleId) => (
  // ...
  <VinylStylePreview styleName={styleId} /* ... */ />
))}

The selected styleId is stored in VisualSettings.vinylStyle and passed to SongVinylOverlay as styleName.

Step 4 — Use the style

tsx
<SongVinylOverlay
  styleName="retro"
  song={song}
  platform={platform}
  visible={visible}
  pageSessionId={pageSessionId}
/>

Design Guidelines

  • Decorate, don't restructure — Override colors, gradients, borders, and shadows under .sd-vinyl-style-<name>. Avoid changing position, width, or animation keyframes in style files.
  • Respect state classes — Test with --playing, --pending, and --interactive modifiers.
  • Album-driven colors — Prefer var(--vinyl-color) so cover art tinting works out of the box.
  • Preview component — Use VinylStylePreview with the same styleName in pickers for consistent thumbnails.

Package Development

bash
pnpm --filter @spindeck/vinyl-ui dev    # Watch build
pnpm --filter @spindeck/vinyl-ui build  # Production build
pnpm --filter @spindeck/vinyl-ui lint   # Lint

CSS is exported via:

  • @spindeck/vinyl-ui/styles/base.css
  • @spindeck/vinyl-ui/styles/<name>.css

See also Extending @spindeck/ui for app-wide theme families and the Material Contract.

Contributors

The avatar of contributor named as dongguacute dongguacute

Changelog

Released under the Apache License 2.0.