A Astro blog hosted on Vercel

update links & add cow moo

+6
.prettierrc
··· 12 12 "options": { 13 13 "parser": "astro" 14 14 } 15 + }, 16 + { 17 + "files": "*.css", 18 + "options": { 19 + "parser": "css" 20 + } 15 21 } 16 22 ] 17 23 }
+62 -76
package-lock.json
··· 15 15 "@atproto/api": "^0.13.35", 16 16 "@iconify-json/fa6-brands": "^1.2.5", 17 17 "@iconify-json/fa6-solid": "^1.2.3", 18 - "@vercel/analytics": "^1.4.1", 19 - "@vercel/speed-insights": "^1.1.0", 20 18 "astro": "^5.13.4", 21 19 "svelte": "^5.16.0", 22 20 "typescript": "^5.7.2" 23 21 }, 24 - "devDependencies": {} 22 + "devDependencies": { 23 + "prettier": "^3.6.2", 24 + "prettier-plugin-astro": "^0.14.1" 25 + } 25 26 }, 26 27 "node_modules/@astrojs/compiler": { 27 28 "version": "2.12.2", ··· 1691 1692 "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", 1692 1693 "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", 1693 1694 "license": "ISC" 1694 - }, 1695 - "node_modules/@vercel/analytics": { 1696 - "version": "1.5.0", 1697 - "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.5.0.tgz", 1698 - "integrity": "sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==", 1699 - "license": "MPL-2.0", 1700 - "peerDependencies": { 1701 - "@remix-run/react": "^2", 1702 - "@sveltejs/kit": "^1 || ^2", 1703 - "next": ">= 13", 1704 - "react": "^18 || ^19 || ^19.0.0-rc", 1705 - "svelte": ">= 4", 1706 - "vue": "^3", 1707 - "vue-router": "^4" 1708 - }, 1709 - "peerDependenciesMeta": { 1710 - "@remix-run/react": { 1711 - "optional": true 1712 - }, 1713 - "@sveltejs/kit": { 1714 - "optional": true 1715 - }, 1716 - "next": { 1717 - "optional": true 1718 - }, 1719 - "react": { 1720 - "optional": true 1721 - }, 1722 - "svelte": { 1723 - "optional": true 1724 - }, 1725 - "vue": { 1726 - "optional": true 1727 - }, 1728 - "vue-router": { 1729 - "optional": true 1730 - } 1731 - } 1732 - }, 1733 - "node_modules/@vercel/speed-insights": { 1734 - "version": "1.2.0", 1735 - "resolved": "https://registry.npmjs.org/@vercel/speed-insights/-/speed-insights-1.2.0.tgz", 1736 - "integrity": "sha512-y9GVzrUJ2xmgtQlzFP2KhVRoCglwfRQgjyfY607aU0hh0Un6d0OUyrJkjuAlsV18qR4zfoFPs/BiIj9YDS6Wzw==", 1737 - "hasInstallScript": true, 1738 - "license": "Apache-2.0", 1739 - "peerDependencies": { 1740 - "@sveltejs/kit": "^1 || ^2", 1741 - "next": ">= 13", 1742 - "react": "^18 || ^19 || ^19.0.0-rc", 1743 - "svelte": ">= 4", 1744 - "vue": "^3", 1745 - "vue-router": "^4" 1746 - }, 1747 - "peerDependenciesMeta": { 1748 - "@sveltejs/kit": { 1749 - "optional": true 1750 - }, 1751 - "next": { 1752 - "optional": true 1753 - }, 1754 - "react": { 1755 - "optional": true 1756 - }, 1757 - "svelte": { 1758 - "optional": true 1759 - }, 1760 - "vue": { 1761 - "optional": true 1762 - }, 1763 - "vue-router": { 1764 - "optional": true 1765 - } 1766 - } 1767 1695 }, 1768 1696 "node_modules/acorn": { 1769 1697 "version": "8.15.0", ··· 4726 4654 "node": "^10 || ^12 || >=14" 4727 4655 } 4728 4656 }, 4657 + "node_modules/prettier": { 4658 + "version": "3.6.2", 4659 + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", 4660 + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", 4661 + "dev": true, 4662 + "license": "MIT", 4663 + "bin": { 4664 + "prettier": "bin/prettier.cjs" 4665 + }, 4666 + "engines": { 4667 + "node": ">=14" 4668 + }, 4669 + "funding": { 4670 + "url": "https://github.com/prettier/prettier?sponsor=1" 4671 + } 4672 + }, 4673 + "node_modules/prettier-plugin-astro": { 4674 + "version": "0.14.1", 4675 + "resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.14.1.tgz", 4676 + "integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==", 4677 + "dev": true, 4678 + "license": "MIT", 4679 + "dependencies": { 4680 + "@astrojs/compiler": "^2.9.1", 4681 + "prettier": "^3.0.0", 4682 + "sass-formatter": "^0.7.6" 4683 + }, 4684 + "engines": { 4685 + "node": "^14.15.0 || >=16.0.0" 4686 + } 4687 + }, 4729 4688 "node_modules/prismjs": { 4730 4689 "version": "1.30.0", 4731 4690 "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", ··· 5154 5113 "fsevents": "~2.3.2" 5155 5114 } 5156 5115 }, 5116 + "node_modules/s.color": { 5117 + "version": "0.0.15", 5118 + "resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz", 5119 + "integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==", 5120 + "dev": true, 5121 + "license": "MIT" 5122 + }, 5123 + "node_modules/sass-formatter": { 5124 + "version": "0.7.9", 5125 + "resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.9.tgz", 5126 + "integrity": "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==", 5127 + "dev": true, 5128 + "license": "MIT", 5129 + "dependencies": { 5130 + "suf-log": "^2.5.3" 5131 + } 5132 + }, 5157 5133 "node_modules/sax": { 5158 5134 "version": "1.4.1", 5159 5135 "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", ··· 5389 5365 "license": "MIT", 5390 5366 "dependencies": { 5391 5367 "inline-style-parser": "0.2.4" 5368 + } 5369 + }, 5370 + "node_modules/suf-log": { 5371 + "version": "2.5.3", 5372 + "resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz", 5373 + "integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==", 5374 + "dev": true, 5375 + "license": "MIT", 5376 + "dependencies": { 5377 + "s.color": "0.0.15" 5392 5378 } 5393 5379 }, 5394 5380 "node_modules/svelte": {
+4 -3
package.json
··· 16 16 "@atproto/api": "^0.13.35", 17 17 "@iconify-json/fa6-brands": "^1.2.5", 18 18 "@iconify-json/fa6-solid": "^1.2.3", 19 - "@vercel/analytics": "^1.4.1", 20 - "@vercel/speed-insights": "^1.1.0", 21 19 "astro": "^5.13.4", 22 20 "svelte": "^5.16.0", 23 21 "typescript": "^5.7.2" 24 22 }, 25 - "devDependencies": {} 23 + "devDependencies": { 24 + "prettier": "^3.6.2", 25 + "prettier-plugin-astro": "^0.14.1" 26 + } 26 27 }
public/moo.wav

This is a binary file and will not be displayed.

+1
src/components/BandcampWishlist.astro
··· 17 17 border-radius: 4px; 18 18 color: var(--base); 19 19 padding: 0.25rem 0.5rem; 20 + text-decoration: none; 20 21 } 21 22 </style>
+1
src/components/LiberaPayDonate.astro
··· 17 17 border-radius: 4px; 18 18 color: var(--base); 19 19 padding: 0.25rem 0.5rem; 20 + text-decoration: none; 20 21 } 21 22 </style>
-1
src/components/Link.astro
··· 11 11 display: flex; 12 12 align-items: center; 13 13 gap: 8px; 14 - text-decoration: none; 15 14 color: var(--text); 16 15 } 17 16
+22 -4
src/components/Navigation.astro
··· 1 1 --- 2 - import { Link, ThemeToggle } from "@/components"; 2 + import { Link } from '@/components'; 3 3 --- 4 4 5 + <script> 6 + const homeLink = document.querySelector<HTMLAnchorElement>('a[href="/"]'); 7 + const mooAudio = document.querySelector<HTMLAudioElement>('audio#moo'); 8 + 9 + homeLink?.addEventListener('click', (event) => { 10 + event.preventDefault(); 11 + mooAudio?.addEventListener('ended', function () { 12 + const anchor = event.target as HTMLAnchorElement; 13 + window.location.href = anchor.href; 14 + }); 15 + mooAudio?.play(); 16 + }); 17 + </script> 18 + 19 + <audio id="moo"> 20 + <source src="/moo.wav" type="audio/wav" /> 21 + </audio> 5 22 <nav> 6 23 <ol> 7 24 <li> ··· 12 29 </li> 13 30 <li> 14 31 <Link href="/blog">Blog</Link> 15 - </li> 16 - <li> 17 - <ThemeToggle client:load /> 18 32 </li> 19 33 </ol> 20 34 </nav> ··· 44 58 nav > ol > li:first-child { 45 59 flex-grow: 1; 46 60 font-size: large; 61 + } 62 + 63 + nav > ol > li:first-child > a { 64 + text-decoration: none; 47 65 } 48 66 </style>
-24
src/components/ThemeToggle.svelte
··· 1 - <script lang="ts"> 2 - import type { HTMLButtonAttributes } from "svelte/elements"; 3 - import { Button } from "@/components"; 4 - 5 - interface Props extends HTMLButtonAttributes {} 6 - 7 - let { onclick, ...props }: Props = $props(); 8 - 9 - let isDark = $state(true); 10 - 11 - function onToggleClick() { 12 - isDark = !isDark; 13 - document.body.setAttribute("data-theme", isDark ? "dark" : "light"); 14 - } 15 - </script> 16 - 17 - <Button onclick={onToggleClick} {...props}> 18 - <span class="sr-only">Toggle color palette theme</span> 19 - <iconify-icon 20 - icon={isDark ? "fa6-solid:moon" : "fa6-solid:sun"} 21 - width={32} 22 - height={32} 23 - ></iconify-icon> 24 - </Button>
+9 -10
src/components/index.ts
··· 1 - export { default as BandcampWishlist } from "./BandcampWishlist.astro"; 2 - export { default as BlogPreviewCard } from "./BlogPreviewCard.astro"; 3 - export { default as Footer } from "./Footer.astro"; 4 - export { default as Head } from "./Head.astro"; 5 - export { default as LiberaPayDonate } from "./LiberaPayDonate.astro"; 6 - export { default as Link } from "./Link.astro"; 7 - export { default as Navigation } from "./Navigation.astro"; 8 - export { default as BlueskyComments } from "./BlueskyComments.svelte"; 9 - export { default as Button } from "./Button.svelte"; 10 - export { default as ThemeToggle } from "./ThemeToggle.svelte"; 1 + export { default as BandcampWishlist } from './BandcampWishlist.astro'; 2 + export { default as BlogPreviewCard } from './BlogPreviewCard.astro'; 3 + export { default as Footer } from './Footer.astro'; 4 + export { default as Head } from './Head.astro'; 5 + export { default as LiberaPayDonate } from './LiberaPayDonate.astro'; 6 + export { default as Link } from './Link.astro'; 7 + export { default as Navigation } from './Navigation.astro'; 8 + export { default as BlueskyComments } from './BlueskyComments.svelte'; 9 + export { default as Button } from './Button.svelte';
+68 -59
src/styles/global.css
··· 1 1 :root { 2 - font-family: Helvetica Neue, Helvetica, sans-serif; 2 + font-family: 3 + Helvetica Neue, 4 + Helvetica, 5 + sans-serif; 6 + 7 + color-scheme: light dark; 3 8 } 4 9 5 - body[data-theme="light"] { 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; 10 + @media (prefers-color-scheme: dark) { 11 + body { 12 + --rosewater: #f5e0dc; 13 + --flamingo: #f2cdcd; 14 + --pink: #f5c2e7; 15 + --mauve: #cba6f7; 16 + --red: #f38ba8; 17 + --maroon: #eba0ac; 18 + --peach: #fab387; 19 + --yellow: #f9e2af; 20 + --green: #a6e3a1; 21 + --teal: #94e2d5; 22 + --sky: #89dceb; 23 + --sapphire: #74c7ec; 24 + --blue: #89b4fa; 25 + --lavender: #b4befe; 26 + --text: #cdd6f4; 27 + --subtext1: #bac2de; 28 + --subtext0: #a6adc8; 29 + --overlay2: #9399b2; 30 + --overlay1: #7f849c; 31 + --overlay0: #6c7086; 32 + --surface2: #585b70; 33 + --surface1: #45475a; 34 + --surface0: #313244; 35 + --base: #1e1e2e; 36 + --mantle: #181825; 37 + --crust: #11111b; 38 + } 32 39 } 33 40 34 - body[data-theme="dark"] { 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; 41 + @media (prefers-color-scheme: light) { 42 + body { 43 + --rosewater: #dc8a78; 44 + --flamingo: #dd7878; 45 + --pink: #ea76cb; 46 + --mauve: #8839ef; 47 + --red: #d20f39; 48 + --maroon: #e64553; 49 + --peach: #fe640b; 50 + --yellow: #df8e1d; 51 + --green: #40a02b; 52 + --teal: #179299; 53 + --sky: #04a5e5; 54 + --sapphire: #209fb5; 55 + --blue: #1e66f5; 56 + --lavender: #7287fd; 57 + --text: #4c4f69; 58 + --subtext1: #5c5f77; 59 + --subtext0: #6c6f85; 60 + --overlay2: #7c7f93; 61 + --overlay1: #8c8fa1; 62 + --overlay0: #9ca0b0; 63 + --surface2: #acb0be; 64 + --surface1: #bcc0cc; 65 + --surface0: #ccd0da; 66 + --base: #eff1f5; 67 + --mantle: #e6e9ef; 68 + --crust: #dce0e8; 69 + } 61 70 } 62 71 63 72 body { ··· 164 173 } 165 174 166 175 @font-face { 167 - font-family: "Atkinson"; 168 - src: url("/fonts/atkinson-regular.woff") format("woff"); 176 + font-family: 'Atkinson'; 177 + src: url('/fonts/atkinson-regular.woff') format('woff'); 169 178 font-weight: 400; 170 179 font-style: normal; 171 180 font-display: swap; 172 181 } 173 182 174 183 @font-face { 175 - font-family: "Atkinson"; 176 - src: url("/fonts/atkinson-bold.woff") format("woff"); 184 + font-family: 'Atkinson'; 185 + src: url('/fonts/atkinson-bold.woff') format('woff'); 177 186 font-weight: 700; 178 187 font-style: normal; 179 188 font-display: swap;
+14
src/utils/cookies.ts
··· 1 + export function getCookie(name: string): string | null { 2 + const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); 3 + return match ? decodeURIComponent(match[2]) : null; 4 + } 5 + 6 + export function setCookie(name: string, value: string, days?: number): void { 7 + let expires = ''; 8 + if (days) { 9 + const date = new Date(); 10 + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 11 + expires = `; expires=${date.toUTCString()}`; 12 + } 13 + document.cookie = `${name}=${encodeURIComponent(value)}${expires}; path=/`; 14 + }
+1
src/utils/index.ts
··· 1 1 export { default as formatDate } from "./formatDate"; 2 2 export { default as formatBlogTitleUrl } from "./formatBlogTitleUrl"; 3 + export * from './cookies';