subtick-demoviewer
v1.0.4
Published
CS2 demo viewer component by Subtick — drop-in replacement for any project using the same demo-chunk API format
Downloads
57
Maintainers
Readme
subtick-demoviewer
CS2 demo viewer component by Subtick. Drop-in replacement for any project that uses the same demo-chunk API format.
Installation
npm install subtick-demoviewerUsage
1. Register the component
// main.js or wherever you register global components
import { DemoViewer } from 'subtick-demoviewer'
import 'subtick-demoviewer/style.css'
app.component('DemoViewer', DemoViewer)Or use it locally in a single-file component:
<script setup>
import { DemoViewer } from 'subtick-demoviewer'
import 'subtick-demoviewer/style.css'
</script>2. Replace your existing viewer
Before (csstrats old Viewer.vue):
<template>
<div class="relative w-full h-full">
<OldViewer />
</div>
</template>After:
<template>
<div class="relative w-full h-full">
<DemoViewer
:demo-id="demo.id"
:demo-rounds="demo.rounds"
:map-metadata="demo.map_metadata"
:version="demo.version"
:weapons="weaponsMap"
:demo-icons="demoIcons"
chunk-url="/api/demo-chunk"
map-image-base-url="/images/demo"
/>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { DemoViewer } from 'subtick-demoviewer'
import 'subtick-demoviewer/style.css'
// Your existing data — nothing changes here
const props = defineProps(['demo', 'weaponImages', 'iconImages'])
const weaponsMap = computed(() =>
Object.fromEntries((props.weaponImages ?? []).map(w => [w.name.toLowerCase(), w]))
)
const demoIcons = computed(() =>
Object.fromEntries((props.iconImages ?? []).map(i => [i.name, i]))
)
</script>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| demoId | Number\|String | required | Internal demo ID, sent to your chunk endpoint as demo_id |
| demoRounds | Array | [] | Array of round objects { round_number, meta: { CT_score, T_score, winner, freeze_end } } |
| mapMetadata | Object | null | { name, pos_x, pos_y, scale } — used for coordinate math and map image loading |
| version | Number | 1.0 | Demo version, passed to the worker |
| chunkUrl | String | '/api/demo-chunk' | Your endpoint. Called as chunkUrl?demo_id=X&round_index=Y |
| mapImageBaseUrl | String | '/images/demo' | Base path for map SVGs. Loads {base}/{mapName}.svg |
| playerIconBaseUrl | String | 'https://csstrats.b-cdn.net/icons' | Base URL for ct_player.svg / t_player.svg |
| weapons | Object | {} | Weapon map { 'weapon-name': { url, svg_path } } |
| demoIcons | Object | {} | Icon map { icon_name: { base64 } } used in kill feed |
| primaryColor | String | '#a78bfa' | Accent colour for UI elements |
| initialRound | Number | null | Which round to load first (overrides ?round= URL param) |
Chunk endpoint format
Your /api/demo-chunk endpoint must return JSON in this shape (same as csstrats today):
{
"meta": {
"freeze_end": 12345,
"start": 12300,
"CT_score": 3,
"T_score": 2
},
"ticks": {
"12345": {
"players": [
{
"steamid": "76561198...",
"name": "Player",
"team_name": "ct",
"X": 500.0,
"Y": -200.0,
"yaw": 90,
"health": 100,
"armor_value": 100,
"balance": 4750,
"flash_duration": 0,
"has_defuser": false,
"has_helmet": true,
"is_walking": false,
"active_weapon_name": "ak-47",
"inventory": []
}
],
"bomb": null,
"weapon_fires": [],
"grenades": []
}
},
"smokes": [],
"kills": {
"12400": [
{
"tick": 12400,
"attacker_name": "Player",
"attacker_side": "ct",
"victim_name": "Enemy",
"victim_side": "t",
"victim_steamid": "76561198...",
"weapon": "ak-47",
"headshot": true
}
]
}
}Styling
The component is self-contained and uses Tailwind utility classes internally (via inline styles where needed). It injects minimal global CSS scoped to .sdv-root.
To change the accent colour:
<DemoViewer primary-color="#22d3ee" ... />Or override the CSS variable in your stylesheet:
.sdv-root {
--sdv-primary: #22d3ee;
}Publishing to npm
See PUBLISHING.md for step-by-step instructions.
