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 margin: 0;
121 padding: 0;
122 display: flex;
123 flex-direction: column;
124 min-height: 100vh;
125 width: 100vw;
126 overflow-x: hidden;
127 }
128
129 header {
130 border-bottom: var(--section-border);
131 }
132
133 nav {
134 display: flex;
135 gap: var(--1);
136 flex-direction: column;
137 align-items: center;
138 padding: var(--2) 0;
139 }
140
141 nav > :nth-child(3) {
142 display: none;
143 }
144
145 @media (width >= 1050px) {
146 nav {
147 flex-direction: row;
148 }
149
150 nav > :nth-child(3) {
151 display: flex;
152 }
153 }
154
155 nav a {
156 text-decoration: none;
157
158 &:hover {
159 text-decoration: underline;
160 }
161 }
162
163 nav > a:first-child {
164 font-size: var(--2);
165 display: flex;
166 gap: 5px;
167 flex-direction: row;
168 align-items: center;
169 justify-content: center;
170 background-color: var(--secondary);
171 padding: 5px 7px;
172 border-radius: 5px;
173
174 & > span > span {
175 transform: translateY(-2px);
176 }
177 }
178
179 nav > span {
180 display: flex;
181 flex-direction: row;
182 gap: var(--2);
183 }
184
185 nav > span:nth-child(2) {
186 flex-grow: 1;
187 }
188
189 .nav-icon {
190 width: var(--4);
191 height: var(--4);
192 border-radius: 100%;
193 transition: transform 800ms
194 linear(
195 0,
196 0.36 6.7%,
197 0.635 13.9%,
198 0.832 21.8%,
199 0.904 26.1%,
200 0.958 30.6%,
201 1.015 38.6%,
202 1.036 48.1%,
203 1.034 56.4%,
204 1.005 81.2%,
205 1
206 );
207
208 @starting-style {
209 transform: translateX(calc(-1 * 50vw - var(--5))) rotate(-300deg);
210 }
211 }
212
213 main {
214 flex-grow: 1;
215 }
216
217 footer > p {
218 margin: var(--1);
219 }
220
221 footer {
222 border-top: var(--section-border);
223 margin-top: var(--4);
224 display: flex;
225 flex-direction: column;
226 align-items: center;
227 justify-content: center;
228 padding: var(--2) 0;
229 }
230</style>