diff --git a/data/manifest.json b/data/manifest.json index 2856e38..e5d11d8 100644 --- a/data/manifest.json +++ b/data/manifest.json @@ -1,6 +1,6 @@ { "name": "Zeke Zhang Portfolio", - "version": "1.5.5", + "version": "1.6.0", "copyright": { "name": "zekezhang.com" }, diff --git a/data/projects.json b/data/projects.json index ad863b8..e97c866 100644 --- a/data/projects.json +++ b/data/projects.json @@ -50,6 +50,11 @@ "src": "https://www.youtube.com/embed/q15wEzLeKXw?si=N_EUmE4cvzq_bukx", "alt": "Shara Clarke | Video Presentation", "isVideo": true, + "videoSettings": { + "isAutoplay": true, + "isMutted": true, + "isLoop": true + }, "isHero": true }, { @@ -76,7 +81,12 @@ "src": "https://www.youtube.com/embed/9m0n3p4oGuM?si=jqiFEiP3uotYyXx2", "alt": "Shara Clarke | Boundary Expansion Diagram", "className": "md:col-span-1 col-span-2 md:h-auto", - "isVideo": true + "isVideo": true, + "videoSettings": { + "isAutoplay": true, + "isMutted": true, + "isLoop": true + } }, { "src": "/projects/sharaClarke/diagram_3.webp", @@ -215,7 +225,12 @@ "src": "https://www.youtube.com/embed/6B2L0CH7QGg?si=YKckKJ2IYIaZzzGH", "alt": "Synthetic Dunescapes | Video Presentation", "isVideo": true, - "isHero": true + "isHero": true, + "videoSettings": { + "isAutoplay": true, + "isMutted": true, + "isLoop": true + } }, { "src": "/projects/syntheticDunescapes/piezoelectric-wall.avif", @@ -250,7 +265,12 @@ "text": "The aggregation process is driven by a series of atomic discrete units that self-assemble into larger structures. This process is informed by the principles of swarm intelligence, where individual agents interact with their environment and each other to achieve a collective goal. The aggregation algorithm is designed to optimize the distribution of units, ensuring structural stability and adaptability to changing environmental conditions." }, "className": "md:col-span-3 col-span-6 md:h-auto", - "isVideo": true + "isVideo": true, + "videoSettings": { + "isAutoplay": true, + "isMutted": true, + "isLoop": true + } }, { "src": "/projects/syntheticDunescapes/2.webp", @@ -449,7 +469,12 @@ "src": "https://www.youtube.com/embed/ov6RFqihmLQ?si=dZVZYMSjo_UtNDIV", "alt": "Jakarta Rising | Video Presentation", "isVideo": true, - "isHero": true + "isHero": true, + "videoSettings": { + "isAutoplay": true, + "isMutted": true, + "isLoop": true + } }, { "src": "/projects/jakartaRising/main.webp", @@ -550,7 +575,13 @@ "src": "https://www.youtube.com/embed/ffDNh79mnEE?si=RPGW12y582pP_6ck", "alt": "Terra//Form | Video Presentation", "isVideo": true, - "isHero": true + "isHero": true, + "videoSettings": { + "isAutoplay": true, + "isMutted": true, + "isLoop": true, + "startAt": "1:50" + } }, { "src": "/projects/terraForm/main.webp", @@ -671,7 +702,12 @@ { "src": "https://www.youtube.com/embed/TwejWaHOx3Q?si=DhLvhsljq9CSON7E", "alt": "Mongrel Assembly | ML-Agents in Unity with Grasshopper for Discrete Aggregation and Finite Element Analysis", - "isVideo": true + "isVideo": true, + "videoSettings": { + "isAutoplay": true, + "isMutted": true, + "isLoop": true + } }, { "src": "/projects/mongrelAssembly/4.webp", diff --git a/data/schema/projects-schema.json b/data/schema/projects-schema.json index 0470e39..c314bc2 100644 --- a/data/schema/projects-schema.json +++ b/data/schema/projects-schema.json @@ -254,6 +254,33 @@ "type": "boolean", "description": "Media is video?", "default": true + }, + "videoSettings": { + "type": "object", + "description": "Video settings", + "additionalProperties": false, + "properties": { + "isAutoplay": { + "type": "boolean", + "description": "Autoplay video?", + "default": true + }, + "isMutted": { + "type": "boolean", + "description": "Mute video by default?", + "default": true + }, + "isLoop": { + "type": "boolean", + "description": "Loop video?", + "default": true + }, + "startAt": { + "type": ["string", "number"], + "description": "Start video at (in minutes or seconds)", + "default": "0:00" + } + } } }, "required": ["src", "alt"], @@ -286,6 +313,11 @@ "not": { "required": ["sizes"] } + }, + "else": { + "not": { + "required": ["videoSettings"] + } } }, { diff --git a/src/components/layout/projectDetailSection.jsx b/src/components/layout/projectDetailSection.jsx index 71fb69e..9d3cb95 100644 --- a/src/components/layout/projectDetailSection.jsx +++ b/src/components/layout/projectDetailSection.jsx @@ -6,6 +6,7 @@ import BlurImage from "../ui/media/blur"; import ScrollToTopButton from "../ui/button/scrollToTopButton"; import ExpandableText from "../ui/expandableText"; import CustomCarousel from "../ui/media/customCarousel"; +import { ConvertMinutesToSeconds } from "@/lib/math"; const ProjectDetailsSection = ({ data, className }) => (
@@ -78,7 +79,14 @@ const ProjectInfo = ({ data }) => {
{/* If there is no video, show the image; otherwise, show the video */} {heroVideo ? ( - + ) : ( { return (
- +
); }; diff --git a/src/components/ui/media/lightbox.jsx b/src/components/ui/media/lightbox.jsx index 51c6a9c..005ced7 100644 --- a/src/components/ui/media/lightbox.jsx +++ b/src/components/ui/media/lightbox.jsx @@ -36,7 +36,7 @@ import { import Image from "next/image"; import { YoutubeVideo } from "./youtube-video"; -import { GetYoutubeThumbnail } from "@/lib/getYoutube"; +import { GetYoutubeThumbnail } from "@/lib/youtubeUtil"; import { ConstructYoutubeAltText } from "@/lib/constructAltText"; import { cn } from "@/lib/utils"; import CarouselWraper from "../carousel/carouselWraper"; diff --git a/src/components/ui/media/youtube-video.jsx b/src/components/ui/media/youtube-video.jsx index adee87b..d4db9c4 100644 --- a/src/components/ui/media/youtube-video.jsx +++ b/src/components/ui/media/youtube-video.jsx @@ -1,16 +1,28 @@ import React from "react"; import { cn } from "@/utils/cn"; +import { SetYoutubeUrl } from "@/lib/youtubeUtil"; +import propTypes from "prop-types"; -const YoutubeVideo = ({ src, alt, mute, className }) => { +const YoutubeVideo = ({ src, alt, className, startTime, isAutoplay, isLoop, isMutted }) => { return (