Files for my website
bwc9876.dev
1---
2import { Image, getImage } from "astro:assets";
3
4import defaultOg from "@assets/default-og.webp";
5import cow from "@assets/cow.webp";
6import Socials from "@components/Socials.astro";
7import IconLink from "@components/IconLink.astro";
8import "@styles/style.css";
9import type { ImageMetadata } from "astro";
10
11export interface Props {
12 title: string;
13 appendTitle?: boolean;
14 description?: string;
15 keywords?: string[];
16 og?: {
17 src: ImageMetadata;
18 alt: string;
19 };
20}
21
22const { title, appendTitle, description: oldDescription, keywords, og: oldOg } = Astro.props;
23
24const og = {
25 src: (
26 await getImage({
27 src: oldOg?.src ?? defaultOg,
28 format: "webp"
29 })
30 ).src,
31 alt: oldOg?.alt ?? "Ben C's Profile Picture"
32};
33
34og.src = `${Astro.url.origin}${og.src}`;
35
36const description = oldDescription ?? "Ben C's software development portfolio";
37const fullTitle = (appendTitle ?? true) ? `${title} | Ben C` : title;
38const canonical = Astro.url.toString();
39---
40
41<!doctype html>
42<html lang="en">
43 <head>
44 <title>{fullTitle}</title>
45 <meta charset="UTF-8" />
46 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
47 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
48 <meta name="description" content={description} />
49 <meta name="keywords" content={["portfolio"].concat(keywords ?? []).join(", ")} />
50 <meta name="generator" content={Astro.generator} />
51 <meta property="og:type" content="website" />
52 <meta property="og:title" content={fullTitle} />
53 <meta property="og:description" content={description} />
54 <meta property="og:url" content={canonical} />
55 <meta property="og:image" content={og.src} />
56 <meta property="og:image:alt" content={og.alt} />
57 <meta name="twitter:title" content={fullTitle} />
58 <meta name="twitter:description" content={description} />
59 <meta name="twitter:image" content={og.src} />
60 <link rel="canonical" href={canonical} />
61 <link rel="icon" href="/favicon.ico" />
62 <link
63 rel="alternate"
64 type="application/rss+xml"
65 title="Ben C's Blog"
66 href={new URL("feed.xml", Astro.site)}
67 />
68 <style is:global>
69 @view-transition {
70 navigation: auto;
71 }
72 </style>
73 </head>
74 <body id="top">
75 <header class="container">
76 <nav aria-label="Main navigation">
77 <a href="/"
78 ><Image
79 class="nav-icon"
80 width={150}
81 height={150}
82 transition:name="pfp"
83 transition:persist
84 format="webp"
85 alt="Ben C's Avatar"
86 src={cow}
87 />
88 <span style="color: var(--text);"><span class="gradient-text">Bwc9876</span>.dev</span>
89 </a>
90 <span>
91 <a href="/projects">Projects</a>
92 <a href="/blog">Blog</a>
93 <a href="/uses">Uses</a>
94 </span>
95 <Socials labelPlacement="bottom" />
96 </nav>
97 </header>
98 <main transition:name="main" class="container">
99 <slot />
100 </main>
101 <footer transition:name="footer" class="container">
102 <p>
103 <IconLink
104 label="Back To Top"
105 placement="top"
106 icon="arrow-bar-up"
107 data-tooltip="Back To Top"
108 href="#top"
109 />
110 </p>
111 <p class="copyright">© Ben C 2026</p>
112 <Socials labelPlacement="top" />
113 </footer>
114 </body>
115</html>
116
117<style>
118 body {
119 background-color: var(--background);
120 background-image: url("@assets/bg-tile.webp");
121 background-repeat: round;
122 background-blend-mode: multiply;
123 animation:
124 rainbow 5s linear infinite,
125 bg-scroll 120s linear infinite;
126
127 margin: 0;
128 padding: 0;
129 display: flex;
130 flex-direction: column;
131 min-height: 100vh;
132 width: 100vw;
133 overflow-x: hidden;
134 }
135
136 body > * {
137 background-color: color(from var(--background) srgb r g b / 70%);
138 backdrop-filter: blur(200px);
139 padding: 0 var(--4);
140 }
141
142 header {
143 border-bottom: var(--section-border);
144 }
145
146 @keyframes bg-scroll {
147 0% {
148 background-position: 0 0;
149 }
150 50% {
151 background-position: 50% 50%;
152 }
153 100% {
154 background-position: 100% 100%;
155 }
156 }
157
158 @keyframes rainbow {
159 0% {
160 backdrop-filter: hue-rotate(0deg);
161 }
162 50% {
163 backdrop-filter: hue-rotate(180deg);
164 }
165 100% {
166 backdrop-filter: hue-rotate(360deg);
167 }
168 }
169
170 nav {
171 display: flex;
172 gap: var(--1);
173 flex-direction: column;
174 align-items: center;
175 padding: var(--2) 0;
176 }
177
178 nav > :nth-child(3) {
179 display: none;
180 }
181
182 @media (width >= 1050px) {
183 nav {
184 flex-direction: row;
185 }
186
187 nav > :nth-child(3) {
188 display: flex;
189 }
190 }
191
192 nav a {
193 text-decoration: none;
194
195 &:hover {
196 text-decoration: underline;
197 }
198 }
199
200 nav > a:first-child {
201 font-size: var(--2);
202 display: flex;
203 gap: 5px;
204 flex-direction: row;
205 align-items: center;
206 justify-content: center;
207 background-color: color(from var(--secondary) srgb r g b / 67%);
208 padding: 5px 7px;
209 border-radius: 5px;
210
211 & > span > span {
212 transform: translateY(-2px);
213 }
214 }
215
216 nav > span {
217 display: flex;
218 flex-direction: row;
219 gap: var(--2);
220 }
221
222 nav > span:nth-child(2) {
223 flex-grow: 1;
224 }
225
226 .nav-icon {
227 width: var(--4);
228 height: var(--4);
229 border-radius: 100%;
230 transition: transform 800ms
231 linear(
232 0,
233 0.36 6.7%,
234 0.635 13.9%,
235 0.832 21.8%,
236 0.904 26.1%,
237 0.958 30.6%,
238 1.015 38.6%,
239 1.036 48.1%,
240 1.034 56.4%,
241 1.005 81.2%,
242 1
243 );
244
245 @starting-style {
246 transform: translateX(calc(-1 * 50vw - var(--5))) rotate(-300deg);
247 }
248 }
249
250 main {
251 flex-grow: 1;
252 }
253
254 footer > p {
255 margin: var(--1);
256 }
257
258 footer {
259 border-top: var(--section-border);
260 display: flex;
261 flex-direction: column;
262 align-items: center;
263 justify-content: center;
264 padding: var(--2) 0;
265 }
266</style>