Files for my website
bwc9876.dev
1---
2import { Image } from "astro:assets";
3import backdrop from "@assets/backdrop.webp";
4import type { CollectionEntry } from "astro:content";
5
6export interface Props {
7 project: CollectionEntry<"projects">;
8}
9
10const stringToNumber = (str: string, seed = 3) => {
11 let h1 = 0xdeadbeef ^ seed,
12 h2 = 0x41c6ce57 ^ seed;
13 for (let i = 0, ch; i < str.length; i++) {
14 ch = str.charCodeAt(i);
15 h1 = Math.imul(h1 ^ ch, 2654435761);
16 h2 = Math.imul(h2 ^ ch, 1597334677);
17 }
18 h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
19 h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
20 return 4294967296 * (2097151 & h2) + (h1 >>> 0);
21};
22
23const getHueFromText = (text: string): string => `hue-rotate(${stringToNumber(text) % 360}deg)`;
24
25const { project } = Astro.props;
26---
27
28<a href={`/projects/${project.id}/`}>
29 <article>
30 <div>
31 <Image
32 class="thumbnail"
33 transition:name={`project-img-${project.id}`}
34 src={project.data.image}
35 densities={[1, 1.5, 2]}
36 width="400"
37 alt=""
38 />
39 <Image
40 style={{ filter: getHueFromText(project.id) }}
41 class="backdrop"
42 src={backdrop}
43 alt="Backdrop"
44 />
45 </div>
46 <div>
47 <h3 class="project-name">{project.data.name}</h3>
48 <small>{project.data.tags.join(", ")}</small>
49 <p>{project.data.summary}</p>
50 </div>
51 </article>
52</a>
53
54<style>
55 :not(.project-name) {
56 text-decoration: none !important;
57 }
58
59 small {
60 margin: 0 var(--1);
61 display: block;
62 text-wrap: balance;
63 }
64
65 h3 {
66 margin: var(--1) var(--1) 0 var(--1);
67 }
68
69 p {
70 margin: var(--1);
71 }
72
73 article {
74 display: flex;
75 flex-direction: column;
76 height: 100%;
77 color: var(--text);
78 background-color: var(--secondary);
79 border-radius: 5px;
80 }
81
82 a {
83 transition: transform cubic-bezier(0.68, -0.55, 0.27, 1.55) 0.4s;
84 }
85
86 a:hover {
87 transform: translateY(calc(var(--1) * -1));
88 }
89
90 a:hover img {
91 transform: scale(1.06);
92 }
93
94 span {
95 flex-grow: 1;
96 text-align: center;
97 display: flex;
98 align-items: center;
99 justify-content: center;
100 }
101
102 div:first-child {
103 display: flex;
104 flex-direction: column;
105 align-items: center;
106 }
107
108 div {
109 overflow: hidden;
110 position: relative;
111 }
112
113 img,
114 div:first-child {
115 border-top-left-radius: 5px;
116 border-top-right-radius: 5px;
117 }
118
119 img {
120 position: relative;
121 object-fit: contain;
122 width: 100%;
123 height: auto;
124 transition: transform ease-in-out 0.4s;
125 height: var(--14);
126 }
127
128 img.thumbnail {
129 z-index: 2;
130 }
131
132 img.backdrop {
133 position: absolute;
134 transform: scale(1.1);
135 inset: 0;
136 object-fit: cover;
137 filter: blur(6px);
138 }
139</style>