First demo
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -22,3 +22,6 @@ pnpm-debug.log*
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
|
||||
# vscode
|
||||
*.code-workspace
|
||||
31
Dockerfile
Normal file
31
Dockerfile
Normal file
@ -0,0 +1,31 @@
|
||||
# Base image
|
||||
FROM oven/bun:1 AS base
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies with bun
|
||||
FROM base AS deps
|
||||
COPY package.json bun.lock* ./
|
||||
RUN bun install --no-save --frozen-lockfile
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
RUN bun run build -d
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3000
|
||||
|
||||
# Copy built static site + server
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY server.ts ./server.ts
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["bun", "run", "server.ts"]
|
||||
@ -1,5 +1,13 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({});
|
||||
export default defineConfig({
|
||||
output: "static",
|
||||
|
||||
vite: {
|
||||
plugins: [tailwindcss()]
|
||||
}
|
||||
});
|
||||
100
bun.lock
100
bun.lock
@ -4,7 +4,13 @@
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"astro": "^5.16.8",
|
||||
"simple-icons": "^16.5.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -133,8 +139,16 @@
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
||||
@ -203,6 +217,38 @@
|
||||
|
||||
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="],
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
||||
|
||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
@ -215,6 +261,8 @@
|
||||
|
||||
"@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="],
|
||||
|
||||
"@types/node": ["@types/node@25.0.6", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q=="],
|
||||
|
||||
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
@ -247,6 +295,8 @@
|
||||
|
||||
"boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
||||
|
||||
"camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
@ -323,6 +373,8 @@
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
|
||||
|
||||
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||
@ -351,6 +403,8 @@
|
||||
|
||||
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"h3": ["h3@1.15.4", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.2", "radix3": "^1.1.2", "ufo": "^1.6.1", "uncrypto": "^0.1.3" } }, "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ=="],
|
||||
|
||||
"hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="],
|
||||
@ -393,10 +447,36 @@
|
||||
|
||||
"is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
|
||||
|
||||
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
||||
|
||||
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
@ -589,6 +669,8 @@
|
||||
|
||||
"shiki": ["shiki@3.21.0", "", { "dependencies": { "@shikijs/core": "3.21.0", "@shikijs/engine-javascript": "3.21.0", "@shikijs/engine-oniguruma": "3.21.0", "@shikijs/langs": "3.21.0", "@shikijs/themes": "3.21.0", "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w=="],
|
||||
|
||||
"simple-icons": ["simple-icons@16.5.0", "", {}, "sha512-72nn0oHADKx6Hknu7q6M0vfL8LiCUMKABOHane2+4xdqaFBSHfNNBjuZioihiqVQMz7IvVle4NKAM0IlXvl/9A=="],
|
||||
|
||||
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
||||
|
||||
"smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="],
|
||||
@ -605,6 +687,10 @@
|
||||
|
||||
"svgo": ["svgo@4.0.0", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.4.1" }, "bin": "./bin/svgo.js" }, "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
|
||||
|
||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||
|
||||
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||
@ -629,6 +715,8 @@
|
||||
|
||||
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"unifont": ["unifont@0.7.1", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-0lg9M1cMYvXof8//wZBq6EDEfbwv4++t7+dYpXeS2ypaLuZJmUFYEwTm412/1ED/Wfo/wyzSu6kNZEr9hgRNfg=="],
|
||||
@ -691,6 +779,18 @@
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
17
docker-compose.yml
Normal file
17
docker-compose.yml
Normal file
@ -0,0 +1,17 @@
|
||||
services:
|
||||
serpkah:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
network: host
|
||||
# ports:
|
||||
# - 3000:3000
|
||||
networks:
|
||||
- external_network
|
||||
restart: always
|
||||
container_name: serpkah
|
||||
|
||||
networks:
|
||||
external_network:
|
||||
external: true
|
||||
|
||||
10
package.json
10
package.json
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "",
|
||||
"name": "serpkah-linktree",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
@ -9,6 +9,12 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.16.8"
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"astro": "^5.16.8",
|
||||
"simple-icons": "^16.5.0",
|
||||
"tailwindcss": "^4.1.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.5"
|
||||
}
|
||||
}
|
||||
BIN
public/avatar.png
Normal file
BIN
public/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
66
server.ts
Normal file
66
server.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { serve } from "bun";
|
||||
|
||||
const port = Number(process.env.PORT ?? "3000");
|
||||
|
||||
function contentType(pathname: string): string | undefined {
|
||||
if (pathname.endsWith(".html")) return "text/html; charset=utf-8";
|
||||
if (pathname.endsWith(".css")) return "text/css; charset=utf-8";
|
||||
if (pathname.endsWith(".js")) return "application/javascript; charset=utf-8";
|
||||
if (pathname.endsWith(".mjs")) return "application/javascript; charset=utf-8";
|
||||
if (pathname.endsWith(".json")) return "application/json; charset=utf-8";
|
||||
if (pathname.endsWith(".svg")) return "image/svg+xml";
|
||||
if (pathname.endsWith(".png")) return "image/png";
|
||||
if (pathname.endsWith(".jpg") || pathname.endsWith(".jpeg")) return "image/jpeg";
|
||||
if (pathname.endsWith(".webp")) return "image/webp";
|
||||
if (pathname.endsWith(".ico")) return "image/x-icon";
|
||||
if (pathname.endsWith(".woff")) return "font/woff";
|
||||
if (pathname.endsWith(".woff2")) return "font/woff2";
|
||||
if (pathname.endsWith(".txt")) return "text/plain; charset=utf-8";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const distDir = `${process.cwd()}/dist`;
|
||||
|
||||
serve({
|
||||
port,
|
||||
async fetch(req) {
|
||||
const url = new URL(req.url);
|
||||
|
||||
// Normalize paths
|
||||
let pathname = decodeURIComponent(url.pathname);
|
||||
|
||||
// Default document
|
||||
if (pathname === "/") pathname = "/index.html";
|
||||
|
||||
const filePath = `${distDir}${pathname}`;
|
||||
|
||||
// Try direct file first
|
||||
let file = Bun.file(filePath);
|
||||
if (!(await file.exists())) {
|
||||
// If request is for a "route", serve index.html (SPA-ish fallback)
|
||||
// Astro static pages typically exist as real files, but this fallback helps
|
||||
// if you add client-side routing later.
|
||||
file = Bun.file(`${distDir}/index.html`);
|
||||
if (!(await file.exists())) {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
const headers = new Headers();
|
||||
const ct = contentType(pathname);
|
||||
if (ct) headers.set("Content-Type", ct);
|
||||
|
||||
// Cache policy:
|
||||
// - HTML: no-cache (so updates show quickly)
|
||||
// - assets: cache long (Astro fingerprints assets by default)
|
||||
if (pathname.endsWith(".html")) {
|
||||
headers.set("Cache-Control", "no-cache");
|
||||
} else {
|
||||
headers.set("Cache-Control", "public, max-age=31536000, immutable");
|
||||
}
|
||||
|
||||
return new Response(file, { headers });
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Serving dist on http://0.0.0.0:${port}`);
|
||||
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="115" height="48"><path fill="#17191E" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="url(#a)" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="#17191E" d="M.02 30.31s4.02-1.95 8.05-1.95l3.04-9.4c.11-.45.44-.76.82-.76.37 0 .7.31.82.76l3.04 9.4c4.77 0 8.05 1.95 8.05 1.95L17 11.71c-.2-.56-.53-.91-.98-.91H7.83c-.44 0-.76.35-.97.9L.02 30.31Zm42.37-5.97c0 1.64-2.05 2.62-4.88 2.62-1.85 0-2.5-.45-2.5-1.41 0-1 .8-1.49 2.65-1.49 1.67 0 3.09.03 4.73.23v.05Zm.03-2.04a21.37 21.37 0 0 0-4.37-.36c-5.32 0-7.82 1.25-7.82 4.18 0 3.04 1.71 4.2 5.68 4.2 3.35 0 5.63-.84 6.46-2.92h.14c-.03.5-.05 1-.05 1.4 0 1.07.18 1.16 1.06 1.16h4.15a16.9 16.9 0 0 1-.36-4c0-1.67.06-2.93.06-4.62 0-3.45-2.07-5.64-8.56-5.64-2.8 0-5.9.48-8.26 1.19.22.93.54 2.83.7 4.06 2.04-.96 4.95-1.37 7.2-1.37 3.11 0 3.97.71 3.97 2.15v.57Zm11.37 3c-.56.07-1.33.07-2.12.07-.83 0-1.6-.03-2.12-.1l-.02.58c0 2.85 1.87 4.52 8.45 4.52 6.2 0 8.2-1.64 8.2-4.55 0-2.74-1.33-4.09-7.2-4.39-4.58-.2-4.99-.7-4.99-1.28 0-.66.59-1 3.65-1 3.18 0 4.03.43 4.03 1.35v.2a46.13 46.13 0 0 1 4.24.03l.02-.55c0-3.36-2.8-4.46-8.2-4.46-6.08 0-8.13 1.49-8.13 4.39 0 2.6 1.64 4.23 7.48 4.48 4.3.14 4.77.62 4.77 1.28 0 .7-.7 1.03-3.71 1.03-3.47 0-4.35-.48-4.35-1.47v-.13Zm19.82-12.05a17.5 17.5 0 0 1-6.24 3.48c.03.84.03 2.4.03 3.24l1.5.02c-.02 1.63-.04 3.6-.04 4.9 0 3.04 1.6 5.32 6.58 5.32 2.1 0 3.5-.23 5.23-.6a43.77 43.77 0 0 1-.46-4.13c-1.03.34-2.34.53-3.78.53-2 0-2.82-.55-2.82-2.13 0-1.37 0-2.65.03-3.84 2.57.02 5.13.07 6.64.11-.02-1.18.03-2.9.1-4.04-2.2.04-4.65.07-6.68.07l.07-2.93h-.16Zm13.46 6.04a767.33 767.33 0 0 1 .07-3.18H82.6c.07 1.96.07 3.98.07 6.92 0 2.95-.03 4.99-.07 6.93h5.18c-.09-1.37-.11-3.68-.11-5.65 0-3.1 1.26-4 4.12-4 1.33 0 2.28.16 3.1.46.03-1.16.26-3.43.4-4.43-.86-.25-1.81-.41-2.96-.41-2.46-.03-4.26.98-5.1 3.38l-.17-.02Zm22.55 3.65c0 2.5-1.8 3.66-4.64 3.66-2.81 0-4.61-1.1-4.61-3.66s1.82-3.52 4.61-3.52c2.82 0 4.64 1.03 4.64 3.52Zm4.71-.11c0-4.96-3.87-7.18-9.35-7.18-5.5 0-9.23 2.22-9.23 7.18 0 4.94 3.49 7.59 9.21 7.59 5.77 0 9.37-2.65 9.37-7.6Z"/><defs><linearGradient id="a" x1="6.33" x2="19.43" y1="40.8" y2="34.6" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1024" fill="none"><path fill="url(#a)" fill-rule="evenodd" d="M-217.58 475.75c91.82-72.02 225.52-29.38 341.2-44.74C240 415.56 372.33 315.14 466.77 384.9c102.9 76.02 44.74 246.76 90.31 366.31 29.83 78.24 90.48 136.14 129.48 210.23 57.92 109.99 169.67 208.23 155.9 331.77-13.52 121.26-103.42 264.33-224.23 281.37-141.96 20.03-232.72-220.96-374.06-196.99-151.7 25.73-172.68 330.24-325.85 315.72-128.6-12.2-110.9-230.73-128.15-358.76-12.16-90.14 65.87-176.25 44.1-264.57-26.42-107.2-167.12-163.46-176.72-273.45-10.15-116.29 33.01-248.75 124.87-320.79Z" clip-rule="evenodd" style="opacity:.154"/><path fill="url(#b)" fill-rule="evenodd" d="M1103.43 115.43c146.42-19.45 275.33-155.84 413.5-103.59 188.09 71.13 409 212.64 407.06 413.88-1.94 201.25-259.28 278.6-414.96 405.96-130 106.35-240.24 294.39-405.6 265.3-163.7-28.8-161.93-274.12-284.34-386.66-134.95-124.06-436-101.46-445.82-284.6-9.68-180.38 247.41-246.3 413.54-316.9 101.01-42.93 207.83 21.06 316.62 6.61Z" clip-rule="evenodd" style="opacity:.154"/><defs><linearGradient id="b" x1="373" x2="1995.44" y1="1100" y2="118.03" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient><linearGradient id="a" x1="107.37" x2="1130.66" y1="1993.35" y2="1026.31" gradientUnits="userSpaceOnUse"><stop stop-color="#3245FF"/><stop offset="1" stop-color="#BC52EE"/></linearGradient></defs></svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
120
src/components/LinkCard.astro
Normal file
120
src/components/LinkCard.astro
Normal file
@ -0,0 +1,120 @@
|
||||
---
|
||||
import { resolveIcon } from '../lib/icons';
|
||||
import type { LinkItem } from '../lib/types';
|
||||
|
||||
const { item } = Astro.props as { item: LinkItem };
|
||||
|
||||
const icon = resolveIcon(item.icon);
|
||||
const brand = item.brandColor ?? icon.hex ?? '#ffffff';
|
||||
|
||||
// Heuristic: determine if brand color is "dark" using relative luminance.
|
||||
// Supports #RGB and #RRGGBB only (which matches simple-icons + your overrides).
|
||||
function hexToRgb(hex: string) {
|
||||
const h = hex.trim().replace('#', '');
|
||||
const full =
|
||||
h.length === 3
|
||||
? h
|
||||
.split('')
|
||||
.map((c) => c + c)
|
||||
.join('')
|
||||
: h;
|
||||
if (full.length !== 6) return null;
|
||||
const r = parseInt(full.slice(0, 2), 16);
|
||||
const g = parseInt(full.slice(2, 4), 16);
|
||||
const b = parseInt(full.slice(4, 6), 16);
|
||||
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return null;
|
||||
return { r, g, b };
|
||||
}
|
||||
|
||||
function srgbToLinear(c: number) {
|
||||
const v = c / 255;
|
||||
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
function luminance(hex: string) {
|
||||
const rgb = hexToRgb(hex);
|
||||
if (!rgb) return 1; // if unknown, treat as bright
|
||||
const R = srgbToLinear(rgb.r);
|
||||
const G = srgbToLinear(rgb.g);
|
||||
const B = srgbToLinear(rgb.b);
|
||||
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
||||
}
|
||||
|
||||
const L = luminance(brand);
|
||||
const autoForeground = L < 0.22 ? '#ffffff' : L > 0.72 ? '#ffffff' : brand;
|
||||
const foreground = item.iconForeground ?? autoForeground;
|
||||
|
||||
const subtitle = item.subtitle?.trim();
|
||||
|
||||
// Stronger tint for dark brands so the tile still reads as “brand”
|
||||
const tintPct = L < 0.22 ? 34 : L > 0.72 ? 22 : 28;
|
||||
|
||||
const borderPct = L < 0.22 ? 65 : L > 0.72 ? 50 : 55;
|
||||
|
||||
const tileBg = `color-mix(in srgb, ${brand} ${tintPct}%, transparent)`;
|
||||
const tileBorder = `color-mix(in srgb, ${brand} ${borderPct}%, rgba(255,255,255,0.15))`;
|
||||
---
|
||||
|
||||
<a
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
class="group flex items-center gap-5 rounded-2xl border border-white/10 bg-white/[0.06] px-5 py-4 transition
|
||||
hover:-translate-y-0.5 hover:border-white/20 hover:bg-white/[0.09]
|
||||
focus:outline-none focus-visible:ring-2 focus-visible:ring-white/30"
|
||||
>
|
||||
<span
|
||||
class="relative flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl border"
|
||||
style={`--brand:${brand}; background:${tileBg}; border-color:${tileBorder};`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span class="h-8 w-8" style={`color: ${foreground};`} set:html={icon.svg} />
|
||||
<span
|
||||
class="pointer-events-none absolute inset-0 rounded-2xl"
|
||||
style="box-shadow: inset 0 1px 0 rgba(255,255,255,0.25);"></span>
|
||||
</span>
|
||||
|
||||
<span class="min-w-0 flex-1">
|
||||
<span
|
||||
class="block truncate text-[15px] font-semibold tracking-tight text-white"
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
{
|
||||
subtitle ? (
|
||||
<span class="mt-0.5 block truncate text-sm text-white/65">
|
||||
{subtitle}
|
||||
</span>
|
||||
) : null
|
||||
}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="grid h-9 w-9 place-items-center rounded-xl border border-white/10 bg-white/[0.04] text-white/70 transition
|
||||
group-hover:border-white/20 group-hover:bg-white/[0.06] group-hover:text-white/85"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M9 18l6-6-6-6"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
/* Ensure injected SVG inherits currentColor */
|
||||
:global(svg) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
:global(svg path) {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
@ -1,210 +0,0 @@
|
||||
---
|
||||
import astroLogo from '../assets/astro.svg';
|
||||
import background from '../assets/background.svg';
|
||||
---
|
||||
|
||||
<div id="container">
|
||||
<img id="background" src={background.src} alt="" fetchpriority="high" />
|
||||
<main>
|
||||
<section id="hero">
|
||||
<a href="https://astro.build"
|
||||
><img src={astroLogo.src} width="115" height="48" alt="Astro Homepage" /></a
|
||||
>
|
||||
<h1>
|
||||
To get started, open the <code><pre>src/pages</pre></code> directory in your project.
|
||||
</h1>
|
||||
<section id="links">
|
||||
<a class="button" href="https://docs.astro.build">Read our docs</a>
|
||||
<a href="https://astro.build/chat"
|
||||
>Join our Discord <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M107.7 8.07A105.15 105.15 0 0 0 81.47 0a72.06 72.06 0 0 0-3.36 6.83 97.68 97.68 0 0 0-29.11 0A72.37 72.37 0 0 0 45.64 0a105.89 105.89 0 0 0-26.25 8.09C2.79 32.65-1.71 56.6.54 80.21a105.73 105.73 0 0 0 32.17 16.15 77.7 77.7 0 0 0 6.89-11.11 68.42 68.42 0 0 1-10.85-5.18c.91-.66 1.8-1.34 2.66-2a75.57 75.57 0 0 0 64.32 0c.87.71 1.76 1.39 2.66 2a68.68 68.68 0 0 1-10.87 5.19 77 77 0 0 0 6.89 11.1 105.25 105.25 0 0 0 32.19-16.14c2.64-27.38-4.51-51.11-18.9-72.15ZM42.45 65.69C36.18 65.69 31 60 31 53s5-12.74 11.43-12.74S54 46 53.89 53s-5.05 12.69-11.44 12.69Zm42.24 0C78.41 65.69 73.25 60 73.25 53s5-12.74 11.44-12.74S96.23 46 96.12 53s-5.04 12.69-11.43 12.69Z"
|
||||
></path></svg
|
||||
>
|
||||
</a>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<a href="https://astro.build/blog/astro-5/" id="news" class="box">
|
||||
<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||
><path
|
||||
d="M24.667 12c1.333 1.414 2 3.192 2 5.334 0 4.62-4.934 5.7-7.334 12C18.444 28.567 18 27.456 18 26c0-4.642 6.667-7.053 6.667-14Zm-5.334-5.333c1.6 1.65 2.4 3.43 2.4 5.333 0 6.602-8.06 7.59-6.4 17.334C13.111 27.787 12 25.564 12 22.666c0-4.434 7.333-8 7.333-16Zm-6-5.333C15.111 3.555 16 5.556 16 7.333c0 8.333-11.333 10.962-5.333 22-3.488-.774-6-4-6-8 0-8.667 8.666-10 8.666-20Z"
|
||||
fill="#111827"></path></svg
|
||||
>
|
||||
<h2>What's New in Astro 5.0?</h2>
|
||||
<p>
|
||||
From content layers to server islands, click to learn more about the new features and
|
||||
improvements in Astro 5.0
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
filter: blur(100px);
|
||||
}
|
||||
|
||||
#container {
|
||||
font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
main {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#hero {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 22px;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
#links {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
#links a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
color: #111827;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
#links a:hover {
|
||||
color: rgb(78, 80, 86);
|
||||
}
|
||||
|
||||
#links a svg {
|
||||
height: 1em;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
#links a.button {
|
||||
color: white;
|
||||
background: linear-gradient(83.21deg, #3245ff 0%, #bc52ee 100%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.12),
|
||||
inset 0 -2px 0 rgba(0, 0, 0, 0.24);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#links a.button:hover {
|
||||
color: rgb(230, 230, 230);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family:
|
||||
ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono',
|
||||
monospace;
|
||||
font-weight: normal;
|
||||
background: linear-gradient(14deg, #d83333 0%, #f041ff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 1em;
|
||||
font-weight: normal;
|
||||
color: #111827;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #4b5563;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.006em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
display: inline-block;
|
||||
background:
|
||||
linear-gradient(66.77deg, #f3cddd 0%, #f5cee7 100%) padding-box,
|
||||
linear-gradient(155deg, #d83333 0%, #f041ff 18%, #f5cee7 45%) border-box;
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.box {
|
||||
padding: 16px;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border-radius: 16px;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
#news {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
max-width: 300px;
|
||||
text-decoration: none;
|
||||
transition: background 0.2s;
|
||||
backdrop-filter: blur(50px);
|
||||
}
|
||||
|
||||
#news:hover {
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
|
||||
@media screen and (max-height: 368px) {
|
||||
#news {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
#container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#hero {
|
||||
display: block;
|
||||
padding-top: 10%;
|
||||
}
|
||||
|
||||
#links {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#links a.button {
|
||||
padding: 14px 18px;
|
||||
}
|
||||
|
||||
#news {
|
||||
right: 16px;
|
||||
left: 16px;
|
||||
bottom: 2.5rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
41
src/data/links.json
Normal file
41
src/data/links.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"profile": {
|
||||
"name": "Serpkah",
|
||||
"bio": "Bio oder sowas...",
|
||||
"avatar": "/avatar.png"
|
||||
},
|
||||
"sections": [
|
||||
{
|
||||
"title": "Social",
|
||||
"items": [
|
||||
{ "label": "Mastodon", "url": "https://example.com", "icon": "mastodon" },
|
||||
{ "label": "Bluesky", "url": "https://example.com", "icon": "bluesky" },
|
||||
{ "label": "VRChat", "url": "https://example.com", "icon": "vrchat", "brandColor": "#000000", "iconForeground": "#ffffff" },
|
||||
{ "label": "Fur Affinity", "url": "https://example.com", "icon": "furaffinity" },
|
||||
{ "label": "Barq", "url": "https://example.com", "icon": "barq", "brandColor": "#ff6a00" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Messaging",
|
||||
"items": [
|
||||
{ "label": "Telegram", "url": "https://example.com", "icon": "telegram" },
|
||||
{ "label": "Signal", "url": "https://example.com", "icon": "signal" },
|
||||
{ "label": "Matrix", "url": "https://example.com", "icon": "matrix", "iconForeground": "#ffffff" },
|
||||
{ "label": "iMessage", "url": "https://example.com", "icon": "imessage", "brandColor": "#34C759", "iconForeground": "#ffffff" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Self-hosted",
|
||||
"items": [
|
||||
{ "label": "Nextcloud", "url": "https://example.com", "icon": "nextcloud" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Contact",
|
||||
"items": [
|
||||
{ "label": "Email", "url": "mailto:you@example.com", "icon": "mail" },
|
||||
{ "label": "Website", "url": "https://example.com", "icon": "link" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
78
src/icons/barq.svg
Normal file
78
src/icons/barq.svg
Normal file
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="100.000000pt" height="100.000000pt" viewBox="0 0 100.000000 100.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M845 820 c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23
|
||||
20 -3 0 3 -9 13 -20z"/>
|
||||
<path d="M168 813 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
|
||||
<path d="M217 808 c-6 -4 104 -7 245 -8 141 0 259 3 261 7 6 10 -491 11 -506
|
||||
1z"/>
|
||||
<path d="M821 804 c12 -15 11 -16 -11 -10 -22 6 -23 5 -7 -5 13 -7 22 -8 29
|
||||
-1 11 11 3 32 -14 32 -6 0 -5 -7 3 -16z"/>
|
||||
<path d="M758 803 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
|
||||
<path d="M165 790 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/>
|
||||
<path d="M843 772 c-9 -5 -10 -18 -5 -38 4 -16 7 -92 7 -169 -3 -215 -2 -213
|
||||
-30 -240 -24 -25 -26 -25 -205 -25 l-180 0 -68 -42 c-86 -53 -102 -67 -95 -83
|
||||
3 -9 -7 -17 -29 -23 l-33 -8 27 -6 c15 -3 25 -1 22 3 -9 14 6 10 22 -6 17 -17
|
||||
44 -20 44 -5 0 5 -8 7 -17 4 -10 -3 4 8 30 25 26 18 52 29 58 26 5 -4 8 -1 7
|
||||
7 -2 7 5 12 14 12 10 -1 15 3 11 9 -4 6 0 8 11 4 10 -4 15 -3 11 3 -14 22 42
|
||||
30 219 30 114 0 187 4 191 10 4 6 11 7 17 4 7 -4 8 -2 4 5 -4 6 -3 18 3 24 6
|
||||
8 11 89 11 207 l2 194 -26 33 c-24 30 -25 33 -9 42 10 6 14 10 8 11 -5 0 -16
|
||||
-4 -22 -8z"/>
|
||||
<path d="M85 750 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0 -8
|
||||
-4 -11 -10z"/>
|
||||
<path d="M125 749 c-11 -16 -1 -19 13 -3 7 8 8 14 3 14 -5 0 -13 -5 -16 -11z"/>
|
||||
<path d="M890 720 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0 -4
|
||||
-4 -4 -10z"/>
|
||||
<path d="M98 592 c4 -169 9 -188 10 -47 1 72 -2 135 -6 142 -4 6 -6 -37 -4
|
||||
-95z"/>
|
||||
<path d="M118 583 c4 -185 9 -203 10 -35 0 83 -3 152 -6 152 -4 0 -6 -53 -4
|
||||
-117z"/>
|
||||
<path d="M180 539 l0 -70 46 3 c55 4 77 26 56 58 -6 11 -12 28 -12 38 0 27
|
||||
-20 42 -57 42 l-33 0 0 -71z m60 32 c0 -6 -4 -13 -10 -16 -5 -3 -10 1 -10 9 0
|
||||
9 5 16 10 16 6 0 10 -4 10 -9z m10 -56 c0 -8 -7 -15 -15 -15 -8 0 -15 7 -15
|
||||
15 0 8 7 15 15 15 8 0 15 -7 15 -15z"/>
|
||||
<path d="M336 563 c-38 -77 -41 -93 -17 -93 12 0 21 5 21 10 0 6 11 10 24 10
|
||||
14 0 28 -4 31 -10 3 -5 15 -10 25 -10 17 0 16 6 -12 70 -17 39 -35 70 -39 70
|
||||
-5 0 -19 -21 -33 -47z m44 -44 c0 -5 -7 -9 -15 -9 -9 0 -12 6 -9 15 6 15 24
|
||||
11 24 -6z"/>
|
||||
<path d="M460 540 c0 -56 3 -70 15 -70 10 0 15 10 15 28 l1 27 22 -27 c13 -16
|
||||
31 -28 41 -28 17 0 17 1 1 26 -12 19 -14 29 -6 37 31 31 -2 77 -55 77 l-34 0
|
||||
0 -70z m58 22 c2 -7 -3 -12 -12 -12 -9 0 -16 7 -16 16 0 17 22 14 28 -4z"/>
|
||||
<path d="M601 584 c-12 -15 -21 -34 -21 -44 0 -25 38 -70 60 -70 10 0 30 -7
|
||||
46 -14 15 -8 31 -13 36 -10 12 8 10 34 -4 34 -9 0 -9 3 0 12 19 19 14 76 -8
|
||||
98 -30 30 -83 27 -109 -6z m84 -20 c19 -20 16 -43 -7 -58 -35 -22 -74 27 -48
|
||||
59 16 19 35 19 55 -1z"/>
|
||||
<path d="M760 565 c0 -33 4 -45 15 -45 8 0 15 6 15 14 0 7 3 28 6 45 6 27 4
|
||||
31 -15 31 -18 0 -21 -6 -21 -45z"/>
|
||||
<path d="M760 486 c0 -10 9 -16 21 -16 24 0 21 23 -4 28 -10 2 -17 -3 -17 -12z"/>
|
||||
<path d="M102 390 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z"/>
|
||||
<path d="M126 355 c5 -29 5 -29 -14 -12 -14 13 -22 15 -27 7 -4 -6 -4 -14 -1
|
||||
-17 3 -4 6 0 6 7 0 7 9 1 21 -14 11 -14 18 -30 14 -36 -3 -5 0 -7 8 -4 8 3 18
|
||||
0 22 -6 5 -8 11 -7 22 2 12 10 17 9 28 -5 14 -19 32 -6 20 13 -8 13 -42 13
|
||||
-50 0 -7 -12 -25 -4 -25 11 0 6 5 7 10 4 6 -3 10 -1 10 5 0 7 -8 9 -22 5 -17
|
||||
-6 -20 -4 -15 9 4 9 2 26 -4 38 -8 20 -9 19 -3 -7z"/>
|
||||
<path d="M940 344 c0 -8 5 -12 10 -9 6 4 8 11 5 16 -9 14 -15 11 -15 -7z"/>
|
||||
<path d="M921 334 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
|
||||
<path d="M900 295 c-10 -12 -10 -15 4 -15 9 0 16 7 16 15 0 8 -2 15 -4 15 -2
|
||||
0 -9 -7 -16 -15z"/>
|
||||
<path d="M185 260 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0 -7
|
||||
-4 -4 -10z"/>
|
||||
<path d="M170 241 c0 -5 7 -12 16 -15 14 -5 15 -4 4 9 -14 17 -20 19 -20 6z"/>
|
||||
<path d="M210 208 c-3 -26 -1 -43 3 -39 5 5 7 26 5 47 l-3 39 -5 -47z"/>
|
||||
<path d="M805 230 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/>
|
||||
<path d="M171 203 c7 -12 15 -20 18 -17 3 2 -3 12 -13 22 -17 16 -18 16 -5 -5z"/>
|
||||
<path d="M865 210 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
|
||||
-8 -4 -11 -10z"/>
|
||||
<path d="M170 160 c0 -5 5 -10 10 -10 6 0 10 5 10 10 0 6 -4 10 -10 10 -5 0
|
||||
-10 -4 -10 -10z"/>
|
||||
<path d="M377 156 c-3 -8 0 -17 7 -19 10 -4 12 1 9 14 -6 23 -9 24 -16 5z"/>
|
||||
<path d="M265 100 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0 -7
|
||||
-4 -4 -10z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
75
src/icons/imessage.svg
Normal file
75
src/icons/imessage.svg
Normal file
@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="49.609371mm"
|
||||
height="49.609375mm"
|
||||
viewBox="0 0 49.609371 49.609375"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1">
|
||||
<linearGradient
|
||||
id="rect826_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-199.231"
|
||||
y1="295.18689"
|
||||
x2="-199.231"
|
||||
y2="349.70929"
|
||||
gradientTransform="matrix(2.7839,0,0,-2.7839,670.4032,1178.1656)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#0CBD2A"
|
||||
id="stop1" />
|
||||
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#5BF675"
|
||||
id="stop2" />
|
||||
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(98.689583,-2.9104166)">
|
||||
<g
|
||||
id="g963"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,-104.51042,-45.058541)">
|
||||
|
||||
<linearGradient
|
||||
id="linearGradient4"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-199.231"
|
||||
y1="295.18689"
|
||||
x2="-199.231"
|
||||
y2="349.70929"
|
||||
gradientTransform="matrix(2.7839,0,0,-2.7839,670.4032,1178.1656)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#0CBD2A"
|
||||
id="stop3" />
|
||||
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#5BF675"
|
||||
id="stop4" />
|
||||
|
||||
</linearGradient>
|
||||
|
||||
<path
|
||||
id="rect826"
|
||||
class="st0"
|
||||
d="m 63.3,181.3 h 104.9 c 22.8,0 41.3,18.5 41.3,41.3 v 104.9 c 0,22.8 -18.5,41.3 -41.3,41.3 H 63.3 C 40.5,368.8 22,350.3 22,327.5 V 222.6 c 0,-22.8 18.5,-41.3 41.3,-41.3 z"
|
||||
style="fill:url(#rect826_1_)" />
|
||||
|
||||
<path
|
||||
id="path922"
|
||||
class="st1"
|
||||
d="m 115.8,213.8 c -38,0 -68.8,25.7 -68.8,57.3 0,20.1 12.7,38.7 33.4,49.1 -2.7,6.1 -6.8,11.8 -12,16.8 10.2,-1.8 19.8,-5.5 28,-11 6.3,1.6 12.9,2.4 19.5,2.4 38,0 68.8,-25.7 68.8,-57.3 -0.1,-31.6 -30.9,-57.3 -68.9,-57.3 z"
|
||||
style="fill:#ffffff" />
|
||||
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
26
src/icons/vrchat.svg
Normal file
26
src/icons/vrchat.svg
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="100.000000pt" height="100.000000pt" viewBox="0 0 100.000000 100.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M79 911 l-29 -29 0 -267 c0 -289 3 -310 55 -333 18 -8 98 -12 253
|
||||
-12 l227 0 90 -112 c69 -86 96 -114 117 -116 44 -5 58 28 58 133 l0 91 29 11
|
||||
c61 22 66 44 69 293 4 268 0 303 -38 342 l-28 28 -387 0 -387 0 -29 -29z m797
|
||||
-19 c30 -20 34 -55 34 -290 0 -227 -1 -241 -21 -266 -14 -17 -30 -26 -49 -26
|
||||
l-29 0 -3 -112 c-2 -66 -7 -113 -13 -115 -6 -2 -51 49 -100 112 l-88 115 -237
|
||||
0 c-197 0 -239 3 -258 16 -22 15 -22 18 -22 276 0 229 2 264 17 280 15 17 40
|
||||
18 387 18 204 0 376 -4 382 -8z"/>
|
||||
<path d="M208 778 c-16 -12 -14 -25 33 -172 27 -88 55 -166 61 -173 13 -17 53
|
||||
-17 65 0 11 13 103 311 103 333 0 17 -39 29 -52 16 -6 -6 -27 -71 -48 -144
|
||||
l-37 -133 -12 45 c-64 238 -74 257 -113 228z"/>
|
||||
<path d="M517 783 c-4 -3 -7 -84 -7 -179 0 -165 1 -173 20 -179 36 -11 50 12
|
||||
50 81 0 57 2 64 20 64 14 0 29 -19 55 -70 39 -77 58 -92 83 -66 15 15 14 21
|
||||
-16 81 l-31 64 29 30 c51 50 37 141 -25 166 -34 15 -166 21 -178 8z m151 -62
|
||||
c14 -9 19 -61 8 -80 -4 -6 -27 -11 -52 -11 l-44 0 0 50 0 50 38 0 c20 0 43 -4
|
||||
50 -9z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@ -1,22 +1,38 @@
|
||||
---
|
||||
import '../styles/global.css';
|
||||
|
||||
const { title = 'Links' } = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>Astro Basics</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<body class="min-h-screen bg-neutral-950 text-neutral-50">
|
||||
<div class="pointer-events-none fixed inset-0">
|
||||
<div
|
||||
class="absolute -top-40 left-1/2 h-[520px] w-[520px] -translate-x-1/2 rounded-full bg-violet-500/15 blur-3xl"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="absolute top-24 right-[-120px] h-[420px] w-[420px] rounded-full bg-sky-500/10 blur-3xl"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main class="relative mx-auto w-full max-w-xl px-4 py-10 sm:py-14">
|
||||
<div
|
||||
class="rounded-3xl border border-white/10 bg-white/5 shadow-[0_18px_60px_rgba(0,0,0,0.45)] backdrop-blur"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<footer class="mt-6 text-center text-sm text-white/60">
|
||||
© {new Date().getFullYear()}
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
62
src/lib/icons.ts
Normal file
62
src/lib/icons.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import type { SimpleIcon } from "simple-icons";
|
||||
import * as SI from "simple-icons";
|
||||
|
||||
type ResolvedIcon = {
|
||||
title: string;
|
||||
svg: string; // full <svg ...>...</svg>
|
||||
hex?: string; // brand color, if known
|
||||
};
|
||||
|
||||
const customSvgs = import.meta.glob("../icons/*.svg", {
|
||||
as: "raw",
|
||||
eager: true
|
||||
}) as Record<string, string>;
|
||||
|
||||
function getCustomSvgBySlug(slug: string): string | undefined {
|
||||
const normalized = slug.trim().toLowerCase();
|
||||
const matchKey = Object.keys(customSvgs).find((k) => k.endsWith(`/${normalized}.svg`));
|
||||
return matchKey ? customSvgs[matchKey] : undefined;
|
||||
}
|
||||
|
||||
function getSimpleIconBySlug(slug: string): SimpleIcon | undefined {
|
||||
const pascal = slug
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/(^\w|-\w)/g, (m) => m.replace("-", "").toUpperCase());
|
||||
const key = `si${pascal}` as keyof typeof SI;
|
||||
return SI[key] as unknown as SimpleIcon | undefined;
|
||||
}
|
||||
|
||||
const FALLBACK: Record<string, ResolvedIcon> = {
|
||||
mail: {
|
||||
title: "Email",
|
||||
svg: `<svg viewBox="0 0 24 24" role="img" aria-label="Email"><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4-8 5L4 8V6l8 5 8-5v2z"/></svg>`
|
||||
},
|
||||
link: {
|
||||
title: "Link",
|
||||
svg: `<svg viewBox="0 0 24 24" role="img" aria-label="Link"><path d="M3.9 12a5 5 0 0 1 5-5h3v2h-3a3 3 0 1 0 0 6h3v2h-3a5 5 0 0 1-5-5zm7-1h4v2h-4v-2zm6.2-4h-3V5h3a5 5 0 0 1 0 10h-3v-2h3a3 3 0 1 0 0-6z"/></svg>`
|
||||
}
|
||||
};
|
||||
|
||||
export function resolveIcon(slug: string): ResolvedIcon {
|
||||
const normalized = slug.trim().toLowerCase();
|
||||
|
||||
// 1) Custom icon file wins
|
||||
const custom = getCustomSvgBySlug(normalized);
|
||||
if (custom) {
|
||||
return { title: normalized, svg: custom };
|
||||
}
|
||||
|
||||
// 2) Built-in fallback icons
|
||||
if (FALLBACK[normalized]) return FALLBACK[normalized];
|
||||
|
||||
// 3) Simple Icons
|
||||
const si = getSimpleIconBySlug(normalized);
|
||||
if (!si) return FALLBACK.link;
|
||||
|
||||
return {
|
||||
title: si.title,
|
||||
hex: `#${si.hex}`,
|
||||
svg: `<svg viewBox="0 0 24 24" role="img" aria-label="${si.title}"><path d="${si.path}"/></svg>`
|
||||
};
|
||||
}
|
||||
24
src/lib/types.ts
Normal file
24
src/lib/types.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export type Profile = {
|
||||
name: string;
|
||||
bio?: string;
|
||||
avatar?: string;
|
||||
};
|
||||
|
||||
export type LinkItem = {
|
||||
label: string;
|
||||
url: string;
|
||||
icon: string;
|
||||
subtitle?: string;
|
||||
brandColor?: string;
|
||||
iconForeground?: string;
|
||||
};
|
||||
|
||||
export type LinkSection = {
|
||||
title: string;
|
||||
items: LinkItem[];
|
||||
};
|
||||
|
||||
export type LinksData = {
|
||||
profile: Profile;
|
||||
sections: LinkSection[];
|
||||
};
|
||||
@ -1,11 +1,60 @@
|
||||
---
|
||||
import Welcome from '../components/Welcome.astro';
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import LinkCard from '../components/LinkCard.astro';
|
||||
import dataRaw from '../data/links.json';
|
||||
import BaseLayout from '../layouts/Layout.astro';
|
||||
import type { LinksData } from '../lib/types';
|
||||
|
||||
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
|
||||
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
|
||||
const data = dataRaw as LinksData;
|
||||
const profile = data.profile;
|
||||
const sections = data.sections ?? [];
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Welcome />
|
||||
</Layout>
|
||||
<BaseLayout title={`${profile.name} | Links`}>
|
||||
<section
|
||||
class="flex flex-col items-center px-6 pb-6 pt-10 text-center sm:px-10"
|
||||
>
|
||||
{
|
||||
profile.avatar ? (
|
||||
<img
|
||||
class="h-24 w-24 rounded-full border border-white/10 bg-white/5 object-cover shadow-md"
|
||||
src={profile.avatar}
|
||||
alt={`${profile.name} avatar`}
|
||||
loading="eager"
|
||||
/>
|
||||
) : (
|
||||
<div class="h-24 w-24 rounded-full border border-white/10 bg-white/5" />
|
||||
)
|
||||
}
|
||||
|
||||
<h1 class="mt-5 text-2xl font-semibold tracking-tight sm:text-[28px]">
|
||||
{profile.name}
|
||||
</h1>
|
||||
|
||||
{
|
||||
profile.bio ? (
|
||||
<p class="mt-3 max-w-md text-[15px] leading-relaxed text-white/70">
|
||||
{profile.bio}
|
||||
</p>
|
||||
) : null
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="px-4 pb-8 sm:px-6 sm:pb-10">
|
||||
<div class="grid gap-8">
|
||||
{
|
||||
sections.map((section) => (
|
||||
<div>
|
||||
<h2 class="mb-3 text-xs font-semibold uppercase tracking-[0.18em] text-white/55">
|
||||
{section.title}
|
||||
</h2>
|
||||
<div class="grid gap-3">
|
||||
{section.items.map((item) => (
|
||||
<LinkCard item={item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
1
src/styles/global.css
Normal file
1
src/styles/global.css
Normal file
@ -0,0 +1 @@
|
||||
@import "tailwindcss";
|
||||
@ -1,5 +1,15 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
"include": [
|
||||
".astro/types.d.ts",
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"astro/client"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user