A Astro blog hosted on Vercel

remove gallery and add donation buttons

+7
astro.config.mjs
··· 11 11 integrations: [mdx(), sitemap(), svelte()], 12 12 image: { 13 13 domains: ["cdn.bsky.app", "i.imgur.com"], 14 + }, 15 + vite: { 16 + server: { 17 + headers: { 18 + 'Content-Security-Policy': "script-src 'self' https://liberapay.com;" 19 + } 20 + } 14 21 } 15 22 });
+1 -1
src/components/atoms/Button.svelte
··· 22 22 } 23 23 24 24 button:hover { 25 - color: var(--subtext0); 25 + filter: brightness(1.1); 26 26 } 27 27 </style>
+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
··· 1 - export {default as Link} from "./Link.svelte"; 2 - export {default as Button} from "./Button.svelte"; 1 + export { default as Link } from "./Link.astro"; 2 + export { default as Button } from "./Button.svelte";
+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
··· 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
··· 1 - export {default as ThemeToggle} from "./ThemeToggle.svelte"; 1 + export { default as ThemeToggle } from "./ThemeToggle.svelte"; 2 + export { default as BandcampWishlist } from "./BandcampWishlist.astro"; 3 + export { default as LiberaPayDonate } from "./LiberaPayDonate.astro";
+7 -15
src/components/organisms/Footer.astro
··· 1 1 --- 2 2 import { BLUESKY_LINK, TANGLED_SH_LINK, SIGNAL_LINK } from "@/consts"; 3 + import { Link } from "@/components/atoms"; 3 4 4 5 const today = new Date(); 5 6 --- 6 7 7 8 <footer> 8 9 <div class="social-links"> 9 - <a href={BLUESKY_LINK} target="_blank"> 10 + <Link href={BLUESKY_LINK} target="_blank"> 10 11 <span class="sr-only">Follow on Bluesky</span> 11 12 <iconify-icon icon={"fa6-brands:bluesky"} height={"2rem"}></iconify-icon> 12 - </a> 13 - <a href={TANGLED_SH_LINK} target="_blank"> 13 + </Link> 14 + <Link href={TANGLED_SH_LINK} target="_blank"> 14 15 <span class="sr-only">Follow on Tangled.sh</span> 15 16 <iconify-icon icon={"fa6-brands:git-alt"} height={"2rem"}></iconify-icon> 16 - </a> 17 - <a href={SIGNAL_LINK} target="_blank"> 17 + </Link> 18 + <Link href={SIGNAL_LINK} target="_blank"> 18 19 <span class="sr-only">Follow on Signal</span> 19 20 <iconify-icon icon={"fa6-brands:signal-messenger"} height={"2rem"} 20 21 ></iconify-icon> 21 - </a> 22 + </Link> 22 23 </div> 23 24 &copy; {today.getFullYear()} ClayCow. All rights reserved. 24 25 </footer> ··· 38 39 display: flex; 39 40 justify-content: center; 40 41 gap: 1rem; 41 - } 42 - 43 - .social-links a { 44 - text-decoration: none; 45 - color: var(--text); 46 - } 47 - 48 - .social-links a:hover { 49 - color: var(--subtext0); 50 42 } 51 43 </style>
+36 -46
src/components/organisms/Navigation.astro
··· 1 1 --- 2 2 import { Link } from "@/components/atoms"; 3 3 import { ThemeToggle } from "@/components/molecules"; 4 - import { BLUESKY_LINK } from "@/consts"; 5 4 --- 6 5 7 6 <nav> 8 - <ol> 9 - <li> 10 - <Link href="/"> 11 - <span class="sr-only">Go to the home page</span> 12 - 🐄 13 - </Link> 14 - </li> 15 - <li> 16 - <Link href="/blog">Blog</Link> 17 - </li> 18 - <li> 19 - <Link href="/gallery">Gallery</Link> 20 - </li> 21 - <li> 22 - <Link href={BLUESKY_LINK} target="_blank"> 23 - <iconify-icon icon={"fa6-brands:bluesky"} height={"1rem"} 24 - ></iconify-icon> Bluesky 25 - </Link> 26 - </li> 27 - <li> 28 - <ThemeToggle client:load /> 29 - </li> 30 - </ol> 7 + <ol> 8 + <li> 9 + <Link href="/"> 10 + <span class="sr-only">Go to the home page</span> 11 + 🐄 12 + </Link> 13 + </li> 14 + <li> 15 + <Link href="/blog">Blog</Link> 16 + </li> 17 + <li> 18 + <ThemeToggle client:load /> 19 + </li> 20 + </ol> 31 21 </nav> 32 22 33 23 <style> 34 - nav { 35 - display: flex; 36 - flex-direction: column; 37 - } 24 + nav { 25 + display: flex; 26 + flex-direction: column; 27 + } 38 28 39 - nav > ol { 40 - list-style: none; 41 - display: flex; 42 - align-items: center; 43 - flex-direction: row; 44 - gap: 1rem; 45 - padding: 0; 46 - } 29 + nav > ol { 30 + list-style: none; 31 + display: flex; 32 + align-items: center; 33 + flex-direction: row; 34 + gap: 1rem; 35 + padding: 0; 36 + } 47 37 48 - nav > ol > li > a { 49 - display: flex; 50 - flex-direction: row; 51 - align-items: center; 52 - height: 1ch; 53 - } 38 + nav > ol > li > a { 39 + display: flex; 40 + flex-direction: row; 41 + align-items: center; 42 + height: 1ch; 43 + } 54 44 55 - nav > ol > li:first-child { 56 - flex-grow: 1; 57 - font-size: large; 58 - } 45 + nav > ol > li:first-child { 46 + flex-grow: 1; 47 + font-size: large; 48 + } 59 49 </style>
+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
··· 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
··· 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 + }