Personal Site
1import BoxTlbr from "/assets/box-tlbr.png";
2import Box4x3Mask from "/assets/box-4x3-mask.png";
3import { experimental_AstroContainer } from "astro/container";
4import Header from "/components/Header.astro";
5
6const header = await experimental_AstroContainer
7 .create()
8 .then((x) => x.renderToString(Header))
9 .then((x) =>
10 // match data-astro-cid attributes where there no value
11 // this is because xml hates boolean attributes for some reason. :(
12 x.replaceAll(/data-astro-cid-[a-zA-Z0-9]*(?!=["'])/gm, (m) => m + '=""'),
13 )
14 .then((x) =>
15 // match wbr tags (with data astro cid) and close em
16 x.replaceAll(/<wbr data-astro-cid-[a-zA-Z0-9]*="">/gm, (m) => m + "</wbr>"),
17 );
18
19export async function GET() {
20 return new Response(
21 `
22<!DOCTYPE XSL [ <!ENTITY shy ""> ]>
23<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
24 <xsl:output method="html" />
25
26 <xsl:template match="/">
27 <html lang="en">
28 <head>
29 <title>RSS | vielle.dev</title>
30 <style>
31 :root {
32 font-family: sans-serif;
33 interpolate-size: allow-keywords;
34 image-rendering: pixelated;
35 }
36
37 * {
38 margin: 0;
39 padding: 0;
40 }
41
42 html,
43 body {
44 min-width: 30ch;
45 }
46
47 .blog {
48 width: 30ch;
49 margin: auto;
50 }
51
52 .intro {
53 max-width: 60ch;
54 padding-inline: 20px;
55 margin: 20px auto;
56 }
57
58 img,
59 svg,
60 iframe,
61 audio,
62 video {
63 max-width: 100%;
64 display: block;
65 }
66
67 .post {
68 border-image: url("${BoxTlbr.src}") 10 10 fill / 20px 20px round;
69 padding: 30px;
70 }
71
72 img {
73 width: 100%;
74 aspect-ratio: 4/3;
75 object-fit: cover;
76 mask-image: url("${Box4x3Mask.src}");
77 mask-size: 100%;
78 }
79
80 a {
81 color: black;
82 text-decoration-line: none;
83 }
84
85 a:hover,
86 a:focus,
87 a:active {
88 text-decoration-line: underline;
89 }
90
91 a:active {
92 text-decoration-style: dashed;
93 }
94
95 /* from Header.astro */
96 header {
97 border-image: var(--box-blr-png) 10 fill / 20px / 20px round;
98 margin: 0 20px 20px;
99 padding: 10px 20px;
100 height: 3rem; /* 2rem * 1.5 */
101 /* render over feed bg */
102 z-index: 2;
103
104 display: flex;
105 flex-direction: row;
106 justify-content: space-between;
107 align-items: center;
108
109 @media (max-width: 650px) {
110 flex-direction: column;
111 align-items: start;
112 height: 4.5rem; /* (2rem + 1rem) * 1.5 */
113
114 nav {
115 margin-inline: auto;
116 contain: inline-size;
117 width: 100%;
118 overflow: auto;
119 scrollbar-width: thin;
120 }
121
122 h1 {
123 width: 100%;
124 text-align: center;
125 }
126 }
127
128 & > nav > ul {
129 display: flex;
130 flex-direction: row;
131 align-items: center;
132 justify-content: start;
133 gap: 10px;
134 z-index: 999;
135 width: fit-content;
136 margin-inline: auto;
137
138 & > li {
139 display: flex;
140 flex-direction: row;
141 align-items: center;
142 gap: 10px;
143
144 &::marker {
145 content: none;
146 }
147
148 & + &::before {
149 content: "";
150 background-image: var(--dot-png);
151 background-size: contain;
152 width: 9px;
153 height: 9px;
154 display: block;
155 }
156 }
157 }
158 }
159
160 nav > ul > li:last-child > details > ul {
161 right: 10px;
162 }
163
164 details {
165 summary {
166 cursor: pointer;
167 text-wrap: nowrap;
168 }
169
170 & > ul {
171 position: absolute;
172 z-index: 99999;
173
174 @media (max-width: 650px) {
175 inset: auto 15px;
176 }
177
178 margin-top: 10px;
179 padding: 20px;
180 padding-left: 40px;
181 & ul {
182 margin-left: 10px;
183 }
184
185 border-image: var(--box-tlbr-png) 10 fill / 20px round;
186 }
187 }
188
189 header h1 {
190 margin-block: 0;
191 position: sticky;
192 inset: 0;
193 }
194 </style>
195 </head>
196
197 <body>
198 ${header}
199 <section class="intro">
200 <h1>Blog Posts</h1>
201 <p>If you want to use this page in your RSS reader, simply paste this page's URL in and it'll just work. To view the original RSS, view the page source. The styling of this page is made possible by <a href="https://developer.mozilla.org/en-US/docs/Web/XML/XSLT" target="_blank">XSLT</a></p>
202 </section>
203 <section class="blog">
204 <xsl:for-each select="/rss/channel/item">
205 <a target="_blank">
206 <xsl:attribute name="href">
207 <xsl:value-of select="link" />
208 </xsl:attribute>
209 <article class="post">
210
211 <img alt="">
212 <xsl:attribute name="src">
213 <xsl:value-of select="image" />
214 </xsl:attribute>
215 </img>
216
217 <h2 class="title"><xsl:value-of select="title" /></h2>
218 <div class="bio"><xsl:value-of select="description" /></div>
219 <time><xsl:value-of select="pubDate" /></time>
220
221 </article>
222 </a>
223 </xsl:for-each>
224 </section>
225 </body>
226 </html>
227 </xsl:template>
228</xsl:stylesheet>
229 `,
230 {
231 headers: {
232 "Content-Type": "text/xml",
233 },
234 },
235 );
236}