= ({
},
};
+ /**
+ * If showPlayerEmbed is true use ReactPlayer to render the video
+ * Otherwise, use the Vidstack MediaPlayer. @TEMPORARY_FALLBACK
+ * @TODO [https://skillsoftdev.atlassian.net/browse/GM-998]
+ * Remove ReactPlayer once Vidstack is validated.
+ */
+ if (showPlayerEmbed) {
+ return (
+
+ {isMounted ? (
+ }
+ playing={autoplay}
+ title={videoTitle}
+ url={videoUrl as BaseReactPlayerProps['url']}
+ height="100%"
+ width="100%"
+ onReady={() => {
+ onReady?.();
+ setLoading(false);
+ }}
+ onPlay={onPlay}
+ />
+ ) : null}
+
+ );
+ }
+
return (
-
- {isMounted ? (
- }
- playing={autoplay}
- title={videoTitle}
- url={videoUrl}
- width={width}
- onReady={(player: ReactPlayerWithWrapper) => {
- onReady?.(player);
- setLoading(false);
- }}
- onPlay={onPlay}
- />
- ) : null}
-
+ <>
+ {isMounted && (
+ setLoading(false)} />
+ )}
+ >
);
};
diff --git a/packages/gamut/src/Video/lib/ReactPlayer.tsx b/packages/gamut/src/Video/lib/ReactPlayer.tsx
new file mode 100644
index 0000000000..8684872b16
--- /dev/null
+++ b/packages/gamut/src/Video/lib/ReactPlayer.tsx
@@ -0,0 +1,55 @@
+import { PlayIcon } from '@codecademy/gamut-icons';
+import { css, theme } from '@codecademy/gamut-styles';
+import styled from '@emotion/styled';
+import ReactPlayer from 'react-player';
+
+import { FlexBox } from '../../Box';
+
+export const ReactVideoPlayer = styled(ReactPlayer)(
+ css({
+ padding: 0,
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ '& :focus-visible': {
+ outlineOffset: '3px',
+ },
+ 'video::-webkit-media-controls-panel': {
+ backgroundImage: `linear-gradient(
+ transparent 15%,
+ ${theme.colors['navy-900']} 55%
+ )`,
+ },
+ })
+);
+
+const StyledFlexBox = styled(FlexBox)(
+ css({
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center',
+ position: 'relative',
+ color: 'white',
+ width: '100%',
+ height: '100%',
+ opacity: '0.5',
+ bg: 'black',
+ })
+);
+
+export const OverlayPlayButton = ({ videoTitle }: { videoTitle?: string }) => {
+ return (
+
+
+
+ );
+};
+
+/**
+ * @remarks ReactPlayer has optional key 'wrapper' that we require for the onReady callback
+ */
+
+export type ReactPlayerWithWrapper = ReactPlayer & { wrapper: HTMLElement };
diff --git a/packages/gamut/src/Video/lib/VideoLayout.tsx b/packages/gamut/src/Video/lib/VideoLayout.tsx
new file mode 100644
index 0000000000..6607144b49
--- /dev/null
+++ b/packages/gamut/src/Video/lib/VideoLayout.tsx
@@ -0,0 +1,48 @@
+import { useCurrentMode } from '@codecademy/gamut-styles';
+import { DefaultVideoLayout } from '@vidstack/react/player/layouts/default';
+import {
+ DefaultLayoutTranslations,
+ ThumbnailSrc,
+} from '@vidstack/react/types/vidstack';
+
+import { customLayoutSlots } from './slotOverrides';
+import { defaultTranslations } from './utils/constants';
+import { customIcons } from './utils/icons';
+
+export type VideoLayoutProps = {
+ controls?: boolean;
+ thumbnails?: ThumbnailSrc;
+ translations?: Partial;
+};
+
+/**
+ * VideoLayout component
+ *
+ * This component is responsible for rendering the layout of the video player.
+ * It uses the DefaultVideoLayout component from the vidstack library and applies
+ * custom icons, slots, and other configurations.
+ * For more info see: https://vidstack.io/docs/player/components/layouts/default-layout
+ */
+export const VideoLayout: React.FC = ({
+ controls,
+ thumbnails,
+ translations = {},
+}) => {
+ const mode = useCurrentMode();
+
+ return (
+
+ );
+};
diff --git a/packages/gamut/src/Video/lib/VidstackPlayer.tsx b/packages/gamut/src/Video/lib/VidstackPlayer.tsx
new file mode 100644
index 0000000000..6aac83a589
--- /dev/null
+++ b/packages/gamut/src/Video/lib/VidstackPlayer.tsx
@@ -0,0 +1,127 @@
+/* eslint-disable gamut/no-css-standalone */
+import '../styles/vds_base_theme.scss';
+
+import { styledOptions } from '@codecademy/gamut-styles';
+import styled, { CSSObject } from '@emotion/styled';
+import {
+ isYouTubeProvider,
+ MediaPlayer,
+ MediaPlayerInstance,
+ MediaProvider,
+ MediaProviderAdapter,
+ Poster,
+ Track,
+ useMediaRemote,
+ useMediaState,
+} from '@vidstack/react';
+import React, { useRef } from 'react';
+
+import { Box } from '../../Box';
+import { VideoProps } from '..';
+import { keyboardShortcuts } from './utils/constants';
+import { vdsVariables } from './utils/variables';
+import { VideoLayout } from './VideoLayout';
+
+const VariableProvider = styled(Box, styledOptions(['variables']))<{
+ variables?: CSSObject;
+}>(({ variables }) => variables, {
+ width: '100%',
+ height: '100%',
+ position: 'relative',
+});
+
+type VidstackPlayerProps = VideoProps & {
+ onLoad: () => void;
+};
+export const VidstackPlayer: React.FC = ({
+ autoplay = false,
+ controls = true,
+ loop = false,
+ muted = false,
+ onPlay,
+ onReady,
+ onLoad,
+ placeholderImage,
+ videoTitle,
+ videoUrl,
+ textTracks,
+ thumbnails,
+ translations,
+ width,
+ height,
+ className,
+ showDefaultProviderControls = false,
+}) => {
+ const player = useRef(null);
+ const paused = useMediaState('paused', player);
+ const mediaRemote = useMediaRemote(player);
+
+ /**
+ * !Workaround for Vidstack Youtube Provider issue with native controls!
+ * (Not needed with Custom Controls i.e when controls props is not set to true in )
+ * When the provider changes or player is in waiting state, we need to force re-play the video.
+ * This is because of an issue in Vidstack where Youtube videos with native controls keeps pausing.
+ */
+ const onProviderChange = async (provider: MediaProviderAdapter | null) => {
+ if (isYouTubeProvider(provider)) {
+ await provider.play();
+ }
+ };
+ const onPlayerWaiting = () => {
+ if (isYouTubeProvider(player.current?.provider) && paused) {
+ mediaRemote?.togglePaused();
+ }
+ };
+
+ return (
+
+ {
+ if (autoplay && player.current?.muted && !muted) {
+ mediaRemote?.unmute();
+ }
+ }}
+ onCanPlay={onReady}
+ onProviderChange={onProviderChange}
+ onWaiting={onPlayerWaiting}
+ >
+
+ {placeholderImage && (
+
+ )}
+ {textTracks?.map((track) => (
+
+ ))}
+
+
+
+
+ );
+};
diff --git a/packages/gamut/src/Video/lib/slotOverrides/SeekBackwards.tsx b/packages/gamut/src/Video/lib/slotOverrides/SeekBackwards.tsx
new file mode 100644
index 0000000000..0ba9b2c231
--- /dev/null
+++ b/packages/gamut/src/Video/lib/slotOverrides/SeekBackwards.tsx
@@ -0,0 +1,19 @@
+import { Timer10Icon } from '@codecademy/gamut-icons';
+import { SeekButton, Tooltip } from '@vidstack/react';
+import { useDefaultLayoutWord } from '@vidstack/react/player/layouts/default';
+
+export const SeekBackwardsButton = () => {
+ const seekBackwardText = useDefaultLayoutWord('Seek Backward');
+ return (
+
+
+
+
+
+
+
+ {seekBackwardText}
+
+
+ );
+};
diff --git a/packages/gamut/src/Video/lib/slotOverrides/index.tsx b/packages/gamut/src/Video/lib/slotOverrides/index.tsx
new file mode 100644
index 0000000000..e4ed627435
--- /dev/null
+++ b/packages/gamut/src/Video/lib/slotOverrides/index.tsx
@@ -0,0 +1,27 @@
+import {
+ DefaultVideoLayoutProps,
+ DefaultVideoLayoutSlots,
+} from '@vidstack/react/types/vidstack-react';
+
+import { SeekBackwardsButton } from './SeekBackwards';
+
+/**
+ * Custom layout slots for the video player
+ *
+ * This defines custom slots for the DefaultVideoLayout component and
+ * overrides certain default slots with custom components or null values.
+ * For more info see: https://vidstack.io/docs/player/components/layouts/default-layout/?styling=default-theme#slots
+ */
+export const customLayoutSlots:
+ | DefaultVideoLayoutSlots
+ | DefaultVideoLayoutProps = {
+ smallLayout: {
+ beforeCaptionButton: ,
+ },
+ largeLayout: {
+ afterPlayButton: ,
+ },
+ googleCastButton: null,
+ airPlayButton: null,
+ downloadButton: null,
+};
diff --git a/packages/gamut/src/Video/lib/utils/constants.ts b/packages/gamut/src/Video/lib/utils/constants.ts
new file mode 100644
index 0000000000..8023f3cbee
--- /dev/null
+++ b/packages/gamut/src/Video/lib/utils/constants.ts
@@ -0,0 +1,30 @@
+import { MediaKeyShortcuts } from '@vidstack/react';
+import { DefaultLayoutTranslations } from '@vidstack/react/types/vidstack';
+
+export const keyboardShortcuts: MediaKeyShortcuts = {
+ togglePaused: 'k Space',
+ toggleMuted: 'm',
+ toggleFullscreen: 'f',
+ togglePictureInPicture: 'i',
+ toggleCaptions: 'c',
+ seekBackward: 'j J ArrowLeft',
+ seekForward: 'l L ArrowRight',
+ volumeUp: 'ArrowUp',
+ volumeDown: 'ArrowDown',
+ speedUp: '>',
+ slowDown: '<',
+};
+
+export const defaultTranslations: Partial = {
+ 'Seek Backward': 'Back 10 seconds',
+ 'Enter PiP': 'Enter picture-in-picture',
+ 'Exit PiP': 'Exit picture-in-picture',
+ 'Enter Fullscreen': 'Enter fullscreen',
+ 'Exit Fullscreen': 'Exit fullscreen',
+ 'Caption Styles': 'Caption styles',
+ 'Text Background': 'Text background',
+ 'Closed-Captions On': 'Closed-Captions on',
+ 'Closed-Captions Off': 'Closed-Captions off',
+ 'Display Background': 'Display background',
+ 'Keyboard Animations': 'Keyboard animations',
+};
diff --git a/packages/gamut/src/Video/lib/utils/icons.tsx b/packages/gamut/src/Video/lib/utils/icons.tsx
new file mode 100644
index 0000000000..905f1d8778
--- /dev/null
+++ b/packages/gamut/src/Video/lib/utils/icons.tsx
@@ -0,0 +1,61 @@
+import {
+ ClosedCaptionDisabledIcon,
+ ClosedCaptionIcon,
+ FullscreenIcon,
+ GearIcon,
+ ListIcon,
+ MiniAccessibilityIcon,
+ MiniCloseCaptioningIcon,
+ MiniLiveVideoIcon,
+ MinimizeIcon,
+ PauseIcon,
+ PipExitIcon,
+ PipIcon,
+ PlayIcon,
+ RefreshIcon,
+ VolumeControlFullIcon,
+ VolumeControlMediumIcon,
+ VolumeControlMuteIcon,
+} from '@codecademy/gamut-icons';
+import {
+ DefaultLayoutIcon,
+ DefaultLayoutIcons,
+ defaultLayoutIcons,
+} from '@vidstack/react/player/layouts/default';
+
+const IconWrapper = (Icon: DefaultLayoutIcon) => (props: any) =>
+ ;
+
+export const customIcons: DefaultLayoutIcons = {
+ ...defaultLayoutIcons,
+ PlayButton: {
+ Play: PlayIcon as DefaultLayoutIcon,
+ Pause: PauseIcon as DefaultLayoutIcon,
+ Replay: RefreshIcon as DefaultLayoutIcon,
+ },
+ MuteButton: {
+ Mute: IconWrapper(VolumeControlMuteIcon as DefaultLayoutIcon),
+ VolumeHigh: IconWrapper(VolumeControlFullIcon as DefaultLayoutIcon),
+ VolumeLow: IconWrapper(VolumeControlMediumIcon as DefaultLayoutIcon),
+ },
+ CaptionButton: {
+ Off: IconWrapper(ClosedCaptionDisabledIcon as DefaultLayoutIcon),
+ On: IconWrapper(ClosedCaptionIcon as DefaultLayoutIcon),
+ },
+ PIPButton: {
+ Enter: IconWrapper(PipIcon as DefaultLayoutIcon),
+ Exit: IconWrapper(PipExitIcon as DefaultLayoutIcon),
+ },
+ Menu: {
+ ...defaultLayoutIcons.Menu,
+ Accessibility: MiniAccessibilityIcon as DefaultLayoutIcon,
+ Playback: MiniLiveVideoIcon as DefaultLayoutIcon,
+ Captions: MiniCloseCaptioningIcon as DefaultLayoutIcon,
+ Chapters: ListIcon as DefaultLayoutIcon,
+ Settings: GearIcon as DefaultLayoutIcon,
+ },
+ FullscreenButton: {
+ Enter: FullscreenIcon as DefaultLayoutIcon,
+ Exit: MinimizeIcon as DefaultLayoutIcon,
+ },
+};
diff --git a/packages/gamut/src/Video/lib/utils/variables.ts b/packages/gamut/src/Video/lib/utils/variables.ts
new file mode 100644
index 0000000000..fe8c01443d
--- /dev/null
+++ b/packages/gamut/src/Video/lib/utils/variables.ts
@@ -0,0 +1,47 @@
+import { theme } from '@codecademy/gamut-styles';
+
+interface Variables {
+ [key: string]: string;
+}
+
+/**
+ * Variables for the Vidstack Player
+ * list of variables can be found here:
+ * https://vidstack.io/docs/player/components/layouts/default-layout/?styling=default-theme#css-variables
+ */
+export const vdsVariables: Variables = {
+ // Video Variables
+ '--video-font-family': theme.fontFamily.base,
+ '--video-brand': theme.colors['gray-100'],
+ '--video-controls-color': theme.colors['gray-100'],
+ '--video-focus-ring-color': theme.colors.primary,
+ '--video-bg': theme.colors.black,
+ '--video-border-radius': theme.borderRadii.md,
+ // Media/Control Variables
+ '--media-button-border-radius': theme.borderRadii.md,
+ '--media-button-padding': `0px ${theme.spacing[8]}`,
+ '--media-tooltip-bg-color': theme.colors['background-contrast'],
+ '--media-tooltip-border-radius': theme.borderRadii.sm,
+ '--media-tooltip-border': `1px solid ${theme.colors['border-primary']}`,
+ '--media-tooltip-color': theme.colors.text,
+ '--media-tooltip-font-size': theme.fontSize[14],
+ '--media-tooltip-padding': theme.spacing[4],
+ '--media-menu-checkbox-bg-active': theme.colors.primary,
+ '--media-menu-checkbox-bg': theme.colors['text-disabled'],
+ '--media-menu-checkbox-width': '36px',
+ '--media-menu-checkbox-height': '18px',
+ '--media-menu-checkbox-handle-border': `1px solid ${theme.colors.background}`,
+ '--media-slider-preview-border-radius': theme.borderRadii.sm,
+ '--media-slider-value-bg': theme.colors['background-contrast'],
+ '--media-slider-value-border-radius': theme.borderRadii.sm,
+ '--media-slider-value-border': `1px solid ${theme.colors['border-primary']}`,
+ '--media-slider-value-color': theme.colors.text,
+ '--media-slider-value-padding': theme.spacing[4],
+ '--media-tooltip-exit-animation': 'vds-tooltip-exit 0s',
+ '--media-tooltip-enter-animation': 'vds-tooltip-enter 0s',
+ '--media-menu-bg': theme.colors.background,
+ '--media-menu-top-bar-bg': theme.colors.background,
+ '--media-menu-section-bg': theme.colors['background-selected'],
+ '--media-menu-border': `1px solid ${theme.colors['border-primary']}`,
+ '--media-menu-border-radius': theme.borderRadii.sm,
+};
diff --git a/packages/gamut/src/Video/styles/index.module.scss b/packages/gamut/src/Video/styles/index.module.scss
deleted file mode 100644
index 898fbb3936..0000000000
--- a/packages/gamut/src/Video/styles/index.module.scss
+++ /dev/null
@@ -1,45 +0,0 @@
-@import "~@codecademy/gamut-styles/utils";
-
-.videoWrapper {
- position: relative;
- width: 100%;
- padding-top: 56.25%; /* Player ratio: 100 / (1280 / 720) */
- border-radius: 4px;
- overflow: hidden;
- &.loading {
- background-color: $color-blue-1100;
- }
-}
-
-.overlay {
- background-color: rgba(0, 0, 0, 0.5);
- width: 100%;
- height: 100%;
- color: $color-white;
- display: flex;
- justify-content: center;
- flex-direction: column;
- align-items: center;
- position: relative;
-}
-
-.iframe {
- width: 100% !important;
- height: 100% !important;
- border: 0;
- padding: 0;
- position: absolute;
- top: 0;
- left: 0;
- & :focus-visible {
- outline-offset: 3px;
- }
-}
-
-.hoverButton {
- width: 15%;
- height: 26.7%;
- min-width: px-rem(75px);
- min-height: px-rem(75px);
- color: $color-white;
-}
diff --git a/packages/gamut/src/Video/styles/vds_base_theme.scss b/packages/gamut/src/Video/styles/vds_base_theme.scss
new file mode 100644
index 0000000000..7f57a55dee
--- /dev/null
+++ b/packages/gamut/src/Video/styles/vds_base_theme.scss
@@ -0,0 +1,99 @@
+@import "~@vidstack/react/player/styles/default/theme.css";
+@import "~@vidstack/react/player/styles/default/layouts/video.css";
+@import "~@vidstack/react/player/styles/default/layouts/audio.css";
+@import "~@codecademy/gamut-styles/utils";
+
+// Youtube overlay
+iframe.vds-youtube[data-no-controls] {
+ height: 100%;
+}
+
+[data-started] iframe.vds-youtube[data-no-controls] {
+ height: 1000%;
+}
+
+// Control buttons
+:where(.vds-button .vds-icon) {
+ border-radius: unset;
+}
+
+.vds-controls button {
+ border-radius: var(--media-button-border-radius);
+}
+
+:where(.vds-video-layout[data-sm]) :where(.vds-button) {
+ padding: 0px px-rem(4px) !important;
+}
+
+// Small layout volume / time slider
+:where(.vds-video-layout[data-sm] .vds-volume-slider .vds-slider-track) {
+ background-color: var(--color-gray-600);
+}
+:where(.vds-video-layout[data-sm] .vds-volume-slider .vds-slider-track-fill) {
+ background-color: var(--color-secondary);
+}
+.vds-video-layout .vds-time-slider .vds-slider-value {
+ border: unset;
+ color: white;
+}
+
+// Tooltip
+.vds-tooltip-content::after {
+ content: "";
+ -webkit-transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ transform: rotate(45deg);
+ border-style: solid;
+ display: block;
+ width: 1rem;
+ height: 1rem;
+ position: absolute;
+ border-color: currentColor;
+}
+:where(.vds-tooltip-content[data-placement~="top"])::after {
+ border-width: 0 1px 1px 0;
+ bottom: -0.5rem;
+ left: calc(50% - 0.5rem);
+ background-image: linear-gradient(
+ to top left,
+ var(--color-background-contrast) 55%,
+ rgba(0, 0, 0, 0) 20%
+ );
+}
+
+:where(.vds-tooltip-content[data-placement~="bottom"])::after {
+ border-width: 1px 0px 0px 1px;
+ top: -0.5rem;
+ left: calc(50% - 0.5rem);
+ background-image: linear-gradient(
+ to bottom right,
+ var(--color-background-contrast) 55%,
+ rgba(0, 0, 0, 0) 20%
+ );
+}
+
+:where(.vds-tooltip-content[data-placement~="start"])::after {
+ left: calc(0% + 0.5rem);
+}
+:where(.vds-tooltip-content[data-placement~="end"])::after {
+ left: unset;
+ right: calc(0% + 0.5rem);
+}
+
+@keyframes vds-tooltip-exit {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+@keyframes vds-tooltip-enter {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
diff --git a/packages/styleguide/src/lib/Molecules/Video/Video.stories.tsx b/packages/styleguide/src/lib/Molecules/Video/Video.stories.tsx
index e3166f0d7f..438db33028 100644
--- a/packages/styleguide/src/lib/Molecules/Video/Video.stories.tsx
+++ b/packages/styleguide/src/lib/Molecules/Video/Video.stories.tsx
@@ -25,9 +25,34 @@ export const Vimeo: Story = {
export const VideoWithPlaceholder: Story = {
args: {
- videoUrl: 'https://player.vimeo.com/video/188237476',
- videoTitle: 'A Dream Within a Dream',
- placeholderImage: 'https://placekitten.com/400/300',
- autoplay: true,
+ videoUrl: 'https://files.vidstack.io/sprite-fight/hls/stream.m3u8',
+ videoTitle: 'Sprite Fight',
+ placeholderImage: 'https://files.vidstack.io/sprite-fight/poster.webp',
+ autoplay: false,
+
+ textTracks: [
+ {
+ src: 'https://files.vidstack.io/sprite-fight/subs/english.vtt',
+ label: 'English',
+ language: 'en-US',
+ kind: 'subtitles',
+ },
+ {
+ src: 'https://files.vidstack.io/sprite-fight/subs/spanish.vtt',
+ label: 'Spanish',
+ language: 'es-ES',
+ kind: 'subtitles',
+ default: true,
+ },
+ {
+ src: 'https://files.vidstack.io/sprite-fight/chapters.vtt',
+ language: 'en-US',
+ kind: 'chapters',
+ type: 'vtt',
+ default: true,
+ },
+ ],
+
+ thumbnails: 'https://files.vidstack.io/sprite-fight/thumbnails.vtt',
},
};
diff --git a/packages/styleguide/src/lib/Organisms/Markdown/example.md b/packages/styleguide/src/lib/Organisms/Markdown/example.md
index ee8578c45d..3b2edf22a0 100644
--- a/packages/styleguide/src/lib/Organisms/Markdown/example.md
+++ b/packages/styleguide/src/lib/Organisms/Markdown/example.md
@@ -347,17 +347,21 @@ Use the `printf()` function.
### Iframes
-Vimeo and Youtube video iframes will be rendered by our Video component, otherwise they'll render the original code.
+Vimeo and Youtube video iframes will be rendered by our Video component (if showPlayerEmbed is true it will be rendered in actual embed), otherwise they'll render the original code.
-
-
+
### Video
`video`s with an `src` or a `source` video file will be rendered by our Video component, otherwise they'll render the original code. Videos with a `style` prop or another restricted prop will be stripped of that property.
+Insert `track` to add subtitles / captions to the video.
-
+
diff --git a/yarn.lock b/yarn.lock
index 393aafe53d..f1036a8d80 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1541,6 +1541,14 @@
"@floating-ui/core" "^1.6.0"
"@floating-ui/utils" "^0.2.8"
+"@floating-ui/dom@^1.6.10":
+ version "1.6.12"
+ resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz#6333dcb5a8ead3b2bf82f33d6bc410e95f54e556"
+ integrity sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==
+ dependencies:
+ "@floating-ui/core" "^1.6.0"
+ "@floating-ui/utils" "^0.2.8"
+
"@floating-ui/utils@^0.2.8":
version "0.2.8"
resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62"
@@ -3997,6 +4005,14 @@
resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
+"@vidstack/react@^1.12.12":
+ version "1.12.12"
+ resolved "https://registry.npmjs.org/@vidstack/react/-/react-1.12.12.tgz#abf8b794edb461bc4b7de6e97be355a19d75e102"
+ integrity sha512-rEwCkZdp/K0FopUU76lgYJqzOZa3AGM2FumQnTPPSCP9jwShYReY9mXgl4/UP5CH3v7MkaVl5ZT67rhfjPxx9Q==
+ dependencies:
+ "@floating-ui/dom" "^1.6.10"
+ media-captions "^1.0.4"
+
"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1":
version "1.12.1"
resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb"
@@ -7674,12 +7690,12 @@ fraction.js@^4.3.7:
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
framer-motion@^11.18.0:
- version "11.18.0"
- resolved "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.0.tgz#574f311dd91333fad59214cc8e2dee0f5bab99c4"
- integrity sha512-Vmjl5Al7XqKHzDFnVqzi1H9hzn5w4eN/bdqXTymVpU2UuMQuz9w6UPdsL9dFBeH7loBlnu4qcEXME+nvbkcIOw==
+ version "11.18.2"
+ resolved "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz#0c6bd05677f4cfd3b3bdead4eb5ecdd5ed245718"
+ integrity sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==
dependencies:
- motion-dom "^11.16.4"
- motion-utils "^11.16.0"
+ motion-dom "^11.18.1"
+ motion-utils "^11.18.1"
tslib "^2.4.0"
fresh@0.5.2:
@@ -10282,6 +10298,11 @@ mdn-data@2.0.4:
resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
+media-captions@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/media-captions/-/media-captions-1.0.4.tgz#aaacb6a554f7549fc7308b6b7cf766686cf42fad"
+ integrity sha512-cyDNmuZvvO4H27rcBq2Eudxo9IZRDCOX/I7VEyqbxsEiD2Ei7UYUhG/Sc5fvMZjmathgz3fEK7iAKqvpY+Ux1w==
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -10592,17 +10613,17 @@ modify-values@^1.0.0, modify-values@^1.0.1:
resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==
-motion-dom@^11.16.4:
- version "11.16.4"
- resolved "https://registry.npmjs.org/motion-dom/-/motion-dom-11.16.4.tgz#7c18dbe8f8b0d4210a89992cda30f5926f003777"
- integrity sha512-2wuCie206pCiP2K23uvwJeci4pMFfyQKpWI0Vy6HrCTDzDCer4TsYtT7IVnuGbDeoIV37UuZiUr6SZMHEc1Vww==
+motion-dom@^11.18.1:
+ version "11.18.1"
+ resolved "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz#e7fed7b7dc6ae1223ef1cce29ee54bec826dc3f2"
+ integrity sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==
dependencies:
- motion-utils "^11.16.0"
+ motion-utils "^11.18.1"
-motion-utils@^11.16.0:
- version "11.16.0"
- resolved "https://registry.npmjs.org/motion-utils/-/motion-utils-11.16.0.tgz#e75865442278be49e411ca9105c9139edc572811"
- integrity sha512-ngdWPjg31rD4WGXFi0eZ00DQQqKKu04QExyv/ymlC+3k+WIgYVFbt6gS5JsFPbJODTF/r8XiE/X+SsoT9c0ocw==
+motion-utils@^11.18.1:
+ version "11.18.1"
+ resolved "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz#671227669833e991c55813cf337899f41327db5b"
+ integrity sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==
ms@2.0.0:
version "2.0.0"
@@ -13544,16 +13565,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
-"string-width-cjs@npm:string-width@^4.2.0":
- version "4.2.3"
- resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -13657,14 +13669,7 @@ stringify-entities@^3.0.0:
character-entities-legacy "^1.0.0"
xtend "^4.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
- version "6.0.1"
- resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -13743,6 +13748,11 @@ style-loader@^3.3.0, style-loader@^3.3.1:
resolved "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz#f30f786c36db03a45cbd55b6a70d930c479090e7"
integrity sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==
+style-loader@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz#0ea96e468f43c69600011e0589cb05c44f3b17a5"
+ integrity sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==
+
stylehacks@^6.1.1:
version "6.1.1"
resolved "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz#543f91c10d17d00a440430362d419f79c25545a6"
@@ -15038,7 +15048,7 @@ wordwrap@^1.0.0:
resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -15056,15 +15066,6 @@ wrap-ansi@^6.0.1:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"