diff --git a/src/app/now-playing.tsx b/src/app/now-playing.tsx
index 2bbfea4..3dbf012 100644
--- a/src/app/now-playing.tsx
+++ b/src/app/now-playing.tsx
@@ -34,10 +34,15 @@ const SUB_CONTROLS: { icon: IconName; label: string }[] = [
const DISMISS_DISTANCE = 140;
const DISMISS_VELOCITY = 1000;
-const MAX_CONTENT_WIDTH = 390;
+const MAX_CONTENT_WIDTH = 408;
+const CONTENT_SIDE_PADDING = spacing.lg;
+const NARROW_CONTENT_SIDE_PADDING = spacing.md;
const MEDIA_AREA_MIN = 220;
const MEDIA_AREA_MAX = 360;
const ART_SIZE_MAX = 340;
+const SCOPE_HEIGHT_MIN = 150;
+const SCOPE_HEIGHT_MAX = 220;
+const SCOPE_HEIGHT_RATIO = 9 / 16;
const HEADER_HEIGHT = 32;
const CONTENT_TOP_PADDING = spacing.sm;
const CONTENT_BOTTOM_PADDING = spacing.lg;
@@ -63,6 +68,8 @@ interface NowPlayingLayout {
contentWidth: number;
mediaHeight: number;
artSize: number;
+ scopeWidth: number;
+ scopeHeight: number;
mediaTopMargin: number;
mediaBottomGap: number;
showSecondaryControls: boolean;
@@ -73,7 +80,8 @@ function clamp(value: number, min: number, max: number): number {
}
function getNowPlayingLayout(windowWidth: number, availableHeight: number): NowPlayingLayout {
- const contentPadding = windowWidth < 360 ? spacing.lg : spacing.xl;
+ const contentPadding =
+ windowWidth < 360 ? NARROW_CONTENT_SIDE_PADDING : CONTENT_SIDE_PADDING;
const contentWidth = Math.max(0, Math.min(windowWidth - contentPadding * 2, MAX_CONTENT_WIDTH));
const mediaMax = Math.min(contentWidth, MEDIA_AREA_MAX);
const mediaMin = Math.min(mediaMax, MEDIA_AREA_MIN);
@@ -96,12 +104,19 @@ function getNowPlayingLayout(windowWidth: number, availableHeight: number): NowP
const heightBoundMedia = availableHeight - fixedHeight;
const mediaHeight = Math.round(clamp(heightBoundMedia, mediaMin, mediaMax));
const artSize = Math.min(mediaHeight, ART_SIZE_MAX);
+ const scopeWidth = contentWidth;
+ const scopeHeight = Math.min(
+ mediaHeight,
+ Math.round(clamp(scopeWidth * SCOPE_HEIGHT_RATIO, SCOPE_HEIGHT_MIN, SCOPE_HEIGHT_MAX))
+ );
return {
contentPadding,
contentWidth,
mediaHeight,
artSize,
+ scopeWidth,
+ scopeHeight,
mediaTopMargin,
mediaBottomGap,
showSecondaryControls,
@@ -228,17 +243,18 @@ export default function NowPlayingScreen() {
style={[
styles.scopeSurface,
{
- width: layout.contentWidth,
- height: layout.mediaHeight,
+ width: layout.scopeWidth,
+ height: layout.scopeHeight,
},
]}
>
) : (
diff --git a/src/components/SpectrumCurve.tsx b/src/components/SpectrumCurve.tsx
index 730ad35..7e098b4 100644
--- a/src/components/SpectrumCurve.tsx
+++ b/src/components/SpectrumCurve.tsx
@@ -1,5 +1,5 @@
import { useMemo } from 'react';
-import { Canvas, Group, LinearGradient, Path, Skia, vec } from '@shopify/react-native-skia';
+import { Canvas, Group, LinearGradient, Path, Rect, Skia, vec } from '@shopify/react-native-skia';
import { colors } from '@/theme';
interface SpectrumCurveProps {
@@ -14,6 +14,10 @@ interface SpectrumCurveProps {
fillOpacity?: number;
/** Adds a soft wider stroke under the line for a glow. */
glow?: boolean;
+ /** Fades the left/right edges into the background so stage views do not end abruptly. */
+ edgeFade?: boolean;
+ edgeFadeColor?: string;
+ edgeFadeWidth?: number;
}
/** #rrggbb -> rgba() with the given alpha. */
@@ -69,6 +73,9 @@ export function SpectrumCurve({
lineWidth = 2,
fillOpacity = 1,
glow = false,
+ edgeFade = false,
+ edgeFadeColor = colors.bgPrimary,
+ edgeFadeWidth = 28,
}: SpectrumCurveProps) {
const pad = lineWidth;
const { line, fill } = useMemo(
@@ -107,6 +114,24 @@ export function SpectrumCurve({
strokeCap="round"
color={color}
/>
+ {edgeFade && (
+ <>
+
+
+
+
+
+
+ >
+ )}
);
}
diff --git a/src/components/Visualizer.tsx b/src/components/Visualizer.tsx
index 4b220ef..f1fe13a 100644
--- a/src/components/Visualizer.tsx
+++ b/src/components/Visualizer.tsx
@@ -18,6 +18,7 @@ interface VisualizerProps {
interactive?: boolean;
showChrome?: boolean;
mode?: Mode;
+ edgeFade?: boolean;
}
/**
@@ -31,6 +32,7 @@ export function Visualizer({
interactive = true,
showChrome = true,
mode: controlledMode,
+ edgeFade = false,
}: VisualizerProps) {
const [uncontrolledMode, setUncontrolledMode] = useState('spectrum');
const mode = controlledMode ?? uncontrolledMode;
@@ -56,7 +58,7 @@ export function Visualizer({
{mode === 'spectrum' ? (
-
+
) : (