+7
astro.config.mjs
+7
astro.config.mjs
+1
-1
src/components/atoms/Button.svelte
+1
-1
src/components/atoms/Button.svelte
+21
src/components/atoms/Link.astro
+21
src/components/atoms/Link.astro
···
1
+
---
2
+
const { ...props } = Astro.props;
3
+
---
4
+
5
+
<a {...props}>
6
+
<slot />
7
+
</a>
8
+
9
+
<style>
10
+
a {
11
+
display: flex;
12
+
align-items: center;
13
+
gap: 8px;
14
+
text-decoration: none;
15
+
color: var(--text);
16
+
}
17
+
18
+
a:hover {
19
+
filter: brightness(1.1);
20
+
}
21
+
</style>
+2
-2
src/components/atoms/index.ts
+2
-2
src/components/atoms/index.ts
+21
src/components/molecules/BandcampWishlist.astro
+21
src/components/molecules/BandcampWishlist.astro
···
1
+
---
2
+
import { Link } from "@/components/atoms";
3
+
---
4
+
5
+
<Link
6
+
class="bandcamp-wishlist"
7
+
href="https://bandcamp.com/claycow/wishlist"
8
+
target="_blank"
9
+
>
10
+
<iconify-icon icon={"fa6-brands:bandcamp"} height={"1rem"}></iconify-icon>
11
+
Bandcamp Wishlist
12
+
</Link>
13
+
14
+
<style>
15
+
.bandcamp-wishlist {
16
+
background-color: var(--sapphire);
17
+
border-radius: 4px;
18
+
color: var(--base);
19
+
padding: 0.25rem 0.5rem;
20
+
}
21
+
</style>
+21
src/components/molecules/LiberaPayDonate.astro
+21
src/components/molecules/LiberaPayDonate.astro
···
1
+
---
2
+
import { Link } from "@/components/atoms";
3
+
---
4
+
5
+
<Link
6
+
class="libera-pay-donate"
7
+
href="https://liberapay.com/claycow/donate"
8
+
target="_blank"
9
+
>
10
+
<iconify-icon icon={"simple-icons:liberapay"} height={"1rem"}></iconify-icon>
11
+
Donate
12
+
</Link>
13
+
14
+
<style>
15
+
.libera-pay-donate {
16
+
background-color: var(--yellow);
17
+
border-radius: 4px;
18
+
color: var(--base);
19
+
padding: 0.25rem 0.5rem;
20
+
}
21
+
</style>
+3
-1
src/components/molecules/index.ts
+3
-1
src/components/molecules/index.ts
+5
-5
src/components/organisms/index.ts
+5
-5
src/components/organisms/index.ts
···
1
-
export {default as Navigation} from "./Navigation.astro";
2
-
export {default as Head} from "./Head.astro";
3
-
export {default as Footer} from "./Footer.astro";
4
-
export {default as BlogPreviewCard} from "./BlogPreviewCard.astro";
5
-
export {default as BlueskyComments} from "./BlueskyComments.svelte";
1
+
export { default as Navigation } from "./Navigation.astro";
2
+
export { default as Head } from "./Head.astro";
3
+
export { default as Footer } from "./Footer.astro";
4
+
export { default as BlogPreviewCard } from "./BlogPreviewCard.astro";
5
+
export { default as BlueskyComments } from "./BlueskyComments.svelte";
+94
-75
src/layouts/BlogPost.astro
+94
-75
src/layouts/BlogPost.astro
···
1
1
---
2
2
import type { CollectionEntry } from "astro:content";
3
3
import {
4
-
Head,
5
-
Footer,
6
-
Navigation,
7
-
BlueskyComments,
4
+
Head,
5
+
Footer,
6
+
Navigation,
7
+
BlueskyComments,
8
8
} from "@/components/organisms";
9
9
import { formatDate } from "@/utils";
10
10
import { Image } from "astro:assets";
11
11
import SpeedInsights from "@vercel/speed-insights/astro";
12
12
import Analytics from "@vercel/analytics/astro";
13
+
import { BandcampWishlist, LiberaPayDonate } from "@/components/molecules";
13
14
14
15
type Props = CollectionEntry<"blog">["data"];
15
16
···
17
18
---
18
19
19
20
<html lang="en">
20
-
<head>
21
-
<Head title={title} description={description} />
22
-
<style>
23
-
article {
24
-
display: flex;
25
-
flex-direction: column;
26
-
width: 100%;
27
-
gap: 2rem;
28
-
}
21
+
<head>
22
+
<Head title={title} description={description} />
23
+
<style>
24
+
article {
25
+
display: flex;
26
+
flex-direction: column;
27
+
width: 100%;
28
+
gap: 2rem;
29
+
}
30
+
31
+
.prose > header {
32
+
display: flex;
33
+
flex-direction: column;
34
+
gap: 1rem;
35
+
}
36
+
37
+
.widgets {
38
+
display: flex;
39
+
flex-direction: row;
40
+
flex-wrap: wrap;
41
+
gap: 0.5rem;
42
+
}
29
43
30
-
.hero-image {
31
-
object-fit: cover;
32
-
}
44
+
.hero-image {
45
+
object-fit: cover;
46
+
}
33
47
34
-
.date {
35
-
display: flex;
36
-
flex-wrap: wrap;
37
-
gap: 0.5rem;
38
-
font-style: italic;
39
-
font-size: x-small;
40
-
}
48
+
.date {
49
+
display: flex;
50
+
flex-wrap: wrap;
51
+
gap: 0.5rem;
52
+
font-style: italic;
53
+
font-size: x-small;
54
+
}
41
55
42
-
.published {
43
-
flex-grow: 1;
44
-
}
56
+
.published {
57
+
flex-grow: 1;
58
+
}
45
59
46
-
.last-updated {
47
-
font-weight: bold;
48
-
color: var(--red);
49
-
}
50
-
</style>
51
-
</head>
60
+
.last-updated {
61
+
font-weight: bold;
62
+
color: var(--red);
63
+
}
64
+
</style>
65
+
</head>
52
66
53
-
<body data-theme="dark">
54
-
<Navigation />
55
-
<main>
56
-
<article>
57
-
{
58
-
image && (
59
-
<Image
60
-
class="hero-image"
61
-
src={image.src}
62
-
alt={image.alt}
63
-
width={1020}
64
-
height={510}
65
-
loading={"lazy"}
66
-
/>
67
-
)
68
-
}
69
-
<div class="prose">
70
-
<div class="title">
71
-
<h1>{title}</h1>
72
-
<div class="date">
73
-
<span class="published">
74
-
Published on {formatDate(date)}
75
-
</span>
76
-
{
77
-
updatedDate && (
78
-
<span class="last-updated">
79
-
(Last updated on{" "}
80
-
{formatDate(updatedDate)})
81
-
</span>
82
-
)
83
-
}
84
-
</div>
85
-
</div>
86
-
<hr />
87
-
<slot />
88
-
</div>
89
-
<BlueskyComments client:load blogTitle={title} />
90
-
</article>
91
-
</main>
92
-
<Footer />
93
-
<SpeedInsights />
94
-
<Analytics />
95
-
</body>
67
+
<body data-theme="dark">
68
+
<Navigation />
69
+
<main>
70
+
<article>
71
+
{
72
+
image && (
73
+
<Image
74
+
class="hero-image"
75
+
src={image.src}
76
+
alt={image.alt}
77
+
width={1020}
78
+
height={510}
79
+
loading={"lazy"}
80
+
/>
81
+
)
82
+
}
83
+
<div class="prose">
84
+
<header>
85
+
<div class="title">
86
+
<h1>{title}</h1>
87
+
<div class="date">
88
+
<span class="published">
89
+
Published on {formatDate(date)}
90
+
</span>
91
+
{
92
+
updatedDate && (
93
+
<span class="last-updated">
94
+
(Last updated on {formatDate(updatedDate)})
95
+
</span>
96
+
)
97
+
}
98
+
</div>
99
+
</div>
100
+
<div class="widgets">
101
+
<LiberaPayDonate />
102
+
<BandcampWishlist />
103
+
</div>
104
+
</header>
105
+
<hr />
106
+
<slot />
107
+
</div>
108
+
<BlueskyComments client:load blogTitle={title} />
109
+
</article>
110
+
</main>
111
+
<Footer />
112
+
<SpeedInsights />
113
+
<Analytics />
114
+
</body>
96
115
</html>
+121
-119
src/styles/global.css
+121
-119
src/styles/global.css
···
1
-
:root {}
1
+
:root {
2
+
font-family: Helvetica Neue, Helvetica, sans-serif;
3
+
}
2
4
3
5
body[data-theme="light"] {
4
-
--rosewater: #dc8a78;
5
-
--flamingo: #dd7878;
6
-
--pink: #ea76cb;
7
-
--mauve: #8839ef;
8
-
--red: #d20f39;
9
-
--maroon: #e64553;
10
-
--peach: #fe640b;
11
-
--yellow: #df8e1d;
12
-
--green: #40a02b;
13
-
--teal: #179299;
14
-
--sky: #04a5e5;
15
-
--sapphire: #209fb5;
16
-
--blue: #1e66f5;
17
-
--lavender: #7287fd;
18
-
--text: #4c4f69;
19
-
--subtext1: #5c5f77;
20
-
--subtext0: #6c6f85;
21
-
--overlay2: #7c7f93;
22
-
--overlay1: #8c8fa1;
23
-
--overlay0: #9ca0b0;
24
-
--surface2: #acb0be;
25
-
--surface1: #bcc0cc;
26
-
--surface0: #ccd0da;
27
-
--base: #eff1f5;
28
-
--mantle: #e6e9ef;
29
-
--crust: #dce0e8;
6
+
--rosewater: #dc8a78;
7
+
--flamingo: #dd7878;
8
+
--pink: #ea76cb;
9
+
--mauve: #8839ef;
10
+
--red: #d20f39;
11
+
--maroon: #e64553;
12
+
--peach: #fe640b;
13
+
--yellow: #df8e1d;
14
+
--green: #40a02b;
15
+
--teal: #179299;
16
+
--sky: #04a5e5;
17
+
--sapphire: #209fb5;
18
+
--blue: #1e66f5;
19
+
--lavender: #7287fd;
20
+
--text: #4c4f69;
21
+
--subtext1: #5c5f77;
22
+
--subtext0: #6c6f85;
23
+
--overlay2: #7c7f93;
24
+
--overlay1: #8c8fa1;
25
+
--overlay0: #9ca0b0;
26
+
--surface2: #acb0be;
27
+
--surface1: #bcc0cc;
28
+
--surface0: #ccd0da;
29
+
--base: #eff1f5;
30
+
--mantle: #e6e9ef;
31
+
--crust: #dce0e8;
30
32
}
31
33
32
34
body[data-theme="dark"] {
33
-
--rosewater: #f5e0dc;
34
-
--flamingo: #f2cdcd;
35
-
--pink: #f5c2e7;
36
-
--mauve: #cba6f7;
37
-
--red: #f38ba8;
38
-
--maroon: #eba0ac;
39
-
--peach: #fab387;
40
-
--yellow: #f9e2af;
41
-
--green: #a6e3a1;
42
-
--teal: #94e2d5;
43
-
--sky: #89dceb;
44
-
--sapphire: #74c7ec;
45
-
--blue: #89b4fa;
46
-
--lavender: #b4befe;
47
-
--text: #cdd6f4;
48
-
--subtext1: #bac2de;
49
-
--subtext0: #a6adc8;
50
-
--overlay2: #9399b2;
51
-
--overlay1: #7f849c;
52
-
--overlay0: #6c7086;
53
-
--surface2: #585b70;
54
-
--surface1: #45475a;
55
-
--surface0: #313244;
56
-
--base: #1e1e2e;
57
-
--mantle: #181825;
58
-
--crust: #11111b;
35
+
--rosewater: #f5e0dc;
36
+
--flamingo: #f2cdcd;
37
+
--pink: #f5c2e7;
38
+
--mauve: #cba6f7;
39
+
--red: #f38ba8;
40
+
--maroon: #eba0ac;
41
+
--peach: #fab387;
42
+
--yellow: #f9e2af;
43
+
--green: #a6e3a1;
44
+
--teal: #94e2d5;
45
+
--sky: #89dceb;
46
+
--sapphire: #74c7ec;
47
+
--blue: #89b4fa;
48
+
--lavender: #b4befe;
49
+
--text: #cdd6f4;
50
+
--subtext1: #bac2de;
51
+
--subtext0: #a6adc8;
52
+
--overlay2: #9399b2;
53
+
--overlay1: #7f849c;
54
+
--overlay0: #6c7086;
55
+
--surface2: #585b70;
56
+
--surface1: #45475a;
57
+
--surface0: #313244;
58
+
--base: #1e1e2e;
59
+
--mantle: #181825;
60
+
--crust: #11111b;
59
61
}
60
62
61
63
body {
62
-
display: flex;
63
-
flex-direction: column;
64
-
background-color: var(--crust);
65
-
color: var(--text);
66
-
min-height: 100vh;
67
-
margin: 0;
64
+
display: flex;
65
+
flex-direction: column;
66
+
background-color: var(--crust);
67
+
font-size: 1rem;
68
+
color: var(--text);
69
+
min-height: 100vh;
70
+
margin: 0;
68
71
69
-
padding: 0 2rem;
72
+
padding: 0 2rem;
70
73
}
71
74
72
75
main {
73
-
display: flex;
74
-
flex-direction: column;
75
-
align-items: center;
76
-
align-self: center;
77
-
flex-grow: 1;
78
-
width: clamp(200px, 100%, 1028px);
76
+
display: flex;
77
+
flex-direction: column;
78
+
align-items: center;
79
+
align-self: center;
80
+
flex-grow: 1;
81
+
width: clamp(200px, 100%, 1028px);
79
82
}
80
83
81
84
h2 {
82
-
color: var(--red);
85
+
color: var(--red);
83
86
}
84
87
85
88
h3 {
86
-
color: var(--yellow);
89
+
color: var(--yellow);
87
90
}
88
91
89
92
h4 {
90
-
color: var(--green);
93
+
color: var(--green);
91
94
}
92
95
93
96
h5 {
94
-
color: var(--blue);
97
+
color: var(--blue);
95
98
}
96
99
97
100
h6 {
98
-
color: var(--mauve);
101
+
color: var(--mauve);
99
102
}
100
103
101
104
pre {
102
-
padding: 1rem;
103
-
border-radius: 0.5rem;
105
+
padding: 1rem;
106
+
border-radius: 0.5rem;
104
107
}
105
108
106
109
a {
107
-
color: var(--blue);
110
+
color: var(--blue);
108
111
}
109
112
110
113
h1,
···
119
122
figure,
120
123
iframe,
121
124
ul {
122
-
margin: 1rem 0;
125
+
margin: 1rem 0;
123
126
}
124
127
125
128
blockquote {
126
-
border-left: solid 0.5rem var(--text);
127
-
padding: 0.25rem 1rem;
129
+
border-left: solid 0.5rem var(--text);
130
+
padding: 0.25rem 1rem;
128
131
}
129
132
130
133
img {
131
-
border-radius: 0.5rem;
132
-
width: 100%;
134
+
border-radius: 0.5rem;
135
+
width: 100%;
133
136
}
134
137
135
138
iframe {
136
-
border-radius: 0.5rem;
137
-
border: none;
139
+
border-radius: 0.5rem;
140
+
border: none;
138
141
}
139
142
140
143
figure {
141
-
margin: 0;
144
+
margin: 0;
142
145
}
143
146
144
-
figure>img {
145
-
margin-bottom: 0;
147
+
figure > img {
148
+
margin-bottom: 0;
146
149
}
147
150
148
-
figure>figcaption {
149
-
color: var(--subtext0);
150
-
font-size: small;
151
+
figure > figcaption {
152
+
color: var(--subtext0);
153
+
font-size: small;
151
154
}
152
155
153
-
figure>figcaption>p {
154
-
margin-top: 0;
156
+
figure > figcaption > p {
157
+
margin-top: 0;
155
158
}
156
159
157
160
@media only screen and (max-width: 600px) {
158
-
body {
159
-
padding: 0 1rem;
160
-
}
161
+
body {
162
+
padding: 0 1rem;
163
+
}
161
164
}
162
165
163
166
@font-face {
164
-
font-family: 'Atkinson';
165
-
src: url('/fonts/atkinson-regular.woff') format('woff');
166
-
font-weight: 400;
167
-
font-style: normal;
168
-
font-display: swap;
167
+
font-family: "Atkinson";
168
+
src: url("/fonts/atkinson-regular.woff") format("woff");
169
+
font-weight: 400;
170
+
font-style: normal;
171
+
font-display: swap;
169
172
}
170
173
171
174
@font-face {
172
-
font-family: 'Atkinson';
173
-
src: url('/fonts/atkinson-bold.woff') format('woff');
174
-
font-weight: 700;
175
-
font-style: normal;
176
-
font-display: swap;
175
+
font-family: "Atkinson";
176
+
src: url("/fonts/atkinson-bold.woff") format("woff");
177
+
font-weight: 700;
178
+
font-style: normal;
179
+
font-display: swap;
177
180
}
178
181
179
-
180
182
.sr-only {
181
-
border: 0;
182
-
padding: 0;
183
-
margin: 0;
184
-
position: absolute !important;
185
-
height: 1px;
186
-
width: 1px;
187
-
overflow: hidden;
188
-
/* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
189
-
clip: rect(1px 1px 1px 1px);
190
-
/* maybe deprecated but we need to support legacy browsers */
191
-
clip: rect(1px, 1px, 1px, 1px);
192
-
/* modern browsers, clip-path works inwards from each corner */
193
-
clip-path: inset(50%);
194
-
/* added line to stop words getting smushed together (as they go onto separate lines and some screen readers do not understand line feeds as a space */
195
-
white-space: nowrap;
196
-
}
183
+
border: 0;
184
+
padding: 0;
185
+
margin: 0;
186
+
position: absolute !important;
187
+
height: 1px;
188
+
width: 1px;
189
+
overflow: hidden;
190
+
/* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
191
+
clip: rect(1px 1px 1px 1px);
192
+
/* maybe deprecated but we need to support legacy browsers */
193
+
clip: rect(1px, 1px, 1px, 1px);
194
+
/* modern browsers, clip-path works inwards from each corner */
195
+
clip-path: inset(50%);
196
+
/* added line to stop words getting smushed together (as they go onto separate lines and some screen readers do not understand line feeds as a space */
197
+
white-space: nowrap;
198
+
}