├── .nvmrc ├── .yarnrc.yml ├── .eslintignore ├── .gitignore ├── .restyled.yml ├── src ├── lib │ ├── css.ts │ ├── classnames.ts │ └── external │ │ └── steam │ │ └── index.ts ├── assets │ ├── images │ │ ├── amphineko.png │ │ ├── blueprint-bg.svg │ │ ├── blueprint-footer.svg │ │ ├── blueprint-panels.svg │ │ ├── background-header.svg │ │ └── background.svg │ └── fonts │ │ └── redacted │ │ ├── otf │ │ ├── RedactedScript-Bold.otf │ │ ├── RedactedScript-Light.otf │ │ └── RedactedScript-Regular.otf │ │ ├── ttf │ │ ├── RedactedScript-Bold.ttf │ │ ├── RedactedScript-Light.ttf │ │ └── RedactedScript-Regular.ttf │ │ ├── woff │ │ ├── RedactedScript-Bold.woff2 │ │ ├── RedactedScript-Light.woff2 │ │ └── RedactedScript-Regular.woff2 │ │ └── license ├── assets.d.ts ├── main.tsx ├── components │ ├── username.tsx │ ├── layout │ │ └── index.tsx │ ├── typography │ │ ├── fonts.tsx │ │ └── index.tsx │ └── display │ │ ├── footer.tsx │ │ ├── labels.tsx │ │ ├── accounts.tsx │ │ ├── capsule.tsx │ │ └── header.tsx ├── index.tsx └── config.tsx ├── .prettierrc.json ├── .env ├── index.html ├── vite.config.ts ├── .devcontainer └── devcontainer.json ├── tsconfig.json ├── LICENSE ├── .eslintrc.json ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.11.1 -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | vite.config.ts -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .yarn 2 | dist 3 | node_modules 4 | yarn-error.log -------------------------------------------------------------------------------- /.restyled.yml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | restylers: 3 | - "!jq" 4 | - "*" 5 | -------------------------------------------------------------------------------- /src/lib/css.ts: -------------------------------------------------------------------------------- 1 | export const BORDER_RADIUS = '0.5rem' 2 | export const BORDER_RADIUS_SMALL = '0.25rem' 3 | -------------------------------------------------------------------------------- /src/assets/images/amphineko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amphineko/reactiveneko/HEAD/src/assets/images/amphineko.png -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": false, 4 | "singleQuote": true, 5 | "tabWidth": 4 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/fonts/redacted/otf/RedactedScript-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amphineko/reactiveneko/HEAD/src/assets/fonts/redacted/otf/RedactedScript-Bold.otf -------------------------------------------------------------------------------- /src/assets/fonts/redacted/otf/RedactedScript-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amphineko/reactiveneko/HEAD/src/assets/fonts/redacted/otf/RedactedScript-Light.otf -------------------------------------------------------------------------------- /src/assets/fonts/redacted/ttf/RedactedScript-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amphineko/reactiveneko/HEAD/src/assets/fonts/redacted/ttf/RedactedScript-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/redacted/ttf/RedactedScript-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amphineko/reactiveneko/HEAD/src/assets/fonts/redacted/ttf/RedactedScript-Light.ttf -------------------------------------------------------------------------------- /src/assets/fonts/redacted/otf/RedactedScript-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amphineko/reactiveneko/HEAD/src/assets/fonts/redacted/otf/RedactedScript-Regular.otf -------------------------------------------------------------------------------- /src/assets/fonts/redacted/ttf/RedactedScript-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amphineko/reactiveneko/HEAD/src/assets/fonts/redacted/ttf/RedactedScript-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/redacted/woff/RedactedScript-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amphineko/reactiveneko/HEAD/src/assets/fonts/redacted/woff/RedactedScript-Bold.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/redacted/woff/RedactedScript-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amphineko/reactiveneko/HEAD/src/assets/fonts/redacted/woff/RedactedScript-Light.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/redacted/woff/RedactedScript-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amphineko/reactiveneko/HEAD/src/assets/fonts/redacted/woff/RedactedScript-Regular.woff2 -------------------------------------------------------------------------------- /src/lib/classnames.ts: -------------------------------------------------------------------------------- 1 | export const classnames = (classnames: Record) => { 2 | return Object.entries(classnames) 3 | .filter(([, value]) => value) 4 | .map(([key]) => key) 5 | .join(' ') 6 | } 7 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | DEPLOY_TARGET=demo 2 | STEAM_GET_PLAYER_SUMMARIES=https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=0AD6D96E1BF0A718C86C574AAADABC43&steamids=76561198013029151,https://steam-api-proxy.stdnkmm.workers.dev/getPlayerSummaries?steamids=76561198013029151 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react' 2 | import { defineConfig } from 'vite' 3 | import { imagetools } from 'vite-imagetools' 4 | 5 | export default defineConfig({ 6 | base: '', 7 | plugins: [ 8 | react({ 9 | babel: { 10 | plugins: ['styled-jsx/babel'], 11 | }, 12 | }), 13 | imagetools(), 14 | ], 15 | }) 16 | -------------------------------------------------------------------------------- /src/assets.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*?format=avif' { 2 | const value: string 3 | export default value 4 | } 5 | 6 | declare module '*?format=jpeg' { 7 | const value: string 8 | export default value 9 | } 10 | 11 | declare module '*?format=png' { 12 | const value: string 13 | export default value 14 | } 15 | 16 | declare module '*?format=webp' { 17 | const value: string 18 | export default value 19 | } 20 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import React from 'react' 5 | import ReactDOM from 'react-dom/client' 6 | import { IndexPage } from '.' 7 | import { IBMPlexMono, RedactedFont } from './components/typography/fonts' 8 | 9 | function Main() { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | 19 | const rootElement = document.getElementById('root') 20 | 21 | if (rootElement) { 22 | ReactDOM.createRoot(rootElement).render( 23 | 24 |
25 | , 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "default", 3 | "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye", 4 | "features": { 5 | "ghcr.io/devcontainers/features/common-utils:2": {} 6 | }, 7 | "customizations": { 8 | "vscode": { 9 | "extensions": [ 10 | "bierner.github-markdown-preview", 11 | "dbaeumer.vscode-eslint", 12 | "esbenp.prettier-vscode" 13 | ], 14 | "settings": { 15 | "[typescript]": { 16 | "editor.defaultFormatter": "esbenp.prettier-vscode" 17 | }, 18 | "[typescriptreact]": { 19 | "editor.defaultFormatter": "esbenp.prettier-vscode" 20 | } 21 | } 22 | } 23 | }, 24 | "forwardPorts": [ 25 | 5173 26 | ] 27 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "ES2020", 5 | "DOM", 6 | "DOM.Iterable" 7 | ], 8 | "module": "ESNext", 9 | "skipLibCheck": true, 10 | "target": "ES2020", 11 | "useDefineForClassFields": true, 12 | /* Bundler mode */ 13 | "allowImportingTsExtensions": true, 14 | "isolatedModules": true, 15 | "jsx": "react-jsx", 16 | "moduleResolution": "bundler", 17 | "noEmit": true, 18 | "resolveJsonModule": true, 19 | /* Linting */ 20 | "strict": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | }, 25 | "include": [ 26 | "src/**/*.ts", 27 | "src/**/*.tsx" 28 | ], 29 | "exclude": [ 30 | "node_modules", 31 | ], 32 | } -------------------------------------------------------------------------------- /src/components/username.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useQuery } from 'react-query' 3 | 4 | const queryKey = '7f50365a-f904-470b-b22b-62b2fc59ec4a' 5 | 6 | /** 7 | * Latest username will be fetch on mount. Fetch errors will be silently ignored and pre-fetched data will be used. 8 | */ 9 | export const UpdatedUsername = ({ 10 | fn, 11 | initialData, 12 | queryKey: key, 13 | }: { 14 | fn: () => string | Promise 15 | initialData: string 16 | queryKey: string 17 | }) => { 18 | const { data, refetch } = useQuery([queryKey, key], fn, { 19 | enabled: false, 20 | initialData: initialData, 21 | keepPreviousData: true, 22 | }) 23 | 24 | useEffect(() => { 25 | void refetch() 26 | }, [refetch]) 27 | 28 | return ( 29 | <> 30 | {data} 31 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/assets/images/blueprint-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 30 | 31 | background 32 | 12em 33 | #939597 GREY 34 | -------------------------------------------------------------------------------- /src/assets/images/blueprint-footer.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 30 | 31 | footer 32 | 12em 33 | #101820 BLACK 34 | -------------------------------------------------------------------------------- /src/assets/images/blueprint-panels.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 30 | 31 | panel background 32 | 12em 33 | #f5f5f5 WHITE 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016-2023 amphineko and all contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/eslintrc", 3 | "env": { 4 | "browser": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/strict-type-checked", 9 | "plugin:import/recommended", 10 | "plugin:import/typescript", 11 | "plugin:react-hooks/recommended", 12 | "plugin:react/recommended", 13 | "plugin:prettier/recommended" 14 | ], 15 | "parser": "@typescript-eslint/parser", 16 | "parserOptions": { 17 | "ecmaFeatures": { 18 | "jsx": true 19 | }, 20 | "ecmaVersion": 2020, 21 | "sourceType": "module", 22 | "project": "./tsconfig.json" 23 | }, 24 | "root": true, 25 | "rules": { 26 | "import/no-unresolved": [ 27 | "error", 28 | { 29 | "ignore": [ 30 | ".*?format=\\w+$" 31 | ] 32 | } 33 | ], 34 | "react/no-unknown-property": [ 35 | "error", 36 | { 37 | "ignore": [ 38 | "global", 39 | "jsx" 40 | ] 41 | } 42 | ], 43 | "react/react-in-jsx-scope": "off" 44 | }, 45 | "settings": { 46 | "react": { 47 | "version": "detect" 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/components/layout/index.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren, ReactNode } from 'react' 2 | import { BORDER_RADIUS } from '../../lib/css' 3 | 4 | export const Column = ({ children, width }: PropsWithChildren<{ width?: string }>) => ( 5 | <> 6 |
{children}
7 | 18 | 19 | ) 20 | 21 | export const Row = ({ 22 | background, 23 | children, 24 | style, 25 | }: PropsWithChildren<{ 26 | background?: string 27 | breakpoint?: string 28 | style?: ReactNode 29 | }>) => ( 30 | <> 31 |
{children}
32 | 49 | {style} 50 | 51 | ) 52 | -------------------------------------------------------------------------------- /src/lib/external/steam/index.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from 'react-query' 2 | 3 | const queryKey = 'd257d14988e43e79db261655b965ed43' // random string 4 | 5 | export const fetchSteamPersonaName = async (urls: string[]) => { 6 | const errors = [] 7 | 8 | for (const url of urls) { 9 | try { 10 | const response = await fetch(url) 11 | if (!response.ok) { 12 | throw new Error(`HTTP error: ${response.status} ${response.statusText}`) 13 | } 14 | 15 | const result = (await response.json()) as { 16 | response?: { players?: { personaname?: string }[] } 17 | } 18 | const personaName = result.response?.players?.[0]?.personaname 19 | 20 | if (typeof personaName !== 'string') { 21 | throw new Error('result.response?.players?.[0]?.personaname is not a string') 22 | } 23 | 24 | return personaName 25 | } catch (e) { 26 | errors.push(e) 27 | } 28 | } 29 | 30 | throw new Error( 31 | [ 32 | `Failed to read Steam API from any of the provided URLs: ${urls.join(', ')}`, 33 | ...errors.map((error: unknown) => String(error)), 34 | ].join('\n'), 35 | ) 36 | } 37 | 38 | export const useSteamPersonaName = (urls?: string[], initialData?: string) => { 39 | return useQuery([queryKey], async () => await fetchSteamPersonaName(urls ?? []), { 40 | cacheTime: Infinity, 41 | initialData, 42 | 43 | // don't refetch as usernames are not expected to change often 44 | refetchInterval: false, 45 | refetchOnWindowFocus: false, 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amphineko", 3 | "version": "0.0.1", 4 | "description": "", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/amphineko/amphineko.git" 8 | }, 9 | "author": "amphineko ", 10 | "license": "MIT", 11 | "bugs": { 12 | "url": "https://github.com/amphineko/amphineko/issues" 13 | }, 14 | "homepage": "https://futa.moe/amphineko/", 15 | "scripts": { 16 | "dev": "vite", 17 | "build": "tsc && vite build", 18 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 19 | "preview": "vite preview" 20 | }, 21 | "dependencies": { 22 | "@fortawesome/fontawesome-svg-core": "^6.1.1", 23 | "@fortawesome/free-solid-svg-icons": "^6.1.1", 24 | "@fortawesome/react-fontawesome": "^0.2.0", 25 | "@ibm/plex": "^6.4.1", 26 | "@vitejs/plugin-react": "^4.2.1", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "react-icons": "^4.3.1", 30 | "react-query": "^3.34.19", 31 | "styled-jsx": "^5.1.2", 32 | "vite": "^5.3.6" 33 | }, 34 | "devDependencies": { 35 | "@types/node": "20.4.5", 36 | "@types/react": "^18.2.59", 37 | "@types/react-dom": "^18.2.19", 38 | "@typescript-eslint/eslint-plugin": "^6.2.0", 39 | "@typescript-eslint/parser": "^6.2.0", 40 | "eslint": "^8.13.0", 41 | "eslint-config-next": "^13.1.6", 42 | "eslint-config-prettier": "^8.5.0", 43 | "eslint-import-resolver-typescript": "^3.6.1", 44 | "eslint-plugin-import": "^2.26.0", 45 | "eslint-plugin-prettier": "^5.0.0", 46 | "eslint-plugin-react": "^7.29.4", 47 | "eslint-plugin-react-hooks": "^4.4.0", 48 | "prettier": "^3.0.0", 49 | "sharp": "^0.33.2", 50 | "svgo": "^3.2.0", 51 | "typescript": "^5.1.6", 52 | "vite-imagetools": "^6.2.9" 53 | }, 54 | "packageManager": "yarn@4.1.0", 55 | "type": "module" 56 | } 57 | -------------------------------------------------------------------------------- /src/components/typography/fonts.tsx: -------------------------------------------------------------------------------- 1 | import RedactedOtf from '../../assets/fonts/redacted/otf/RedactedScript-Bold.otf' 2 | import RedactedTtf from '../../assets/fonts/redacted/ttf/RedactedScript-Bold.ttf' 3 | import RedactedWoff2 from '../../assets/fonts/redacted/woff/RedactedScript-Bold.woff2' 4 | import IBMPlexMonoBoldWoff from '@ibm/plex/IBM-Plex-Mono/fonts/complete/woff/IBMPlexMono-Bold.woff' 5 | import IBMPlexMonoBoldWoff2 from '@ibm/plex/IBM-Plex-Mono/fonts/complete/woff2/IBMPlexMono-Bold.woff2' 6 | import IBMPlexMonoLightWoff from '@ibm/plex/IBM-Plex-Mono/fonts/complete/woff/IBMPlexMono-Light.woff' 7 | import IBMPlexMonoLightWoff2 from '@ibm/plex/IBM-Plex-Mono/fonts/complete/woff2/IBMPlexMono-Light.woff2' 8 | 9 | export const IBMPlexMono = () => ( 10 | 35 | ) 36 | 37 | export const RedactedFont = () => ( 38 | 50 | ) 51 | -------------------------------------------------------------------------------- /src/components/display/footer.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react' 2 | import { IconType } from 'react-icons' 3 | import { TbExternalLink } from 'react-icons/tb' 4 | import { BORDER_RADIUS } from '../../lib/css' 5 | import Background from '../../assets/images/blueprint-footer.svg' 6 | 7 | export const FooterParagraph = ({ 8 | backgroundColor, 9 | color, 10 | children, 11 | icon: Icon, 12 | }: PropsWithChildren<{ backgroundColor?: string; color?: string; icon: IconType }>) => ( 13 | <> 14 |

15 | 16 | 17 | 18 | {children} 19 | {backgroundColor} 20 |

21 | 38 | 39 | ) 40 | 41 | export const FooterLink = ({ children, href }: PropsWithChildren<{ href: string }>) => ( 42 | <> 43 | 44 | {children} 45 | 46 | 47 | 48 | 49 | 61 | 62 | ) 63 | 64 | export const Footer = ({ children }: PropsWithChildren) => ( 65 | <> 66 |
{children}
67 | 75 | 76 | ) 77 | -------------------------------------------------------------------------------- /src/components/display/labels.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren, ReactNode } from 'react' 2 | 3 | export const LabelItem = ({ children }: PropsWithChildren) => { 4 | return ( 5 | 6 | {children} 7 | 26 | 27 | ) 28 | } 29 | 30 | export const LabelGroup = ({ 31 | children, 32 | icon, 33 | title, 34 | }: PropsWithChildren<{ 35 | icon?: ReactNode 36 | title?: string 37 | }>) => { 38 | return ( 39 |
40 | {icon} 41 | 42 |
{children}
43 | 44 | 77 |
78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reactive/atomicneko template 2 | 3 | This repository is the repository of my homepage (https://futa.moe/amphineko/) that can be used as a template. 4 | 5 | Visit [reactiveneko.vercel.app](https://reactiveneko.vercel.app/) or [reactiveneko.pages.dev](https://reactiveneko.pages.dev/) for a live deployment of the master branch. 6 | 7 | This repository is the ~~experimental~~ next version of [atomicneko](https://github.com/amphineko/atomicneko). 8 | 9 | ## Getting Started 10 | 11 | Indeed, you should inspect `pages/index.tsx` to change the content of the page before deploying. 12 | 13 | To start a local development server, run `yarn` to install dependencies, and `yarn dev` to start the development server. 14 | 15 | The server should be available at `http://localhost:3000`. 16 | 17 | ## Deploying 18 | 19 | There are multiple ways to deploy this project. Please see https://nextjs.org/docs/deployment for more information. 20 | 21 | Here are some recommended options: Vercel, Netlify, GitHub Pages (requires turning off image optimization, see below), and Cloudflare Pages. 22 | 23 | ## Exporting 24 | 25 | Alternatively, a statically generated version of this project can be exported using `yarn export`. 26 | 27 | You may experience some issues during the export process, due to the usage of `next/image` component. 28 | 29 | Following the [official documentation](https://nextjs.org/docs/messages/export-image-api) or error messages, you can use `next export` by disabling image optimization. 30 | 31 | ``` 32 | % yarn export 33 | ... 34 | Error: Image Optimization using Next.js' default loader is not compatible with `next export`. 35 | Possible solutions: 36 | - Use `next start` to run a server, which includes the Image Optimization API. 37 | - Configure `images.unoptimized = true` in `next.config.js` to disable the Image Optimization API. 38 | Read more: https://nextjs.org/docs/messages/export-image-api 39 | at /Users/amphineko/Devcat/reactiveneko/node_modules/next/dist/export/index.js:153:23 40 | at async Span.traceAsyncFn (/Users/amphineko/Devcat/reactiveneko/node_modules/next/dist/trace/trace.js:79:20) 41 | ``` 42 | 43 | ## FAQs 44 | 45 | ### Dynamic Steam profile name doesn't work 46 | 47 | There are some options to solve this issue. 48 | 49 | **With official Steam API**: Update `NEXT_PUBLIC_STEAM_GET_PLAYER_SUMMARIES` in the `.env` file or your deployment settings, to use your own Steam API key and Steam ID. Please notice that you should configure your Steam API key to return your own domain name for CORS requests. 50 | 51 | **Proxy server**: Alternatively, you can use a proxy server to handle the CORS requests. 52 | 53 | **Replace with static values**: You can still replace the dynamic component with a static name on the page, like how the osu! profile name is displayed. 54 | 55 | ## Roadmap 56 | 57 | - [x] Migrate to React and next.js 58 | - [x] Add dynamically fetched usernames 59 | - [ ] Add static site generation (blocked by `next/image` unsupported by `next export`) 60 | - [ ] Add other roadmap items 61 | -------------------------------------------------------------------------------- /src/components/display/accounts.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren, ReactNode } from 'react' 2 | import { Dimmed, Redacted } from '../typography' 3 | import { Capsule } from './capsule' 4 | import { BORDER_RADIUS } from '../../lib/css' 5 | 6 | const Account = ({ 7 | children, 8 | capsuleBackground, 9 | href = '#', 10 | icon, 11 | iconBackground, 12 | platform, 13 | redacted = false, 14 | redactedHoverToShow = false, 15 | }: PropsWithChildren<{ 16 | capsuleBackground?: string 17 | href?: string 18 | icon?: ReactNode 19 | iconBackground?: string 20 | platform: string 21 | redacted?: boolean 22 | redactedHoverToShow?: boolean 23 | }>) => { 24 | return ( 25 | 26 | {platform} 27 | {redacted ? {children} : children} 28 | 29 | ) 30 | } 31 | 32 | const Category = ({ children, title }: PropsWithChildren<{ title: string }>) => ( 33 |
34 | {title} 35 | {children} 36 | 62 |
63 | ) 64 | 65 | const Container = ({ 66 | background, 67 | children, 68 | }: PropsWithChildren<{ 69 | background?: string 70 | }>) => ( 71 |
72 | {children} 73 | 87 |
88 | ) 89 | 90 | export interface AccountShowcaseProps { 91 | groups: { 92 | title: string 93 | accounts: { 94 | title: string 95 | href?: string 96 | icon: ReactNode 97 | redacted?: boolean 98 | } 99 | } 100 | } 101 | 102 | export const AccountShowcase = { 103 | Account: Account, 104 | Category: Category, 105 | Container: Container, 106 | } 107 | -------------------------------------------------------------------------------- /src/components/display/capsule.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren, ReactNode } from 'react' 2 | import { classnames } from '../../lib/classnames' 3 | import { BORDER_RADIUS_SMALL } from '../../lib/css' 4 | 5 | export const Capsule = ({ 6 | background, 7 | children, 8 | href, 9 | icon, 10 | iconBackground, 11 | iconColor, 12 | iconType, 13 | sharp, 14 | }: PropsWithChildren<{ 15 | background?: string 16 | href?: string 17 | icon?: ReactNode 18 | iconBackground?: string 19 | iconColor?: string 20 | iconType?: 'round' | 'sharp' 21 | sharp?: boolean 22 | }>) => { 23 | const className = classnames({ 24 | capsule: true, 25 | sharp, 26 | }) 27 | 28 | const iconContainerClassName = classnames({ 29 | 'icon-container': true, 30 | round: iconType === 'round' || !iconType, 31 | sharp: iconType === 'sharp', 32 | }) 33 | 34 | const child = ( 35 | <> 36 | {icon ? {icon} : null} 37 | {children} 38 | 60 | 61 | ) 62 | 63 | return ( 64 | <> 65 | {href ? ( 66 | 67 | {child} 68 | 69 | ) : ( 70 | {child} 71 | )} 72 | 73 | 102 | 103 | ) 104 | } 105 | -------------------------------------------------------------------------------- /src/components/typography/index.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react' 2 | import { TbExternalLink } from 'react-icons/tb' 3 | import { classnames } from '../../lib/classnames' 4 | 5 | export const Description = ({ 6 | background, 7 | children, 8 | }: PropsWithChildren<{ 9 | background?: string 10 | }>) => ( 11 | <> 12 |
{children}
13 | 20 | 21 | ) 22 | 23 | export const DescriptionTitle = ({ 24 | children, 25 | fontSize, 26 | smallCaps, 27 | }: PropsWithChildren<{ fontSize?: string; smallCaps?: boolean }>) => ( 28 | <> 29 |

35 | {children} 36 |

37 | 52 | 53 | ) 54 | 55 | export const Dimmed = ({ children }: PropsWithChildren) => ( 56 | <> 57 | {children} 58 | 63 | 64 | ) 65 | 66 | export const ExternalLink = ({ children, href }: PropsWithChildren<{ href: string }>) => ( 67 | <> 68 | 69 | {children} 70 | 71 | 72 | 73 | 74 | 85 | 86 | ) 87 | 88 | export const Paragraph = ({ children }: PropsWithChildren) => ( 89 | <> 90 |

{children}

91 | 98 | 99 | ) 100 | 101 | export const Redacted = ({ 102 | children, 103 | hoverToShow, 104 | }: PropsWithChildren<{ 105 | hoverToShow?: boolean 106 | }>) => ( 107 | <> 108 | 109 | {children} 110 | {children} 111 | 112 | 132 | 133 | ) 134 | -------------------------------------------------------------------------------- /src/assets/fonts/redacted/license: -------------------------------------------------------------------------------- 1 | Copyright 2013 The Redacted Project Authors (https://github.com/christiannaths/redacted-font) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | https://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /src/assets/images/background-header.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/assets/images/background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import type { IconType } from 'react-icons' 3 | import { TbBulb, TbGitBranch } from 'react-icons/tb' 4 | import Background from './assets/images/blueprint-bg.svg' 5 | import { AccountShowcase } from './components/display/accounts.tsx' 6 | import { Capsule } from './components/display/capsule' 7 | import { Footer, FooterLink, FooterParagraph } from './components/display/footer' 8 | import { Header, ProfileAddonGroup, ProfileAddons } from './components/display/header' 9 | import { Dimmed } from './components/typography' 10 | import { 11 | ACCOUNTS, 12 | COPYRIGHT, 13 | DESCRIPTION_PARAGRAPHS, 14 | PANEL_BACKGROUND, 15 | PROFILE_NAME, 16 | PROFILE_PICTURE, 17 | PROFILE_TAGS, 18 | } from './config.tsx' 19 | import { classnames } from './lib/classnames.ts' 20 | 21 | function ProfileLabel({ children, comment, icon }: { children: string; comment?: string; icon?: ReactNode }) { 22 | const className = classnames({ 23 | label: true, 24 | 'no-comment': !comment, 25 | 'no-icon': !icon, 26 | }) 27 | return ( 28 | 29 | 30 | {children} 31 | {comment && ( 32 | 33 | {comment} 34 | 35 | )} 36 | 37 | 58 | 59 | ) 60 | } 61 | 62 | export const IndexPage = () => { 63 | return ( 64 |
65 |
66 | 67 | {PROFILE_TAGS.map(({ tags, title }) => ( 68 | 69 | {tags.map(({ comment, icon, tag }) => ( 70 | 71 | {tag} 72 | 73 | ))} 74 | 75 | ))} 76 | 77 |
78 | 79 | 80 | {ACCOUNTS.map((category) => ( 81 | 82 | {category.accounts.map(({ icon, iconBackground, name, platform, redacted, url }) => ( 83 | 92 | {name} 93 | 94 | ))} 95 | 96 | ))} 97 | 98 | 99 | {DESCRIPTION_PARAGRAPHS} 100 | 101 |
102 | 103 | 104 | Fork this template on GitHub: amphineko/reactiveneko 105 | 106 | 107 | 108 | {COPYRIGHT} 109 | 110 |
111 | 112 | 121 | 122 | 137 |
138 | ) 139 | } 140 | 141 | export default IndexPage 142 | -------------------------------------------------------------------------------- /src/config.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | TbBrandGithub, 3 | TbBrandSteam, 4 | TbBrandTelegram, 5 | TbBrandTwitter, 6 | TbBrandWeibo, 7 | TbCookie, 8 | TbNetwork, 9 | TbSourceCode, 10 | } from 'react-icons/tb' 11 | import ProfilePictureOriginal from './assets/images/amphineko.png' 12 | import ProfilePictureAvif from './assets/images/amphineko.png?format=avif' 13 | import ProfilePictureJpeg from './assets/images/amphineko.png?format=jpeg' 14 | import ProfilePicturePng from './assets/images/amphineko.png?format=png' 15 | import ProfilePictureWebp from './assets/images/amphineko.png?format=webp' 16 | import { ProfileNameStandout, ProfilePictureSources } from './components/display/header' 17 | import { Description, DescriptionTitle, ExternalLink, Paragraph } from './components/typography' 18 | import PanelBackground from './assets/images/blueprint-panels.svg' 19 | import FooterBackground from './assets/images/blueprint-footer.svg' 20 | import PageBackground from './assets/images/blueprint-bg.svg' 21 | import { Row } from './components/layout' 22 | 23 | export const PANEL_BACKGROUND = PanelBackground 24 | export const FOOTER_BACKGROUND = FooterBackground 25 | export const PAGE_BACKGROUND = PageBackground 26 | 27 | export const PROFILE_PICTURE: ProfilePictureSources = { 28 | src: ProfilePictureOriginal, 29 | avif: ProfilePictureAvif, 30 | jpeg: ProfilePictureJpeg, 31 | png: ProfilePicturePng, 32 | webp: ProfilePictureWebp, 33 | } 34 | 35 | export const PROFILE_NAME = ( 36 | <> 37 | {/* amphi[ne]ko */} 38 | amphi 39 | 46 | ne 47 | 48 | ko 49 | 50 | ) 51 | 52 | export const PROFILE_TAGS: ProfileHeaderTagGroup[] = [ 53 | { 54 | title: 'also-known-as', 55 | tags: [ 56 | { 57 | tag: 'atomic-akarin', 58 | comment: 'since 201?', 59 | }, 60 | { 61 | tag: '1kar0s', 62 | comment: 'since 202?', 63 | }, 64 | ], 65 | }, 66 | { 67 | title: 'area-of-work', 68 | tags: [ 69 | { 70 | tag: 'neteng', 71 | icon: , 72 | }, 73 | { 74 | tag: 'swe', 75 | icon: , 76 | }, 77 | ], 78 | }, 79 | { 80 | title: 'languages', 81 | tags: [ 82 | { 83 | tag: 'zh-cmn-Hans', 84 | comment: 'native', 85 | }, 86 | { 87 | tag: 'en-GB', 88 | }, 89 | { 90 | tag: 'en-US', 91 | }, 92 | { 93 | tag: 'ja', 94 | comment: 'learning', 95 | }, 96 | ], 97 | }, 98 | ] 99 | 100 | export const ACCOUNTS: Accounts[] = [ 101 | { 102 | type: 'oss', 103 | accounts: [ 104 | { 105 | platform: 'GitHub', 106 | name: 'amphineko', 107 | url: 'https://github.com/amphineko/', 108 | icon: , 109 | iconBackground: '#000000', 110 | }, 111 | ], 112 | }, 113 | { 114 | type: 'social-accounts', 115 | accounts: [ 116 | { 117 | platform: 'Telegram', 118 | name: '@amphineko', 119 | url: 'https://telegram.me/amphineko', 120 | icon: , 121 | iconBackground: '#0088ccaa', 122 | }, 123 | { 124 | platform: 'Twitter', 125 | name: '@amphineko', 126 | url: 'https://twitter.com/amphineko/', 127 | icon: , 128 | iconBackground: '#1da1f2aa', 129 | }, 130 | { 131 | platform: 'Weibo', 132 | name: 'redacted', 133 | icon: , 134 | iconBackground: '#ff9933aa', 135 | redacted: true, 136 | }, 137 | ], 138 | }, 139 | { 140 | type: 'gaming', 141 | accounts: [ 142 | { 143 | platform: 'osu!', 144 | name: 'Rukatan', 145 | url: 'https://osu.ppy.sh/users/1344051', 146 | icon: , 147 | iconBackground: '#f062a1aa', 148 | }, 149 | { 150 | platform: 'Steam', 151 | name: '1kar0s', 152 | url: 'https://steamcommunity.com/id/amphineko/', 153 | icon: , 154 | iconBackground: '#000000aa', 155 | }, 156 | { 157 | platform: 'VATSIM', 158 | name: 'N190AP', 159 | url: 'https://stats.vatsim.net/stats/1499554', 160 | icon: , 161 | iconBackground: '#ff9933aa', 162 | }, 163 | ], 164 | }, 165 | ] 166 | 167 | export const DESCRIPTION_PARAGRAPHS = ( 168 | 169 | 170 | what am i doing? 171 | FAANG network enginner since 2022. 172 | passionate full-stack software developer and open-source contributor. 173 | amateur network engineer operating own Internet autonomous systems. 174 | 175 | 176 | what do i love? 177 | ardently love of FPS, simulation and AVG. 178 | rhythm game is LIFE! 179 | 180 | retired and mission-collection only{' '} 181 | Ingress agent. 182 | 183 | 184 | dreamed to be a civil aviation pilot. 185 | 186 | 187 | 188 | ) 189 | 190 | export const COPYRIGHT = 'Copyright © 2015-2024 amphineko. Illustrations have their own licenses.' 191 | 192 | interface Account { 193 | platform: string 194 | name: string | JSX.Element 195 | url?: string 196 | icon: JSX.Element 197 | iconBackground: string 198 | redacted?: boolean 199 | } 200 | 201 | interface Accounts { 202 | type: string 203 | accounts: Account[] 204 | } 205 | 206 | interface ProfileHeaderTag { 207 | tag: string 208 | comment?: string 209 | icon?: JSX.Element 210 | } 211 | 212 | interface ProfileHeaderTagGroup { 213 | title: string 214 | tags: ProfileHeaderTag[] 215 | } 216 | -------------------------------------------------------------------------------- /src/components/display/header.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren, ReactNode } from 'react' 2 | import { BORDER_RADIUS, BORDER_RADIUS_SMALL } from '../../lib/css' 3 | 4 | export const ProfileNameStandout = ({ 5 | backgroundColor, 6 | children, 7 | hoverColor, 8 | hoverRuby, 9 | href, 10 | ruby, 11 | }: PropsWithChildren<{ 12 | backgroundColor: string 13 | hoverColor: string 14 | hoverRuby?: string 15 | href?: string 16 | ruby?: string 17 | }>) => { 18 | ruby = ruby || '' 19 | hoverRuby = hoverRuby || ruby || '' 20 | href = href || '#' 21 | 22 | return ( 23 | 24 | {children} 25 | 63 | 64 | ) 65 | } 66 | 67 | export const ProfileName = ({ children }: PropsWithChildren) => ( 68 |
69 |

{children}

70 | common-name 71 | 100 |
101 | ) 102 | 103 | export const ProfileAddonGroup = ({ 104 | children, 105 | title, 106 | }: PropsWithChildren<{ 107 | title: string 108 | }>) => ( 109 |
110 |
111 |

{title}

112 |
113 |
{children}
114 | 146 |
147 | ) 148 | 149 | export const ProfileAddons = ({ children }: PropsWithChildren) => ( 150 |
151 | {children} 152 | 172 |
173 | ) 174 | 175 | export interface ProfilePictureSources { 176 | src: string 177 | avif?: string 178 | jpeg?: string 179 | png?: string 180 | webp?: string 181 | } 182 | 183 | const ProfilePicture = ({ avif, jpeg, png, webp }: ProfilePictureSources) => ( 184 | 185 | {avif && } 186 | {webp && } 187 | {png && } 188 | profile picture 189 | 190 | ) 191 | 192 | export const Header = ({ 193 | background, 194 | children, 195 | profileName, 196 | profilePicture, 197 | }: PropsWithChildren<{ 198 | background?: string 199 | profileName: ReactNode 200 | profilePicture: ProfilePictureSources 201 | }>) => ( 202 |
203 |
204 | 205 | 206 | 207 |
208 | 209 |
210 | {profileName} 211 | {children} 212 |
213 | 214 | 260 |
261 | ) 262 | --------------------------------------------------------------------------------