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 aria-hidden="true"
43 role="presentation"
44 src={backdrop}
45 alt="Backdrop"
46 />
47 </div>
48 <div>
49 <h3 class="project-name">{project.data.name}</h3>
50 <small>{project.data.tags.join(", ")}</small>
51 <p>{project.data.summary}</p>
52 </div>
53 </article>
54</a>
55
56<style>
57 :not(.project-name) {
58 text-decoration: none !important;
59 }
60
61 small {
62 margin: 0 var(--1);
63 display: block;
64 text-wrap: balance;
65 }
66
67 h3 {
68 margin: var(--1) var(--1) 0 var(--1);
69 }
70
71 p {
72 margin: var(--1);
73 }
74
75 article {
76 display: flex;
77 flex-direction: column;
78 height: 100%;
79 color: var(--text);
80 background-color: var(--secondary);
81 border-radius: 5px;
82 }
83
84 a {
85 transition: transform cubic-bezier(0.68, -0.55, 0.27, 1.55) 0.4s;
86 }
87
88 a:hover {
89 transform: translateY(calc(var(--1) * -1));
90 }
91
92 a:hover img {
93 transform: scale(1.06);
94 }
95
96 span {
97 flex-grow: 1;
98 text-align: center;
99 display: flex;
100 align-items: center;
101 justify-content: center;
102 }
103
104 div:first-child {
105 display: flex;
106 flex-direction: column;
107 align-items: center;
108 }
109
110 div {
111 overflow: hidden;
112 position: relative;
113 }
114
115 img,
116 div:first-child {
117 border-top-left-radius: 5px;
118 border-top-right-radius: 5px;
119 }
120
121 img {
122 position: relative;
123 object-fit: contain;
124 width: 100%;
125 height: auto;
126 transition: transform ease-in-out 0.4s;
127 height: var(--14);
128 }
129
130 img.thumbnail {
131 z-index: 2;
132 }
133
134 img.backdrop {
135 position: absolute;
136 transform: scale(1.1);
137 inset: 0;
138 object-fit: cover;
139 filter: blur(6px);
140 }
141</style>