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;
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
@ -37,48 +66,34 @@ const leftProject = position == "left";
>
<slot />
{
image && alt && (
<>
image &&
layers.map((layer) => {
const layerProps = renderLayer(image, "dark", layer);
return (
<Image
src={image}
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) },
]}
src={layerProps.src}
alt={alt ?? ""}
width={700}
class={layerProps.class}
class:list={layerProps.classList}
/>
);
})
}
{
image_light &&
layers.map((layer) => {
const layerProps = renderLayer(image_light, "light", layer);
return (
<Image
src={image}
alt={alt}
src={layerProps.src}
alt={alt ?? ""}
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:list={[
leftProject ? "left" : "right",
{ "has-light-image": Boolean(image_light) },
]}
class={layerProps.class}
class:list={layerProps.classList}
/>
{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

View file

@ -62,26 +62,7 @@ import Project from "./Project.astro";
endless!`}
tags={["HTML", "CSS", "Javascript", "C#"]}
>
<TaskWidget
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
/>
<TaskWidget position="right" client:visible />
</Project>
</div>
</div>

View file

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

View file

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