35 Commits

Author SHA1 Message Date
afed42de49 updated Nav-Search + removed string-sanitizer + added csgo-sharecode
Some checks failed
CSGOWTF/csgowtf/pipeline/head There was a failure building this commit
2022-03-27 21:11:18 +02:00
9ac3228f5d updated MatchChat 2022-03-27 14:56:09 +02:00
106ef97ede wip
Some checks failed
CSGOWTF/csgowtf/pipeline/head There was a failure building this commit
2022-03-26 17:45:14 +01:00
552188c8a9 updated InfoModal.vue + infoState.ts 2022-03-25 20:32:57 +01:00
ce70fa2e6f updated FooterComponent.vue to use script setup 2022-03-25 20:19:23 +01:00
5591e75c86 updated MatchRounds type 2022-03-25 20:18:55 +01:00
1236c2ca2d updated DetailsComponent.vue 2022-03-25 16:22:01 +01:00
9cbbfe9393 updated DamageSite.vue 2022-03-25 16:19:13 +01:00
f6dd2ea1c4 updated CookieConsentBtn.vue 2022-03-25 16:09:58 +01:00
70fb352d7f updated PlayerView.vue 2022-03-25 16:09:39 +01:00
67cc06abdf updated MatchesTable.vue 2022-03-25 16:05:38 +01:00
3ad55c7fc4 updated MatchView.vue 2022-03-25 15:49:54 +01:00
0a355ff2bd updated Match.ts to use correct type 2022-03-25 15:48:43 +01:00
16addf0bca updated playersArr.ts to use correct type 2022-03-25 15:48:15 +01:00
5cb339483a updated ExploreView.vue 2022-03-24 11:35:03 +01:00
7daa47bb64 updated PlayerView.vue to use <script setup lang="ts"> 2022-03-24 11:12:24 +01:00
8ed371d5fb updated HomeView.vue to use <script setup lang="ts"> 2022-03-24 11:12:01 +01:00
a45215dce1 updated PrivacyPolicy.vue 2022-03-24 11:11:37 +01:00
a03dad2a0e updated router 2022-03-24 11:11:20 +01:00
18cd1ecdc9 updated statusCode 2022-03-24 11:10:50 +01:00
7a866c9d50 fixed types and LocalStoragePlayer.ts 2022-03-24 11:10:31 +01:00
fe7b851157 updated infoState.ts 2022-03-24 10:20:20 +01:00
53225dffd4 added lib "es2021" to tsconfig 2022-03-24 10:20:05 +01:00
0c9d6e7975 updated ApiRequests.ts 2022-03-24 10:19:18 +01:00
640eddc365 added custom types 2022-03-24 10:18:57 +01:00
190064497e removed api due to missing types 2022-03-24 10:18:22 +01:00
d0d17ccd3d updated NavComponent.vue to use new <script setup lang="ts"> syntax 2022-03-22 10:11:27 +01:00
d479573f41 updated ExploreView.vue to use new <script setup lang="ts"> syntax 2022-03-22 10:11:12 +01:00
012b56f184 updated App.vue to use new <script setup lang="ts"> syntax 2022-03-22 10:10:56 +01:00
0e727716a3 updated utils from js to ts 2022-03-22 10:09:57 +01:00
7523286236 added pinia store 2022-03-22 10:09:28 +01:00
2c3685f594 updated constants 2022-03-22 10:08:57 +01:00
cbe770ecd7 added api 2022-03-22 10:08:24 +01:00
328f463cdb updated gitignore
Some checks failed
CSGOWTF/csgowtf/pipeline/head There was a failure building this commit
2022-03-18 11:43:44 +01:00
9a6d24193d upgrade from webpack to vite + typescript
Some checks failed
CSGOWTF/csgowtf/pipeline/head There was a failure building this commit
2022-03-18 11:40:43 +01:00
782 changed files with 15408 additions and 33373 deletions

View File

@@ -1,80 +0,0 @@
# 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

15
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,15 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");
module.exports = {
"root": true,
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript/recommended",
"@vue/eslint-config-prettier"
],
"env": {
"vue/setup-compiler-macros": true,
}
}

305
.gitignore vendored
View File

@@ -1,52 +1,287 @@
.DS_Store # Created by https://www.toptal.com/developers/gitignore/api/webstorm+all,yarn,windows,linux,node,vuejs
node_modules # Edit at https://www.toptal.com/developers/gitignore?templates=webstorm+all,yarn,windows,linux,node,vuejs
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
### Linux ###
*~
.env
# 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*
# Editor directories and files # Diagnostic reports (https://nodejs.org/api/report.html)
.vscode/* report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Build artifacts # Runtime data
dist pids
dist-ssr *.pid
*.local *.seed
*.pid.lock
# Test coverage # Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage coverage
*.lcov *.lcov
# nyc test coverage
.nyc_output .nyc_output
# Playwright # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
/test-results/ .grunt
/playwright-report/
/playwright/.cache/
# Vercel # Bower dependency directory (https://bower.io/)
.vercel bower_components
# Temporary files # node-waf configuration
.tmp .lock-wscript
tmp
*.tmp # 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.local
.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 cche 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
a

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

1
.nvmrc
View File

@@ -1 +0,0 @@
20.11.0

View File

@@ -1,30 +0,0 @@
.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

View File

@@ -1,17 +0,0 @@
{
"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"
}
}
]
}

View File

@@ -1,6 +0,0 @@
node_modules/
build/
.svelte-kit/
dist/
**/*.js
**/*.ts

View File

@@ -1,13 +0,0 @@
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
}
};

View File

@@ -1 +0,0 @@
nodejs 20.11.0

View File

@@ -1,100 +0,0 @@
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

File diff suppressed because one or more lines are too long

785
.yarn/releases/yarn-3.2.0.cjs vendored Normal file

File diff suppressed because one or more lines are too long

7
.yarnrc.yml Normal file
View File

@@ -0,0 +1,7 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.2.0.cjs

60
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,60 @@
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'
}
}
}
}

327
README.md
View File

@@ -1,314 +1,27 @@
# CS2.WTF # CSGOW.TF
[![SvelteKit](https://img.shields.io/badge/SvelteKit-5.0-FF3E00?style=flat-square&logo=svelte)](https://kit.svelte.dev/) [![Vue3](https://img.shields.io/badge/created%20with-Vue3-%2342b883?style=flat-square)](https://vuejs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-3178C6?style=flat-square&logo=typescript)](https://www.typescriptlang.org/) [![Go](https://img.shields.io/badge/created%20with-Go-%2379d4fd?style=flat-square)](https://go.dev/)
[![Tailwind CSS](https://img.shields.io/badge/Tailwind-3.4-06B6D4?style=flat-square&logo=tailwindcss)](https://tailwindcss.com/)
[![GPL3](https://img.shields.io/badge/licence-GPL3-%23007ec6?style=flat-square)](https://git.harting.dev/CSGOWTF/csgowtf/src/branch/master/LICENSE) [![GPL3](https://img.shields.io/badge/licence-GPL3-%23007ec6?style=flat-square)](https://git.harting.dev/CSGOWTF/csgowtf/src/branch/master/LICENSE)
[![Liberapay](https://img.shields.io/badge/donate%20on-LiberaPay-%23f6c915?style=flat-square)](https://liberapay.com/CSGOWTF/) [![Liberapay](https://img.shields.io/badge/donate%20on-LiberaPay-%23f6c915?style=flat-square)](https://liberapay.com/CSGOWTF/)
[![status-badge](https://ci.somegit.dev/api/badges/CSGOWTF/csgowtf/status.svg?branch=cs2-port)](https://ci.somegit.dev/CSGOWTF/csgowtf) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/csgowtf?style=flat-square)](https://liberapay.com/CSGOWTF/)
[![Website](https://img.shields.io/website?down_message=down&label=csgow.tf&style=flat-square&up_message=up&url=https%3A%2F%2Fcsgow.tf)](https://csgow.tf/)
<!--[![Typescript](https://img.shields.io/badge/created%20with-typescript-%233178c6?style=flat-square)](https://www.typescriptlang.org/)-->
**Statistics for CS2 matchmaking matches** - A complete rewrite of CSGOW.TF with modern web technologies. ### Statistics for CS:GO matchmaking matches.
--- ---
## 🚀 Quick Start ## Backend
This is the frontend to the [csgowtfd](https://git.harting.dev/CSGOWTF/csgowtfd) backend.
### Prerequisites
## Tips on how to contribute
- **Node.js** ≥ 18.0.0 (v20.11.0 recommended - see `.nvmrc`) - If you are implementing or fixing an issue, please comment on the issue so work is not duplicated.
- **npm** or **yarn** - If you want to implement a new feature, create an issue first describing the issue, so we know about it.
- Don't commit unnecessary changes to the codebase or debugging code.
### Installation - Write meaningful commits or squash them.
- Please try to follow the code style of the rest of the codebase.
```bash - Only make pull requests to the dev branch.
# Clone the repository - Only implement one feature per pull request to keep it easy to understand.
git clone https://somegit.dev/CSGOWTF/csgowtf.git - Expect comments or questions on your pull request from the project maintainers. We try to keep the code as consistent and maintainable as possible.
cd csgowtf - Each pull request should come from a new branch in your fork, it should have a meaningful name.
# 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:
[![Liberapay](https://img.shields.io/badge/donate%20on-LiberaPay-%23f6c915?style=for-the-badge)](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

1154
TODO.md

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,393 +0,0 @@
# 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.**

View File

@@ -1,587 +0,0 @@
# 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

View File

@@ -1,480 +0,0 @@
# 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**

View File

@@ -1,335 +0,0 @@
# 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

View File

@@ -1,460 +0,0 @@
# 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();
```

1
env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -1,54 +0,0 @@
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'
}
}
];

62
index.html Normal file
View File

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width,initial-scale=1.0" name="viewport">
<meta content="Track your CSGO matches and see your match details."
name="description">
<meta content="index, follow, archive"
name="robots">
<meta content="Track your CSGO matches and see your match details."
property="st:section">
<meta content="csgoWTF - Open source CSGO data platform"
name="twitter:title">
<meta content="Track your CSGO matches and see your match details."
name="twitter:description">
<meta content="summary_large_image"
name="twitter:card">
<meta content="https://csgow.tf/"
property="og:url">
<meta content="csgoWTF - Open source CSGO data platform"
property="og:title">
<meta content="Track your CSGO matches and see your match details."
property="og:description">
<meta content="website"
property="og:type">
<meta content="en_US"
property="og:locale">
<meta content="csgoWTF - Open source CSGO data platform"
property="og:site_name">
<meta content="https://csgow.tf/images/logo.png"
name="twitter:image">
<meta content="https://csgow.tf/images/logo.png"
property="og:image">
<meta content="1024"
property="og:image:width">
<meta content="526"
property="og:image:height">
<meta content="https://csgow.tf/images/logo.png"
property="og:image:secure_url">
<link href="/images/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180">
<link href="/images/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png">
<link href="/images/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
<link href="/site.webmanifest" rel="manifest">
<link rel="preconnect" href="https://steamcdn-a.akamaihd.net" crossorigin>
<link rel="dns-prefetch" href="https://steamcdn-a.akamaihd.net">
<link rel="preconnect" href="https://api.csgow.tf" crossorigin>
<link rel="dns-prefetch" href="https://api.csgow.tf">
<link rel="preconnect" href="https://piwik.harting.hosting" crossorigin>
<link rel="dns-prefetch" href="https://piwik.harting.hosting">
<title>csgoWTF</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

9106
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,79 +1,49 @@
{ {
"name": "cs2wtf", "name": "csgowtf",
"version": "2.0.0", "version": "1.0.7",
"description": "Statistics for CS2 matchmaking matches", "scripts": {
"private": true, "dev": "vite",
"type": "module", "host": "vite --host",
"scripts": { "build": "vue-tsc --noEmit && vite build",
"dev": "vite dev", "preview": "vite preview --port 5050",
"build": "vite build", "typecheck": "vue-tsc --noEmit",
"preview": "vite preview", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", },
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "dependencies": {
"lint": "prettier --check . && eslint .", "@popperjs/core": "^2.11.4",
"lint:fix": "prettier --write . && eslint --fix .", "axios": "^0.26.1",
"format": "prettier --write .", "bootstrap": "^5.1.3",
"test": "vitest run", "bootstrap-icons": "^1.8.1",
"test:watch": "vitest", "csgo-sharecode": "^3.0.1",
"test:coverage": "vitest run --coverage", "echarts": "^5.3.1",
"test:e2e": "playwright test", "fork-awesome": "^1.2.0",
"test:e2e:ui": "playwright test --ui", "http-status-codes": "^2.2.0",
"test:e2e:debug": "playwright test --debug", "iso-639-1": "^2.1.13",
"prepare": "husky" "jquery": "^3.6.0",
}, "luxon": "^2.3.1",
"dependencies": { "pinia": "^2.0.12",
"@sveltejs/kit": "^2.0.0", "vue": "^3.2.31",
"axios": "^1.6.0", "vue-matomo": "^4.1.0",
"chart.js": "^4.5.1", "vue-router": "^4.0.14",
"svelte": "^5.0.0", "vue3-cookies": "^1.0.6",
"zod": "^3.22.0" "vuex": "^4.0.2"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.40.0", "@rushstack/eslint-patch": "^1.1.1",
"@sveltejs/adapter-auto": "^3.0.0", "@types/echarts": "^4.9.13",
"@sveltejs/adapter-node": "^5.0.0", "@types/luxon": "^2.3.1",
"@sveltejs/vite-plugin-svelte": "^4.0.0", "@types/node": "^16.11.26",
"@testing-library/jest-dom": "^6.0.0", "@vitejs/plugin-vue": "^2.2.4",
"@testing-library/svelte": "^5.0.0", "@vue/eslint-config-prettier": "^7.0.0",
"@types/node": "^20.10.0", "@vue/eslint-config-typescript": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^7.0.0", "@vue/tsconfig": "^0.1.3",
"@typescript-eslint/parser": "^7.0.0", "eslint": "^8.11.0",
"@vitest/coverage-v8": "^1.0.0", "eslint-plugin-vue": "^8.5.0",
"autoprefixer": "^10.4.0", "prettier": "^2.6.0",
"daisyui": "^4.0.0", "sass": "^1.49.9",
"eslint": "^8.57.0", "typescript": "~4.6.2",
"eslint-config-prettier": "^9.1.0", "vite": "^2.8.6",
"eslint-plugin-svelte": "^2.35.0", "vue-tsc": "^0.33.2"
"globals": "^15.0.0", },
"husky": "^9.0.0", "packageManager": "yarn@3.2.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"
}
} }

View File

@@ -1,36 +0,0 @@
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;

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 504 B

After

Width:  |  Height:  |  Size: 504 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 214 KiB

View File

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 210 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 212 KiB

View File

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 217 KiB

View File

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 214 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 209 KiB

View File

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 212 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 211 KiB

View File

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 214 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 211 KiB

View File

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 208 KiB

View File

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 212 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 213 KiB

View File

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 278 KiB

View File

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 213 KiB

View File

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 212 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 231 KiB

View File

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 212 KiB

View File

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 212 KiB

View File

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 214 KiB

View File

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 210 KiB

View File

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 211 KiB

View File

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 212 KiB

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Some files were not shown because too many files have changed in this diff Show More