mirror of
https://github.com/liyunze-coding/RythonDev.git
synced 2026-09-22 15:53:57 +00:00
added video project component
This commit is contained in:
parent
d2f28d2b7e
commit
d612d159f0
4 changed files with 229 additions and 202 deletions
|
|
@ -1,206 +1,65 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import VideoProject from "./VideoProject.svelte";
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const videos = document.querySelectorAll<HTMLVideoElement>(".playable");
|
|
||||||
|
|
||||||
let userInteracted = false;
|
|
||||||
|
|
||||||
const handleUserInteraction = () => {
|
|
||||||
userInteracted = true;
|
|
||||||
document.removeEventListener("click", handleUserInteraction);
|
|
||||||
document.removeEventListener("keydown", handleUserInteraction);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("click", handleUserInteraction);
|
|
||||||
document.addEventListener("keydown", handleUserInteraction);
|
|
||||||
|
|
||||||
videos.forEach((video) => {
|
|
||||||
// Ensure the video element supports play and pause methods
|
|
||||||
if (
|
|
||||||
typeof video.play === "function" &&
|
|
||||||
typeof video.pause === "function"
|
|
||||||
) {
|
|
||||||
video.addEventListener("mouseenter", () => {
|
|
||||||
if (userInteracted) {
|
|
||||||
video.volume = 0.2;
|
|
||||||
video.muted = false;
|
|
||||||
video.play();
|
|
||||||
} else {
|
|
||||||
video.volume = 0;
|
|
||||||
video.muted = true;
|
|
||||||
video.play();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
video.addEventListener("mouseleave", () => {
|
|
||||||
video.pause();
|
|
||||||
video.currentTime = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if the screen width is less than or equal to 768px (mobile screen size)
|
|
||||||
if (window.innerWidth <= 768) {
|
|
||||||
video.muted = true;
|
|
||||||
video.autoplay = true;
|
|
||||||
video.loop = true;
|
|
||||||
video.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-fit flex-col items-center pt-20 [perspective:1000px]">
|
<div class="flex h-fit flex-col items-center pt-20 [perspective:1000px]">
|
||||||
<div class="w-full max-w-[1440px]">
|
<div class="w-full max-w-[1440px]">
|
||||||
<div
|
<VideoProject
|
||||||
class="flex h-fit w-full flex-col justify-center px-10 lg:flex-row lg:px-20"
|
image_src="/images/projects/chess-opencv.webp"
|
||||||
>
|
image_alt="computer vision chess bot"
|
||||||
<div
|
position="left"
|
||||||
class="image-container relative mb-5 flex aspect-video w-full [perspective:200px] lg:mr-5 lg:mb-0 lg:ml-auto lg:w-1/2"
|
video_src="/videos/chess_opencv_demo.webm"
|
||||||
>
|
title="Computer Vision Chess Bot"
|
||||||
<img
|
description={`
|
||||||
src="/images/projects/chess-opencv.webp"
|
A script that plays against opponents on lichess. Uses
|
||||||
alt="computer vision chess bot"
|
OpenCV to detect the board and pieces, and the Stockfish
|
||||||
class="absolute top-1/2
|
chess engine to determine the best moves. Then uses
|
||||||
left-1/2 max-w-full
|
PyAutoGUI to play the moves on the browser.
|
||||||
[transform:rotateY(5deg)_rotateX(0deg)_translateZ(-50px)_translateX(-45%)_translateY(-50%)] rounded-xl
|
`}
|
||||||
border-2 border-solid border-white opacity-40"
|
tags={[
|
||||||
width="700"
|
{
|
||||||
/>
|
text: "Python",
|
||||||
|
textColor: "white",
|
||||||
<!-- svelte-ignore a11y-media-has-caption -->
|
bgColor: "var(--color-python)",
|
||||||
<video
|
},
|
||||||
width="700"
|
{
|
||||||
class="playable absolute top-1/2
|
text: "OpenCV",
|
||||||
left-1/2 max-w-full
|
textColor: "white",
|
||||||
[transform:rotateY(5deg)_rotateX(0deg)_translateZ(-50px)_translateX(-43%)_translateY(-52%)] rounded-xl
|
bgColor: "var(--color-opencv)",
|
||||||
border-2 border-solid border-white opacity-100"
|
},
|
||||||
>
|
{
|
||||||
<source
|
text: "PyAutoGUI",
|
||||||
src="/videos/chess_opencv_demo.webm"
|
textColor: "white",
|
||||||
type="video/webm"
|
bgColor: "black",
|
||||||
/>
|
},
|
||||||
</video>
|
]}
|
||||||
</div>
|
></VideoProject>
|
||||||
<div
|
<VideoProject
|
||||||
class="flex h-fit w-full flex-col items-center justify-center lg:h-[340px] lg:w-1/2 lg:items-start lg:px-10"
|
image_src="/images/projects/aim-lab.webp"
|
||||||
>
|
image_alt="Computer Vision AimLab Bot"
|
||||||
<h2 class="mb-5 text-center text-3xl font-bold lg:text-left">
|
position="right"
|
||||||
Computer Vision Chess bot
|
video_src="/videos/aim_lab_demo.webm"
|
||||||
</h2>
|
title="Computer Vision AimLab Bot"
|
||||||
<p
|
description={`
|
||||||
class="max-w-lg text-center text-xl text-balance lg:max-w-none lg:text-left"
|
A script that plays AimLab, a game that helps improve your aim in FPS games. Uses OpenCV to detect the targets and win32API to move the mouse and click.
|
||||||
>
|
`}
|
||||||
A script that plays against opponents on lichess. Uses
|
tags={[
|
||||||
OpenCV to detect the board and pieces, and the Stockfish
|
{
|
||||||
chess engine to determine the best moves. Then uses
|
text: "Python",
|
||||||
PyAutoGUI to play the moves on the browser.
|
textColor: "white",
|
||||||
</p>
|
bgColor: "var(--color-python)",
|
||||||
<div
|
},
|
||||||
class="mt-5 flex w-full flex-row flex-wrap justify-center lg:justify-start"
|
{
|
||||||
>
|
text: "OpenCV",
|
||||||
<div
|
textColor: "white",
|
||||||
class="my-1 mr-1 w-fit
|
bgColor: "var(--color-opencv)",
|
||||||
rounded-full bg-[#3776AB] px-3 py-1 text-white
|
},
|
||||||
transition-colors duration-300"
|
{
|
||||||
>
|
text: "win32API",
|
||||||
Python
|
textColor: "white",
|
||||||
</div>
|
bgColor: "var(--color-win32api)",
|
||||||
<div
|
},
|
||||||
class="my-1 mr-1 w-fit
|
]}
|
||||||
rounded-full bg-[#3178C6] px-3 py-1 text-white
|
></VideoProject>
|
||||||
transition-colors duration-300"
|
|
||||||
>
|
|
||||||
OpenCV
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="my-1 mr-1 w-fit
|
|
||||||
rounded-full bg-black px-3 py-1 text-white
|
|
||||||
transition-colors duration-300"
|
|
||||||
>
|
|
||||||
PyAutoGUI
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="mt-10 flex h-fit w-full flex-col-reverse justify-center px-10 lg:flex-row lg:px-16"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex h-fit w-full flex-col items-center justify-center lg:h-[340px] lg:w-1/2 lg:items-end lg:px-10"
|
|
||||||
>
|
|
||||||
<h2 class="mb-5 text-center text-3xl font-bold lg:text-right">
|
|
||||||
Computer Vision AimLab Bot
|
|
||||||
</h2>
|
|
||||||
<p
|
|
||||||
class="max-w-lg text-center text-xl text-balance lg:max-w-none lg:text-right"
|
|
||||||
>
|
|
||||||
A script that plays AimLab, a game that helps improve your
|
|
||||||
aim in FPS games. Uses OpenCV to detect the targets and
|
|
||||||
win32API to move the mouse and click.
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
class="mt-5 mb-10 flex w-full flex-row flex-wrap justify-center lg:mb-0 lg:justify-end"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="my-1 mr-1 w-fit
|
|
||||||
rounded-full bg-[#3776AB] px-3 py-1 text-white
|
|
||||||
transition-colors duration-300"
|
|
||||||
>
|
|
||||||
Python
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="my-1 mr-1 w-fit
|
|
||||||
rounded-full bg-[#3178C6] px-3 py-1 text-white
|
|
||||||
transition-colors duration-300"
|
|
||||||
>
|
|
||||||
OpenCV
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="my-1 mr-1 w-fit
|
|
||||||
rounded-full bg-[#0078D4] px-3 py-1 text-white
|
|
||||||
transition-colors duration-300"
|
|
||||||
>
|
|
||||||
win32API
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="image-container relative mb-5 flex aspect-video w-full [perspective:200px] lg:mr-5 lg:mb-0 lg:ml-auto lg:w-1/2"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/images/projects/aim-lab.webp"
|
|
||||||
alt="aim lab"
|
|
||||||
class="absolute top-1/2
|
|
||||||
left-1/2 max-w-full [transform:translateX(-48%)_translateY(-48%)_translateZ(-50px)_rotateY(-3deg)] rounded-xl
|
|
||||||
border-2 border-solid border-white opacity-40"
|
|
||||||
width="700"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-media-has-caption -->
|
|
||||||
<video
|
|
||||||
width="700"
|
|
||||||
class="playable absolute top-1/2
|
|
||||||
left-1/2 max-w-full
|
|
||||||
[transform:translateX(-50%)_translateY(-50%)_translateZ(-50px)_rotateY(-3deg)] rounded-xl
|
|
||||||
border-2 border-solid border-white opacity-100"
|
|
||||||
><source src="/videos/aim_lab_demo.webm" /></video
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
.image-container img,
|
|
||||||
.image-container video {
|
|
||||||
transition: transform 0.5s ease-in-out;
|
|
||||||
}
|
|
||||||
@media (min-width: 1025px) {
|
|
||||||
.image-container:hover > img,
|
|
||||||
.image-container:hover > video {
|
|
||||||
transform: rotateY(0deg) rotateX(0deg) translateZ(0px)
|
|
||||||
translateX(-50%) translateY(-50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ const leftProject = position == "left";
|
||||||
border-2 border-solid border-white rounded-xl`}
|
border-2 border-solid border-white rounded-xl`}
|
||||||
class:list={leftProject
|
class:list={leftProject
|
||||||
? "[transform:translateX(50px)_translateY(5px)_rotateY(5deg)_rotateX(0deg)_translateZ(-50px)]"
|
? "[transform:translateX(50px)_translateY(5px)_rotateY(5deg)_rotateX(0deg)_translateZ(-50px)]"
|
||||||
: "[transform:translateZ(-50px)_rotateY(-3deg)]"}
|
: "[transform:translateX(10px)_translateY(5px)_translateZ(-50px)_rotateY(-3deg)]"}
|
||||||
width="700"
|
width="700"
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,23 @@ import Project from "./Project.astro";
|
||||||
to add their own tasks to the widget for accountability!
|
to add their own tasks to the widget for accountability!
|
||||||
Easy to setup and the customization possibilities are
|
Easy to setup and the customization possibilities are
|
||||||
endless!`}
|
endless!`}
|
||||||
|
tags={[
|
||||||
|
{
|
||||||
|
text: "HTML",
|
||||||
|
bgColor: "var(--color-html)",
|
||||||
|
textColor: "white",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "CSS",
|
||||||
|
bgColor: "var(--color-blue-600)",
|
||||||
|
textColor: "white",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Javascript",
|
||||||
|
bgColor: "var(--color-yellow-500)",
|
||||||
|
textColor: "black",
|
||||||
|
},
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<TaskWidget
|
<TaskWidget
|
||||||
classList={`
|
classList={`
|
||||||
|
|
@ -117,13 +134,15 @@ import Project from "./Project.astro";
|
||||||
|
|
||||||
<style is:global>
|
<style is:global>
|
||||||
.image-container img,
|
.image-container img,
|
||||||
.image-container .widget {
|
.image-container .widget,
|
||||||
|
.image-container video {
|
||||||
transition: transform 0.5s ease-in-out;
|
transition: transform 0.5s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1025px) {
|
@media (min-width: 1025px) {
|
||||||
.image-container:hover > img,
|
.image-container:hover > img,
|
||||||
.image-container:hover .widget {
|
.image-container:hover .widget,
|
||||||
|
.image-container:hover video {
|
||||||
transform: rotateY(0deg) rotateX(0deg) translateZ(0px) translateX(0)
|
transform: rotateY(0deg) rotateX(0deg) translateZ(0px) translateX(0)
|
||||||
translateY(0);
|
translateY(0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
149
src/components/projects/VideoProject.svelte
Normal file
149
src/components/projects/VideoProject.svelte
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
interface Tag {
|
||||||
|
text: string;
|
||||||
|
textColor: string;
|
||||||
|
bgColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
position?: "left" | "right";
|
||||||
|
image_src: string;
|
||||||
|
image_alt: string;
|
||||||
|
video_src: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags?: Tag[];
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
position = "left",
|
||||||
|
image_src,
|
||||||
|
image_alt,
|
||||||
|
video_src,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
tags = [],
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
const positionLeft = position == "left";
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const videos = document.querySelectorAll<HTMLVideoElement>(".playable");
|
||||||
|
|
||||||
|
let userInteracted = false;
|
||||||
|
|
||||||
|
const handleUserInteraction = () => {
|
||||||
|
userInteracted = true;
|
||||||
|
document.removeEventListener("click", handleUserInteraction);
|
||||||
|
document.removeEventListener("keydown", handleUserInteraction);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("click", handleUserInteraction);
|
||||||
|
document.addEventListener("keydown", handleUserInteraction);
|
||||||
|
|
||||||
|
videos.forEach((video) => {
|
||||||
|
// Ensure the video element supports play and pause methods
|
||||||
|
if (
|
||||||
|
typeof video.play === "function" &&
|
||||||
|
typeof video.pause === "function"
|
||||||
|
) {
|
||||||
|
video.addEventListener("mouseenter", () => {
|
||||||
|
if (userInteracted) {
|
||||||
|
video.volume = 0.2;
|
||||||
|
video.muted = false;
|
||||||
|
video.play();
|
||||||
|
} else {
|
||||||
|
video.volume = 0;
|
||||||
|
video.muted = true;
|
||||||
|
video.play();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
video.addEventListener("mouseleave", () => {
|
||||||
|
video.pause();
|
||||||
|
video.currentTime = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the screen width is less than or equal to 768px (mobile screen size)
|
||||||
|
if (window.innerWidth <= 768) {
|
||||||
|
video.muted = true;
|
||||||
|
video.autoplay = true;
|
||||||
|
video.loop = true;
|
||||||
|
video.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex h-fit w-full flex-col justify-center px-10 lg:flex-row lg:px-20"
|
||||||
|
class:lg:flex-row={positionLeft}
|
||||||
|
class:lg:flex-row-reverse={!positionLeft}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="image-container relative mb-5 flex aspect-video w-full [perspective:200px] lg:mr-5 lg:mb-0 lg:ml-auto lg:w-1/2"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={image_src}
|
||||||
|
alt={image_alt}
|
||||||
|
class="absolute max-w-full
|
||||||
|
rounded-xl
|
||||||
|
border-2 border-solid border-white opacity-40
|
||||||
|
{positionLeft
|
||||||
|
? '[transform:rotateY(5deg)_translateX(50px)_translateY(5px)_translateZ(-50px)]'
|
||||||
|
: '[transform:rotateY(-5deg)_translateX(-20px)_translateY(5px)_translateZ(-50px)]'}"
|
||||||
|
width="700"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
|
<video
|
||||||
|
width="700"
|
||||||
|
class="playable absolute max-w-full
|
||||||
|
rounded-xl border-2 border-solid border-white
|
||||||
|
opacity-100
|
||||||
|
{positionLeft
|
||||||
|
? '[transform:rotateY(5deg)_translateX(60px)_translateZ(-50px)]'
|
||||||
|
: '[transform:rotateY(-5deg)_translateX(-30px)_translateZ(-50px)]'}"
|
||||||
|
>
|
||||||
|
<source src={video_src} type="video/webm" />
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex h-fit w-full flex-col items-center justify-center lg:h-[340px] lg:w-1/2 lg:px-10
|
||||||
|
{positionLeft ? 'lg:items-start' : 'lg:items-end'}"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="mb-5 text-center text-3xl font-bold
|
||||||
|
{positionLeft ? 'lg:text-left' : 'lg:text-right'}"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
class="max-w-lg text-center text-xl text-balance lg:max-w-none
|
||||||
|
{positionLeft ? 'lg:text-left' : 'lg:text-right'}"
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="mt-5 flex w-full flex-row flex-wrap justify-center
|
||||||
|
{positionLeft ? 'lg:justify-start' : 'lg:justify-end'}"
|
||||||
|
>
|
||||||
|
{#each tags as tag}
|
||||||
|
<div
|
||||||
|
class="my-1 mr-1 w-fit
|
||||||
|
rounded-full px-3 py-1
|
||||||
|
transition-colors duration-300"
|
||||||
|
style={`
|
||||||
|
background-color: ${tag.bgColor || "black"};
|
||||||
|
color: ${tag.textColor || "white"};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{tag.text}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Loading…
Reference in a new issue