diff --git a/components/resource/content/Content.tsx b/components/resource/content/Content.tsx
index 4ad9f202..8040c866 100644
--- a/components/resource/content/Content.tsx
+++ b/components/resource/content/Content.tsx
@@ -14,6 +14,7 @@ import { Image } from "./components/image/Image";
import { Pre } from "./components/pre/Pre";
import { Quote } from "./components/quote/Quote";
import { Sandbox } from "./components/sandbox/Sandbox";
+import { Video } from "./components/video/Video";
interface ContentProps {
readonly content: MDXRemoteSerializeResult;
@@ -32,6 +33,7 @@ const customMdxComponents = {
Image,
Link,
Quote,
+ Video,
Highlight,
Sandbox: ({ id }: { id: string }) => ,
pre: Pre,
diff --git a/components/resource/content/components/video/Video.tsx b/components/resource/content/components/video/Video.tsx
new file mode 100644
index 00000000..6368ef13
--- /dev/null
+++ b/components/resource/content/components/video/Video.tsx
@@ -0,0 +1,57 @@
+import clsx from "clsx";
+import { memo, useRef, useState } from "react";
+
+import PauseIcon from "public/svg/pause.svg";
+import PlayIcon from "public/svg/play.svg";
+
+import styles from "./video.module.scss";
+
+type VideoProps = { readonly alt?: string } & JSX.IntrinsicElements["video"];
+
+export const Video = memo(({ alt, ...props }: VideoProps) => {
+ const [paused, setPaused] = useState(!props.autoPlay);
+ const [controlsVisible, setControlsVisible] = useState(false);
+ const ref = useRef(null);
+
+ const togglePlay = () => {
+ if (ref.current) {
+ if (ref.current.paused) {
+ setControlsVisible(false);
+ setPaused(false);
+ void ref.current.play();
+ } else {
+ setControlsVisible(true);
+ setPaused(true);
+ ref.current.pause();
+ }
+ }
+ };
+
+ return (
+
+
+
+ {alt ? {alt} : null}
+
+ );
+});
+
+Video.displayName = "Video";
diff --git a/components/resource/content/components/video/video.module.scss b/components/resource/content/components/video/video.module.scss
new file mode 100644
index 00000000..08865891
--- /dev/null
+++ b/components/resource/content/components/video/video.module.scss
@@ -0,0 +1,57 @@
+@use "styles/_mixins";
+
+.wrapper {
+ width: auto;
+ height: auto;
+ margin: 4rem -1.2rem;
+ position: relative;
+ @include mixins.flex;
+ flex-flow: column wrap;
+ gap: 1.4rem;
+
+ @include mixins.mediaquery("sm") {
+ margin: 4rem -2rem;
+ }
+
+ .videoWrapper {
+ position: relative;
+
+ .video {
+ max-width: 100%;
+ border-radius: 2rem;
+ cursor: pointer;
+ }
+
+ .control {
+ position: absolute;
+ inset: 0;
+ width: 6.5rem;
+ height: 6.5rem;
+ border-radius: 50%;
+ border: 0 none;
+ transition: opacity 500ms ease;
+ background-color: rgba(0, 0, 0, 0.5);
+ margin: auto;
+ pointer-events: none;
+ opacity: 0;
+ color: var(--white-200);
+ @include mixins.flex;
+
+ &.visible {
+ opacity: 1;
+ }
+
+ .icon {
+ width: 50%;
+ }
+ }
+ }
+
+ .caption {
+ font-size: 1.4rem;
+ font-style: italic;
+ opacity: 0.6;
+ padding: 0 1rem;
+ text-align: center;
+ }
+}
diff --git a/public/svg/contribute.svg b/public/svg/contribute.svg
index 0bcdf3eb..ecc31a9c 100644
--- a/public/svg/contribute.svg
+++ b/public/svg/contribute.svg
@@ -1,3 +1,3 @@
diff --git a/public/svg/fire.svg b/public/svg/fire.svg
index 8972c244..5593dae4 100644
--- a/public/svg/fire.svg
+++ b/public/svg/fire.svg
@@ -1,3 +1,3 @@
diff --git a/public/svg/pause.svg b/public/svg/pause.svg
new file mode 100644
index 00000000..ae070ccc
--- /dev/null
+++ b/public/svg/pause.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/svg/play.svg b/public/svg/play.svg
new file mode 100644
index 00000000..3740fe5f
--- /dev/null
+++ b/public/svg/play.svg
@@ -0,0 +1,3 @@
+