├── .bun-version ├── .github └── workflows │ ├── ci.yml │ └── deploy.yml ├── .gitignore ├── .node-version ├── .npmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── bun.lockb ├── eslint.config.js ├── package.json ├── postcss.config.js ├── src ├── app.css ├── app.d.ts ├── app.html ├── lib │ ├── AtCoderContestResult.ts │ ├── calculateRating.test.ts │ ├── calculateRating.ts │ ├── getRatingColor.ts │ ├── index.ts │ └── server │ │ └── fetchAtCoderContestResults.ts └── routes │ ├── +layout.svelte │ ├── +page.svelte │ └── api │ └── [userId] │ └── [contestType] │ └── +server.ts ├── static └── favicon.svg ├── svelte.config.js ├── tailwind.config.ts ├── tsconfig.json ├── vite.config.ts └── wrangler.toml /.bun-version: -------------------------------------------------------------------------------- 1 | 1.1.42 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | - name: Setup tools 17 | uses: jdx/mise-action@v2 18 | - name: Install Dependencies 19 | run: bun install --frozen-lockfile 20 | - name: Check format and lint 21 | run: bun run lint 22 | 23 | check: 24 | runs-on: ubuntu-24.04 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | - name: Setup tools 29 | uses: jdx/mise-action@v2 30 | - name: Install Dependencies 31 | run: bun install --frozen-lockfile 32 | - name: Check TypeScript 33 | run: bun run check 34 | 35 | test: 36 | runs-on: ubuntu-24.04 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v3 40 | - name: Setup tools 41 | uses: jdx/mise-action@v2 42 | - name: Install Dependencies 43 | run: bun install --frozen-lockfile 44 | - name: Run tests 45 | run: bun run test 46 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Cloudflare Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-24.04 13 | permissions: 14 | contents: read 15 | deployments: write 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | - name: Setup tools 20 | uses: jdx/mise-action@v2 21 | - name: Install Dependencies 22 | run: bun install --frozen-lockfile 23 | - name: Build 24 | run: bun run build 25 | - name: Publish to Cloudflare Pages 26 | uses: cloudflare/wrangler-action@v3 27 | with: 28 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} 29 | accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 30 | command: pages deploy --commit-dirty=true 31 | gitHubToken: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | .netlify 7 | .wrangler 8 | /.svelte-kit 9 | /build 10 | 11 | # OS 12 | .DS_Store 13 | Thumbs.db 14 | 15 | # Env 16 | .env 17 | .env.* 18 | !.env.example 19 | !.env.test 20 | 21 | # Vite 22 | vite.config.js.timestamp-* 23 | vite.config.ts.timestamp-* 24 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 22.12.0 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 7 | "overrides": [ 8 | { 9 | "files": "*.svelte", 10 | "options": { 11 | "parser": "svelte" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 3w36zj6 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 | # AtCoder Rating Estimator 2 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3w36zj6/atcoder-rating-estimator/be2432b283504198e353a72045301d5f9f892221/bun.lockb -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import prettier from 'eslint-config-prettier'; 2 | import js from '@eslint/js'; 3 | import { includeIgnoreFile } from '@eslint/compat'; 4 | import svelte from 'eslint-plugin-svelte'; 5 | import globals from 'globals'; 6 | import { fileURLToPath } from 'node:url'; 7 | import ts from 'typescript-eslint'; 8 | const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); 9 | 10 | export default ts.config( 11 | includeIgnoreFile(gitignorePath), 12 | js.configs.recommended, 13 | ...ts.configs.recommended, 14 | ...svelte.configs['flat/recommended'], 15 | prettier, 16 | ...svelte.configs['flat/prettier'], 17 | { 18 | languageOptions: { 19 | globals: { 20 | ...globals.browser, 21 | ...globals.node 22 | } 23 | } 24 | }, 25 | { 26 | files: ['**/*.svelte'], 27 | 28 | languageOptions: { 29 | parserOptions: { 30 | parser: ts.parser 31 | } 32 | } 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atcoder-rating-estimator", 3 | "private": true, 4 | "version": "0.0.1", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite dev", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 12 | "format": "prettier --write .", 13 | "lint": "prettier --check . && eslint .", 14 | "test:unit": "vitest", 15 | "test": "npm run test:unit -- --run", 16 | "deploy": "bun run build && wrangler pages deploy", 17 | "cf-typegen": "wrangler types && mv worker-configuration.d.ts src/" 18 | }, 19 | "devDependencies": { 20 | "@cloudflare/workers-types": "^4.20241224.0", 21 | "@eslint/compat": "^1.2.3", 22 | "@sveltejs/adapter-auto": "^3.0.0", 23 | "@sveltejs/adapter-cloudflare": "^5.0.0", 24 | "@sveltejs/kit": "^2.0.0", 25 | "@sveltejs/vite-plugin-svelte": "^5.0.0", 26 | "autoprefixer": "^10.4.20", 27 | "eslint": "^9.7.0", 28 | "eslint-config-prettier": "^9.1.0", 29 | "eslint-plugin-svelte": "^2.36.0", 30 | "globals": "^15.0.0", 31 | "prettier": "^3.3.2", 32 | "prettier-plugin-svelte": "^3.2.6", 33 | "prettier-plugin-tailwindcss": "^0.6.5", 34 | "svelte": "^5.0.0", 35 | "svelte-check": "^4.0.0", 36 | "tailwindcss": "^3.4.9", 37 | "typescript": "^5.0.0", 38 | "typescript-eslint": "^8.0.0", 39 | "vite": "^6.0.0", 40 | "vitest": "^2.0.4", 41 | "wrangler": "^3.99.0" 42 | }, 43 | "dependencies": { 44 | "@tailwindcss/container-queries": "^0.1.1", 45 | "@tailwindcss/typography": "^0.5.15", 46 | "chart.js": "^4.4.7" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://svelte.dev/docs/kit/types#app.d.ts 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/AtCoderContestResult.ts: -------------------------------------------------------------------------------- 1 | export type AtCoderContestResult = { 2 | isRated: boolean; 3 | place: number; 4 | oldRating: number; 5 | newRating: number; 6 | performance: number; 7 | innerPerformance: number; 8 | contestScreenName: string; 9 | contestName: string; 10 | contestNameEn: string; 11 | endTime: string; 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/calculateRating.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { calculateAlgorithmRating, calculateHeuristicRating } from './calculateRating'; 3 | 4 | describe('calculateAlgorithmRating', () => { 5 | it("should return the correct rating for tourist's performances", () => { 6 | // https://atcoder.jp/users/tourist/history 7 | const contestResults = [ 8 | { performance: 3920 }, 9 | { performance: 3200 }, 10 | { performance: 4310 }, 11 | { performance: 4391 }, 12 | { performance: 4356 }, 13 | { performance: 4017 }, 14 | { performance: 3687 }, 15 | { performance: 4152 }, 16 | { performance: 4395 }, 17 | { performance: 4415 } 18 | ]; 19 | const expectedRatings = [2720, 2851, 3368, 3647, 3802, 3834, 3806, 3860, 3949, 4021]; 20 | 21 | for (const i of Array(contestResults.length).keys()) { 22 | expect(calculateAlgorithmRating(contestResults.slice(0, i + 1))).toBeCloseTo( 23 | expectedRatings[i], 24 | 0 25 | ); 26 | } 27 | }); 28 | it("should return the correct rating for chokudai's performances", () => { 29 | // https://atcoder.jp/users/chokudai/history 30 | const contestResults = [ 31 | { performance: 2455 }, 32 | { performance: 3192 }, 33 | { performance: 3623 }, 34 | { performance: 2974 }, 35 | { performance: 2813 }, 36 | { performance: 3002 }, 37 | { performance: 3311 }, 38 | { performance: 2998 }, 39 | { performance: 2898 }, 40 | { performance: 3457 } 41 | ]; 42 | const expectedRatings = [1255, 2155, 2676, 2728, 2733, 2775, 2872, 2887, 2885, 2978]; 43 | 44 | for (const i of Array(contestResults.length).keys()) { 45 | expect(calculateAlgorithmRating(contestResults.slice(0, i + 1))).toBeCloseTo( 46 | expectedRatings[i], 47 | 0 48 | ); 49 | } 50 | }); 51 | }); 52 | 53 | describe('calculateHeuristicRating', () => { 54 | it("should return the correct rating for tourist's performances", () => { 55 | // https://atcoder.jp/users/tourist/history 56 | 57 | const contestResults = [ 58 | { performance: 3254 }, 59 | { performance: 2119 }, 60 | { performance: 2471 }, 61 | { performance: 1297 }, 62 | { performance: 1446 } 63 | ]; 64 | const expectedRatings = [2254, 2299, 2382, 2382, 2383]; 65 | 66 | for (const i of Array(contestResults.length).keys()) { 67 | expect(calculateHeuristicRating(contestResults.slice(0, i + 1))).toBeCloseTo( 68 | expectedRatings[i], 69 | 0 70 | ); 71 | } 72 | }); 73 | it("should return the correct rating for chokudai's performances", () => { 74 | // https://atcoder.jp/users/chokudai/history 75 | const contestResults = [ 76 | { performance: 1338 }, 77 | { performance: 2399 }, 78 | { performance: 2531 }, 79 | { performance: 2789 }, 80 | { performance: 2269 } 81 | ]; 82 | const expectedRatings = [343, 1453, 1850, 2160, 2215]; 83 | 84 | for (const i of Array(contestResults.length).keys()) { 85 | expect(calculateHeuristicRating(contestResults.slice(0, i + 1))).toBeCloseTo( 86 | expectedRatings[i], 87 | 0 88 | ); 89 | } 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /src/lib/calculateRating.ts: -------------------------------------------------------------------------------- 1 | import type { AtCoderContestResult } from './AtCoderContestResult'; 2 | 3 | export const applyRatingCorrection = (rating: number): number => { 4 | if (rating < 400) { 5 | return 400 / Math.exp((400 - rating) / 400); 6 | } 7 | return rating; 8 | }; 9 | 10 | export const inverseRatingCorrection = (correctedRating: number): number => { 11 | if (correctedRating >= 400) { 12 | return correctedRating; 13 | } 14 | return 400 - 400 * Math.log(400 / correctedRating); 15 | }; 16 | 17 | type ContestResult = Pick; 18 | 19 | export const calculateAlgorithmRating = (contestResults: ContestResult[]): number => { 20 | if (contestResults.length === 0) { 21 | throw new Error('performances is empty'); 22 | } 23 | const f = (n: number) => { 24 | const F = (n: number) => { 25 | let numerator = 0; 26 | let denominator = 0; 27 | for (let i = 1; i <= n; i++) { 28 | numerator += Math.pow(0.81, i); 29 | denominator += Math.pow(0.9, i); 30 | } 31 | return Math.sqrt(numerator) / denominator; 32 | }; 33 | const FInf = Math.sqrt(0.81 / (1 - 0.81)) / (0.9 / (1 - 0.9)); 34 | return ((F(n) - FInf) / (F(1) - FInf)) * 1200; 35 | }; 36 | const g = (x: number) => Math.pow(2, x / 800); 37 | const gInv = (y: number) => 800 * Math.log2(y); 38 | 39 | let weightedGSum = 0; 40 | let weightSum = 0; 41 | const performances = contestResults.map((result) => result.performance).reverse(); 42 | for (let i = 0; i < performances.length; i++) { 43 | const weight = Math.pow(0.9, i); 44 | weightedGSum += g(performances[i]) * weight; 45 | weightSum += weight; 46 | } 47 | 48 | let rating = gInv(weightedGSum / weightSum) - f(performances.length); 49 | rating = applyRatingCorrection(rating); 50 | return rating; 51 | }; 52 | 53 | export const calculateHeuristicRating = (contestResults: ContestResult[]): number => { 54 | const S = 724.4744301; 55 | const R = 0.8271973364; 56 | 57 | const extendedPerformances = contestResults 58 | .map((result) => result.performance) 59 | .flatMap((p) => Array.from({ length: 100 }, (_, j) => p - S * Math.log(j + 1))) 60 | .sort((a, b) => b - a); 61 | 62 | let ratingDenominator = 0; 63 | let ratingNumerator = 0; 64 | 65 | for (let i = 0; i < 100; i++) { 66 | const power = R ** (i + 1); 67 | ratingDenominator += extendedPerformances[i] * power; 68 | ratingNumerator += power; 69 | } 70 | 71 | let rating = ratingDenominator / ratingNumerator; 72 | rating = applyRatingCorrection(rating); 73 | return rating; 74 | }; 75 | -------------------------------------------------------------------------------- /src/lib/getRatingColor.ts: -------------------------------------------------------------------------------- 1 | export const getRatingColor = (rating: number): [number, number, number] => { 2 | if (rating < 400) return [128, 128, 128]; 3 | else if (rating < 800) return [128, 64, 0]; 4 | else if (rating < 1200) return [0, 128, 0]; 5 | else if (rating < 1600) return [0, 192, 192]; 6 | else if (rating < 2000) return [0, 0, 255]; 7 | else if (rating < 2400) return [192, 192, 0]; 8 | else if (rating < 2800) return [255, 128, 0]; 9 | return [255, 0, 0]; 10 | }; 11 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /src/lib/server/fetchAtCoderContestResults.ts: -------------------------------------------------------------------------------- 1 | import type { AtCoderContestResult } from '$lib/AtCoderContestResult'; 2 | 3 | export type ContestType = 'algorithm' | 'heuristic'; 4 | 5 | type RawAtCoderContestResult = { 6 | IsRated: boolean; 7 | Place: number; 8 | OldRating: number; 9 | NewRating: number; 10 | Performance: number; 11 | InnerPerformance: number; 12 | ContestScreenName: string; 13 | ContestName: string; 14 | ContestNameEn: string; 15 | EndTime: string; 16 | }; 17 | 18 | export const fetchAtCoderContestResults = async ( 19 | userId: string, 20 | contestType: ContestType 21 | ): Promise => { 22 | // NOTE: Using proxy to bypass AtCoder's Web Application Firewall. 23 | //const url = `https://atcoder.jp/users/${userId}/history/json?contestType=${contestType === 'algorithm' ? 'algo' : 'heuristic'}`; 24 | const url = `https://script.google.com/macros/s/AKfycby-CKUsGoe7YZBeIf9FMwvCK6JIfqnVkN-8764iNkRApGPns158Q6vJPxpDdnGfsDXU/exec?type=${contestType === 'algorithm' ? 'algo' : 'heuristic'}&id=${userId}`; 25 | const response = await fetch(url); 26 | const contestResults = await response.json(); 27 | 28 | return contestResults.map((contestResult) => { 29 | return { 30 | isRated: contestResult.IsRated, 31 | place: contestResult.Place, 32 | oldRating: contestResult.OldRating, 33 | newRating: contestResult.NewRating, 34 | performance: contestResult.Performance, 35 | innerPerformance: contestResult.InnerPerformance, 36 | contestScreenName: contestResult.ContestScreenName, 37 | contestName: contestResult.ContestName, 38 | contestNameEn: contestResult.ContestNameEn, 39 | endTime: contestResult.EndTime 40 | }; 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 |
9 |
10 | {@render children()} 11 |
12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 198 | 199 | 200 | AtCoder Rating Estimator 201 | 205 | 206 | 207 |
208 |
209 |

AtCoder Rating Estimator

210 |

211 | AtCoderのRatedコンテストで目標レーティングへの到達に必要なパフォーマンスを計算しグラフで表示します。 212 |

213 |
214 |
215 |

コンテスト区分

216 |
217 | 245 |
246 |
247 |
248 |

過去のパフォーマンス

249 |
250 | 258 |
259 | 260 |
261 | 267 |
268 |

269 | 改行区切りで入力してください。パフォーマンスの値はマイナス補正適用後の値を入力してください。 270 |

271 |
272 | 277 |
278 |
279 | 285 |
286 |
287 |
288 |

現在のレーティング

289 |

{rating || '未参加'}

290 |
291 |
292 |

次回のレーティング

293 |
294 |
295 |
296 |

Twitterでつぶやく

297 |
    298 |
  • 299 | Tweet 304 |
  • 305 |
306 |
307 |
308 |

View on GitHub

309 |

310 | atcoder-rating-estimator 316 |

317 |
318 |
319 |

Bibliography

320 | 330 |
331 |
332 | -------------------------------------------------------------------------------- /src/routes/api/[userId]/[contestType]/+server.ts: -------------------------------------------------------------------------------- 1 | import { fetchAtCoderContestResults } from '$lib/server/fetchAtCoderContestResults'; 2 | import { json } from '@sveltejs/kit'; 3 | 4 | export const GET = async ({ params }) => { 5 | if (params.contestType !== 'algorithm' && params.contestType !== 'heuristic') { 6 | return json([], { status: 400 }); 7 | } 8 | 9 | const contestResults = await fetchAtCoderContestResults(params.userId, params.contestType); 10 | 11 | return json(contestResults); 12 | }; 13 | -------------------------------------------------------------------------------- /static/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-cloudflare'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://svelte.dev/docs/kit/integrations 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. 12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 13 | // See https://svelte.dev/docs/kit/adapters for more information about adapters. 14 | adapter: adapter() 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import containerQueries from '@tailwindcss/container-queries'; 2 | import typography from '@tailwindcss/typography'; 3 | import type { Config } from 'tailwindcss'; 4 | 5 | export default { 6 | content: ['./src/**/*.{html,js,svelte,ts}'], 7 | 8 | theme: { 9 | extend: {} 10 | }, 11 | 12 | plugins: [typography, containerQueries] 13 | } satisfies Config; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler", 13 | "types": ["@cloudflare/workers-types/2023-07-01"] 14 | } 15 | // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 16 | // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 17 | // 18 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 19 | // from the referenced tsconfig.json - TypeScript does not merge them in 20 | } 21 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | import { sveltekit } from '@sveltejs/kit/vite'; 3 | 4 | export default defineConfig({ 5 | // @ts-expect-error https://github.com/sveltejs/kit/issues/13102#issuecomment-2515203461 6 | plugins: [sveltekit()], 7 | 8 | test: { 9 | include: ['src/**/*.{test,spec}.{js,ts}'] 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | #:schema node_modules/wrangler/config-schema.json 2 | name = "atcoder-rating-estimator" 3 | compatibility_date = "2024-12-24" 4 | pages_build_output_dir = ".svelte-kit/cloudflare" 5 | --------------------------------------------------------------------------------