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
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) },
]}
width={700}
/>
<Image
src={image}
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) },
]}
/>
{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"}
/>
</>
)}
</>
)
image &&
layers.map((layer) => {
const layerProps = renderLayer(image, "dark", layer);
return (
<Image
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={layerProps.src}
alt={alt ?? ""}
width={700}
class={layerProps.class}
class:list={layerProps.classList}
/>
);
})
}
<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,74 +135,83 @@
];
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-header flex w-full flex-row items-center justify-between
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
border-3 border-solid
border-gray-600 bg-black
px-5 py-2 font-['Fredoka',_sans-serif] text-sm text-white
lg:text-xl"
>
<span>Hover here!</span>
<span>0/100+</span>
</div>
<div
class="scroll-parent relative h-[82%] overflow-hidden rounded-br-2xl rounded-bl-2xl font-['Fredoka',_sans-serif] lg:min-h-0"
>
<div class="scroll-element primary absolute w-full px-1">
{#each streamers as { streamer, tasks }}
<div
class="task-widget-item my-2 w-full overflow-hidden rounded-md bg-[#404040] px-5 py-1
text-sm overflow-ellipsis whitespace-nowrap text-white lg:text-base"
>
{streamer} :
<ol class="list-inside list-decimal">
{#each tasks as task}
<li
class={task.completed
? "task-completed text-gray-400 line-through"
: "task-incomplete text-white"}
>
{task.name}
</li>
{/each}
</ol>
</div>
{/each}
>
<span>Hover here!</span>
<span>0/100+</span>
</div>
<div class="scroll-element secondary absolute w-full px-1">
{#each streamers as { streamer, tasks }}
<div
class="task-widget-item my-2 w-full overflow-hidden rounded-md bg-[#404040] px-5 py-1
<div
class="scroll-parent relative h-[82%] overflow-hidden rounded-br-2xl rounded-bl-2xl font-['Fredoka',_sans-serif] lg:min-h-0"
>
<div class="scroll-element primary absolute w-full px-1">
{#each streamers as { streamer, tasks }}
<div
class="task-widget-item my-2 w-full overflow-hidden rounded-md bg-[#404040] px-5 py-1
text-sm overflow-ellipsis whitespace-nowrap text-white lg:text-base"
>
{streamer} :
<ol class="list-inside list-decimal">
{#each tasks as task}
<li
class={task.completed
? "task-completed line-through"
: "task-incomplete"}
>
{task.name}
</li>
{/each}
</ol>
</div>
{/each}
>
{streamer} :
<ol class="list-inside list-decimal">
{#each tasks as task}
<li
class={task.completed
? "task-completed text-gray-400 line-through"
: "task-incomplete text-white"}
>
{task.name}
</li>
{/each}
</ol>
</div>
{/each}
</div>
<div class="scroll-element secondary absolute w-full px-1">
{#each streamers as { streamer, tasks }}
<div
class="task-widget-item my-2 w-full overflow-hidden rounded-md bg-[#404040] px-5 py-1
text-sm overflow-ellipsis whitespace-nowrap text-white lg:text-base"
>
{streamer} :
<ol class="list-inside list-decimal">
{#each tasks as task}
<li
class={task.completed
? "task-completed line-through"
: "task-incomplete"}
>
{task.name}
</li>
{/each}
</ol>
</div>
{/each}
</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"
>
<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'}"
width="700"
/>
{#snippet renderLayer(
kind: "image" | "video",
layerClass: string,
extraClass: string,
)}
{#if kind === "image"}
<img
src={image_src}
alt={image_alt}
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 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}
<!-- 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'}"
>
<source src={video_src} type="video/webm" />
</video>
{@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