try to match to steam game

This commit is contained in:
2026-10-16 05:28:15 +02:00
parent 89fb745e74
commit ad7726d978
5 changed files with 271 additions and 30 deletions
@@ -0,0 +1,127 @@
<script setup lang="ts">
import { computed, useTemplateRef } from "vue";
import { useElementBounding, useElementSize, useWindowSize } from "@vueuse/core";
import type { PlanningSlotOut } from "../api/types.gen";
import PlanningCover from "./PlanningCover.vue";
defineProps<{ entry: PlanningSlotOut }>();
const track = useTemplateRef<HTMLElement>("track");
const stack = useTemplateRef<HTMLElement>("stack");
const { top: trackTop, height: trackHeight } = useElementBounding(track);
const { height: stackHeight } = useElementSize(stack);
const { height: windowHeight } = useWindowSize();
// The games follow the scroll: kept in the middle of the screen, but never
// leaving the slot (stuck to its top/bottom when it's only partly visible)
const offset = computed(() => {
const wanted = windowHeight.value / 2 - stackHeight.value / 2 - trackTop.value;
const max = Math.max(0, trackHeight.value - stackHeight.value);
return Math.min(max, Math.max(0, wanted));
});
</script>
<template>
<div class="planning-multi h-full rounded-md p-[2px]" :style="{ '--slot-color': entry.color }">
<div class="planning-multi-inner relative h-full rounded-[5px] p-2 flex flex-col overflow-hidden">
<div class="planning-multi-title font-black uppercase text-sm tracking-widest mb-2">
<i class="mdi mdi-star-four-points mr-1"></i>{{ entry.title }}
</div>
<div ref="track" class="relative flex-1">
<div ref="stack" class="absolute inset-x-0 grid grid-cols-2 gap-3"
:style="{ transform: `translateY(${offset}px)` }">
<component :is="game.store_url ? 'a' : 'div'" v-for="game in entry.games" :key="game.name"
:href="game.store_url || undefined" target="_blank" rel="noopener noreferrer"
class="block aspect-[2/3] rounded overflow-hidden shadow-lg bg-base-200
transition-transform hover:scale-[1.05]">
<PlanningCover :game="game" :color="entry.color" />
</component>
</div>
</div>
<!-- Light sweeping down the whole slot -->
<div class="planning-multi-sheen pointer-events-none absolute inset-0"></div>
</div>
</div>
</template>
<style scoped>
@property --planning-angle {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
/* Rotating gradient border + glow in the slot color */
.planning-multi {
background: conic-gradient(from var(--planning-angle),
var(--slot-color), #ffd76a, var(--slot-color), #ffffff, var(--slot-color));
box-shadow: 0 0 24px -4px var(--slot-color);
animation: planning-spin 6s linear infinite, planning-glow 3s ease-in-out infinite alternate;
}
.planning-multi-inner {
background: linear-gradient(180deg,
color-mix(in srgb, var(--slot-color) 35%, var(--color-base-200)),
var(--color-base-200) 40%,
color-mix(in srgb, var(--slot-color) 20%, var(--color-base-200)));
}
.planning-multi-title {
background: linear-gradient(90deg, #fff 20%, #ffd76a 40%, #fff 60%);
background-size: 200% 100%;
background-clip: text;
color: transparent;
animation: planning-shimmer 3s linear infinite;
}
.planning-multi-sheen {
background: linear-gradient(180deg, transparent 0%, rgb(255 255 255 / 0.12) 50%, transparent 100%);
background-size: 100% 600px;
background-repeat: no-repeat;
animation: planning-sheen 7s ease-in-out infinite;
}
@keyframes planning-spin {
to {
--planning-angle: 360deg;
}
}
@keyframes planning-glow {
to {
box-shadow: 0 0 40px 0 var(--slot-color);
}
}
@keyframes planning-shimmer {
from {
background-position: 100% 0;
}
to {
background-position: -100% 0;
}
}
@keyframes planning-sheen {
from {
background-position: 0 -600px;
}
to {
background-position: 0 calc(100% + 600px);
}
}
@media (prefers-reduced-motion: reduce) {
.planning-multi,
.planning-multi-title,
.planning-multi-sheen {
animation: none;
}
}
</style>