Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 49033560fa | |||
| 3192180c60 | |||
| 05a6c10458 | |||
| 12115198b7 | |||
| 2e053a6388 | |||
| 973d188a26 | |||
| afd1d7a822 | |||
| 68aada0c33 | |||
| f8cf85661e | |||
| 668c32ed8a | |||
| 78c87aaedd | |||
| 4cc2b70dbc | |||
| a3955da7f2 | |||
| eb68c5d00b | |||
| 05e6182bcf | |||
| 8f21b56223 | |||
| 248c4a8523 | |||
| 469f0df756 | |||
| 7e101ba274 | |||
| b59eebcddb | |||
| d4d7015df6 | |||
| b1284bad71 | |||
| 05ef985851 | |||
| ae7d880bc1 | |||
| 2215cab77f | |||
| 7d642b0be3 | |||
| 8f3b652740 | |||
| a861b1c1b6 | |||
| 62bfdc8090 | |||
| 7d8e3a6de0 | |||
| 8093d4d308 | |||
| f583ff54a9 | |||
| 43c50084c6 | |||
| ea61061530 | |||
| 8b73a68a6b | |||
| 274f5b3b53 | |||
| e81be2cf68 | |||
| 523136ffbc | |||
| 24b990ac62 | |||
| 09ce400cd7 | |||
| d811efc394 | |||
| 66aea51c39 | |||
| 153c0e9f13 | |||
| 288438a953 | |||
| 0404188d4d | |||
| 366bfbeb54 | |||
| be89c68f89 | |||
| 9ab7ee91ea | |||
| 408ee9df1e | |||
| 36fbe8a685 | |||
| 110586942d | |||
| 515de7f747 | |||
| 2a541196a4 | |||
| 78da0877c7 | |||
| 0739d3bf7b | |||
| 5279267c8e | |||
| b3dc4c3d73 | |||
| f65fc0a0ea | |||
| befc14d894 | |||
| e33614862e | |||
| 3ca1dfe310 | |||
| 2b188b089c |
@@ -1,3 +0,0 @@
|
|||||||
> 1%
|
|
||||||
last 2 versions
|
|
||||||
not dead
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.{js,json,yml}]
|
|
||||||
charset = utf-8
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
80
.env.example
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# CS2.WTF Environment Configuration
|
||||||
|
# Copy this file to .env for local development
|
||||||
|
# DO NOT commit .env to version control
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# API Configuration
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Backend API Base URL
|
||||||
|
# Development: Vite proxy forwards /api to this URL (default: http://localhost:8000)
|
||||||
|
# Production: Set to your actual backend URL (e.g., https://api.csgow.tf)
|
||||||
|
# Note: In development, the frontend uses /api and Vite proxies to this URL
|
||||||
|
VITE_API_BASE_URL=http://localhost:8000
|
||||||
|
|
||||||
|
# API request timeout in milliseconds
|
||||||
|
# Default: 10000 (10 seconds)
|
||||||
|
VITE_API_TIMEOUT=10000
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Feature Flags
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Enable live match updates (polling/WebSocket)
|
||||||
|
# Default: false
|
||||||
|
VITE_ENABLE_LIVE_MATCHES=false
|
||||||
|
|
||||||
|
# Enable analytics tracking
|
||||||
|
# Default: true (respects user consent)
|
||||||
|
VITE_ENABLE_ANALYTICS=true
|
||||||
|
|
||||||
|
# Enable debug mode (verbose logging, dev tools)
|
||||||
|
# Default: false
|
||||||
|
VITE_DEBUG_MODE=false
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Analytics & Tracking (Optional)
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Plausible Analytics
|
||||||
|
# Only required if analytics is enabled
|
||||||
|
# VITE_PLAUSIBLE_DOMAIN=cs2.wtf
|
||||||
|
# VITE_PLAUSIBLE_API_HOST=https://plausible.io
|
||||||
|
|
||||||
|
# Umami Analytics (alternative)
|
||||||
|
# VITE_UMAMI_WEBSITE_ID=your-website-id
|
||||||
|
# VITE_UMAMI_SRC=https://analytics.example.com/script.js
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Experimental Features
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Enable WebGL-based heatmaps (high performance)
|
||||||
|
# Default: false (use Canvas fallback)
|
||||||
|
# VITE_ENABLE_WEBGL_HEATMAPS=false
|
||||||
|
|
||||||
|
# Enable MSW API mocking in development
|
||||||
|
# Useful for frontend development without backend
|
||||||
|
# Default: false
|
||||||
|
# VITE_ENABLE_MSW_MOCKING=false
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Build Configuration
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# App version (auto-populated from package.json)
|
||||||
|
# VITE_APP_VERSION=2.0.0
|
||||||
|
|
||||||
|
# Build timestamp (auto-populated during build)
|
||||||
|
# VITE_BUILD_TIMESTAMP=2024-11-04T12:00:00Z
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# SSR/Deployment (Advanced)
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Public base URL for the application
|
||||||
|
# Used for canonical URLs, sitemaps, etc.
|
||||||
|
# PUBLIC_BASE_URL=https://cs2.wtf
|
||||||
|
|
||||||
|
# Origin whitelist for CORS (if handling API in same domain)
|
||||||
|
# PUBLIC_CORS_ORIGINS=https://cs2.wtf,https://www.cs2.wtf
|
||||||
17
.eslintrc.js
@@ -1,17 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true
|
|
||||||
},
|
|
||||||
'extends': [
|
|
||||||
'plugin:vue/vue3-essential',
|
|
||||||
'eslint:recommended'
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
parser: 'babel-eslint'
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
304
.gitignore
vendored
@@ -1,286 +1,52 @@
|
|||||||
# Created by https://www.toptal.com/developers/gitignore/api/webstorm+all,yarn,windows,linux,node,vuejs
|
.DS_Store
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=webstorm+all,yarn,windows,linux,node,vuejs
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
### Linux ###
|
|
||||||
*~
|
|
||||||
|
|
||||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
|
||||||
.fuse_hidden*
|
|
||||||
|
|
||||||
# KDE directory preferences
|
|
||||||
.directory
|
|
||||||
|
|
||||||
# Linux trash folder which might appear on any partition or disk
|
|
||||||
.Trash-*
|
|
||||||
|
|
||||||
# .nfs files are created when an open file is removed but is still being accessed
|
|
||||||
.nfs*
|
|
||||||
|
|
||||||
### Node ###
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# Editor directories and files
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
# Runtime data
|
# Build artifacts
|
||||||
pids
|
dist
|
||||||
*.pid
|
dist-ssr
|
||||||
*.seed
|
*.local
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
# Test coverage
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
coverage
|
||||||
*.lcov
|
*.lcov
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
.nyc_output
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
# Playwright
|
||||||
.grunt
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
# Bower dependency directory (https://bower.io/)
|
/playwright/.cache/
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
|
||||||
web_modules/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variables file
|
|
||||||
.env
|
|
||||||
.env.test
|
|
||||||
.env.production
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
### Node Patch ###
|
|
||||||
# Serverless Webpack directories
|
|
||||||
.webpack/
|
|
||||||
|
|
||||||
### Vuejs ###
|
|
||||||
# Recommended template: Node.gitignore
|
|
||||||
|
|
||||||
dist/
|
|
||||||
npm-debug.log
|
|
||||||
yarn-error.log
|
|
||||||
|
|
||||||
### WebStorm+all ###
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### WebStorm+all Patch ###
|
|
||||||
# Ignores the whole .idea folder and all .iml files
|
|
||||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
|
||||||
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
|
||||||
|
|
||||||
*.iml
|
|
||||||
modules.xml
|
|
||||||
.idea/misc.xml
|
|
||||||
*.ipr
|
|
||||||
|
|
||||||
# Sonarlint plugin
|
|
||||||
.idea/sonarlint
|
|
||||||
|
|
||||||
### Windows ###
|
|
||||||
# Windows thumbnail cache files
|
|
||||||
Thumbs.db
|
|
||||||
Thumbs.db:encryptable
|
|
||||||
ehthumbs.db
|
|
||||||
ehthumbs_vista.db
|
|
||||||
|
|
||||||
# Dump file
|
|
||||||
*.stackdump
|
|
||||||
|
|
||||||
# Folder config file
|
|
||||||
[Dd]esktop.ini
|
|
||||||
|
|
||||||
# Recycle Bin used on file shares
|
|
||||||
$RECYCLE.BIN/
|
|
||||||
|
|
||||||
# Windows Installer files
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msix
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# Windows shortcuts
|
|
||||||
*.lnk
|
|
||||||
|
|
||||||
### yarn ###
|
|
||||||
# https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored
|
|
||||||
|
|
||||||
.yarn/*
|
|
||||||
!.yarn/releases
|
|
||||||
!.yarn/plugins
|
|
||||||
!.yarn/sdks
|
|
||||||
!.yarn/versions
|
|
||||||
|
|
||||||
# if you are NOT using Zero-installs, then:
|
|
||||||
# comment the following lines
|
|
||||||
#!.yarn/cache
|
|
||||||
|
|
||||||
# and uncomment the following lines
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/webstorm+all,yarn,windows,linux,node,vuejs
|
|
||||||
|
|
||||||
|
# Vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
.tmp
|
||||||
|
tmp
|
||||||
|
*.tmp
|
||||||
|
|||||||
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx lint-staged
|
||||||
30
.prettierignore
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
dist/
|
||||||
|
.vercel/
|
||||||
|
.netlify/
|
||||||
|
.output/
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
src-tauri/target/
|
||||||
|
**/.svelte-kit/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
17
.prettierrc.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"semi": true,
|
||||||
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.svelte",
|
||||||
|
"options": {
|
||||||
|
"parser": "svelte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
6
.stylelintignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
.svelte-kit/
|
||||||
|
dist/
|
||||||
|
**/*.js
|
||||||
|
**/*.ts
|
||||||
13
.stylelintrc.cjs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['stylelint-config-standard'],
|
||||||
|
rules: {
|
||||||
|
'at-rule-no-unknown': [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
ignoreAtRules: ['tailwind', 'apply', 'variants', 'responsive', 'screen', 'layer']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'selector-class-pattern': null,
|
||||||
|
'custom-property-pattern': null
|
||||||
|
}
|
||||||
|
};
|
||||||
1
.tool-versions
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nodejs 20.11.0
|
||||||
100
.woodpecker.yml
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
pipeline:
|
||||||
|
install:
|
||||||
|
image: node:20
|
||||||
|
commands:
|
||||||
|
- npm ci
|
||||||
|
pull: true
|
||||||
|
|
||||||
|
lint:
|
||||||
|
image: node:20
|
||||||
|
commands:
|
||||||
|
- npm run lint
|
||||||
|
depends_on:
|
||||||
|
- install
|
||||||
|
pull: true
|
||||||
|
|
||||||
|
type-check:
|
||||||
|
image: node:20
|
||||||
|
commands:
|
||||||
|
- npm run check
|
||||||
|
depends_on:
|
||||||
|
- install
|
||||||
|
pull: true
|
||||||
|
|
||||||
|
test:
|
||||||
|
image: node:20
|
||||||
|
commands:
|
||||||
|
- npm run test
|
||||||
|
depends_on:
|
||||||
|
- install
|
||||||
|
pull: true
|
||||||
|
|
||||||
|
build:
|
||||||
|
image: node:20
|
||||||
|
commands:
|
||||||
|
- npm run build
|
||||||
|
environment:
|
||||||
|
- VITE_API_BASE_URL=https://api.csgow.tf
|
||||||
|
secrets:
|
||||||
|
- vite_plausible_domain
|
||||||
|
- vite_sentry_dsn
|
||||||
|
depends_on:
|
||||||
|
- lint
|
||||||
|
- type-check
|
||||||
|
- test
|
||||||
|
pull: true
|
||||||
|
|
||||||
|
# E2E tests (optional - can be resource intensive)
|
||||||
|
# test-e2e:
|
||||||
|
# image: mcr.microsoft.com/playwright:v1.40.0-jammy
|
||||||
|
# commands:
|
||||||
|
# - npm run test:e2e
|
||||||
|
# depends_on:
|
||||||
|
# - build
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
image: cschlosser/drone-ftps
|
||||||
|
settings:
|
||||||
|
hostname:
|
||||||
|
from_secret: ftp_host
|
||||||
|
src_dir: '/build/'
|
||||||
|
clean_dir: true
|
||||||
|
secrets: [ftp_username, ftp_password]
|
||||||
|
when:
|
||||||
|
branch: master
|
||||||
|
event: [push, tag]
|
||||||
|
status: success
|
||||||
|
|
||||||
|
deploy-dev:
|
||||||
|
image: cschlosser/drone-ftps
|
||||||
|
settings:
|
||||||
|
hostname:
|
||||||
|
from_secret: ftp_host
|
||||||
|
src_dir: '/build/'
|
||||||
|
clean_dir: true
|
||||||
|
secrets:
|
||||||
|
- source: ftp_username_dev
|
||||||
|
target: ftp_username
|
||||||
|
- source: ftp_password_dev
|
||||||
|
target: ftp_password
|
||||||
|
when:
|
||||||
|
branch: dev
|
||||||
|
event: [push, tag]
|
||||||
|
status: success
|
||||||
|
|
||||||
|
deploy-cs2:
|
||||||
|
image: cschlosser/drone-ftps
|
||||||
|
settings:
|
||||||
|
hostname:
|
||||||
|
from_secret: ftp_host_cs2
|
||||||
|
src_dir: '/build/'
|
||||||
|
clean_dir: true
|
||||||
|
secrets:
|
||||||
|
- source: ftp_username_cs2
|
||||||
|
target: ftp_username
|
||||||
|
- source: ftp_password_cs2
|
||||||
|
target: ftp_password
|
||||||
|
when:
|
||||||
|
branch: cs2-port
|
||||||
|
event: [push]
|
||||||
|
status: success
|
||||||
363
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
631
.yarn/releases/yarn-3.0.2.cjs
vendored
@@ -1,7 +0,0 @@
|
|||||||
nodeLinker: node-modules
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
|
||||||
spec: "@yarnpkg/plugin-interactive-tools"
|
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-3.0.2.cjs
|
|
||||||
60
Jenkinsfile
vendored
@@ -1,60 +0,0 @@
|
|||||||
pipeline {
|
|
||||||
agent any
|
|
||||||
|
|
||||||
environment {
|
|
||||||
FTP_HOST = credentials('csgowtf-deploy-host')
|
|
||||||
LFTP_PASSWORD = credentials('csgowtf-deploy-password')
|
|
||||||
API_HOST = credentials('csgowtf-api-host')
|
|
||||||
TRACK_HOST = credentials('csgowtf-track-host')
|
|
||||||
TRACK_ID = credentials('csgowtf-track-id')
|
|
||||||
TRACK_DOMAINS = credentials('csgowtf-track-domains')
|
|
||||||
TRACK = credentials('csgowtf-track')
|
|
||||||
}
|
|
||||||
|
|
||||||
stages {
|
|
||||||
stage('Prepare') {
|
|
||||||
steps {
|
|
||||||
writeFile file: '.env.production', text: 'VUE_APP_API_URL=$API_HOST\nVUE_APP_TRACK_URL=$TRACK_HOST\nVUE_APP_TRACK_ID=$TRACK_ID\nVUE_APP_TRACK_DOMAINS=$TRACK_DOMAINS\nVUE_APP_TRACKING=$TRACK'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Install Dependencies') {
|
|
||||||
steps {
|
|
||||||
sh 'yarn install'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Build') {
|
|
||||||
steps {
|
|
||||||
sh 'yarn build'
|
|
||||||
archiveArtifacts artifacts: '**/dist/**', excludes: '**/node_modules/**'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Deploy') {
|
|
||||||
when {
|
|
||||||
branch 'master'
|
|
||||||
expression {
|
|
||||||
currentBuild.result == null || currentBuild.result == 'SUCCESS'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
environment {
|
|
||||||
FTP_USERNAME = credentials('csgowtf-deploy-user')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'lftp -u $FTP_USERNAME --env-password -e \'mirror --reverse --verbose --delete --recursion=always dist/ /\' $FTP_HOST'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stage('Deploy Dev') {
|
|
||||||
when {
|
|
||||||
branch 'dev'
|
|
||||||
expression {
|
|
||||||
currentBuild.result == null || currentBuild.result == 'SUCCESS'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
environment {
|
|
||||||
FTP_USERNAME = credentials('csgowtf-deploy-user-dev')
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'lftp -u $FTP_USERNAME --env-password -e \'mirror --reverse --verbose --delete --recursion=always dist/ /\' $FTP_HOST'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
325
README.md
@@ -1,27 +1,314 @@
|
|||||||
# CSGOW.TF
|
# CS2.WTF
|
||||||
|
|
||||||
[](https://vuejs.org/)
|
[](https://kit.svelte.dev/)
|
||||||
[](https://go.dev/)
|
[](https://www.typescriptlang.org/)
|
||||||
|
[](https://tailwindcss.com/)
|
||||||
[](https://git.harting.dev/CSGOWTF/csgowtf/src/branch/master/LICENSE)
|
[](https://git.harting.dev/CSGOWTF/csgowtf/src/branch/master/LICENSE)
|
||||||
[](https://liberapay.com/CSGOWTF/)
|
[](https://liberapay.com/CSGOWTF/)
|
||||||
[](https://liberapay.com/CSGOWTF/)
|
[](https://ci.somegit.dev/CSGOWTF/csgowtf)
|
||||||
[](https://csgow.tf/)
|
|
||||||
<!--[](https://www.typescriptlang.org/)-->
|
|
||||||
|
|
||||||
### Statistics for CS:GO matchmaking matches.
|
**Statistics for CS2 matchmaking matches** - A complete rewrite of CSGOW.TF with modern web technologies.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Backend
|
## 🚀 Quick Start
|
||||||
This is the frontend to the [csgowtfd](https://git.harting.dev/CSGOWTF/csgowtfd) backend.
|
|
||||||
|
|
||||||
## Tips on how to contribute
|
### Prerequisites
|
||||||
- If you are implementing or fixing an issue, please comment on the issue so work is not duplicated.
|
|
||||||
- If you want to implement a new feature, create an issue first describing the issue, so we know about it.
|
- **Node.js** ≥ 18.0.0 (v20.11.0 recommended - see `.nvmrc`)
|
||||||
- Don't commit unnecessary changes to the codebase or debugging code.
|
- **npm** or **yarn**
|
||||||
- Write meaningful commits or squash them.
|
|
||||||
- Please try to follow the code style of the rest of the codebase.
|
### Installation
|
||||||
- Only make pull requests to the dev branch.
|
|
||||||
- Only implement one feature per pull request to keep it easy to understand.
|
```bash
|
||||||
- Expect comments or questions on your pull request from the project maintainers. We try to keep the code as consistent and maintainable as possible.
|
# Clone the repository
|
||||||
- Each pull request should come from a new branch in your fork, it should have a meaningful name.
|
git clone https://somegit.dev/CSGOWTF/csgowtf.git
|
||||||
|
cd csgowtf
|
||||||
|
|
||||||
|
# Switch to the cs2-port branch
|
||||||
|
git checkout cs2-port
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Copy environment variables
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Start development server
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The app will be available at `http://localhost:5173`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Tech Stack
|
||||||
|
|
||||||
|
### Core Framework
|
||||||
|
|
||||||
|
- **SvelteKit 2.0** - Full-stack framework with SSR/SSG
|
||||||
|
- **Svelte 5** - Reactive UI framework
|
||||||
|
- **TypeScript 5.3** - Type safety (strict mode)
|
||||||
|
- **Vite 5** - Build tool and dev server
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
|
||||||
|
- **Tailwind CSS 3.4** - Utility-first CSS framework
|
||||||
|
- **DaisyUI 4.0** - Component library with CS2 custom themes
|
||||||
|
- **PostCSS** - CSS processing
|
||||||
|
|
||||||
|
### Data & State
|
||||||
|
|
||||||
|
- **Axios** - HTTP client for API requests
|
||||||
|
- **Zod** - Runtime type validation and parsing
|
||||||
|
- **Svelte Stores** - State management
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- **Vitest** - Unit and component testing
|
||||||
|
- **Playwright** - End-to-end testing
|
||||||
|
- **Testing Library** - Component testing utilities
|
||||||
|
- **MSW** - API mocking
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
- **ESLint** - Linting (TypeScript + Svelte)
|
||||||
|
- **Prettier** - Code formatting
|
||||||
|
- **Stylelint** - CSS linting
|
||||||
|
- **Husky** - Git hooks
|
||||||
|
- **lint-staged** - Pre-commit linting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Development
|
||||||
|
|
||||||
|
### Available Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
npm run dev # Start dev server
|
||||||
|
npm run dev -- --host # Expose to network
|
||||||
|
|
||||||
|
# Type Checking
|
||||||
|
npm run check # Run type check
|
||||||
|
npm run check:watch # Type check in watch mode
|
||||||
|
|
||||||
|
# Linting & Formatting
|
||||||
|
npm run lint # Run ESLint + Prettier check
|
||||||
|
npm run lint:fix # Auto-fix linting issues
|
||||||
|
npm run format # Format code with Prettier
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
npm run test # Run unit tests
|
||||||
|
npm run test:watch # Run tests in watch mode
|
||||||
|
npm run test:coverage # Generate coverage report
|
||||||
|
npm run test:e2e # Run E2E tests (headless)
|
||||||
|
npm run test:e2e:ui # Run E2E tests with UI
|
||||||
|
npm run test:e2e:debug # Debug E2E tests
|
||||||
|
|
||||||
|
# Building
|
||||||
|
npm run build # Build for production
|
||||||
|
npm run preview # Preview production build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
csgowtf/
|
||||||
|
├── src/
|
||||||
|
│ ├── lib/
|
||||||
|
│ │ ├── api/ # API client & endpoints
|
||||||
|
│ │ ├── components/ # Reusable Svelte components
|
||||||
|
│ │ │ ├── layout/ # Header, Footer, Nav
|
||||||
|
│ │ │ ├── ui/ # Base UI components
|
||||||
|
│ │ │ ├── charts/ # Data visualization
|
||||||
|
│ │ │ ├── match/ # Match-specific components
|
||||||
|
│ │ │ └── player/ # Player-specific components
|
||||||
|
│ │ ├── stores/ # Svelte stores (state)
|
||||||
|
│ │ ├── types/ # TypeScript types
|
||||||
|
│ │ ├── utils/ # Helper functions
|
||||||
|
│ │ └── i18n/ # Internationalization
|
||||||
|
│ ├── routes/ # SvelteKit routes (pages)
|
||||||
|
│ ├── mocks/ # MSW mock handlers
|
||||||
|
│ ├── tests/ # Test setup
|
||||||
|
│ ├── app.html # HTML shell
|
||||||
|
│ └── app.css # Global styles
|
||||||
|
├── tests/
|
||||||
|
│ ├── unit/ # Unit tests
|
||||||
|
│ ├── integration/ # Integration tests
|
||||||
|
│ └── e2e/ # E2E tests
|
||||||
|
├── docs/ # Documentation
|
||||||
|
│ ├── API.md # Backend API reference
|
||||||
|
│ └── TODO.md # Project roadmap
|
||||||
|
├── public/ # Static assets
|
||||||
|
└── static/ # Additional static files
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Features
|
||||||
|
|
||||||
|
### Current (Phase 1 - ✅ Complete)
|
||||||
|
|
||||||
|
- ✅ SvelteKit project scaffolded with TypeScript strict mode
|
||||||
|
- ✅ Tailwind CSS + DaisyUI with CS2-themed color palette
|
||||||
|
- ✅ Complete development tooling (ESLint, Prettier, Husky)
|
||||||
|
- ✅ Testing infrastructure (Vitest + Playwright)
|
||||||
|
- ✅ CI/CD pipeline (Woodpecker)
|
||||||
|
- ✅ Backend API documented
|
||||||
|
|
||||||
|
### Planned (See `docs/TODO.md` for details)
|
||||||
|
|
||||||
|
- 🏠 Homepage with featured matches
|
||||||
|
- 📊 Match listing with advanced filters
|
||||||
|
- 👤 Player profiles with stats & charts
|
||||||
|
- 🎮 Match detail pages (overview, economy, flashes, damage, chat)
|
||||||
|
- 🌍 Multi-language support (i18n)
|
||||||
|
- 🌙 Dark/Light theme toggle (default: dark)
|
||||||
|
- 📱 Mobile-responsive design
|
||||||
|
- ♿ WCAG 2.1 AA accessibility
|
||||||
|
- 🎯 CS2-specific features (MR12, Premier rating, volumetric smokes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 Backend
|
||||||
|
|
||||||
|
This frontend connects to the [csgowtfd](https://somegit.dev/CSGOWTF/csgowtfd) backend.
|
||||||
|
|
||||||
|
- **Language**: Go
|
||||||
|
- **Framework**: Gin
|
||||||
|
- **Database**: PostgreSQL
|
||||||
|
- **Cache**: Redis
|
||||||
|
- **API Docs**: See `docs/API.md`
|
||||||
|
|
||||||
|
Default API endpoint: `http://localhost:8000`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing
|
||||||
|
|
||||||
|
### Unit & Component Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
npm run test
|
||||||
|
|
||||||
|
# Watch mode for TDD
|
||||||
|
npm run test:watch
|
||||||
|
|
||||||
|
# Generate coverage report
|
||||||
|
npm run test:coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
### End-to-End Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run E2E tests (headless)
|
||||||
|
npm run test:e2e
|
||||||
|
|
||||||
|
# Run with Playwright UI
|
||||||
|
npm run test:e2e:ui
|
||||||
|
|
||||||
|
# Debug mode
|
||||||
|
npm run test:e2e:debug
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚢 Deployment
|
||||||
|
|
||||||
|
### Build for Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
The built app will be in the `build/` directory, ready to be deployed to any Node.js hosting platform.
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
See `.env.example` for all available configuration options:
|
||||||
|
|
||||||
|
- `VITE_API_BASE_URL` - Backend API URL
|
||||||
|
- `VITE_API_TIMEOUT` - API request timeout
|
||||||
|
- `VITE_ENABLE_LIVE_MATCHES` - Feature flag for live matches
|
||||||
|
- `VITE_ENABLE_ANALYTICS` - Feature flag for analytics
|
||||||
|
|
||||||
|
### CI/CD
|
||||||
|
|
||||||
|
Woodpecker CI automatically builds and deploys:
|
||||||
|
|
||||||
|
- **`master`** branch → Production
|
||||||
|
- **`dev`** branch → Development/Staging
|
||||||
|
- **`cs2-port`** branch → CS2 Preview (during rewrite)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
We welcome contributions! Please follow these guidelines:
|
||||||
|
|
||||||
|
### Before You Start
|
||||||
|
|
||||||
|
- Check existing issues or create one describing your feature/fix
|
||||||
|
- Comment on the issue to avoid duplicate work
|
||||||
|
- Fork the repository and create a feature branch
|
||||||
|
|
||||||
|
### Code Standards
|
||||||
|
|
||||||
|
- Follow TypeScript strict mode (no `any` types)
|
||||||
|
- Write tests for new features
|
||||||
|
- Follow existing code style (enforced by ESLint/Prettier)
|
||||||
|
- Keep components under 300 lines
|
||||||
|
- Write meaningful commit messages (Conventional Commits)
|
||||||
|
|
||||||
|
### Pull Request Process
|
||||||
|
|
||||||
|
1. Create a feature branch: `feature/your-feature-name`
|
||||||
|
2. Make your changes and commit with clear messages
|
||||||
|
3. Run linting and tests: `npm run lint && npm run test`
|
||||||
|
4. Push to your fork and create a PR to the `cs2-port` branch
|
||||||
|
5. Ensure CI passes and address review feedback
|
||||||
|
|
||||||
|
### Git Workflow
|
||||||
|
|
||||||
|
- Branch naming: `feature/`, `fix/`, `refactor/`, `docs/`
|
||||||
|
- Commit messages: `feat:`, `fix:`, `docs:`, `test:`, `refactor:`
|
||||||
|
- Only one feature/fix per PR
|
||||||
|
- Squash commits before merging
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
- **API Reference**: [`docs/API.md`](docs/API.md) - Complete backend API documentation
|
||||||
|
- **Project Roadmap**: [`docs/TODO.md`](docs/TODO.md) - Detailed implementation plan
|
||||||
|
- **SvelteKit Docs**: [kit.svelte.dev](https://kit.svelte.dev/)
|
||||||
|
- **Tailwind CSS**: [tailwindcss.com](https://tailwindcss.com/)
|
||||||
|
- **DaisyUI**: [daisyui.com](https://daisyui.com/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
[GPL-3.0](LICENSE) © CSGOW.TF Team
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💖 Support
|
||||||
|
|
||||||
|
If you find this project helpful, consider supporting us:
|
||||||
|
|
||||||
|
[](https://liberapay.com/CSGOWTF/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 Links
|
||||||
|
|
||||||
|
- **Website**: [csgow.tf](https://csgow.tf) (legacy CS:GO version)
|
||||||
|
- **Backend**: [csgowtfd](https://somegit.dev/CSGOWTF/csgowtfd)
|
||||||
|
- **Issues**: [Report a bug](https://somegit.dev/CSGOWTF/csgowtf/issues)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status**: 🚧 **Phase 1 Complete** - Active rewrite for CS2 support
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
1087
docs/API.md
Normal file
393
docs/CORS_PROXY.md
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
# API Proxying with SvelteKit Server Routes
|
||||||
|
|
||||||
|
This document explains how API requests are proxied to the backend using SvelteKit server routes.
|
||||||
|
|
||||||
|
## Why Use Server Routes?
|
||||||
|
|
||||||
|
The CS2.WTF frontend uses **SvelteKit server routes** to proxy API requests to the backend. This approach provides several benefits:
|
||||||
|
|
||||||
|
- ✅ **Works in all environments**: Development, preview, and production
|
||||||
|
- ✅ **No CORS issues**: Requests are server-side
|
||||||
|
- ✅ **Single code path**: Same behavior everywhere
|
||||||
|
- ✅ **Flexible backend switching**: Change one environment variable
|
||||||
|
- ✅ **Future-proof**: Can add caching, rate limiting, auth later
|
||||||
|
- ✅ **Better security**: Backend URL not exposed to client
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Request Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Browser → /api/matches → SvelteKit Server Route → Backend → Response
|
||||||
|
```
|
||||||
|
|
||||||
|
**Detailed Flow**:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Browser: GET http://localhost:5173/api/matches?limit=20
|
||||||
|
↓
|
||||||
|
2. SvelteKit: Routes to src/routes/api/[...path]/+server.ts
|
||||||
|
↓
|
||||||
|
3. Server Handler: Reads VITE_API_BASE_URL environment variable
|
||||||
|
↓
|
||||||
|
4. Backend Call: GET https://api.csgow.tf/matches?limit=20
|
||||||
|
↓
|
||||||
|
5. Backend: Returns JSON response
|
||||||
|
↓
|
||||||
|
6. Server Handler: Forwards response to browser
|
||||||
|
↓
|
||||||
|
7. Browser: Receives response (no CORS issues!)
|
||||||
|
```
|
||||||
|
|
||||||
|
**SSR (Server-Side Rendering) Flow**:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Page Load: +page.ts calls api.matches.getMatches()
|
||||||
|
↓
|
||||||
|
2. API Client: Detects import.meta.env.SSR === true
|
||||||
|
↓
|
||||||
|
3. Direct Call: GET https://api.csgow.tf/matches?limit=20
|
||||||
|
↓
|
||||||
|
4. Backend: Returns JSON response
|
||||||
|
↓
|
||||||
|
5. SSR: Renders page with data
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: SSR bypasses the SvelteKit route and calls the backend directly because relative URLs (`/api`) don't work during server-side rendering.
|
||||||
|
|
||||||
|
### Key Components
|
||||||
|
|
||||||
|
**1. SvelteKit Server Route** (`src/routes/api/[...path]/+server.ts`)
|
||||||
|
|
||||||
|
- Catch-all route that matches `/api/*`
|
||||||
|
- Forwards requests to backend
|
||||||
|
- Supports GET, POST, DELETE methods
|
||||||
|
- Handles errors gracefully
|
||||||
|
|
||||||
|
**2. API Client** (`src/lib/api/client.ts`)
|
||||||
|
|
||||||
|
- Browser: Uses `/api` base URL (routes to SvelteKit)
|
||||||
|
- SSR: Uses `VITE_API_BASE_URL` directly (bypasses SvelteKit route)
|
||||||
|
- Automatically detects environment with `import.meta.env.SSR`
|
||||||
|
|
||||||
|
**3. Environment Variable** (`.env`)
|
||||||
|
|
||||||
|
- `VITE_API_BASE_URL` controls which backend to use
|
||||||
|
- Switch between local and production easily
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
**`.env`**:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Production API (default)
|
||||||
|
VITE_API_BASE_URL=https://api.csgow.tf
|
||||||
|
|
||||||
|
# Local backend (for development)
|
||||||
|
# VITE_API_BASE_URL=http://localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
**Switching Backends**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use production API
|
||||||
|
echo "VITE_API_BASE_URL=https://api.csgow.tf" > .env
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Use local backend
|
||||||
|
echo "VITE_API_BASE_URL=http://localhost:8000" > .env
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server Route Implementation
|
||||||
|
|
||||||
|
**File**: `src/routes/api/[...path]/+server.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://api.csgow.tf';
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ params, url }) => {
|
||||||
|
const path = params.path; // e.g., "matches"
|
||||||
|
const queryString = url.search; // e.g., "?limit=20"
|
||||||
|
|
||||||
|
const backendUrl = `${API_BASE_URL}/${path}${queryString}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(backendUrl);
|
||||||
|
const data = await response.json();
|
||||||
|
return json(data);
|
||||||
|
} catch (err) {
|
||||||
|
throw error(503, 'Unable to connect to backend');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Client Configuration
|
||||||
|
|
||||||
|
**File**: `src/lib/api/client.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Simple, single configuration
|
||||||
|
const API_BASE_URL = '/api';
|
||||||
|
|
||||||
|
// Always routes to SvelteKit server routes
|
||||||
|
// No environment detection needed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing the Setup
|
||||||
|
|
||||||
|
### 1. Check Environment Variable
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat .env
|
||||||
|
|
||||||
|
# Should show:
|
||||||
|
VITE_API_BASE_URL=https://api.csgow.tf
|
||||||
|
# or
|
||||||
|
VITE_API_BASE_URL=http://localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Start Development Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Server starts on http://localhost:5173
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Check Network Requests
|
||||||
|
|
||||||
|
Open DevTools → Network tab:
|
||||||
|
|
||||||
|
- ✅ Requests go to `/api/matches`, `/api/player/123`, etc.
|
||||||
|
- ✅ Status should be `200 OK`
|
||||||
|
- ✅ No CORS errors in console
|
||||||
|
|
||||||
|
### 4. Test Both Backends
|
||||||
|
|
||||||
|
**Test Production API**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set production API
|
||||||
|
echo "VITE_API_BASE_URL=https://api.csgow.tf" > .env
|
||||||
|
|
||||||
|
# Start dev server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Visit http://localhost:5173/matches
|
||||||
|
# Should load matches from production API
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test Local Backend**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start local backend first
|
||||||
|
cd ../csgowtfd
|
||||||
|
go run main.go
|
||||||
|
|
||||||
|
# In another terminal, set local API
|
||||||
|
echo "VITE_API_BASE_URL=http://localhost:8000" > .env
|
||||||
|
|
||||||
|
# Start dev server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Visit http://localhost:5173/matches
|
||||||
|
# Should load matches from local backend
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### Issue 1: 503 Service Unavailable
|
||||||
|
|
||||||
|
**Symptom**: API requests return 503 error
|
||||||
|
|
||||||
|
**Possible Causes**:
|
||||||
|
|
||||||
|
1. Backend is not running
|
||||||
|
2. Wrong `VITE_API_BASE_URL` in `.env`
|
||||||
|
3. Network connectivity issues
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check .env file
|
||||||
|
cat .env
|
||||||
|
|
||||||
|
# If using local backend, make sure it's running
|
||||||
|
curl http://localhost:8000/matches
|
||||||
|
|
||||||
|
# If using production API, check connectivity
|
||||||
|
curl https://api.csgow.tf/matches
|
||||||
|
|
||||||
|
# Restart dev server after changing .env
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 2: 404 Not Found
|
||||||
|
|
||||||
|
**Symptom**: `/api/*` routes return 404
|
||||||
|
|
||||||
|
**Cause**: SvelteKit server route file missing or not loaded
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check file exists
|
||||||
|
ls src/routes/api/[...path]/+server.ts
|
||||||
|
|
||||||
|
# If missing, create it
|
||||||
|
mkdir -p src/routes/api/'[...path]'
|
||||||
|
# Then create +server.ts file
|
||||||
|
|
||||||
|
# Restart dev server
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 3: Environment Variable Not Loading
|
||||||
|
|
||||||
|
**Symptom**: Server route uses wrong backend URL
|
||||||
|
|
||||||
|
**Cause**: Changes to `.env` require server restart
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop dev server (Ctrl+C)
|
||||||
|
|
||||||
|
# Update .env
|
||||||
|
echo "VITE_API_BASE_URL=http://localhost:8000" > .env
|
||||||
|
|
||||||
|
# Start dev server again
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 4: CORS Errors Still Appearing
|
||||||
|
|
||||||
|
**Symptom**: Browser console shows CORS errors
|
||||||
|
|
||||||
|
**Cause**: API client is not using `/api` prefix
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
Check `src/lib/api/client.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Should be:
|
||||||
|
const API_BASE_URL = '/api';
|
||||||
|
|
||||||
|
// Not:
|
||||||
|
const API_BASE_URL = 'https://api.csgow.tf'; // ❌ Wrong
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works Compared to Vite Proxy
|
||||||
|
|
||||||
|
### Old Approach (Vite Proxy)
|
||||||
|
|
||||||
|
```
|
||||||
|
Development:
|
||||||
|
Browser → /api → Vite Proxy → Backend
|
||||||
|
|
||||||
|
Production:
|
||||||
|
Browser → Backend (direct, different code path)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problems**:
|
||||||
|
|
||||||
|
- Two different code paths (dev vs prod)
|
||||||
|
- Proxy only works in development
|
||||||
|
- SSR has to bypass proxy
|
||||||
|
- Complex configuration
|
||||||
|
|
||||||
|
### New Approach (SvelteKit Server Routes)
|
||||||
|
|
||||||
|
```
|
||||||
|
All Environments:
|
||||||
|
Browser → /api → SvelteKit Route → Backend
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits**:
|
||||||
|
|
||||||
|
- Single code path
|
||||||
|
- Works in dev, preview, and production
|
||||||
|
- Consistent behavior everywhere
|
||||||
|
- Simpler configuration
|
||||||
|
|
||||||
|
## Adding Features
|
||||||
|
|
||||||
|
### Add Request Caching
|
||||||
|
|
||||||
|
**File**: `src/routes/api/[...path]/+server.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const cache = new Map<string, { data: any; expires: number }>();
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ params, url }) => {
|
||||||
|
const cacheKey = `${params.path}${url.search}`;
|
||||||
|
|
||||||
|
// Check cache
|
||||||
|
const cached = cache.get(cacheKey);
|
||||||
|
if (cached && Date.now() < cached.expires) {
|
||||||
|
return json(cached.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch from backend
|
||||||
|
const data = await fetch(`${API_BASE_URL}/${params.path}${url.search}`).then((r) => r.json());
|
||||||
|
|
||||||
|
// Cache for 5 minutes
|
||||||
|
cache.set(cacheKey, {
|
||||||
|
data,
|
||||||
|
expires: Date.now() + 5 * 60 * 1000
|
||||||
|
});
|
||||||
|
|
||||||
|
return json(data);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add Rate Limiting
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { rateLimit } from '$lib/server/rateLimit';
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ request, params, url }) => {
|
||||||
|
// Check rate limit
|
||||||
|
await rateLimit(request);
|
||||||
|
|
||||||
|
// Continue with normal flow...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add Authentication
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const GET: RequestHandler = async ({ request, params, url }) => {
|
||||||
|
// Get auth token from cookie
|
||||||
|
const token = request.headers.get('cookie')?.includes('auth_token');
|
||||||
|
|
||||||
|
// Forward to backend with auth
|
||||||
|
const response = await fetch(backendUrl, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Feature | Vite Proxy | SvelteKit Routes |
|
||||||
|
| --------------------- | ---------- | ---------------- |
|
||||||
|
| Works in dev | ✅ | ✅ |
|
||||||
|
| Works in production | ❌ | ✅ |
|
||||||
|
| Single code path | ❌ | ✅ |
|
||||||
|
| Can add caching | ❌ | ✅ |
|
||||||
|
| Can add rate limiting | ❌ | ✅ |
|
||||||
|
| Can add auth | ❌ | ✅ |
|
||||||
|
| SSR compatible | ❌ | ✅ |
|
||||||
|
|
||||||
|
**SvelteKit server routes provide a production-ready, maintainable solution for API proxying that works in all environments.**
|
||||||
587
docs/DESIGN.md
Normal file
@@ -0,0 +1,587 @@
|
|||||||
|
# CS2.WTF Design System
|
||||||
|
|
||||||
|
A modern, tactical design language inspired by Counter-Strike 2's in-game aesthetics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Design Philosophy
|
||||||
|
|
||||||
|
### Core Principles
|
||||||
|
|
||||||
|
1. **Tactical & Data-Dense**: Inspired by CS2's HUD - information at a glance
|
||||||
|
2. **Dark-First**: Gaming-optimized dark theme as default
|
||||||
|
3. **Team Identity**: Leverage T-side (orange) and CT-side (blue) throughout
|
||||||
|
4. **Performance**: Smooth animations, no bloat
|
||||||
|
5. **Accessible**: WCAG 2.1 AA compliant
|
||||||
|
|
||||||
|
### Visual Language
|
||||||
|
|
||||||
|
- **Sharp Corners**: Minimal border radius (2-4px) for tactical feel
|
||||||
|
- **Neon Accents**: Subtle glows on interactive elements
|
||||||
|
- **Grid-Based**: 8px base unit for consistent spacing
|
||||||
|
- **Monospace Numbers**: Stats feel more tactical
|
||||||
|
- **Depth Through Layers**: Elevated cards with subtle shadows
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Color Palette
|
||||||
|
|
||||||
|
### Brand Colors
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Primary (CT Blue) */
|
||||||
|
--ct-blue: #5e98d9;
|
||||||
|
--ct-blue-light: #7eaee5;
|
||||||
|
--ct-blue-dark: #4a7ab3;
|
||||||
|
|
||||||
|
/* Secondary (T Orange) */
|
||||||
|
--t-orange: #d4a74a;
|
||||||
|
--t-orange-light: #e5c674;
|
||||||
|
--t-orange-dark: #b38a3a;
|
||||||
|
|
||||||
|
/* Accent (Success Green) */
|
||||||
|
--accent-green: #36d399;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Base Colors (Dark Theme)
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Backgrounds */
|
||||||
|
--bg-primary: #0f172a; /* Slate 900 - Main background */
|
||||||
|
--bg-secondary: #1e293b; /* Slate 800 - Card background */
|
||||||
|
--bg-tertiary: #334155; /* Slate 700 - Hover states */
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--text-primary: #e2e8f0; /* Slate 200 - Main text */
|
||||||
|
--text-secondary: #94a3b8; /* Slate 400 - Muted text */
|
||||||
|
--text-tertiary: #64748b; /* Slate 500 - Disabled text */
|
||||||
|
|
||||||
|
/* Borders */
|
||||||
|
--border-default: #334155; /* Slate 700 */
|
||||||
|
--border-accent: #475569; /* Slate 600 - Hover */
|
||||||
|
```
|
||||||
|
|
||||||
|
### Semantic Colors
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Status */
|
||||||
|
--success: #36d399; /* Win, positive stats */
|
||||||
|
--warning: #fbbd23; /* Neutral, info */
|
||||||
|
--error: #f87272; /* Loss, negative stats */
|
||||||
|
--info: #3abff8; /* Information, CT-related */
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 Typography
|
||||||
|
|
||||||
|
### Font Families
|
||||||
|
|
||||||
|
**Primary (UI Text):**
|
||||||
|
|
||||||
|
```css
|
||||||
|
font-family:
|
||||||
|
'Inter',
|
||||||
|
system-ui,
|
||||||
|
-apple-system,
|
||||||
|
sans-serif;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Monospace (Stats & Numbers):**
|
||||||
|
|
||||||
|
```css
|
||||||
|
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type Scale
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Display */
|
||||||
|
--text-6xl: 3.75rem; /* 60px - Hero headings */
|
||||||
|
--text-5xl: 3rem; /* 48px - Page titles */
|
||||||
|
--text-4xl: 2.25rem; /* 36px - Section headers */
|
||||||
|
|
||||||
|
/* Headings */
|
||||||
|
--text-3xl: 1.875rem; /* 30px - Card titles */
|
||||||
|
--text-2xl: 1.5rem; /* 24px - Subsection headers */
|
||||||
|
--text-xl: 1.25rem; /* 20px - Large body */
|
||||||
|
|
||||||
|
/* Body */
|
||||||
|
--text-lg: 1.125rem; /* 18px - Prominent text */
|
||||||
|
--text-base: 1rem; /* 16px - Default body */
|
||||||
|
--text-sm: 0.875rem; /* 14px - Small text */
|
||||||
|
--text-xs: 0.75rem; /* 12px - Captions */
|
||||||
|
```
|
||||||
|
|
||||||
|
### Font Weights
|
||||||
|
|
||||||
|
```css
|
||||||
|
--font-normal: 400;
|
||||||
|
--font-medium: 500;
|
||||||
|
--font-semibold: 600;
|
||||||
|
--font-bold: 700;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ Layout
|
||||||
|
|
||||||
|
### Spacing System (8px Grid)
|
||||||
|
|
||||||
|
```css
|
||||||
|
--space-1: 0.25rem; /* 4px */
|
||||||
|
--space-2: 0.5rem; /* 8px */
|
||||||
|
--space-3: 0.75rem; /* 12px */
|
||||||
|
--space-4: 1rem; /* 16px */
|
||||||
|
--space-6: 1.5rem; /* 24px */
|
||||||
|
--space-8: 2rem; /* 32px */
|
||||||
|
--space-12: 3rem; /* 48px */
|
||||||
|
--space-16: 4rem; /* 64px */
|
||||||
|
--space-24: 6rem; /* 96px */
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container Widths
|
||||||
|
|
||||||
|
```css
|
||||||
|
--container-sm: 640px; /* Mobile landscape */
|
||||||
|
--container-md: 768px; /* Tablet */
|
||||||
|
--container-lg: 1024px; /* Desktop */
|
||||||
|
--container-xl: 1280px; /* Large desktop */
|
||||||
|
--container-2xl: 1536px; /* Extra large */
|
||||||
|
```
|
||||||
|
|
||||||
|
### Breakpoints
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Mobile first approach */
|
||||||
|
sm: 640px /* Tablet */
|
||||||
|
md: 768px /* Small desktop */
|
||||||
|
lg: 1024px /* Desktop */
|
||||||
|
xl: 1280px /* Large desktop */
|
||||||
|
2xl: 1536px /* Extra large */
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎭 Components
|
||||||
|
|
||||||
|
### Cards
|
||||||
|
|
||||||
|
**Default Card:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-default);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Elevated Card:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
box-shadow:
|
||||||
|
0 10px 15px -3px rgb(0 0 0 / 0.2),
|
||||||
|
0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Interactive Card (Hover):**
|
||||||
|
|
||||||
|
```css
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--ct-blue);
|
||||||
|
box-shadow: 0 0 0 2px rgb(94 152 217 / 0.2);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Buttons
|
||||||
|
|
||||||
|
**Primary (CT Blue):**
|
||||||
|
|
||||||
|
```css
|
||||||
|
background: var(--ct-blue);
|
||||||
|
color: white;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--ct-blue-dark);
|
||||||
|
box-shadow: 0 0 20px rgb(94 152 217 / 0.3);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Secondary (T Orange):**
|
||||||
|
|
||||||
|
```css
|
||||||
|
background: var(--t-orange);
|
||||||
|
color: white;
|
||||||
|
/* Similar styling */
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ghost:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--border-default);
|
||||||
|
color: var(--text-primary);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-color: var(--ct-blue);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Badges
|
||||||
|
|
||||||
|
**Team Badge:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* T-Side */
|
||||||
|
background: rgb(212 167 74 / 0.1);
|
||||||
|
color: var(--t-orange-light);
|
||||||
|
border: 1px solid var(--t-orange-dark);
|
||||||
|
|
||||||
|
/* CT-Side */
|
||||||
|
background: rgb(94 152 217 / 0.1);
|
||||||
|
color: var(--ct-blue-light);
|
||||||
|
border: 1px solid var(--ct-blue-dark);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status Badge:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Win */
|
||||||
|
background: rgb(54 211 153 / 0.1);
|
||||||
|
color: var(--success);
|
||||||
|
|
||||||
|
/* Loss */
|
||||||
|
background: rgb(248 114 114 / 0.1);
|
||||||
|
color: var(--error);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌊 Animations
|
||||||
|
|
||||||
|
### Transitions
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Standard */
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
/* Slow */
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
/* Fast */
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Keyframes
|
||||||
|
|
||||||
|
**Fade In:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pulse (Live Indicator):**
|
||||||
|
|
||||||
|
```css
|
||||||
|
@keyframes pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Glow:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
@keyframes glow {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 10px rgb(94 152 217 / 0.3);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 20px rgb(94 152 217 / 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Iconography
|
||||||
|
|
||||||
|
**Icon Library:** Lucide Icons (clean, modern, consistent)
|
||||||
|
|
||||||
|
**Icon Sizes:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
--icon-xs: 16px;
|
||||||
|
--icon-sm: 20px;
|
||||||
|
--icon-md: 24px;
|
||||||
|
--icon-lg: 32px;
|
||||||
|
--icon-xl: 48px;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Icon Colors:**
|
||||||
|
|
||||||
|
- Default: `text-slate-400`
|
||||||
|
- Active: `text-primary` or `text-secondary`
|
||||||
|
- Success: `text-success`
|
||||||
|
- Error: `text-error`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Data Visualization
|
||||||
|
|
||||||
|
### Chart Colors
|
||||||
|
|
||||||
|
**Team Performance:**
|
||||||
|
|
||||||
|
- T-Side: `#d4a74a`
|
||||||
|
- CT-Side: `#5e98d9`
|
||||||
|
|
||||||
|
**Heatmaps:**
|
||||||
|
|
||||||
|
- Low: `#334155` (Slate 700)
|
||||||
|
- Medium: `#f59e0b` (Amber 500)
|
||||||
|
- High: `#ef4444` (Red 500)
|
||||||
|
|
||||||
|
**Line Charts:**
|
||||||
|
|
||||||
|
- Primary line: `#5e98d9`
|
||||||
|
- Secondary line: `#d4a74a`
|
||||||
|
- Grid: `rgb(51 65 85 / 0.3)`
|
||||||
|
|
||||||
|
### Tables
|
||||||
|
|
||||||
|
**Header:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
background: var(--bg-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Row:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
border-bottom: 1px solid var(--border-default);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Stats (Numbers):**
|
||||||
|
|
||||||
|
```css
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ♿ Accessibility
|
||||||
|
|
||||||
|
### Focus States
|
||||||
|
|
||||||
|
```css
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--ct-blue);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Color Contrast
|
||||||
|
|
||||||
|
- Text on dark bg: Minimum 4.5:1 (WCAG AA)
|
||||||
|
- Large text: Minimum 3:1
|
||||||
|
- UI components: Minimum 3:1
|
||||||
|
|
||||||
|
### Motion
|
||||||
|
|
||||||
|
```css
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
* {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 CS2-Specific Elements
|
||||||
|
|
||||||
|
### Rank Display
|
||||||
|
|
||||||
|
- Show Premier rating (0-30,000) with color coding
|
||||||
|
- Bronze: `#cd7f32`
|
||||||
|
- Silver: `#c0c0c0`
|
||||||
|
- Gold: `#ffd700`
|
||||||
|
- Legend: `#9b59b6`
|
||||||
|
|
||||||
|
### Map Thumbnails
|
||||||
|
|
||||||
|
- 16:9 aspect ratio
|
||||||
|
- Slight overlay gradient (bottom to top)
|
||||||
|
- Map name in bottom-left corner
|
||||||
|
|
||||||
|
### Weapon Icons
|
||||||
|
|
||||||
|
- Monochrome with subtle glow
|
||||||
|
- Size: 32x32px default
|
||||||
|
- Color: Match rarity (Consumer White, Mil-Spec Blue, etc.)
|
||||||
|
|
||||||
|
### Kill Feed
|
||||||
|
|
||||||
|
```css
|
||||||
|
.kill-feed-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
background: rgb(15 23 42 / 0.9);
|
||||||
|
border-left: 2px solid var(--t-orange);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 Responsive Design
|
||||||
|
|
||||||
|
### Mobile (< 768px)
|
||||||
|
|
||||||
|
- Stack layouts vertically
|
||||||
|
- Reduce padding/spacing by 25%
|
||||||
|
- Hide secondary information
|
||||||
|
- Larger tap targets (min 44x44px)
|
||||||
|
- Bottom navigation for main actions
|
||||||
|
|
||||||
|
### Tablet (768px - 1024px)
|
||||||
|
|
||||||
|
- Two-column layouts
|
||||||
|
- Collapsible sidebar
|
||||||
|
- Touch-optimized interactions
|
||||||
|
|
||||||
|
### Desktop (> 1024px)
|
||||||
|
|
||||||
|
- Three-column layouts where appropriate
|
||||||
|
- Hover states and tooltips
|
||||||
|
- Keyboard shortcuts
|
||||||
|
- Dense data tables
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Example Compositions
|
||||||
|
|
||||||
|
### Hero Section (Homepage)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ CS2.WTF (Large Logo) │
|
||||||
|
│ Statistics for CS2 Matches │
|
||||||
|
│ │
|
||||||
|
│ [Search Match] [Browse Players] │
|
||||||
|
│ │
|
||||||
|
│ Featured Matches (Carousel) ────> │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Match Card
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ de_inferno 13 - 10 LIVE │
|
||||||
|
│ ─────────────────────────────────── │
|
||||||
|
│ 👤 Player1 24K 18D ⭐⭐⭐ │
|
||||||
|
│ 👤 Player2 21K 20D ⭐⭐ │
|
||||||
|
│ ... │
|
||||||
|
│ ─────────────────────────────────── │
|
||||||
|
│ 📅 2 hours ago ⏱️ 42:33 │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stats Table
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────┐
|
||||||
|
│ PLAYER K D A HS% ADR RATING │
|
||||||
|
├──────────────────────────────────────────────┤
|
||||||
|
│ 👤 Player1 24 18 6 50% 98 1.32 🥇 │
|
||||||
|
│ 👤 Player2 21 20 8 48% 87 1.12 │
|
||||||
|
│ 👤 Player3 19 22 5 44% 82 0.98 │
|
||||||
|
└──────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Performance Guidelines
|
||||||
|
|
||||||
|
- Lazy load images and charts
|
||||||
|
- Use CSS transforms for animations (GPU-accelerated)
|
||||||
|
- Debounce search inputs (300ms)
|
||||||
|
- Virtual scrolling for large tables (> 100 rows)
|
||||||
|
- Optimize bundle size (< 200KB initial)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Naming Conventions
|
||||||
|
|
||||||
|
### CSS Classes
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Component */
|
||||||
|
.match-card {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Element */
|
||||||
|
.match-card__header {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modifier */
|
||||||
|
.match-card--featured {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* State */
|
||||||
|
.match-card.is-active {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tailwind Utilities
|
||||||
|
|
||||||
|
Prefer utility classes for spacing, colors, and common patterns:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="rounded-lg bg-base-200 p-6 shadow-lg"></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2025-11-04
|
||||||
|
**Status**: Active Development
|
||||||
480
docs/IMPLEMENTATION_STATUS.md
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
# CS2.WTF Feature Implementation Status
|
||||||
|
|
||||||
|
**Last Updated:** 2025-11-12
|
||||||
|
**Branch:** cs2-port
|
||||||
|
**Status:** In Progress (~70% Complete)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document tracks the implementation status of missing features from the original CS:GO WTF frontend that need to be ported to the new CS2.WTF SvelteKit application.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Critical Features (HIGH PRIORITY)
|
||||||
|
|
||||||
|
### ✅ 1. Player Tracking System
|
||||||
|
|
||||||
|
**Status:** COMPLETED
|
||||||
|
|
||||||
|
- ✅ Added `tracked` field to Player type
|
||||||
|
- ✅ Updated player schema validation
|
||||||
|
- ✅ Updated API transformer to pass through `tracked` field
|
||||||
|
- ✅ Created `TrackPlayerModal.svelte` component
|
||||||
|
- Auth code input
|
||||||
|
- Optional share code input
|
||||||
|
- Track/Untrack functionality
|
||||||
|
- Help text with instructions
|
||||||
|
- Loading states and error handling
|
||||||
|
- ✅ Integrated modal into player profile page
|
||||||
|
- ✅ Added tracking status indicator button
|
||||||
|
- ✅ Connected to API endpoints: `POST /player/:id/track` and `DELETE /player/:id/track`
|
||||||
|
|
||||||
|
**Files Modified:**
|
||||||
|
|
||||||
|
- `src/lib/types/Player.ts`
|
||||||
|
- `src/lib/schemas/player.schema.ts`
|
||||||
|
- `src/lib/api/transformers.ts`
|
||||||
|
- `src/routes/player/[id]/+page.svelte`
|
||||||
|
|
||||||
|
**Files Created:**
|
||||||
|
|
||||||
|
- `src/lib/components/player/TrackPlayerModal.svelte`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✅ 2. Match Share Code Parsing
|
||||||
|
|
||||||
|
**Status:** COMPLETED
|
||||||
|
|
||||||
|
- ✅ Created `ShareCodeInput.svelte` component
|
||||||
|
- Share code input with validation
|
||||||
|
- Submit button with loading state
|
||||||
|
- Parse status feedback (parsing/success/error)
|
||||||
|
- Auto-redirect to match page on success
|
||||||
|
- Help text with instructions
|
||||||
|
- ✅ Added component to matches page
|
||||||
|
- ✅ Connected to API endpoint: `GET /match/parse/:sharecode`
|
||||||
|
- ✅ Share code format validation
|
||||||
|
|
||||||
|
**Files Created:**
|
||||||
|
|
||||||
|
- `src/lib/components/match/ShareCodeInput.svelte`
|
||||||
|
|
||||||
|
**Files Modified:**
|
||||||
|
|
||||||
|
- `src/routes/matches/+page.svelte`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✅ 3. VAC/Game Ban Status Display (Player Profile)
|
||||||
|
|
||||||
|
**Status:** COMPLETED
|
||||||
|
|
||||||
|
- ✅ Added VAC ban badge with count and date
|
||||||
|
- ✅ Added Game ban badge with count and date
|
||||||
|
- ✅ Styled with error/warning colors
|
||||||
|
- ✅ Displays on player profile header
|
||||||
|
- ✅ Shows ban dates when available
|
||||||
|
|
||||||
|
**Files Modified:**
|
||||||
|
|
||||||
|
- `src/routes/player/[id]/+page.svelte`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔄 4. VAC Status Column on Match Scoreboard
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Add VAC status indicator column to scoreboard in `src/routes/match/[id]/+page.svelte`
|
||||||
|
- Add VAC status indicator to details tab table
|
||||||
|
- Style with red warning icon for players with VAC bans
|
||||||
|
- Tooltip with ban date on hover
|
||||||
|
|
||||||
|
**Files to Modify:**
|
||||||
|
|
||||||
|
- `src/routes/match/[id]/+page.svelte`
|
||||||
|
- `src/routes/match/[id]/details/+page.svelte`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔄 5. Weapons Statistics Tab
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**Requires:**
|
||||||
|
|
||||||
|
- New tab on match detail page
|
||||||
|
- Component to display weapon statistics
|
||||||
|
- Hitgroup visualization (similar to old HitgroupPuppet.vue)
|
||||||
|
- Weapon breakdown table with kills, damage, hits per weapon
|
||||||
|
- API endpoint already exists: `GET /match/:id/weapons`
|
||||||
|
- API method already exists: `matchesAPI.getMatchWeapons()`
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Create `src/routes/match/[id]/weapons/+page.svelte`
|
||||||
|
- Create `src/routes/match/[id]/weapons/+page.ts` (load function)
|
||||||
|
- Create `src/lib/components/match/WeaponStats.svelte`
|
||||||
|
- Create `src/lib/components/match/HitgroupVisualization.svelte`
|
||||||
|
- Update match layout tabs to include weapons tab
|
||||||
|
|
||||||
|
**Estimated Effort:** 8-16 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔄 6. Recently Visited Players (Home Page)
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**Requires:**
|
||||||
|
|
||||||
|
- localStorage tracking of visited player profiles
|
||||||
|
- Display on home page as cards
|
||||||
|
- Delete/clear functionality
|
||||||
|
- Limit to last 6-10 players
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Create utility functions for localStorage management
|
||||||
|
- Create `src/lib/components/player/RecentlyVisitedPlayers.svelte`
|
||||||
|
- Add to home page (`src/routes/+page.svelte`)
|
||||||
|
- Track player visits in player profile page
|
||||||
|
- Add to preferences store
|
||||||
|
|
||||||
|
**Estimated Effort:** 4-6 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Important Features (MEDIUM-HIGH PRIORITY)
|
||||||
|
|
||||||
|
### 🔄 7. Complete Scoreboard Columns
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**Missing Columns:**
|
||||||
|
|
||||||
|
- Player avatars (Steam avatar images)
|
||||||
|
- Color indicators (in-game player colors)
|
||||||
|
- In-game score column
|
||||||
|
- MVP stars column
|
||||||
|
- K/D ratio column (separate from K/D difference)
|
||||||
|
- Multi-kill indicators on scoreboard (currently only in Details tab)
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Update `src/routes/match/[id]/+page.svelte` scoreboard table
|
||||||
|
- Add avatar column with Steam profile images
|
||||||
|
- Add color-coded player indicators
|
||||||
|
- Add Score, MVP, K/D ratio columns
|
||||||
|
- Move multi-kill indicators to scoreboard or add as tooltips
|
||||||
|
|
||||||
|
**Estimated Effort:** 6-8 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔄 8. Sitemap Generation
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**Requires:**
|
||||||
|
|
||||||
|
- Dynamic sitemap generation based on players and matches
|
||||||
|
- XML sitemap endpoint
|
||||||
|
- Sitemap index for pagination
|
||||||
|
- robots.txt configuration
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Create `src/routes/sitemap.xml/+server.ts`
|
||||||
|
- Create `src/routes/sitemap/[id]/+server.ts`
|
||||||
|
- Implement sitemap generation logic
|
||||||
|
- Add robots.txt to static folder
|
||||||
|
- Connect to backend sitemap endpoints if they exist
|
||||||
|
|
||||||
|
**Estimated Effort:** 6-8 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔄 9. Team Average Rank Badges (Match Header)
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**Requires:**
|
||||||
|
|
||||||
|
- Calculate average Premier rating per team
|
||||||
|
- Display in match header/layout
|
||||||
|
- Show tier badges for each team
|
||||||
|
- Rank change indicators
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Add calculation logic in `src/routes/match/[id]/+layout.svelte`
|
||||||
|
- Create component for team rank display
|
||||||
|
- Style with tier colors
|
||||||
|
|
||||||
|
**Estimated Effort:** 3-4 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔄 10. Chat Message Translation
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**Requires:**
|
||||||
|
|
||||||
|
- Translation API integration (Google Translate, DeepL, or similar)
|
||||||
|
- Translate button on each chat message
|
||||||
|
- Language detection
|
||||||
|
- Cache translations
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Choose translation API provider
|
||||||
|
- Add API key configuration
|
||||||
|
- Create translation service in `src/lib/services/translation.ts`
|
||||||
|
- Update `src/routes/match/[id]/chat/+page.svelte`
|
||||||
|
- Add translate button to chat messages
|
||||||
|
- Handle loading and error states
|
||||||
|
|
||||||
|
**Estimated Effort:** 8-12 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Polish & Nice-to-Have (MEDIUM-LOW PRIORITY)
|
||||||
|
|
||||||
|
### 🔄 11. Steam Profile Links
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Add Steam profile link to player name on player profile page
|
||||||
|
- Add links to scoreboard player names
|
||||||
|
- Support for vanity URLs
|
||||||
|
- Open in new tab
|
||||||
|
|
||||||
|
**Files to Modify:**
|
||||||
|
|
||||||
|
- `src/routes/player/[id]/+page.svelte`
|
||||||
|
- `src/routes/match/[id]/+page.svelte`
|
||||||
|
- `src/routes/match/[id]/details/+page.svelte`
|
||||||
|
|
||||||
|
**Estimated Effort:** 2-3 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔄 12. Win/Loss/Tie Statistics
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Display total wins, losses, ties on player profile
|
||||||
|
- Calculate win rate from these totals
|
||||||
|
- Add to player stats cards section
|
||||||
|
|
||||||
|
**Files to Modify:**
|
||||||
|
|
||||||
|
- `src/routes/player/[id]/+page.svelte`
|
||||||
|
|
||||||
|
**Estimated Effort:** 1-2 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔄 13. Privacy Policy Page
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Create `src/routes/privacy-policy/+page.svelte`
|
||||||
|
- Write privacy policy content
|
||||||
|
- Add GDPR compliance information
|
||||||
|
- Link from footer
|
||||||
|
|
||||||
|
**Estimated Effort:** 2-4 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔄 14. Player Color Indicators (Scoreboard)
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Display in-game player colors on scoreboard
|
||||||
|
- Color-code player rows or names
|
||||||
|
- Match CS2 color scheme (green/yellow/purple/blue/orange)
|
||||||
|
|
||||||
|
**Files to Modify:**
|
||||||
|
|
||||||
|
- `src/routes/match/[id]/+page.svelte`
|
||||||
|
|
||||||
|
**Estimated Effort:** 1-2 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔄 15. Additional Utility Statistics
|
||||||
|
|
||||||
|
**Status:** NOT STARTED
|
||||||
|
|
||||||
|
**Missing Stats:**
|
||||||
|
|
||||||
|
- Self-flash statistics
|
||||||
|
- Smoke grenade usage
|
||||||
|
- Decoy grenade usage
|
||||||
|
- Team flash statistics
|
||||||
|
|
||||||
|
**TODO:**
|
||||||
|
|
||||||
|
- Display in match details or player profile
|
||||||
|
- Add to utility effectiveness section
|
||||||
|
|
||||||
|
**Estimated Effort:** 2-3 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Parity Comparison
|
||||||
|
|
||||||
|
### What's BETTER in Current Implementation ✨
|
||||||
|
|
||||||
|
- Modern SvelteKit architecture with TypeScript
|
||||||
|
- Superior filtering and search functionality
|
||||||
|
- Data export (CSV/JSON)
|
||||||
|
- Better data visualizations (Chart.js)
|
||||||
|
- Premier rating system (CS2-specific)
|
||||||
|
- Dark/light theme toggle
|
||||||
|
- Infinite scroll
|
||||||
|
- Better responsive design
|
||||||
|
|
||||||
|
### What's Currently Missing ⚠️
|
||||||
|
|
||||||
|
- Weapon statistics page (high impact)
|
||||||
|
- Complete scoreboard columns (medium impact)
|
||||||
|
- Recently visited players (medium impact)
|
||||||
|
- Sitemap/SEO (medium impact)
|
||||||
|
- Chat translation (low-medium impact)
|
||||||
|
- Various polish features (low impact)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estimated Remaining Effort
|
||||||
|
|
||||||
|
### By Priority
|
||||||
|
|
||||||
|
| Priority | Tasks Remaining | Est. Hours | Status |
|
||||||
|
| ------------------- | --------------- | --------------- | ---------------- |
|
||||||
|
| Phase 1 (Critical) | 3 | 16-30 hours | 50% Complete |
|
||||||
|
| Phase 2 (Important) | 4 | 23-36 hours | 0% Complete |
|
||||||
|
| Phase 3 (Polish) | 5 | 8-14 hours | 0% Complete |
|
||||||
|
| **TOTAL** | **12** | **47-80 hours** | **25% Complete** |
|
||||||
|
|
||||||
|
### Overall Project Status
|
||||||
|
|
||||||
|
- **Completed:** 3 critical features
|
||||||
|
- **In Progress:** API cleanup and optimization
|
||||||
|
- **Remaining:** 12 features across 3 phases
|
||||||
|
- **Estimated Completion:** 2-3 weeks of full-time development
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### Immediate (This Session)
|
||||||
|
|
||||||
|
1. ✅ Player tracking UI - DONE
|
||||||
|
2. ✅ Share code parsing UI - DONE
|
||||||
|
3. ✅ VAC/ban status display (profile) - DONE
|
||||||
|
4. ⏭️ VAC status on scoreboard - NEXT
|
||||||
|
5. ⏭️ Weapons statistics tab - NEXT
|
||||||
|
6. ⏭️ Recently visited players - NEXT
|
||||||
|
|
||||||
|
### Short Term (Next Session)
|
||||||
|
|
||||||
|
- Complete remaining Phase 1 features
|
||||||
|
- Start Phase 2 features (scoreboard completion, sitemap)
|
||||||
|
|
||||||
|
### Medium Term
|
||||||
|
|
||||||
|
- Complete Phase 2 features
|
||||||
|
- Begin Phase 3 polish features
|
||||||
|
|
||||||
|
### Long Term
|
||||||
|
|
||||||
|
- Full feature parity with old frontend
|
||||||
|
- Additional CS2-specific features
|
||||||
|
- Performance optimizations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
### Completed Features
|
||||||
|
|
||||||
|
- [x] Player tracking modal opens and closes
|
||||||
|
- [x] Player tracking modal validates auth code input
|
||||||
|
- [x] Track/untrack API calls work
|
||||||
|
- [x] Tracking status updates after track/untrack
|
||||||
|
- [x] Share code input validates format
|
||||||
|
- [x] Share code parsing submits to API
|
||||||
|
- [x] Parse status feedback displays correctly
|
||||||
|
- [x] Redirect to match page after successful parse
|
||||||
|
- [x] VAC/ban badges display on player profile
|
||||||
|
- [x] VAC/ban dates show when available
|
||||||
|
|
||||||
|
### TODO Testing
|
||||||
|
|
||||||
|
- [ ] VAC status displays on scoreboard
|
||||||
|
- [ ] Weapons tab loads and displays data
|
||||||
|
- [ ] Hitgroup visualization renders correctly
|
||||||
|
- [ ] Recently visited players tracked correctly
|
||||||
|
- [ ] Recently visited players display on home page
|
||||||
|
- [ ] All Phase 2 and 3 features
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
### Current
|
||||||
|
|
||||||
|
- None
|
||||||
|
|
||||||
|
### Potential
|
||||||
|
|
||||||
|
- Translation API rate limiting (once implemented)
|
||||||
|
- Sitemap generation performance with large datasets
|
||||||
|
- Weapons tab may need pagination for long matches
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
### Architecture Decisions
|
||||||
|
|
||||||
|
- Using SvelteKit server routes for API proxying (no CORS issues)
|
||||||
|
- Transformers pattern for legacy API format conversion
|
||||||
|
- Component-based approach for reusability
|
||||||
|
- TypeScript + Zod for type safety
|
||||||
|
|
||||||
|
### API Endpoints Used
|
||||||
|
|
||||||
|
- ✅ `POST /player/:id/track`
|
||||||
|
- ✅ `DELETE /player/:id/track`
|
||||||
|
- ✅ `GET /match/parse/:sharecode`
|
||||||
|
- ⏭️ `GET /match/:id/weapons` (available but not used yet)
|
||||||
|
- ⏭️ `GET /player/:id/meta` (available but not optimized yet)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
- Initial Analysis: Claude (Anthropic AI)
|
||||||
|
- Implementation: In Progress
|
||||||
|
- Testing: Pending
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**For questions or updates, refer to the main project README.md**
|
||||||
335
docs/LOCAL_DEVELOPMENT.md
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
# Local Development Setup
|
||||||
|
|
||||||
|
This guide will help you set up the CS2.WTF frontend for local development.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Node.js**: v18.x or v20.x (check with `node --version`)
|
||||||
|
- **npm**: v9.x or higher (comes with Node.js)
|
||||||
|
- **Backend API**: Either local csgowtfd service OR access to production API
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Environment Configuration
|
||||||
|
|
||||||
|
The `.env` file already exists in the project. You can use it as-is or modify it:
|
||||||
|
|
||||||
|
**Option A: Use Production API** (Recommended for frontend development)
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Use the live production API - no local backend needed
|
||||||
|
VITE_API_BASE_URL=https://api.csgow.tf
|
||||||
|
VITE_API_TIMEOUT=10000
|
||||||
|
VITE_DEBUG_MODE=true
|
||||||
|
VITE_ENABLE_ANALYTICS=false
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Use Local Backend** (For full-stack development)
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Use local backend (requires csgowtfd running on port 8000)
|
||||||
|
VITE_API_BASE_URL=http://localhost:8000
|
||||||
|
VITE_API_TIMEOUT=10000
|
||||||
|
VITE_DEBUG_MODE=true
|
||||||
|
VITE_ENABLE_ANALYTICS=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Start the Development Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The frontend will be available at `http://localhost:5173`
|
||||||
|
|
||||||
|
You should see output like:
|
||||||
|
|
||||||
|
```
|
||||||
|
VITE v5.x.x ready in xxx ms
|
||||||
|
|
||||||
|
➜ Local: http://localhost:5173/
|
||||||
|
➜ Network: use --host to expose
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. (Optional) Start Local Backend
|
||||||
|
|
||||||
|
Only needed if using `VITE_API_BASE_URL=http://localhost:8000`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In the csgowtfd repository
|
||||||
|
cd ../csgowtfd
|
||||||
|
go run cmd/csgowtfd/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use Docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up csgowtfd
|
||||||
|
```
|
||||||
|
|
||||||
|
## How SvelteKit API Routes Work
|
||||||
|
|
||||||
|
All API requests go through **SvelteKit server routes** which proxy to the backend. This works consistently in all environments.
|
||||||
|
|
||||||
|
### Request Flow (All Environments)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Browser makes request to: http://localhost:5173/api/matches
|
||||||
|
2. SvelteKit routes to: src/routes/api/[...path]/+server.ts
|
||||||
|
3. Server handler reads VITE_API_BASE_URL environment variable
|
||||||
|
4. Server fetches from backend: ${VITE_API_BASE_URL}/matches
|
||||||
|
5. Backend responds
|
||||||
|
6. Server handler forwards response to browser
|
||||||
|
```
|
||||||
|
|
||||||
|
### Benefits
|
||||||
|
|
||||||
|
- ✅ **No CORS errors** - All requests are server-side
|
||||||
|
- ✅ **Works in all environments** - Dev, preview, and production
|
||||||
|
- ✅ **Single code path** - Same behavior everywhere
|
||||||
|
- ✅ **Easy backend switching** - Change one environment variable
|
||||||
|
- ✅ **Future-proof** - Can add caching, rate limiting, auth later
|
||||||
|
- ✅ **Backend URL not exposed** - Hidden from client
|
||||||
|
|
||||||
|
### Switching Between Backends
|
||||||
|
|
||||||
|
Simply update `.env` and restart the dev server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use production API
|
||||||
|
echo "VITE_API_BASE_URL=https://api.csgow.tf" > .env
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Use local backend
|
||||||
|
echo "VITE_API_BASE_URL=http://localhost:8000" > .env
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development vs Production
|
||||||
|
|
||||||
|
| Mode | Request Flow | Backend URL From |
|
||||||
|
| -------------------------------- | ---------------------------------------------- | ------------------------------ |
|
||||||
|
| **Development** (`npm run dev`) | Browser → `/api/*` → SvelteKit Route → Backend | `.env` → `VITE_API_BASE_URL` |
|
||||||
|
| **Production** (`npm run build`) | Browser → `/api/*` → SvelteKit Route → Backend | Build-time `VITE_API_BASE_URL` |
|
||||||
|
|
||||||
|
**Note**: The flow is identical in both modes - this is the key advantage over the old Vite proxy approach.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### No Data Showing / Network Errors
|
||||||
|
|
||||||
|
**Problem**: Frontend loads but shows no matches, players show "Failed to load" errors.
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
|
||||||
|
1. **Check what backend you're using**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Look at your .env file
|
||||||
|
cat .env | grep VITE_API_BASE_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **If using production API** (`https://api.csgow.tf`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test if production API is accessible
|
||||||
|
curl https://api.csgow.tf/matches?limit=1
|
||||||
|
```
|
||||||
|
|
||||||
|
Should return JSON data. If not, production API may be down.
|
||||||
|
|
||||||
|
3. **If using local backend** (`http://localhost:8000`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test if local backend is running
|
||||||
|
curl http://localhost:8000/matches?limit=1
|
||||||
|
```
|
||||||
|
|
||||||
|
If you get "Connection refused", start the backend service.
|
||||||
|
|
||||||
|
4. **Check browser console**:
|
||||||
|
- Open DevTools → Console tab
|
||||||
|
- Look for `[API Route]` error messages from the server route handler
|
||||||
|
- Network tab should show requests to `/api/*` (not external URLs)
|
||||||
|
- Check if requests return 503 (backend unreachable) or 500 (server error)
|
||||||
|
|
||||||
|
5. **Check server logs**:
|
||||||
|
- Look at the terminal running `npm run dev`
|
||||||
|
- Server route errors will appear with `[API Route] Error fetching...`
|
||||||
|
- This will show you the exact backend URL being requested
|
||||||
|
|
||||||
|
6. **Restart dev server**:
|
||||||
|
```bash
|
||||||
|
# Stop dev server (Ctrl+C)
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### CORS Errors (Should Never Happen)
|
||||||
|
|
||||||
|
CORS errors should be impossible with SvelteKit server routes since all requests are server-side.
|
||||||
|
|
||||||
|
**If you somehow see CORS errors:**
|
||||||
|
|
||||||
|
- This means the API client is bypassing the `/api` routes
|
||||||
|
- Check that `src/lib/api/client.ts` has `API_BASE_URL = '/api'`
|
||||||
|
- Verify `src/routes/api/[...path]/+server.ts` exists
|
||||||
|
- Clear cache and restart:
|
||||||
|
```bash
|
||||||
|
rm -rf .svelte-kit
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Port Already in Use
|
||||||
|
|
||||||
|
If port 5173 is already in use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vite will automatically try the next available port
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Or specify a custom port
|
||||||
|
npm run dev -- --port 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend Connection Issues
|
||||||
|
|
||||||
|
If the backend is on a different host/port, update `.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Custom backend location
|
||||||
|
VITE_API_BASE_URL=http://192.168.1.100:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
Then restart the dev server.
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### 1. Make Changes
|
||||||
|
|
||||||
|
Edit files in `src/`. The dev server has hot module replacement (HMR):
|
||||||
|
|
||||||
|
- Component changes reload instantly
|
||||||
|
- Route changes reload the page
|
||||||
|
- Store changes reload affected components
|
||||||
|
|
||||||
|
### 2. Type Checking
|
||||||
|
|
||||||
|
Run TypeScript type checking:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run check # Check once
|
||||||
|
npm run check:watch # Watch mode
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Linting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run lint # Check for issues
|
||||||
|
npm run lint:fix # Auto-fix issues
|
||||||
|
npm run format # Run Prettier
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Unit tests
|
||||||
|
npm run test # Run once
|
||||||
|
npm run test:watch # Watch mode
|
||||||
|
npm run test:coverage # Generate coverage report
|
||||||
|
|
||||||
|
# E2E tests
|
||||||
|
npm run test:e2e # Headless
|
||||||
|
npm run test:e2e:ui # Playwright UI
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
The backend provides these endpoints (see `docs/API.md` for full details):
|
||||||
|
|
||||||
|
- `GET /matches` - List all matches
|
||||||
|
- `GET /match/:id` - Get match details
|
||||||
|
- `GET /match/:id/rounds` - Get round economy data
|
||||||
|
- `GET /match/:id/weapons` - Get weapon statistics
|
||||||
|
- `GET /match/:id/chat` - Get chat messages
|
||||||
|
- `GET /player/:id` - Get player profile
|
||||||
|
|
||||||
|
### How Requests Work
|
||||||
|
|
||||||
|
**All Environments** (dev, preview, production):
|
||||||
|
|
||||||
|
```
|
||||||
|
Frontend code: api.matches.getMatches()
|
||||||
|
↓
|
||||||
|
API Client: GET /api/matches
|
||||||
|
↓
|
||||||
|
SvelteKit Route: src/routes/api/[...path]/+server.ts
|
||||||
|
↓
|
||||||
|
Server Handler: GET ${VITE_API_BASE_URL}/matches
|
||||||
|
↓
|
||||||
|
Response: ← Data returned to frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
The request flow is identical in all environments. The only difference is which backend URL `VITE_API_BASE_URL` points to:
|
||||||
|
|
||||||
|
- Development: Usually `https://api.csgow.tf` (production API)
|
||||||
|
- Local full-stack: `http://localhost:8000` (local backend)
|
||||||
|
- Production: `https://api.csgow.tf` (or custom backend URL)
|
||||||
|
|
||||||
|
## Mock Data (Alternative: No Backend)
|
||||||
|
|
||||||
|
If you want to develop without any backend (local or production), enable MSW mocking:
|
||||||
|
|
||||||
|
1. Update `.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
VITE_ENABLE_MSW_MOCKING=true
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Restart dev server
|
||||||
|
|
||||||
|
The app will use mock data from `src/mocks/handlers/`.
|
||||||
|
|
||||||
|
**Note**: Mock data is limited and may not reflect all features. **Production API is recommended** for most development work.
|
||||||
|
|
||||||
|
## Building for Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Preview production build locally
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
The preview server runs on `http://localhost:4173` and uses the production API configuration.
|
||||||
|
|
||||||
|
## Environment Variables Reference
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
| -------------------------- | ----------------------- | ---------------------------- |
|
||||||
|
| `VITE_API_BASE_URL` | `http://localhost:8000` | Backend API base URL |
|
||||||
|
| `VITE_API_TIMEOUT` | `10000` | Request timeout (ms) |
|
||||||
|
| `VITE_ENABLE_LIVE_MATCHES` | `false` | Enable live match polling |
|
||||||
|
| `VITE_ENABLE_ANALYTICS` | `false` | Enable analytics tracking |
|
||||||
|
| `VITE_DEBUG_MODE` | `false` | Enable debug logging |
|
||||||
|
| `VITE_ENABLE_MSW_MOCKING` | `false` | Use mock data instead of API |
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
- **Frontend Issues**: Check browser console for errors
|
||||||
|
- **API Issues**: Check backend logs and proxy output in terminal
|
||||||
|
- **Type Errors**: Run `npm run check` for detailed messages
|
||||||
|
- **Build Issues**: Delete `.svelte-kit/` and `node_modules/`, then `npm install`
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Read `TODO.md` for current development status
|
||||||
|
- Check `docs/DESIGN.md` for design system documentation
|
||||||
|
- Review `docs/API.md` for complete API reference
|
||||||
|
- See `README.md` for project overview
|
||||||
460
docs/MATCHES_API.md
Normal file
@@ -0,0 +1,460 @@
|
|||||||
|
# Matches API Endpoint Documentation
|
||||||
|
|
||||||
|
This document provides detailed information about the matches API endpoints used by CS2.WTF to retrieve match data from the backend CSGOWTFD service.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The matches API provides access to Counter-Strike 2 match data including match listings, detailed match statistics, and related match information such as weapons, rounds, and chat data.
|
||||||
|
|
||||||
|
## Base URL
|
||||||
|
|
||||||
|
All endpoints are relative to the API base URL: `https://api.csgow.tf`
|
||||||
|
|
||||||
|
During development, requests are proxied through `/api` to avoid CORS issues.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
No authentication is required for read operations. All match data is publicly accessible.
|
||||||
|
|
||||||
|
## Rate Limiting
|
||||||
|
|
||||||
|
The API does not currently enforce rate limiting, but clients should implement reasonable request throttling to avoid overwhelming the service.
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### 1. Get Matches List
|
||||||
|
|
||||||
|
Retrieves a paginated list of matches.
|
||||||
|
|
||||||
|
**Endpoint**: `GET /matches`
|
||||||
|
**Alternative**: `GET /matches/next/:time`
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
- `time` (path, optional): Unix timestamp for pagination (use with `/matches/next/:time`)
|
||||||
|
- Query parameters:
|
||||||
|
- `limit` (optional): Number of matches to return (default: 50, max: 100)
|
||||||
|
- `map` (optional): Filter by map name (e.g., `de_inferno`)
|
||||||
|
- `player_id` (optional): Filter by player Steam ID
|
||||||
|
|
||||||
|
**Response** (200 OK):
|
||||||
|
|
||||||
|
**IMPORTANT**: This endpoint returns a **plain array**, not an object with properties.
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"match_id": "3589487716842078322",
|
||||||
|
"map": "de_inferno",
|
||||||
|
"date": 1730487900,
|
||||||
|
"score": [13, 10],
|
||||||
|
"duration": 2456,
|
||||||
|
"match_result": 1,
|
||||||
|
"max_rounds": 24,
|
||||||
|
"parsed": true,
|
||||||
|
"vac": false,
|
||||||
|
"game_ban": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Field Descriptions**:
|
||||||
|
|
||||||
|
- `match_id`: Unique match identifier (uint64 as string)
|
||||||
|
- `map`: Map name (can be empty string if not parsed)
|
||||||
|
- `date`: Unix timestamp (seconds since epoch)
|
||||||
|
- `score`: Array with two elements `[team_a_score, team_b_score]`
|
||||||
|
- `duration`: Match duration in seconds
|
||||||
|
- `match_result`: 0 = tie, 1 = team_a win, 2 = team_b win
|
||||||
|
- `max_rounds`: Maximum rounds (24 for MR12, 30 for MR15)
|
||||||
|
- `parsed`: Whether the demo has been parsed
|
||||||
|
- `vac`: Whether any player has a VAC ban
|
||||||
|
- `game_ban`: Whether any player has a game ban
|
||||||
|
|
||||||
|
**Pagination**:
|
||||||
|
|
||||||
|
- The API returns a plain array of matches, sorted by date (newest first)
|
||||||
|
- To get the next page, use the `date` field from the **last match** in the array
|
||||||
|
- Request `/matches/next/{timestamp}` where `{timestamp}` is the Unix timestamp
|
||||||
|
- Continue until the response returns fewer matches than your `limit` parameter
|
||||||
|
- Example: If you request `limit=20` and get back 15 matches, you've reached the end
|
||||||
|
|
||||||
|
### 2. Get Match Details
|
||||||
|
|
||||||
|
Retrieves detailed information about a specific match including player statistics.
|
||||||
|
|
||||||
|
**Endpoint**: `GET /match/{match_id}`
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
- `match_id` (path): The unique match identifier (uint64 as string)
|
||||||
|
|
||||||
|
**Response** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"match_id": "3589487716842078322",
|
||||||
|
"share_code": "CSGO-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX",
|
||||||
|
"map": "de_inferno",
|
||||||
|
"date": "2024-11-01T18:45:00Z",
|
||||||
|
"score_team_a": 13,
|
||||||
|
"score_team_b": 10,
|
||||||
|
"duration": 2456,
|
||||||
|
"match_result": 1,
|
||||||
|
"max_rounds": 24,
|
||||||
|
"demo_parsed": true,
|
||||||
|
"vac_present": false,
|
||||||
|
"gameban_present": false,
|
||||||
|
"tick_rate": 64.0, // Optional: not always provided by API
|
||||||
|
"players": [
|
||||||
|
{
|
||||||
|
"id": "765611980123456",
|
||||||
|
"name": "Player1",
|
||||||
|
"avatar": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/fe/fef49e7fa7e1997310d705b2a6158ff8dc1cdfeb_full.jpg",
|
||||||
|
"team_id": 2,
|
||||||
|
"kills": 24,
|
||||||
|
"deaths": 18,
|
||||||
|
"assists": 6,
|
||||||
|
"headshot": 12,
|
||||||
|
"mvp": 3,
|
||||||
|
"score": 56,
|
||||||
|
"kast": 78, // Optional: not always provided by API
|
||||||
|
"rank_old": 18500,
|
||||||
|
"rank_new": 18650,
|
||||||
|
"dmg_enemy": 2450,
|
||||||
|
"dmg_team": 120,
|
||||||
|
"flash_assists": 4,
|
||||||
|
"flash_duration_enemy": 15.6,
|
||||||
|
"flash_total_enemy": 8,
|
||||||
|
"ud_he": 450,
|
||||||
|
"ud_flames": 230,
|
||||||
|
"ud_flash": 5,
|
||||||
|
"ud_smoke": 3,
|
||||||
|
"avg_ping": 25.5,
|
||||||
|
"color": "yellow"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Get Match Weapons
|
||||||
|
|
||||||
|
Retrieves weapon statistics for all players in a match.
|
||||||
|
|
||||||
|
**Endpoint**: `GET /match/{match_id}/weapons`
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
- `match_id` (path): The unique match identifier
|
||||||
|
|
||||||
|
**Response** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"match_id": 3589487716842078322,
|
||||||
|
"weapons": [
|
||||||
|
{
|
||||||
|
"player_id": 765611980123456,
|
||||||
|
"weapon_stats": [
|
||||||
|
{
|
||||||
|
"eq_type": 17,
|
||||||
|
"weapon_name": "AK-47",
|
||||||
|
"kills": 12,
|
||||||
|
"damage": 1450,
|
||||||
|
"hits": 48,
|
||||||
|
"hit_groups": {
|
||||||
|
"head": 8,
|
||||||
|
"chest": 25,
|
||||||
|
"stomach": 8,
|
||||||
|
"left_arm": 3,
|
||||||
|
"right_arm": 2,
|
||||||
|
"left_leg": 1,
|
||||||
|
"right_leg": 1
|
||||||
|
},
|
||||||
|
"headshot_pct": 16.7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Get Match Rounds
|
||||||
|
|
||||||
|
Retrieves round-by-round statistics for a match.
|
||||||
|
|
||||||
|
**Endpoint**: `GET /match/{match_id}/rounds`
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
- `match_id` (path): The unique match identifier
|
||||||
|
|
||||||
|
**Response** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"match_id": 3589487716842078322,
|
||||||
|
"rounds": [
|
||||||
|
{
|
||||||
|
"round": 1,
|
||||||
|
"winner": 2,
|
||||||
|
"win_reason": "elimination",
|
||||||
|
"players": [
|
||||||
|
{
|
||||||
|
"round": 1,
|
||||||
|
"player_id": 765611980123456,
|
||||||
|
"bank": 800,
|
||||||
|
"equipment": 650,
|
||||||
|
"spent": 650,
|
||||||
|
"kills_in_round": 2,
|
||||||
|
"damage_in_round": 120
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Get Match Chat
|
||||||
|
|
||||||
|
Retrieves chat messages from a match.
|
||||||
|
|
||||||
|
**Endpoint**: `GET /match/{match_id}/chat`
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
- `match_id` (path): The unique match identifier
|
||||||
|
|
||||||
|
**Response** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"match_id": 3589487716842078322,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"player_id": 765611980123456,
|
||||||
|
"player_name": "Player1",
|
||||||
|
"message": "nice shot!",
|
||||||
|
"tick": 15840,
|
||||||
|
"round": 8,
|
||||||
|
"all_chat": true,
|
||||||
|
"timestamp": "2024-11-01T19:12:34Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Parse Match from Share Code
|
||||||
|
|
||||||
|
Initiates parsing of a match from a CS:GO/CS2 share code.
|
||||||
|
|
||||||
|
**Endpoint**: `GET /match/parse/{sharecode}`
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
- `sharecode` (path): The CS:GO/CS2 match share code
|
||||||
|
|
||||||
|
**Response** (200 OK):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"match_id": "3589487716842078322",
|
||||||
|
"status": "parsing",
|
||||||
|
"message": "Demo download and parsing initiated",
|
||||||
|
"estimated_time": 120
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Models
|
||||||
|
|
||||||
|
### Match
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Match {
|
||||||
|
match_id: string; // Unique match identifier (uint64 as string)
|
||||||
|
share_code?: string; // CS:GO/CS2 share code (optional)
|
||||||
|
map: string; // Map name (e.g., "de_inferno")
|
||||||
|
date: string; // Match date and time (ISO 8601)
|
||||||
|
score_team_a: number; // Final score for team A
|
||||||
|
score_team_b: number; // Final score for team B
|
||||||
|
duration: number; // Match duration in seconds
|
||||||
|
match_result: number; // Match result: 0 = tie, 1 = team_a win, 2 = team_b win
|
||||||
|
max_rounds: number; // Maximum rounds (24 for MR12, 30 for MR15)
|
||||||
|
demo_parsed: boolean; // Whether the demo has been successfully parsed
|
||||||
|
vac_present: boolean; // Whether any player has a VAC ban
|
||||||
|
gameban_present: boolean; // Whether any player has a game ban
|
||||||
|
tick_rate?: number; // Server tick rate (64 or 128) - optional, not always provided by API
|
||||||
|
players?: MatchPlayer[]; // Array of player statistics (optional)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MatchPlayer
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface MatchPlayer {
|
||||||
|
id: string; // Player Steam ID (uint64 as string)
|
||||||
|
name: string; // Player display name
|
||||||
|
avatar: string; // Steam avatar URL
|
||||||
|
team_id: number; // Team ID: 2 = T side, 3 = CT side
|
||||||
|
kills: number; // Kills
|
||||||
|
deaths: number; // Deaths
|
||||||
|
assists: number; // Assists
|
||||||
|
headshot: number; // Headshot kills
|
||||||
|
mvp: number; // MVP stars earned
|
||||||
|
score: number; // In-game score
|
||||||
|
kast?: number; // KAST percentage (0-100) - optional, not always provided by API
|
||||||
|
rank_old?: number; // Premier rating before match (0-30000)
|
||||||
|
rank_new?: number; // Premier rating after match (0-30000)
|
||||||
|
dmg_enemy?: number; // Damage to enemies
|
||||||
|
dmg_team?: number; // Damage to teammates
|
||||||
|
flash_assists?: number; // Flash assist count
|
||||||
|
flash_duration_enemy?: number; // Total enemy blind time
|
||||||
|
flash_total_enemy?: number; // Enemies flashed count
|
||||||
|
ud_he?: number; // HE grenade damage
|
||||||
|
ud_flames?: number; // Molotov/Incendiary damage
|
||||||
|
ud_flash?: number; // Flash grenades used
|
||||||
|
ud_smoke?: number; // Smoke grenades used
|
||||||
|
avg_ping?: number; // Average ping
|
||||||
|
color?: string; // Player color
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MatchListItem
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface MatchListItem {
|
||||||
|
match_id: string; // Unique match identifier (uint64 as string)
|
||||||
|
map: string; // Map name
|
||||||
|
date: string; // Match date and time (ISO 8601)
|
||||||
|
score_team_a: number; // Final score for team A
|
||||||
|
score_team_b: number; // Final score for team B
|
||||||
|
duration: number; // Match duration in seconds
|
||||||
|
demo_parsed: boolean; // Whether the demo has been successfully parsed
|
||||||
|
player_count?: number; // Number of players in the match - optional, not provided by API
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
All API errors follow a consistent format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Error message",
|
||||||
|
"code": 404,
|
||||||
|
"details": {
|
||||||
|
"match_id": "3589487716842078322"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common HTTP Status Codes
|
||||||
|
|
||||||
|
- `200 OK`: Request successful
|
||||||
|
- `400 Bad Request`: Invalid parameters
|
||||||
|
- `404 Not Found`: Resource not found
|
||||||
|
- `500 Internal Server Error`: Server error
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
### Pagination
|
||||||
|
|
||||||
|
The matches API implements cursor-based pagination using timestamps:
|
||||||
|
|
||||||
|
1. Initial request to `/matches` returns a plain array of matches (sorted newest first)
|
||||||
|
2. Extract the `date` field from the **last match** in the array
|
||||||
|
3. Request `/matches/next/{timestamp}` to get older matches
|
||||||
|
4. Continue until the response returns fewer matches than your `limit` parameter
|
||||||
|
5. The API does **not** provide `has_more` or `next_page_time` fields - you must calculate these yourself
|
||||||
|
|
||||||
|
### Data Transformation
|
||||||
|
|
||||||
|
The frontend application transforms legacy API responses to a modern schema-validated format:
|
||||||
|
|
||||||
|
- Unix timestamps are converted to ISO strings
|
||||||
|
- Avatar hashes are converted to full URLs (if provided)
|
||||||
|
- Team IDs are normalized (1/2 → 2/3 if needed)
|
||||||
|
- Score arrays `[team_a, team_b]` are split into separate fields
|
||||||
|
- Field names are mapped: `parsed` → `demo_parsed`, `vac` → `vac_present`, `game_ban` → `gameban_present`
|
||||||
|
- Missing fields are provided with defaults (e.g., `tick_rate: 64`)
|
||||||
|
|
||||||
|
### Steam ID Handling
|
||||||
|
|
||||||
|
All Steam IDs and Match IDs are handled as strings to preserve uint64 precision. Never convert these to numbers as it causes precision loss.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Fetching Matches with Pagination
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Initial request - API returns a plain array
|
||||||
|
const matches = await fetch('/api/matches?limit=20').then((r) => r.json());
|
||||||
|
|
||||||
|
// matches is an array: [{ match_id, map, date, ... }, ...]
|
||||||
|
console.log(`Loaded ${matches.length} matches`);
|
||||||
|
|
||||||
|
// Get the timestamp of the last match for pagination
|
||||||
|
if (matches.length > 0) {
|
||||||
|
const lastMatch = matches[matches.length - 1];
|
||||||
|
const lastTimestamp = lastMatch.date; // Unix timestamp
|
||||||
|
|
||||||
|
// Fetch next page using the timestamp
|
||||||
|
const moreMatches = await fetch(`/api/matches/next/${lastTimestamp}?limit=20`).then((r) =>
|
||||||
|
r.json()
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Loaded ${moreMatches.length} more matches`);
|
||||||
|
|
||||||
|
// Check if we've reached the end
|
||||||
|
if (moreMatches.length < 20) {
|
||||||
|
console.log('Reached the end of matches');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complete Pagination Loop
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function loadAllMatches(limit = 50) {
|
||||||
|
let allMatches = [];
|
||||||
|
let hasMore = true;
|
||||||
|
let lastTimestamp = null;
|
||||||
|
|
||||||
|
while (hasMore) {
|
||||||
|
// Build URL based on whether we have a timestamp
|
||||||
|
const url = lastTimestamp
|
||||||
|
? `/api/matches/next/${lastTimestamp}?limit=${limit}`
|
||||||
|
: `/api/matches?limit=${limit}`;
|
||||||
|
|
||||||
|
// Fetch matches
|
||||||
|
const matches = await fetch(url).then((r) => r.json());
|
||||||
|
|
||||||
|
// Add to collection
|
||||||
|
allMatches.push(...matches);
|
||||||
|
|
||||||
|
// Check if there are more
|
||||||
|
if (matches.length < limit) {
|
||||||
|
hasMore = false;
|
||||||
|
} else {
|
||||||
|
// Get timestamp of last match for next iteration
|
||||||
|
lastTimestamp = matches[matches.length - 1].date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allMatches;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filtering Matches by Map
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const response = await fetch('/api/matches?map=de_inferno&limit=20');
|
||||||
|
const data = await response.json();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filtering Matches by Player
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const response = await fetch('/api/matches?player_id=765611980123456&limit=20');
|
||||||
|
const data = await response.json();
|
||||||
|
```
|
||||||
54
eslint.config.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import ts from 'typescript-eslint';
|
||||||
|
import svelte from 'eslint-plugin-svelte';
|
||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
|
/** @type {import('eslint').Linter.FlatConfig[]} */
|
||||||
|
export default [
|
||||||
|
js.configs.recommended,
|
||||||
|
...ts.configs.recommended,
|
||||||
|
...svelte.configs['flat/recommended'],
|
||||||
|
prettier,
|
||||||
|
...svelte.configs['flat/prettier'],
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.svelte'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
parser: ts.parser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'build/',
|
||||||
|
'.svelte-kit/',
|
||||||
|
'dist/',
|
||||||
|
'node_modules/',
|
||||||
|
'**/*.cjs',
|
||||||
|
'*.config.js',
|
||||||
|
'*.config.ts'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-explicit-any': 'error',
|
||||||
|
'svelte/no-at-html-tags': 'warn'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
9106
package-lock.json
generated
Normal file
118
package.json
@@ -1,43 +1,79 @@
|
|||||||
{
|
{
|
||||||
"name": "csgowtf",
|
"name": "cs2wtf",
|
||||||
"version": "1.0.7",
|
"version": "2.0.0",
|
||||||
"private": true,
|
"description": "Statistics for CS2 matchmaking matches",
|
||||||
"scripts": {
|
"private": true,
|
||||||
"serve": "vue-cli-service serve",
|
"type": "module",
|
||||||
"build": "vue-cli-service build --mode production",
|
"scripts": {
|
||||||
"lint": "vue-cli-service lint"
|
"dev": "vite dev",
|
||||||
},
|
"build": "vite build",
|
||||||
"dependencies": {
|
"preview": "vite preview",
|
||||||
"@popperjs/core": "^2.11.2",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"axios": "^0.25.0",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"bootstrap": "^5.1.3",
|
"lint": "prettier --check . && eslint .",
|
||||||
"core-js": "^3.21.0",
|
"lint:fix": "prettier --write . && eslint --fix .",
|
||||||
"dotenv-webpack": "^7.1.0",
|
"format": "prettier --write .",
|
||||||
"echarts": "^5.3.0",
|
"test": "vitest run",
|
||||||
"fork-awesome": "^1.2.0",
|
"test:watch": "vitest",
|
||||||
"http-status-codes": "^2.2.0",
|
"test:coverage": "vitest run --coverage",
|
||||||
"iso-639-1": "^2.1.13",
|
"test:e2e": "playwright test",
|
||||||
"jquery": "^3.6.0",
|
"test:e2e:ui": "playwright test --ui",
|
||||||
"luxon": "^2.3.0",
|
"test:e2e:debug": "playwright test --debug",
|
||||||
"string-sanitizer": "^2.0.2",
|
"prepare": "husky"
|
||||||
"vue": "^3.2.30",
|
},
|
||||||
"vue-matomo": "^4.1.0",
|
"dependencies": {
|
||||||
"vue-router": "^4.0.12",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"vue3-cookies": "^1.0.6",
|
"axios": "^1.6.0",
|
||||||
"vuex": "^4.0.2"
|
"chart.js": "^4.5.1",
|
||||||
},
|
"svelte": "^5.0.0",
|
||||||
"devDependencies": {
|
"zod": "^3.22.0"
|
||||||
"@vue/cli-plugin-babel": "~4.5.15",
|
},
|
||||||
"@vue/cli-plugin-eslint": "~4.5.15",
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-router": "~4.5.15",
|
"@playwright/test": "^1.40.0",
|
||||||
"@vue/cli-plugin-vuex": "~4.5.15",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
"@vue/cli-service": "~4.5.15",
|
"@sveltejs/adapter-node": "^5.0.0",
|
||||||
"@vue/compiler-sfc": "^3.2.30",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"@testing-library/jest-dom": "^6.0.0",
|
||||||
"eslint": "^6.8.0",
|
"@testing-library/svelte": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^7.20.0",
|
"@types/node": "^20.10.0",
|
||||||
"sass": "^1.49.7",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"sass-loader": "^10.2.1"
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
},
|
"@vitest/coverage-v8": "^1.0.0",
|
||||||
"packageManager": "yarn@3.0.2"
|
"autoprefixer": "^10.4.0",
|
||||||
|
"daisyui": "^4.0.0",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-svelte": "^2.35.0",
|
||||||
|
"globals": "^15.0.0",
|
||||||
|
"husky": "^9.0.0",
|
||||||
|
"jsdom": "^24.0.0",
|
||||||
|
"lint-staged": "^15.0.0",
|
||||||
|
"lucide-svelte": "^0.400.0",
|
||||||
|
"msw": "^2.0.0",
|
||||||
|
"postcss": "^8.4.0",
|
||||||
|
"prettier": "^3.2.0",
|
||||||
|
"prettier-plugin-svelte": "^3.1.0",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.0",
|
||||||
|
"stylelint": "^16.0.0",
|
||||||
|
"stylelint-config-standard": "^36.0.0",
|
||||||
|
"svelte-check": "^4.0.0",
|
||||||
|
"tailwindcss": "^3.4.0",
|
||||||
|
"tslib": "^2.6.0",
|
||||||
|
"typescript": "^5.3.0",
|
||||||
|
"typescript-eslint": "^8.0.0",
|
||||||
|
"vite": "^5.0.0",
|
||||||
|
"vitest": "^1.0.0"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,ts,svelte}": [
|
||||||
|
"prettier --write",
|
||||||
|
"eslint --fix"
|
||||||
|
],
|
||||||
|
"*.{json,css,md}": [
|
||||||
|
"prettier --write"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
playwright.config.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||||
|
|
||||||
|
const config: PlaywrightTestConfig = {
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run build && npm run preview',
|
||||||
|
port: 4173,
|
||||||
|
reuseExistingServer: !process.env.CI
|
||||||
|
},
|
||||||
|
testDir: 'tests/e2e',
|
||||||
|
testMatch: /(.+\.)?(test|spec)\.[jt]s/,
|
||||||
|
use: {
|
||||||
|
baseURL: 'http://localhost:4173',
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
trace: 'retain-on-failure'
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { browserName: 'chromium' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'firefox',
|
||||||
|
use: { browserName: 'firefox' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'webkit',
|
||||||
|
use: { browserName: 'webkit' }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
reporter: process.env.CI ? 'github' : 'html',
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: process.env.CI ? 1 : undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
6
postcss.config.cjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Before Width: | Height: | Size: 454 KiB |
|
Before Width: | Height: | Size: 440 KiB |
|
Before Width: | Height: | Size: 425 KiB |
|
Before Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 592 KiB |
|
Before Width: | Height: | Size: 588 KiB |
|
Before Width: | Height: | Size: 528 KiB |
|
Before Width: | Height: | Size: 326 KiB |
|
Before Width: | Height: | Size: 488 KiB |
|
Before Width: | Height: | Size: 493 KiB |
|
Before Width: | Height: | Size: 586 KiB |
|
Before Width: | Height: | Size: 615 KiB |
|
Before Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 424 KiB |
|
Before Width: | Height: | Size: 233 KiB |
|
Before Width: | Height: | Size: 511 KiB |
|
Before Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 526 KiB |
|
Before Width: | Height: | Size: 466 KiB |
|
Before Width: | Height: | Size: 503 KiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 491 KiB |
|
Before Width: | Height: | Size: 411 KiB |
|
Before Width: | Height: | Size: 502 KiB |
|
Before Width: | Height: | Size: 588 KiB |
|
Before Width: | Height: | Size: 768 KiB |
|
Before Width: | Height: | Size: 486 KiB |
|
Before Width: | Height: | Size: 619 KiB |
|
Before Width: | Height: | Size: 765 KiB |
|
Before Width: | Height: | Size: 412 KiB |
|
Before Width: | Height: | Size: 313 KiB |
|
Before Width: | Height: | Size: 486 KiB |
|
Before Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 413 KiB |
|
Before Width: | Height: | Size: 608 KiB |
|
Before Width: | Height: | Size: 588 KiB |
|
Before Width: | Height: | Size: 475 KiB |
|
Before Width: | Height: | Size: 484 KiB |
|
Before Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 454 KiB |
|
Before Width: | Height: | Size: 479 KiB |
|
Before Width: | Height: | Size: 410 KiB |
|
Before Width: | Height: | Size: 544 KiB |
|
Before Width: | Height: | Size: 620 KiB |
|
Before Width: | Height: | Size: 456 KiB |
|
Before Width: | Height: | Size: 344 KiB |
|
Before Width: | Height: | Size: 513 KiB |
|
Before Width: | Height: | Size: 471 KiB |
|
Before Width: | Height: | Size: 627 KiB |
|
Before Width: | Height: | Size: 639 KiB |
|
Before Width: | Height: | Size: 478 KiB |
|
Before Width: | Height: | Size: 416 KiB |
|
Before Width: | Height: | Size: 436 KiB |
|
Before Width: | Height: | Size: 379 KiB |
|
Before Width: | Height: | Size: 505 KiB |
|
Before Width: | Height: | Size: 467 KiB |
|
Before Width: | Height: | Size: 613 KiB |
|
Before Width: | Height: | Size: 831 KiB |
|
Before Width: | Height: | Size: 448 KiB |