├── .cspell.json ├── .editorconfig ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── commitlint.config.ts ├── eslint.config.mjs ├── knip.config.ts ├── next.config.ts ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── postcss.config.mjs ├── prettier.config.js ├── public └── favicon │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ └── site.webmanifest ├── src ├── app │ ├── analytics.tsx │ ├── grid.svg │ ├── layout.tsx │ ├── page.tsx │ ├── primary-links.tsx │ └── sparkle-button.tsx ├── components │ ├── footer.tsx │ └── spotlight.tsx └── styles │ └── globals.css ├── tailwind.config.ts └── tsconfig.json /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", 3 | "dictionaries": ["software-terms", "npm", "fullstack"], 4 | "enableFiletypes": ["tailwindcss"], 5 | "files": ["**", ".vscode/**", ".github/**"], 6 | "ignorePaths": ["pnpm-lock.yaml"], 7 | "useGitignore": true, 8 | "version": "0.2", 9 | "words": [ 10 | "bradlc", 11 | "dbaeumer", 12 | "devdotto", 13 | "honghong", 14 | "lightningcss", 15 | "stackoverflow", 16 | "tszhong0411" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | public-hoist-pattern[]=*prettier* 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22.13.1 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "dbaeumer.vscode-eslint", 5 | "bradlc.vscode-tailwindcss", 6 | "streetsidesoftware.code-spell-checker" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.diagnosticLevel": "Warning", 3 | "cSpell.enabled": true, 4 | 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": "explicit", 7 | "source.organizeImports": "never" 8 | }, 9 | 10 | "editor.formatOnPaste": true, 11 | "editor.formatOnSave": true, 12 | 13 | "eslint.runtime": "node", 14 | "eslint.useFlatConfig": true, 15 | "eslint.workingDirectories": [{ "mode": "auto" }], 16 | 17 | "files.associations": { 18 | "*.css": "tailwindcss", 19 | ".env*": "properties" 20 | }, 21 | 22 | "tailwindCSS.experimental.classRegex": [ 23 | ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], 24 | ["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] 25 | ], 26 | 27 | "typescript.enablePromptUseWorkspaceTsdk": true, 28 | "typescript.preferences.preferTypeOnlyAutoImports": true, 29 | "typescript.tsdk": "node_modules/typescript/lib" 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 tszhong0411 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## Tech Stack 6 | 7 | | Name | Link | 8 | | ---------- | --------------------------------------------------------- | 9 | | Framework | [Next.js](https://nextjs.org/) | 10 | | Deployment | [Vercel](https://vercel.com) | 11 | | Styling | [Tailwindcss](https://tailwindcss.com) | 12 | | Icons | [react-icons](https://react-icons.github.io/react-icons/) | 13 | 14 | ## Getting Started 15 | 16 | Follow these steps to run the project locally on your machine: 17 | 18 | 1. Clone the repository 19 | 20 | ```bash 21 | git clone https://github.com/tszhong0411/links.git 22 | ``` 23 | 24 | 2. Navigate to the project directory 25 | 26 | ```bash 27 | cd links 28 | ``` 29 | 30 | 3. Install dependencies 31 | 32 | ```bash 33 | pnpm install 34 | ``` 35 | 36 | 4. Run the development server 37 | 38 | ```bash 39 | pnpm dev 40 | ``` 41 | 42 |
43 |

44 | Made with ❤️ in Hong Kong 45 |

46 | -------------------------------------------------------------------------------- /commitlint.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'cz-git' 2 | 3 | const config: UserConfig = { 4 | extends: ['@commitlint/config-conventional'] 5 | } 6 | 7 | export default config 8 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import tszhong0411 from '@tszhong0411/eslint-config' 2 | 3 | export default tszhong0411({ 4 | project: './tsconfig.json', 5 | tsconfigRootDir: import.meta.dirname, 6 | react: true, 7 | next: true 8 | }) 9 | -------------------------------------------------------------------------------- /knip.config.ts: -------------------------------------------------------------------------------- 1 | import { type KnipConfig } from 'knip' 2 | 3 | const config: KnipConfig = { 4 | ignoreBinaries: ['only-allow'], 5 | ignoreDependencies: ['prettier-plugin-*'] 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from 'next' 2 | 3 | const nextConfig: NextConfig = { 4 | images: { 5 | remotePatterns: [ 6 | { 7 | protocol: 'https', 8 | hostname: 'honghong.me' 9 | } 10 | ] 11 | } 12 | } 13 | 14 | export default nextConfig 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "links", 4 | "version": "0.0.0", 5 | "description": "tszhong0411's social media links", 6 | "license": "MIT", 7 | "author": "tszhong0411 (https://github.com/tszhong0411/)", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/tszhong0411/links.git" 11 | }, 12 | "type": "module", 13 | "scripts": { 14 | "build": "next build", 15 | "check": "pnpm lint && pnpm type-check && pnpm format:check && pnpm check:spelling && pnpm check:knip", 16 | "check:knip": "knip", 17 | "check:spelling": "cspell --show-context --show-suggestions", 18 | "clean": "rm -rf .next", 19 | "dev": "next dev --turbo", 20 | "format:check": "prettier --cache --check --ignore-path .gitignore --ignore-path .prettierignore .", 21 | "format:write": "prettier --cache --write --list-different --ignore-path .gitignore --ignore-path .prettierignore .", 22 | "preinstall": "npx only-allow pnpm", 23 | "lint": "eslint . --max-warnings 0", 24 | "lint:fix": "eslint --fix .", 25 | "prepare": "simple-git-hooks", 26 | "start": "next start", 27 | "type-check": "tsc --noEmit" 28 | }, 29 | "config": { 30 | "commitizen": { 31 | "path": "./node_modules/cz-git" 32 | } 33 | }, 34 | "dependencies": { 35 | "@tszhong0411/utils": "^0.0.22", 36 | "geist": "^1.3.1", 37 | "next": "15.2.4", 38 | "react": "19.1.0", 39 | "react-dom": "19.1.0", 40 | "react-icons": "^5.5.0" 41 | }, 42 | "devDependencies": { 43 | "@commitlint/cli": "19.8.0", 44 | "@commitlint/config-conventional": "19.8.0", 45 | "@tszhong0411/eslint-config": "^0.1.36", 46 | "@tszhong0411/prettier-config": "^0.0.15", 47 | "@tszhong0411/tsconfig": "^0.0.13", 48 | "@types/node": "22.14.0", 49 | "@types/react": "19.1.0", 50 | "@types/react-dom": "19.1.1", 51 | "cspell": "^8.18.1", 52 | "cz-git": "^1.11.1", 53 | "eslint": "9.23.0", 54 | "knip": "^5.46.5", 55 | "lint-staged": "^15.5.0", 56 | "postcss": "^8.5.3", 57 | "postcss-lightningcss": "^1.0.1", 58 | "postcss-load-config": "^6.0.1", 59 | "prettier": "^3.5.3", 60 | "simple-git-hooks": "^2.12.1", 61 | "tailwindcss": "^3.4.17", 62 | "typescript": "5.8.2" 63 | }, 64 | "lint-staged": { 65 | "*.{cjs,mjs,js,jsx,cts,mts,ts,tsx,json}": "eslint --fix", 66 | "**/*": "prettier --write --ignore-unknown" 67 | }, 68 | "packageManager": "pnpm@10.7.1", 69 | "simple-git-hooks": { 70 | "pre-commit": "pnpm lint-staged", 71 | "commit-msg": "pnpm commitlint --edit $1" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | onlyBuiltDependencies: 2 | - sharp 3 | - simple-git-hooks 4 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | export default { 3 | plugins: { 4 | tailwindcss: {}, 5 | 'postcss-lightningcss': { 6 | browsers: '>= .25%' 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | import tszhong0411 from '@tszhong0411/prettier-config' 2 | 3 | export default tszhong0411() 4 | -------------------------------------------------------------------------------- /public/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tszhong0411/links/6cbbaa8f2b35f03c637d945962faa0a3339e0fa7/public/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tszhong0411/links/6cbbaa8f2b35f03c637d945962faa0a3339e0fa7/public/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tszhong0411/links/6cbbaa8f2b35f03c637d945962faa0a3339e0fa7/public/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tszhong0411/links/6cbbaa8f2b35f03c637d945962faa0a3339e0fa7/public/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tszhong0411/links/6cbbaa8f2b35f03c637d945962faa0a3339e0fa7/public/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tszhong0411/links/6cbbaa8f2b35f03c637d945962faa0a3339e0fa7/public/favicon/favicon.ico -------------------------------------------------------------------------------- /public/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "background_color": "#000000", 3 | "display": "standalone", 4 | "icons": [ 5 | { 6 | "sizes": "192x192", 7 | "src": "/favicon/android-chrome-192x192.png", 8 | "type": "image/png" 9 | }, 10 | { 11 | "sizes": "512x512", 12 | "src": "/favicon/android-chrome-512x512.png", 13 | "type": "image/png" 14 | } 15 | ], 16 | "name": "Links", 17 | "short_name": "Links", 18 | "theme_color": "#000000" 19 | } 20 | -------------------------------------------------------------------------------- /src/app/analytics.tsx: -------------------------------------------------------------------------------- 1 | import Script from 'next/script' 2 | 3 | const Analytics = () => { 4 | if (process.env.NODE_ENV !== 'production') return null 5 | 6 | return ( 7 |