updated projects component

This commit is contained in:
liyunze 2026-09-04 11:22:58 +08:00
parent 09c4c24d77
commit abbed1d6b4
4 changed files with 146 additions and 139 deletions

View file

@ -26,6 +26,35 @@ const {
} = Astro.props; } = Astro.props;
const leftProject = position == "left"; const leftProject = position == "left";
const side = leftProject ? "left" : "right";
const layers = ["background", "frontfacing"] as const;
const renderLayer = (
src: ImageMetadata,
theme: "dark" | "light",
layer: (typeof layers)[number],
) => {
const isBackground = layer === "background";
const isLight = theme === "light";
return {
src,
class: [
"absolute top-1/2 left-1/2 aspect-video max-w-full rounded-xl border-2 border-solid",
isLight
? "project-image-light hidden border-gray-300"
: "project-image-dark border-gray-800",
isBackground ? "background-img opacity-40" : "frontfacing-img opacity-100",
isBackground && isLight ? "brightness-75" : "",
side,
]
.filter(Boolean)
.join(" "),
classList: {
"has-light-image": !isLight && Boolean(image_light),
},
};
};
--- ---
<div <div
@ -37,48 +66,34 @@ const leftProject = position == "left";
> >
<slot /> <slot />
{ {
image && alt && ( image &&
<> layers.map((layer) => {
const layerProps = renderLayer(image, "dark", layer);
return (
<Image <Image
src={image} src={layerProps.src}
alt={alt} alt={alt ?? ""}
class="project-image-dark background-img absolute top-1/2 left-1/2 aspect-video max-w-full rounded-xl border-2 border-solid border-gray-800 opacity-40"
class:list={[
leftProject ? "left" : "right",
{ "has-light-image": Boolean(image_light) },
]}
width={700} width={700}
class={layerProps.class}
class:list={layerProps.classList}
/> />
);
})
}
{
image_light &&
layers.map((layer) => {
const layerProps = renderLayer(image_light, "light", layer);
return (
<Image <Image
src={image} src={layerProps.src}
alt={alt} alt={alt ?? ""}
width={700} width={700}
class="project-image-dark frontfacing-img absolute top-1/2 left-1/2 aspect-video max-w-full rounded-xl border-2 border-solid border-gray-800 opacity-100" class={layerProps.class}
class:list={[ class:list={layerProps.classList}
leftProject ? "left" : "right",
{ "has-light-image": Boolean(image_light) },
]}
/> />
{image_light && ( );
<> })
<Image
src={image_light}
alt={alt}
class="project-image-light background-img absolute top-1/2 left-1/2 hidden aspect-video max-w-full rounded-xl border-2 border-solid border-gray-300 opacity-40"
class:list={leftProject ? "left" : "right"}
width={700}
/>
<Image
src={image_light}
alt={alt}
width={700}
class="project-image-light frontfacing-img absolute top-1/2 left-1/2 hidden aspect-video max-w-full rounded-xl border-2 border-solid border-gray-300 opacity-100"
class:list={leftProject ? "left" : "right"}
/>
</>
)}
</>
)
} }
<div <div

View file

@ -62,26 +62,7 @@ import Project from "./Project.astro";
endless!`} endless!`}
tags={["HTML", "CSS", "Javascript", "C#"]} tags={["HTML", "CSS", "Javascript", "C#"]}
> >
<TaskWidget <TaskWidget position="right" client:visible />
classList={`
widget h-full
[perspective:200px] absolute
w-full
opacity-10
top-1/2 left-1/2
background-widget right
`}
client:visible
/>
<TaskWidget
classList={`
widget h-full
[perspective:200px] absolute
frontfacing-widget right
top-1/2 left-1/2
w-full`}
client:visible
/>
</Project> </Project>
</div> </div>
</div> </div>

View file

@ -135,15 +135,20 @@
]; ];
interface Props { interface Props {
classList: string; position?: "left" | "right";
} }
let { classList }: Props = $props(); let { position = "right" }: Props = $props();
const side = $derived(position === "left" ? "left" : "right");
</script> </script>
<div class={`${classList} overflow-hidden`}> {#snippet widgetLayer(layerClass: string, extraClass: string)}
<!-- task bot --> <div
<div class="task-widget absolute flex h-full min-h-[50vh] w-full flex-col"> class={`widget absolute top-1/2 left-1/2 h-full w-full overflow-hidden [perspective:200px] ${layerClass} ${side} ${extraClass}`}
>
<div
class="task-widget absolute flex h-full min-h-[50vh] w-full flex-col"
>
<div <div
class="task-widget-header flex w-full flex-row items-center justify-between class="task-widget-header flex w-full flex-row items-center justify-between
rounded-md rounded-md
@ -203,6 +208,10 @@
</div> </div>
</div> </div>
</div> </div>
{/snippet}
{@render widgetLayer("background-widget", "opacity-40 brightness-75")}
{@render widgetLayer("frontfacing-widget", "opacity-100")}
<style> <style>
:global(html[data-theme="light"]) .task-widget-header { :global(html[data-theme="light"]) .task-widget-header {

View file

@ -21,7 +21,8 @@
tags = [], tags = [],
}: Props = $props(); }: Props = $props();
const positionLeft: boolean = (() => position == "left")(); const positionLeft = $derived(position === "left");
const side = $derived(positionLeft ? "left" : "right");
onMount(() => { onMount(() => {
const videos = document.querySelectorAll<HTMLVideoElement>(".playable"); const videos = document.querySelectorAll<HTMLVideoElement>(".playable");
@ -63,30 +64,31 @@
<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" 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"
> >
{#snippet renderLayer(
kind: "image" | "video",
layerClass: string,
extraClass: string,
)}
{#if kind === "image"}
<img <img
src={image_src} src={image_src}
alt={image_alt} alt={image_alt}
class="project-media background-img absolute class="project-media absolute top-1/2 left-1/2 max-w-full rounded-xl border-2 border-solid border-gray-800 {layerClass} {extraClass} {side}"
top-1/2
left-1/2 max-w-full rounded-xl border-2
border-solid border-gray-800
opacity-40
{positionLeft ? 'left' : 'right'}"
width="700" width="700"
/> />
{:else}
<!-- svelte-ignore a11y_media_has_caption --> <!-- svelte-ignore a11y_media_has_caption -->
<video <video
width="700" width="700"
class="project-media playable frontfacing-vid absolute class="project-media playable absolute top-1/2 left-1/2 max-w-full rounded-xl border-2 border-solid border-gray-800 {layerClass} {extraClass} {side}"
top-1/2 left-1/2 max-w-full rounded-xl
border-2
border-solid border-gray-800
opacity-100
{positionLeft ? 'left' : 'right'}"
> >
<source src={video_src} type="video/webm" /> <source src={video_src} type="video/webm" />
</video> </video>
{/if}
{/snippet}
{@render renderLayer("image", "background-img", "opacity-40 brightness-75")}
{@render renderLayer("video", "frontfacing-vid", "opacity-100")}
</div> </div>
<div <div
class="flex h-fit w-full flex-col items-center justify-center lg:h-[340px] lg:w-1/2 lg:px-10 class="flex h-fit w-full flex-col items-center justify-center lg:h-[340px] lg:w-1/2 lg:px-10