"@fontsource-variable/source-serif-4": "^5.2.9",
"@iconify-json/mdi": "^1.2.3",
"@vercel/og": "^0.8.5",
- "astro": "5.16.3",
+ "astro": "5.16.5",
"astro-icon": "^1.1.5",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3",
"recharts": "^3.5.1",
},
"devDependencies": {
- "@types/bun": "^1.3.3",
+ "@types/bun": "^1.3.4",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
},
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.43.0", "", { "os": "win32", "cpu": "x64" }, "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw=="],
- "@shikijs/core": ["@shikijs/core@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg=="],
+ "@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
- "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg=="],
+ "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg=="],
- "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA=="],
+ "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ=="],
- "@shikijs/langs": ["@shikijs/langs@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A=="],
+ "@shikijs/langs": ["@shikijs/langs@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0" } }, "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA=="],
- "@shikijs/themes": ["@shikijs/themes@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0" } }, "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ=="],
+ "@shikijs/themes": ["@shikijs/themes@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0" } }, "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ=="],
- "@shikijs/types": ["@shikijs/types@3.15.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw=="],
+ "@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
- "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
+ "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
"@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
- "astro": ["astro@5.16.3", "", { "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.5", "@astrojs/markdown-remark": "6.3.9", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^3.0.1", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.3.1", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.3", "deterministic-object-hash": "^2.0.2", "devalue": "^5.5.0", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.1", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.1", "package-manager-detector": "^1.5.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.3", "shiki": "^3.15.0", "smol-toml": "^1.5.2", "svgo": "^4.0.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.6.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.17.3", "vfile": "^6.0.3", "vite": "^6.4.1", "vitefu": "^1.1.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.3", "zod": "^3.25.76", "zod-to-json-schema": "^3.25.0", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "astro.js" } }, "sha512-KzDk41F9Dspf5fM/Ls4XZhV4/csjJcWBrlenbnp5V3NGwU1zEaJz/HIyrdKdf5yw+FgwCeD2+Yos1Xkx9gnI0A=="],
+ "astro": ["astro@5.16.5", "", { "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.5", "@astrojs/markdown-remark": "6.3.10", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^3.0.1", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.3.1", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.3", "deterministic-object-hash": "^2.0.2", "devalue": "^5.5.0", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.1", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.1", "package-manager-detector": "^1.5.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.3", "shiki": "^3.15.0", "smol-toml": "^1.5.2", "svgo": "^4.0.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.6.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.17.3", "vfile": "^6.0.3", "vite": "^6.4.1", "vitefu": "^1.1.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.3", "zod": "^3.25.76", "zod-to-json-schema": "^3.25.0", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "astro.js" } }, "sha512-QeuM4xzTR0QuXFDNlGVW0BW7rcquKFIkylaPeM4ufii0/RRiPTYtwxDYVZ3KfiMRuuc+nbLD0214kMKTvz/yvQ=="],
"astro-icon": ["astro-icon@1.1.5", "", { "dependencies": { "@iconify/tools": "^4.0.5", "@iconify/types": "^2.0.0", "@iconify/utils": "^2.1.30" } }, "sha512-CJYS5nWOw9jz4RpGWmzNQY7D0y2ZZacH7atL2K9DeJXJVaz7/5WrxeyIxO8KASk1jCM96Q4LjRx/F3R+InjJrw=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
- "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
+ "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="],
- "oniguruma-to-es": ["oniguruma-to-es@4.3.3", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg=="],
+ "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="],
"p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="],
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
- "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
+ "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="],
- "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
+ "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
"react-is": ["react-is@19.1.0", "", {}, "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg=="],
"sharp": ["sharp@0.34.3", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.4", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.3", "@img/sharp-darwin-x64": "0.34.3", "@img/sharp-libvips-darwin-arm64": "1.2.0", "@img/sharp-libvips-darwin-x64": "1.2.0", "@img/sharp-libvips-linux-arm": "1.2.0", "@img/sharp-libvips-linux-arm64": "1.2.0", "@img/sharp-libvips-linux-ppc64": "1.2.0", "@img/sharp-libvips-linux-s390x": "1.2.0", "@img/sharp-libvips-linux-x64": "1.2.0", "@img/sharp-libvips-linuxmusl-arm64": "1.2.0", "@img/sharp-libvips-linuxmusl-x64": "1.2.0", "@img/sharp-linux-arm": "0.34.3", "@img/sharp-linux-arm64": "0.34.3", "@img/sharp-linux-ppc64": "0.34.3", "@img/sharp-linux-s390x": "0.34.3", "@img/sharp-linux-x64": "0.34.3", "@img/sharp-linuxmusl-arm64": "0.34.3", "@img/sharp-linuxmusl-x64": "0.34.3", "@img/sharp-wasm32": "0.34.3", "@img/sharp-win32-arm64": "0.34.3", "@img/sharp-win32-ia32": "0.34.3", "@img/sharp-win32-x64": "0.34.3" } }, "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg=="],
- "shiki": ["shiki@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
+ "shiki": ["shiki@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/engine-javascript": "3.20.0", "@shikijs/engine-oniguruma": "3.20.0", "@shikijs/langs": "3.20.0", "@shikijs/themes": "3.20.0", "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg=="],
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "astro/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.10", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.5", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.19.0", "smol-toml": "^1.5.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A=="],
+
"brotli/base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
title: we're ripping off-the-cuff about dollar rallying, rates, stargate, and deepseek. listen in 👇
date: 2025-01-23
modified: 2025-06-19
-show: Build Weekly Roundup
-description: "2025 Week #4"
+description: "Build Weekly Roundup - 2025 Week #4"
tags:
- ai
- macro
title: Yield curve updates, looking forward to Yen meeting, more DeepSeek analysis
date: 2025-01-30
modified: 2025-06-19
-show: Build Weekly Roundup
-description: "2025 Week #5"
+description: "Build Weekly Roundup - 2025 Week #5"
tags:
- ai
- macro
title: Listen in as we break down Treasury yields, bond liquidity, potential regulatory changes, Bitcoin's mempools, and alternative investments 👇
date: 2025-02-06
modified: 2025-10-24
-show: Build Weekly Roundup
-description: "2025 Week #6"
+description: "Build Weekly Roundup - 2025 Week #6"
tags:
- bitcoin
- macro
title: 🚨 the penny peg has broken, new 6-week t-bills, ukraine peace talks, and more
date: 2025-02-13
modified: 2025-10-24
-show: Build Weekly Roundup
-description: "2025 Week #7"
+description: "Build Weekly Roundup - 2025 Week #7"
tags:
- macro
posse:
title: Talking DOGE cuts 🐶
date: 2025-02-20
modified: 2025-06-06
-show: Build Weekly Roundup
-description: "2025 Week #8"
+description: "Build Weekly Roundup - 2025 Week #8"
tags:
- macro
- bitcoin
title: Fork in the road for Treasury yields
date: 2025-03-13
modified: 2025-06-06
-show: Build Weekly Roundup
-description: "2025 Week #11"
+description: "Build Weekly Roundup - 2025 Week #11"
tags:
- macro
- bitcoin
title: Post-FOMC Landscape and the EU Savings & Investments Union
date: 2025-03-20
modified: 2025-06-06
-show: Build Weekly Roundup
-description: "2025 Week #12"
+description: "Build Weekly Roundup - 2025 Week #12"
tags:
- macro
posse:
title: Liberation Day in America
date: 2025-04-03
modified: 2025-06-06
-show: Build Weekly Roundup
-description: "2025 Week #14"
+description: "Build Weekly Roundup - 2025 Week #14"
tags:
- macro
posse:
title: Tariffs pack a wallop
date: 2025-04-10
modified: 2025-06-06
-show: Build Weekly Roundup
-description: "2025 Week #15"
+description: "Build Weekly Roundup - 2025 Week #15"
tags:
- macro
posse:
title: Retail rushes for gold
date: 2025-04-25
modified: 2025-06-06
-show: Build Weekly Roundup
-description: "2025 Week #17"
+description: "Build Weekly Roundup - 2025 Week #17"
tags:
- macro
- bitcoin
title: The Art of the (Ukraine) Deal
date: 2025-05-01
modified: 2025-06-06
-show: Build Weekly Roundup
-description: "2025 Week #18"
+description: "Build Weekly Roundup - 2025 Week #18"
tags:
- macro
- bitcoin
title: Hong Kong Dollar Squeeze
date: 2025-05-08
modified: 2025-06-06
-show: Build Weekly Roundup
-description: "2025 Week #19"
+description: "Build Weekly Roundup - 2025 Week #19"
tags:
- macro
- bitcoin
title: Not Your Father's Recession
date: 2025-05-15
modified: 2025-06-06
-show: Build Weekly Roundup
-description: "2025 Week #20"
+description: "Build Weekly Roundup - 2025 Week #20"
tags:
- macro
posse:
title: A Pivotal Week
date: 2025-05-22
modified: 2025-06-06
-show: Build Weekly Roundup
-description: "2025 Week #21"
+description: "Build Weekly Roundup - 2025 Week #21"
tags:
- macro
posse:
title: JPM Bends the Knee to Bitcoin
date: 2025-06-06
modified: 2025-06-06
-show: Build Weekly Roundup
-description: "2025 Week #23"
+description: "Build Weekly Roundup - 2025 Week #23"
tags:
- macro
- bitcoin
title: Last Chance to Hit the Exit?
date: 2025-06-12
modified: 2025-06-18
-show: Build Weekly Roundup
-description: "2025 Week #24"
+description: "Build Weekly Roundup - 2025 Week #24"
tags:
- macro
posse:
title: Battle of Fordow
date: 2025-06-18
modified: 2025-06-18
-show: Build Weekly Roundup
-description: "2025 Week #25"
+description: "Build Weekly Roundup - 2025 Week #25"
tags:
- macro
- bitcoin
title: Rebalance of Power
date: 2025-06-26
modified: 2025-07-01
-show: Build Weekly Roundup
-description: "2025 Week #26"
+description: "Build Weekly Roundup - 2025 Week #26"
tags:
- macro
- bitcoin
title: Monetary Policy Disarray
date: 2025-07-17
modified: 2025-07-17
-show: Build Weekly Roundup
-description: "2025 Week #29"
+description: "Build Weekly Roundup - 2025 Week #29"
tags:
- macro
- bitcoin
title: Rate Shortcut Contractors, Inc.
date: 2025-07-25
modified: 2025-07-25
-show: Build Weekly Roundup
-description: "2025 Week #30"
+description: "Build Weekly Roundup - 2025 Week #30"
tags:
- macro
posse:
title: PPI Blowout
date: 2025-08-15
modified: 2025-08-17
-show: Build Weekly Roundup
-description: "2025 Week #33"
+description: "Build Weekly Roundup - 2025 Week #33"
tags:
- macro
posse:
title: Money, Credit and Asset Prices
date: 2025-09-08
modified: 2025-09-09
-show: Build Weekly Roundup
-description: "2025 Week #36"
+description: "Build Weekly Roundup - 2025 Week #36"
tags:
- macro
posse:
title: Climbing the Escalatory Ladder
date: 2025-09-11
modified: 2025-09-12
-show: Build Weekly Roundup
-description: "2025 Week #37"
+description: "Build Weekly Roundup - 2025 Week #37"
tags:
- macro
posse:
title: Dot Slop
date: 2025-09-18
modified: 2025-09-23
-show: Build Weekly Roundup
-description: "2025 Week #38"
+description: "Build Weekly Roundup - 2025 Week #38"
tags:
- macro
- bitcoin
title: Government Shutdown
date: 2025-09-26
modified: 2025-10-02
-show: Build Weekly Roundup
-description: "2025 Week #39"
+description: "Build Weekly Roundup - 2025 Week #39"
tags:
- macro
posse:
title: Window of Heightened Risk
date: 2025-10-02
modified: 2025-10-02
-show: Build Weekly Roundup
-description: "2025 Week #40"
+description: "Build Weekly Roundup - 2025 Week #40"
tags:
- macro
- bitcoin
title: Equity Financing Bubble?
date: 2025-10-09
modified: 2025-10-09
-show: Build Weekly Roundup
-description: "2025 Week #41"
+description: "Build Weekly Roundup - 2025 Week #41"
tags:
- macro
posse:
title: Debasement Awakening
date: 2025-10-16
modified: 2025-10-16
-show: Build Weekly Roundup
-description: "2025 Week #42"
+description: "Build Weekly Roundup - 2025 Week #42"
tags:
- macro
posse:
title: Control the World
date: 2025-10-24
modified: 2025-10-24
-show: Build Weekly Roundup
-description: "2025 Week #43"
+description: "Build Weekly Roundup - 2025 Week #43"
tags:
- macro
- bitcoin
title: Caribbean Chaos
date: 2025-10-30
modified: 2025-10-30
-show: Build Weekly Roundup
-description: "2025 Week #44"
+description: "Build Weekly Roundup - 2025 Week #44"
tags:
- macro
posse:
title: NYC Says Cuo"no"
date: 2025-11-06
modified: 2025-11-06
-show: Build Weekly Roundup
-description: "2025 Week #45"
+description: "Build Weekly Roundup - 2025 Week #45"
tags:
- macro
- bitcoin
title: Likely Suffer a Complete Loss
date: 2025-11-13
modified: 2025-11-13
-show: Build Weekly Roundup
-description: "2025 Week #46"
+description: "Build Weekly Roundup - 2025 Week #46"
tags:
- macro
- bitcoin
title: Reigning In the Offshore Dollar
date: 2025-11-21
modified: 2025-11-21
-show: Build Weekly Roundup
-description: "2025 Week #47"
+description: "Build Weekly Roundup - 2025 Week #47"
tags:
- macro
- bitcoin
title: eSLR Tilts the Scale
date: 2025-11-28
modified: 2025-12-01
-show: Build Weekly Roundup
-description: "2025 Week #48"
+description: "Build Weekly Roundup - 2025 Week #48"
tags:
- ai
- macro
title: ECB Urges Gold Rethink
date: 2025-12-04
modified: 2025-12-04
-show: Build Weekly Roundup
-description: "2025 Week #49"
+description: "Build Weekly Roundup - 2025 Week #49"
tags:
- ai
- macro
title: Trade Route Control
date: 2025-12-12
modified: 2025-12-12
-show: Build Weekly Roundup
-description: "2025 Week #50"
+description: "Build Weekly Roundup - 2025 Week #50"
tags:
- macro
posse:
---
-type: movie
+category: movie
title: 28 Days Later
date: 2025-06-06
modified: 2025-12-07
---
-type: movie
+category: movie
title: A Complete Unknown
date: 2025-06-04
modified: 2025-06-04
---
-type: movie
+category: movie
title: Bugonia
date: 2025-10-30
modified: 2025-11-01
---
-type: movie
+category: movie
title: Burning
date: 2025-02-28
modified: 2025-03-02
---
-type: movie
+category: movie
title: Casino Royale
date: 2025-12-14
modified: 2025-12-16
---
-type: movie
+category: movie
title: Caught Stealing
date: 2025-09-12
modified: 2025-09-13
---
-type: movie
+category: movie
title: Challengers
date: 2024-12-24
modified: 2024-12-26
---
-type: movie
+category: movie
title: Conclave
date: 2025-03-02
modified: 2025-03-18
---
-type: movie
+category: movie
title: Death Becomes Her
date: 2025-10-02
modified: 2025-10-07
---
-type: movie
+category: movie
title: "Den of Thieves: Pantera"
date: 2025-10-18
modified: 2025-10-19
---
-type: movie
+category: movie
title: "F1: The Movie"
date: 2025-10-16
modified: 2025-10-17
---
-type: movie
+category: movie
title: Fallen Angels
date: 2025-08-02
modified: 2025-10-24
---
-type: movie
+category: movie
title: The Hunchback of Notre Dame
date: 2025-03-04
modified: 2025-03-22
---
-type: movie
+category: movie
title: It's What's Inside
date: 2025-03-01
modified: 2025-03-02
---
-type: movie
+category: movie
title: Jurassic Park
date: 2025-09-10
modified: 2025-09-13
---
-type: movie
+category: movie
title: Materialists
date: 2025-08-14
modified: 2025-08-15
---
-type: movie
+category: movie
title: Mulholland Drive
date: 2025-12-06
modified: 2025-12-07
---
-type: movie
+category: movie
title: Nosferatu
date: 2025-09-09
modified: 2025-09-13
---
-type: movie
+category: movie
title: One Battle After Another
date: 2025-10-01
modified: 2025-10-04
---
-type: movie
+category: movie
title: Perfect Days
date: 2025-02-15
modified: 2025-02-15
---
-type: movie
+category: movie
title: Problemista
date: 2025-11-04
modified: 2025-11-06
---
-type: movie
+category: movie
title: Sinners
date: 2025-05-02
modified: 2025-05-02
---
-type: movie
+category: movie
title: "Star Wars: Episode III - Revenge of the Sith"
date: 2025-09-05
modified: 2025-09-07
---
-type: movie
+category: movie
title: Subservience
date: 2024-12-25
modified: 2024-12-26
---
-type: movie
+category: movie
title: Superman
date: 2025-10-04
modified: 2025-10-04
---
-type: movie
+category: movie
title: The Apprentice
date: 2025-09-06
modified: 2025-09-07
---
-type: movie
+category: movie
title: The Boy and the Heron
date: 2024-12-21
modified: 2024-12-23
---
-type: movie
+category: movie
title: The Godfather
date: 2025-11-16
modified: 2025-11-19
---
-type: movie
+category: movie
title: "The Lord of the Rings: The Fellowship of the Ring"
date: 2025-11-26
modified: 2025-11-30
---
-type: movie
+category: movie
title: Wake Up Dead Man
date: 2025-12-15
modified: 2025-12-16
---
-type: movie
+category: movie
title: Warfare
date: 2025-04-11
modified: 2025-04-11
---
-type: movie
+category: movie
title: "Wicked: For Good"
date: 2025-11-25
modified: 2025-11-25
---
-type: movie
+category: movie
title: Wicked
date: 2024-11-29
modified: 2025-06-04
"@fontsource-variable/source-serif-4": "^5.2.9",
"@iconify-json/mdi": "^1.2.3",
"@vercel/og": "^0.8.5",
- "astro": "5.16.3",
+ "astro": "5.16.5",
"astro-icon": "^1.1.5",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3",
"recharts": "^3.5.1"
},
"devDependencies": {
- "@types/bun": "^1.3.3",
+ "@types/bun": "^1.3.4",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3"
}
---
-import { siteAuthor, socials } from '@utils/globals.ts';
+import { siteAuthor, socials } from '@utils/globals';
import { Icon } from 'astro-icon/components';
const currentYear = new Date().getFullYear();
---
-import { siteAuthor } from '@utils/globals.ts';
+import { siteAuthor } from '@utils/globals';
interface Props {
publishedTime: string
---
import { getImage } from 'astro:assets';
-import { siteAuthor, siteTitle, socials } from '@utils/globals.ts';
+import { siteAuthor, siteTitle, socials } from '@utils/globals';
import FavIcon from '@assets/favicon.ico';
interface Props {
---
-import { type InferEntrySchema } from 'astro:content';
-import formatDate from '@utils/formatDate.ts';
+import { type SiteEntrySchema } from '@utils/globals';
+import formatDate from '@utils/formatDate';
import Rating from '@components/ui/rating.astro';
interface Props {
- entryData: InferEntrySchema<'articles' | 'podcasts' | 'reviews'>
+ entryData: SiteEntrySchema
}
const { entryData } = Astro.props;
<details>
<summary>Metadata</summary>
<ul>
- {'show' in entryData && entryData.show && <li>Show: {entryData.show}</li>}
{entryData.description && <li>Description: {entryData.description}</li>}
{'rating' in entryData && entryData.rating && <li>Rating: <Rating rating={entryData.rating} /></li>}
<li>Published: <time datetime={publishedDate}>{publishedDate}</time></li>
---
-import { menuItems } from '@utils/globals.ts';
+import { menuItems } from '@utils/globals';
---
<nav>
<menu>
const rating = review.data.rating;
ratingCounts[rating]++;
});
-const chartData = Object.keys(ratingCounts).map(rating => ({
+const chartData = Object.entries(ratingCounts).map(([rating, count]) => ({
name: rating,
- count: ratingCounts[parseInt(rating)]
+ count: count
}));
---
<Chart chartData={chartData} client:load />
\ No newline at end of file
---
-import generateStarRating from '@utils/generateStarRating.ts';
+import generateStarRating from '@utils/generateStarRating';
interface Props {
rating: number
const { rating } = Astro.props;
const starRating = generateStarRating(rating);
---
-<Fragment>
- {starRating}
-</Fragment>
\ No newline at end of file
+
+{starRating}
\ No newline at end of file
const { title, videoURL } = Astro.props;
const url = new URL(videoURL);
-const videoID = url.searchParams.get("v");
+const videoID = url.searchParams.get('v');
+if (videoID == null) {
+ throw new Error('URL does not contain a v parameter.');
+}
---
<iframe
-import { defineCollection, z } from 'astro:content';
+import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
+import { z } from 'astro/zod';
+
+const baseSchema = z.object({
+ title: z.string(),
+ description: z.string().optional(),
+ date: z.coerce.date(),
+ modified: z.coerce.date().optional(),
+ tags: z.array(z.string()).optional(),
+ posse: z.record(
+ z.string(),
+ z.string().url()
+ ).optional()
+});
const articles = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: 'content/articles' }),
- schema: z.object({
- title: z.string(),
- description: z.string(),
- date: z.coerce.date(),
- modified: z.coerce.date().optional(),
- tags: z.array(z.string()).optional(),
- posse: z.record(z.string(), z.string().url()).optional()
- })
+ schema: baseSchema
});
-const PodcastType = z.enum([
- "audio",
- "video"
-])
-
const podcasts = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: 'content/podcasts' }),
- schema: z.object({
- type: PodcastType,
- title: z.string(),
- show: z.string(),
- description: z.string(),
- date: z.coerce.date(),
- modified: z.coerce.date().optional(),
- tags: z.array(z.string()).optional(),
- posse: z.record(z.string(), z.string().url()).optional()
+ schema: baseSchema.extend({
+ type: z.enum([
+ 'audio',
+ 'video'
+ ]),
+ enclosure: z.object({
+ url: z.string().url(),
+ length: z.number(),
+ type: z.string()
+ }).optional()
})
});
-const ReviewType = z.enum([
- "game",
- "movie",
- "music",
- "show"
-])
-
const reviews = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: 'content/reviews' }),
- schema: z.object({
- type: ReviewType,
- title: z.string(),
- description: z.string().optional(),
+ schema: baseSchema.extend({
rating: z.number().gt(0).lte(5).step(1),
- date: z.coerce.date(),
- modified: z.coerce.date().optional(),
- tags: z.array(z.string()).optional(),
- posse: z.record(z.string(), z.string().url()).optional()
+ category: z.enum([
+ 'game',
+ 'movie',
+ 'music',
+ 'show'
+ ])
})
});
-export const collections = { articles, podcasts, reviews };
\ No newline at end of file
+export const collections = { articles, podcasts, reviews }
\ No newline at end of file
---
import { type CollectionEntry, getCollection, render } from 'astro:content';
-import formatDate from '@utils/formatDate.ts';
+import formatDate from '@utils/formatDate';
import Article from '@layouts/article.astro';
import Metadata from '@components/metadata.astro';
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';
-import formatDate from '@utils/formatDate.ts';
-import generateOpenGraphImage from '@utils/generateOpenGraphImage.ts';
+import formatDate from '@utils/formatDate';
+import generateOpenGraphImage from '@utils/generateOpenGraphImage';
export async function getStaticPaths() {
const articles = await getCollection('articles');
---
import { getCollection } from 'astro:content';
-import formatDate from '@utils/formatDate.ts';
import Base from '@layouts/base.astro';
-import sortByDate from '@utils/sortByDate.ts';
+import sortByDate from '@utils/sortByDate';
import generateContentUrl from '@utils/generateContentUrl';
const articles = await getCollection('articles');
<h2>Articles</h2>
<dl>
{sortedArticles.map((article) => {
- const date = formatDate(article.data.date);
return (
<dt><a href={generateContentUrl(article)}>{article.data.title}</a></dt>
- <dd>
- {article.data.description}
- </dd>
+ <dd>{article.data.description}</dd>
)
})}
</dl>
import { getContainerRenderer as getMDXRenderer } from '@astrojs/mdx';
import rss, { type RSSFeedItem } from '@astrojs/rss';
-import { siteDescription, siteTitle } from '@utils/globals.ts';
+import { siteDescription, siteTitle } from '@utils/globals';
import { type APIContext } from 'astro';
import { getCollection, render } from 'astro:content';
import { loadRenderers } from 'astro:container';
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import generateContentUrl from '@utils/generateContentUrl';
-import generateStarRating from '@utils/generateStarRating.ts';
+import generateStarRating from '@utils/generateStarRating';
export async function GET(context: APIContext) {
const articles = await getCollection('articles');
const { Content } = await render(item);
const content = await container.renderToString(Content);
const categories = (item.data.tags ?? []).concat(item.collection)
- if ('type' in item.data) {
- categories.push(item.data.type);
- }
- if ('show' in item.data) {
- categories.push(item.data.show);
+ if ('category' in item.data) {
+ categories.push(item.data.category);
}
- var description: string;
+ let title: string;
+ let description: string;
switch (item.collection) {
case 'reviews':
const starRating = generateStarRating(item.data.rating);
- description = starRating
+ title = `${item.data.title} - ${starRating}`;
+ description = starRating;
break;
default:
- description = item.data.description;
+ title = item.data.title;
+ description = item.data.description ?? '';
break;
}
feedItems.push({
- title: item.data.title,
+ title: title,
link: link,
pubDate: item.data.date,
description: description,
---
import Base from '@layouts/base.astro';
-import Callout from '@components/ui/callout.astro';
const contributions = [
{
<Base title="Home" description="Personal site of Cameron Otsuka, Head of Data and Analytics at Build Asset Management. Writing on Bitcoin, cryptography, privacy, security, and technology.">
<section>
- <p>I am <strong>Head of Data and Analytics</strong> at Build Asset Management, where I've helped launch a <a href="https://buildbitcoin.com/">private credit fund</a> investing into over-collateralized bitcoin-backed loans, a <a href="https://bfix.fund/">fixed income ETF</a> and related vehicles, and built the internal tech stack that glues everything together.</p>
- <Callout level="info">
- Some topics currently holding my interest: data, analytics, economics, <a href="/bitcoin/">Bitcoin</a>, cryptography, privacy, security, urbanism, skiing …
- </Callout>
+ <p>I am <strong>Head of Data and Analytics</strong> at Build Asset Management, where I've helped launch a <a href="https://buildbitcoin.com/">private credit fund</a> investing into over-collateralized <a href="/bitcoin/">bitcoin</a>-backed loans, a <a href="https://bfix.fund/">fixed income ETF</a> and related vehicles, and built the internal tech stack that glues everything together.</p>
</section>
<section>
<h2>Contributions</h2>
import { type CollectionEntry, getCollection, render } from 'astro:content';
import Article from '@layouts/article.astro';
import Metadata from '@components/metadata.astro';
-import createSlug from '@utils/createSlug.ts';
interface Props {
podcast: CollectionEntry<'podcasts'>
export async function getStaticPaths() {
const podcasts = await getCollection('podcasts');
return podcasts.map(podcast => ({
- params: { show: createSlug(podcast.data.show), id: podcast.id },
+ params: { id: podcast.id },
props: { podcast },
}));
}
---
<Article
title={podcast.data.title}
- description={`${podcast.data.show} - ${podcast.data.description}`}
+ description={podcast.data.description}
publishedTime={podcast.data.date.toISOString()}
modifiedTime={podcast.data.modified?.toISOString() ?? podcast.data.date.toISOString()}
tags={podcast.data.tags}
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';
-import createSlug from '@utils/createSlug.ts';
-import generateOpenGraphImage from '@utils/generateOpenGraphImage.ts';
+import generateOpenGraphImage from '@utils/generateOpenGraphImage';
export async function getStaticPaths() {
const podcasts = await getCollection('podcasts');
return podcasts.map(podcast => ({
- params: { show: createSlug(podcast.data.show), id: podcast.id },
+ params: { id: podcast.id },
props: { podcast },
}));
};
export const GET: APIRoute = async ({ props }) => {
return generateOpenGraphImage(
props.podcast.data.title,
- `${props.podcast.data.show} - ${props.podcast.data.description}`
+ props.podcast.data.description
)
};
\ No newline at end of file
---
import { getCollection } from 'astro:content';
-import formatDate from '@utils/formatDate.ts';
import Base from '@layouts/base.astro';
-import sortByDate from '@utils/sortByDate.ts';
+import sortByDate from '@utils/sortByDate';
import generateContentUrl from '@utils/generateContentUrl';
const podcasts = await getCollection('podcasts');
<Base title="Podcasts" description="Weekly podcast episodes covering capital markets, Bitcoin, economic policy, and geopolitical events. Join Cameron Otsuka in his podcast appearances.">
<h2>Podcasts</h2>
- <table>
- <thead>
- <tr>
- <th>Show</th>
- <th>Title</th>
- <th>Date</th>
- </tr>
- </thead>
- <tbody>
- {sortedPodcasts.map((podcast) => (
- <tr>
- <td>{podcast.data.show}</td>
- <td>
- <dl>
- <dt><a href={generateContentUrl(podcast)}>{podcast.data.title}</a></dt>
- <dd>{podcast.data.description}</dd>
- </dl>
- </td>
- <td>{formatDate(podcast.data.date)}</td>
- </tr>
- ))}
- </tbody>
- </table>
-</Base>
-
-<style>
- table {
- width: 100%;
- }
- td:nth-child(2) {
- text-align: left;
- }
-</style>
\ No newline at end of file
+ <dl>
+ {sortedPodcasts.map((podcast) => {
+ return (
+ <dt><a href={generateContentUrl(podcast)}>{podcast.data.title}</a></dt>
+ <dd>{podcast.data.description}</dd>
+ )
+ })}
+ </dl>
+</Base>
\ No newline at end of file
export async function getStaticPaths() {
const reviews = await getCollection('reviews');
return reviews.map(review => ({
- params: { type: review.data.type, id: review.id },
+ params: { category: review.data.category, id: review.id },
props: { review },
}));
}
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';
-import generateOpenGraphImage from '@utils/generateOpenGraphImage.ts';
-import generateStarRating from '@utils/generateStarRating.ts';
+import generateOpenGraphImage from '@utils/generateOpenGraphImage';
+import generateStarRating from '@utils/generateStarRating';
export async function getStaticPaths() {
const reviews = await getCollection('reviews');
return reviews.map(review => ({
- params: { type: review.data.type, id: review.id },
+ params: { category: review.data.category, id: review.id },
props: { review },
}));
};
import Base from '@layouts/base.astro';
import RatingDistribution from '@components/ratingdistribution/component.astro';
import Rating from '@components/ui/rating.astro';
-import sortByDate from '@utils/sortByDate.ts';
+import sortByDate from '@utils/sortByDate';
import generateContentUrl from '@utils/generateContentUrl';
const reviews = await getCollection('reviews');
<table>
<thead>
<tr>
- <th>Type</th>
+ <th>Category</th>
<th>Title</th>
<th>Rating</th>
</tr>
<tbody>
{sortedReviews.map((review) => (
<tr>
- <td>{review.data.type}</td>
+ <td>{review.data.category}</td>
<td><a href={generateContentUrl(review)}>{review.data.title}</a></td>
<td><Rating rating={review.data.rating} /></td>
</tr>
-export default function (text: string): string {
+export default function createSlug(text: string): string {
return (
text
.trim()
-export default function (date: Date): string {
+export default function formatDate(date: Date): string {
const year = date.getUTCFullYear();
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
const day = String(date.getUTCDate()).padStart(2, '0');
-import formatDate from '@utils/formatDate.ts';
-import createSlug from '@utils/createSlug.ts';
-import { type CollectionEntry } from 'astro:content';
+import formatDate from '@utils/formatDate';
+import { type SiteCollectionEntry } from '@utils/globals';
-export default function (item: CollectionEntry<'articles' | 'podcasts' | 'reviews'>): string {
+export default function generateContentUrl(item: SiteCollectionEntry): string {
switch (item.collection) {
case 'articles':
return `/articles/${formatDate(item.data.date)}-${item.id}/`
case 'podcasts':
- return `/podcasts/${createSlug(item.data.show)}-${item.id}/`
+ return `/podcasts/${item.id}/`
case 'reviews':
- return `/reviews/${item.data.type}/${item.id}/`
+ return `/reviews/${item.data.category}/${item.id}/`
}
}
\ No newline at end of file
import { ImageResponse } from '@vercel/og';
-import { siteAuthor } from '@utils/globals.ts';
+import { siteAuthor } from '@utils/globals';
async function loadFont(fontName: string) {
- var url: string
+ let url: string
switch (fontName) {
case 'Public Sans Variable':
url = `https://cdn.jsdelivr.net/fontsource/fonts/public-sans@latest/latin-400-normal.ttf`;
}
const font = await fetch(url);
- if (font) {
+ if (font.ok) {
return await font.arrayBuffer();
}
throw new Error('failed to load font data');
}
-export default async function (title: string, description: string) {
+export default async function generateOpenGraphImage(title: string, description: string) {
const element = {
type: 'div',
props: {
-export default function (rating: number): string {
- const filledStars = '★'.repeat(rating);
- const unfilledStars = '☆'.repeat(5 - rating);
- return filledStars + unfilledStars;
+export default function generateStarRating(rating: number): string {
+ if (Number.isInteger(rating) && rating >= 1 && rating <= 5) {
+ const filledStars = '★'.repeat(rating);
+ const unfilledStars = '☆'.repeat(5 - rating);
+ return filledStars + unfilledStars;
+ } else {
+ throw new Error('rating is not an integer between 1 and 5');
+ }
}
\ No newline at end of file
+import type { CollectionEntry, InferEntrySchema } from 'astro:content';
+
export const siteTitle = "Cameron Otsuka";
export const siteDescription = "Cameron Otsuka's personal site featuring Bitcoin analysis, capital market insights, and thoughtful commentary on technology, privacy, and culture."
export const siteAuthor = {
"name": "Cameron Otsuka",
"email": "cameron@otsuka.haus"
}
+export type SiteCollectionEntry = CollectionEntry<'articles' | 'podcasts' | 'reviews'>;
+export type SiteEntrySchema = InferEntrySchema<'articles' | 'podcasts' | 'reviews'>;
export const menuItems: {title: string, url: string}[] = [
{ "title": "Articles", "url": "/articles" },
-import { type CollectionEntry } from 'astro:content';
+import { type SiteCollectionEntry } from '@utils/globals';
-export default function (items: CollectionEntry<'articles' | 'podcasts' | 'reviews'>[]): CollectionEntry<'articles' | 'podcasts' | 'reviews'>[] {
+export default function sortByDate(items: SiteCollectionEntry[]): SiteCollectionEntry[] {
return items.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
}
\ No newline at end of file