blanchon commited on
Commit
0a96402
·
1 Parent(s): 8899818

Apply Tailwind v4 shorthand fixes; narrow eslint disables to real FPs

Browse files

Auto-fixes from eslint-plugin-better-tailwindcss (enforce-canonical-classes
re-enabled — these are real Tailwind v4 shorthands, not stylistic preferences):

- h-full w-full → size-full
- h-5 w-5 → size-5
- px-2 py-2 → p-2
- text-{sm,xs} leading-relaxed → text-{sm,xs}/relaxed
- -translate-x-1/2 -translate-y-1/2 → -translate-1/2
- data-[disabled]:foo → data-disabled:foo
- break-words → wrap-break-word

Only no-unknown-classes stays disabled — it can't see classes defined in
Svelte <style> blocks (.vs-beam, .vp-freeze, ...).

eslint.config.js CHANGED
@@ -12,11 +12,9 @@ const tailwindRules = {
12
  ...eslintPluginBetterTailwindcss.configs.recommended.rules,
13
  // Prettier handles physical line wrapping
14
  'better-tailwindcss/enforce-consistent-line-wrapping': 'off',
15
- // Custom CSS classes in Svelte <style> blocks (vs-beam, mp-marker, vp-freeze, ...)
16
- // trip this rule even though they're real classes
17
- 'better-tailwindcss/no-unknown-classes': 'off',
18
- // `h-full w-full` ≠ `size-full` semantically when a parent is asymmetric
19
- 'better-tailwindcss/enforce-canonical-classes': 'off'
20
  };
21
 
22
  export default [
 
12
  ...eslintPluginBetterTailwindcss.configs.recommended.rules,
13
  // Prettier handles physical line wrapping
14
  'better-tailwindcss/enforce-consistent-line-wrapping': 'off',
15
+ // False-positives on classes defined inside Svelte <style> blocks
16
+ // (`vs-beam`, `vp-freeze`, ...) that the plugin can't see
17
+ 'better-tailwindcss/no-unknown-classes': 'off'
 
 
18
  };
19
 
20
  export default [
src/lib/components/grid-tile.svelte CHANGED
@@ -168,12 +168,12 @@
168
  ontimeupdate={onTimeUpdate}
169
  onended={onEnded}
170
  onprogress={onProgress}
171
- class="h-full w-full object-cover"
172
  ></video>
173
 
174
  <!-- top-left: player number, colored per slot -->
175
  <div
176
- class="absolute top-1.5 left-1.5 flex h-6 w-6 items-center justify-center rounded-sm font-mono text-[11px] font-bold text-white/95 shadow-sm ring-1 ring-black/20"
177
  style="background-color: {color};"
178
  >
179
  {player}
@@ -181,7 +181,7 @@
181
 
182
  {#if isLeader}
183
  <div
184
- class="absolute top-1.5 right-1.5 grid h-5 w-5 place-items-center rounded-sm bg-emerald-500/85 text-white shadow-sm ring-1 ring-black/20"
185
  aria-label="Audio source"
186
  title="Audio source"
187
  >
@@ -191,7 +191,7 @@
191
 
192
  {#if !available}
193
  <div
194
- class="absolute right-1.5 bottom-1.5 grid h-5 w-5 place-items-center rounded-sm bg-rose-500/85 text-white shadow-sm ring-1 ring-black/20"
195
  aria-label="Player is dead"
196
  title="Player is dead"
197
  >
 
168
  ontimeupdate={onTimeUpdate}
169
  onended={onEnded}
170
  onprogress={onProgress}
171
+ class="size-full object-cover"
172
  ></video>
173
 
174
  <!-- top-left: player number, colored per slot -->
175
  <div
176
+ class="absolute top-1.5 left-1.5 flex size-6 items-center justify-center rounded-sm font-mono text-[11px] font-bold text-white/95 shadow-sm ring-1 ring-black/20"
177
  style="background-color: {color};"
178
  >
179
  {player}
 
181
 
182
  {#if isLeader}
183
  <div
184
+ class="absolute top-1.5 right-1.5 grid size-5 place-items-center rounded-sm bg-emerald-500/85 text-white shadow-sm ring-1 ring-black/20"
185
  aria-label="Audio source"
186
  title="Audio source"
187
  >
 
191
 
192
  {#if !available}
193
  <div
194
+ class="absolute right-1.5 bottom-1.5 grid size-5 place-items-center rounded-sm bg-rose-500/85 text-white shadow-sm ring-1 ring-black/20"
195
  aria-label="Player is dead"
196
  title="Player is dead"
197
  >
src/lib/components/map-preview.svelte CHANGED
@@ -92,17 +92,17 @@
92
  style={size === undefined ? undefined : `width: ${size}px; height: ${size}px;`}
93
  >
94
  {#if !map}
95
- <Skeleton class="h-full w-full rounded-md" />
96
  {:else}
97
  <img
98
  src={radarUrl(mapName, displayedFloor)}
99
  alt="{mapName} radar"
100
- class="absolute inset-0 h-full w-full rounded-md opacity-70 select-none"
101
  draggable="false"
102
  />
103
  <svg
104
  viewBox="0 0 {RADAR_PX} {RADAR_PX}"
105
- class="absolute inset-0 h-full w-full"
106
  aria-label="Player positions on {mapName}"
107
  >
108
  {#each players as p (p.steamid)}
 
92
  style={size === undefined ? undefined : `width: ${size}px; height: ${size}px;`}
93
  >
94
  {#if !map}
95
+ <Skeleton class="size-full rounded-md" />
96
  {:else}
97
  <img
98
  src={radarUrl(mapName, displayedFloor)}
99
  alt="{mapName} radar"
100
+ class="absolute inset-0 size-full rounded-md opacity-70 select-none"
101
  draggable="false"
102
  />
103
  <svg
104
  viewBox="0 0 {RADAR_PX} {RADAR_PX}"
105
+ class="absolute inset-0 size-full"
106
  aria-label="Player positions on {mapName}"
107
  >
108
  {#each players as p (p.steamid)}
src/lib/components/perspective-grid.svelte CHANGED
@@ -137,7 +137,7 @@
137
  />
138
  <div class="flex items-center gap-1.5 px-0.5 text-xs">
139
  <span
140
- class="inline-block h-2 w-2 shrink-0 rounded-full"
141
  style={`background: ${playerColor(player.player)};`}
142
  ></span>
143
  <span class="truncate font-medium">Player {player.player}</span>
@@ -176,7 +176,7 @@
176
  />
177
  <div class="flex items-center gap-1.5 px-0.5 text-xs">
178
  <span
179
- class="inline-block h-2 w-2 shrink-0 rounded-full"
180
  style={`background: ${playerColor(player.player)};`}
181
  ></span>
182
  <span class="truncate font-medium">Player {player.player}</span>
 
137
  />
138
  <div class="flex items-center gap-1.5 px-0.5 text-xs">
139
  <span
140
+ class="inline-block size-2 shrink-0 rounded-full"
141
  style={`background: ${playerColor(player.player)};`}
142
  ></span>
143
  <span class="truncate font-medium">Player {player.player}</span>
 
176
  />
177
  <div class="flex items-center gap-1.5 px-0.5 text-xs">
178
  <span
179
+ class="inline-block size-2 shrink-0 rounded-full"
180
  style={`background: ${playerColor(player.player)};`}
181
  ></span>
182
  <span class="truncate font-medium">Player {player.player}</span>
src/lib/components/player-grid.svelte CHANGED
@@ -87,7 +87,7 @@
87
  )}
88
  >
89
  <span
90
- class="flex h-7 w-7 shrink-0 items-center justify-center rounded-sm font-mono text-xs font-bold text-white/95 shadow-sm ring-1 ring-black/20"
91
  style="background-color: {playerColor(p.player)};"
92
  >
93
  {p.player}
 
87
  )}
88
  >
89
  <span
90
+ class="flex size-7 shrink-0 items-center justify-center rounded-sm font-mono text-xs font-bold text-white/95 shadow-sm ring-1 ring-black/20"
91
  style="background-color: {playerColor(p.player)};"
92
  >
93
  {p.player}
src/lib/components/round-list.svelte CHANGED
@@ -28,9 +28,7 @@
28
  </script>
29
 
30
  <div class="flex h-full min-h-0 flex-col">
31
- <div
32
- class="border-b px-2 py-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"
33
- >
34
  Rounds <span class="ml-1 text-muted-foreground/50">({rounds.length})</span>
35
  </div>
36
  <ScrollArea class="min-h-0 flex-1">
 
28
  </script>
29
 
30
  <div class="flex h-full min-h-0 flex-col">
31
+ <div class="border-b p-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase">
 
 
32
  Rounds <span class="ml-1 text-muted-foreground/50">({rounds.length})</span>
33
  </div>
34
  <ScrollArea class="min-h-0 flex-1">
src/lib/components/timeline-bar.svelte CHANGED
@@ -43,7 +43,7 @@
43
  aria-disabled={disabled}
44
  class={cn(
45
  'flex items-center gap-3 rounded-lg border bg-card px-3 py-2 shadow-sm',
46
- 'data-[disabled]:pointer-events-none data-[disabled]:opacity-60'
47
  )}
48
  >
49
  <Button
 
43
  aria-disabled={disabled}
44
  class={cn(
45
  'flex items-center gap-3 rounded-lg border bg-card px-3 py-2 shadow-sm',
46
+ 'data-disabled:pointer-events-none data-disabled:opacity-60'
47
  )}
48
  >
49
  <Button
src/lib/components/video-player/playbar.svelte CHANGED
@@ -128,7 +128,7 @@
128
  <div
129
  data-dragging={isDragging}
130
  class={cn(
131
- 'absolute top-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary opacity-0 transition-opacity',
132
  'group-hover/playbar:opacity-100 data-[dragging=true]:opacity-100'
133
  )}
134
  style:left="{visiblePct}%"
 
128
  <div
129
  data-dragging={isDragging}
130
  class={cn(
131
+ 'absolute top-1/2 size-3 -translate-1/2 rounded-full bg-primary opacity-0 transition-opacity',
132
  'group-hover/playbar:opacity-100 data-[dragging=true]:opacity-100'
133
  )}
134
  style:left="{visiblePct}%"
src/lib/components/video-player/video-player.svelte CHANGED
@@ -289,7 +289,7 @@
289
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
290
  <div
291
  bind:this={containerEl}
292
- class="relative h-full w-full overflow-hidden bg-black"
293
  role="region"
294
  aria-label="Video player"
295
  tabindex="0"
@@ -309,7 +309,7 @@
309
  disableremoteplayback
310
  controlslist="nodownload nofullscreen noplaybackrate noremoteplayback"
311
  oncontextmenu={(e) => e.preventDefault()}
312
- class="h-full w-full object-cover"
313
  onloadedmetadata={onLoadedMetadata}
314
  onloadeddata={onLoadedData}
315
  onplay={onPlay}
@@ -323,7 +323,7 @@
323
  bind:this={canvasEl}
324
  data-wipe={dissolveFreeze || undefined}
325
  class={cn(
326
- 'vp-freeze pointer-events-none absolute inset-0 z-10 h-full w-full object-contain',
327
  !showFreezeFrame && 'hidden'
328
  )}
329
  aria-hidden="true"
 
289
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
290
  <div
291
  bind:this={containerEl}
292
+ class="relative size-full overflow-hidden bg-black"
293
  role="region"
294
  aria-label="Video player"
295
  tabindex="0"
 
309
  disableremoteplayback
310
  controlslist="nodownload nofullscreen noplaybackrate noremoteplayback"
311
  oncontextmenu={(e) => e.preventDefault()}
312
+ class="size-full object-cover"
313
  onloadedmetadata={onLoadedMetadata}
314
  onloadeddata={onLoadedData}
315
  onplay={onPlay}
 
323
  bind:this={canvasEl}
324
  data-wipe={dissolveFreeze || undefined}
325
  class={cn(
326
+ 'vp-freeze pointer-events-none absolute inset-0 z-10 size-full object-contain',
327
  !showFreezeFrame && 'hidden'
328
  )}
329
  aria-hidden="true"
src/lib/components/video-stage.svelte CHANGED
@@ -82,7 +82,7 @@
82
  <div class="overflow-hidden rounded-lg border bg-card shadow-sm">
83
  <AspectRatio ratio={16 / 9} class="bg-black">
84
  {#if loading}
85
- <Skeleton class="h-full w-full rounded-none" />
86
  <div class="absolute inset-0 flex items-center justify-center text-muted-foreground">
87
  <div class="text-center">
88
  <SpinnerIcon size={32} weight="duotone" class="mx-auto mb-2 animate-spin opacity-70" />
@@ -96,7 +96,7 @@
96
  <div class="max-w-sm px-6 text-center">
97
  <WarningCircleIcon size={32} weight="duotone" class="mx-auto mb-2 text-destructive" />
98
  <p class="text-sm font-medium text-foreground">Couldn't load clip</p>
99
- <p class="mt-1 text-xs break-words text-muted-foreground">{error}</p>
100
  </div>
101
  </div>
102
  {:else if !chunks.length}
@@ -160,7 +160,7 @@
160
  style="box-shadow: 0 0 24px {switchPing.color}55;"
161
  >
162
  <span
163
- class="inline-flex h-5 w-5 items-center justify-center rounded-sm font-mono text-[11px] font-bold text-white"
164
  style="background-color: {switchPing.color};"
165
  >
166
  {switchPing.player}
 
82
  <div class="overflow-hidden rounded-lg border bg-card shadow-sm">
83
  <AspectRatio ratio={16 / 9} class="bg-black">
84
  {#if loading}
85
+ <Skeleton class="size-full rounded-none" />
86
  <div class="absolute inset-0 flex items-center justify-center text-muted-foreground">
87
  <div class="text-center">
88
  <SpinnerIcon size={32} weight="duotone" class="mx-auto mb-2 animate-spin opacity-70" />
 
96
  <div class="max-w-sm px-6 text-center">
97
  <WarningCircleIcon size={32} weight="duotone" class="mx-auto mb-2 text-destructive" />
98
  <p class="text-sm font-medium text-foreground">Couldn't load clip</p>
99
+ <p class="mt-1 text-xs wrap-break-word text-muted-foreground">{error}</p>
100
  </div>
101
  </div>
102
  {:else if !chunks.length}
 
160
  style="box-shadow: 0 0 24px {switchPing.color}55;"
161
  >
162
  <span
163
+ class="inline-flex size-5 items-center justify-center rounded-sm font-mono text-[11px] font-bold text-white"
164
  style="background-color: {switchPing.color};"
165
  >
166
  {switchPing.player}
src/routes/+page.svelte CHANGED
@@ -151,7 +151,7 @@
151
  </Button>
152
  </div>
153
 
154
- <p class="mt-8 text-sm leading-relaxed text-pretty text-muted-foreground md:text-base">
155
  A rendered dataset of professional Counter Strike 2 matches: every round captured from all ten
156
  player POVs, frame-aligned, with spatialized audio, mouse/keyboard inputs, and per-tick world
157
  state. Built from HLTV demos, served as parquet shards, and browsable here without downloading
@@ -166,7 +166,7 @@
166
  >
167
  Motivation
168
  </h2>
169
- <p class="mx-auto mt-3 max-w-2xl text-center text-sm leading-relaxed text-muted-foreground">
170
  Pro CS2 demos are a goldmine of expert sequential decision-making — long horizons, partial
171
  observability, dense visual signal, and 5v5 multi-agent dynamics. This dataset turns them into
172
  trainable rendered video.
@@ -178,7 +178,7 @@
178
  <div class="font-heading text-sm font-semibold tracking-tight">
179
  {m.title}
180
  </div>
181
- <p class="mt-1 text-xs leading-relaxed text-muted-foreground">
182
  {m.body}
183
  </p>
184
  </div>
@@ -199,7 +199,7 @@
199
  Video
200
  </div>
201
  <div class="mt-1 font-heading text-sm font-semibold">1280×720 · 32 fps</div>
202
- <p class="mt-1 text-xs leading-relaxed text-muted-foreground">
203
  Near-lossless H.264, one stream per player POV. 10 streams per round, all tick-aligned.
204
  </p>
205
  </div>
@@ -208,7 +208,7 @@
208
  Inputs
209
  </div>
210
  <div class="mt-1 font-heading text-sm font-semibold">Keyboard + mouse</div>
211
- <p class="mt-1 text-xs leading-relaxed text-muted-foreground">
212
  Per-tick keystrokes, mouse delta, fire/jump/use, weapon switches — the actions a policy
213
  has to predict.
214
  </p>
@@ -218,7 +218,7 @@
218
  World state
219
  </div>
220
  <div class="mt-1 font-heading text-sm font-semibold">Per-tick game state</div>
221
- <p class="mt-1 text-xs leading-relaxed text-muted-foreground">
222
  Positions, velocities, camera intrinsics, health, armor, ammo, weapons — for all 10
223
  players, every tick.
224
  </p>
@@ -228,7 +228,7 @@
228
  G-buffers <span class="text-amber-600 dark:text-amber-400">· soon</span>
229
  </div>
230
  <div class="mt-1 font-heading text-sm font-semibold">Luminance · depth · vertex IDs</div>
231
- <p class="mt-1 text-xs leading-relaxed text-muted-foreground">
232
  Per-pixel auxiliary channels for self-supervised pretraining and dense prediction heads.
233
  </p>
234
  </div>
@@ -338,7 +338,7 @@
338
  BibTeX
339
  </h2>
340
  <pre
341
- class="mt-3 overflow-x-auto rounded-md border bg-card p-4 text-left font-mono text-xs leading-relaxed text-foreground/90">{`@misc{blanchon2026cs2dataset,
342
  author = {Julien Blanchon},
343
  title = {Counter Strike 2 Dataset},
344
  year = {2026},
 
151
  </Button>
152
  </div>
153
 
154
+ <p class="mt-8 text-sm/relaxed text-pretty text-muted-foreground md:text-base">
155
  A rendered dataset of professional Counter Strike 2 matches: every round captured from all ten
156
  player POVs, frame-aligned, with spatialized audio, mouse/keyboard inputs, and per-tick world
157
  state. Built from HLTV demos, served as parquet shards, and browsable here without downloading
 
166
  >
167
  Motivation
168
  </h2>
169
+ <p class="mx-auto mt-3 max-w-2xl text-center text-sm/relaxed text-muted-foreground">
170
  Pro CS2 demos are a goldmine of expert sequential decision-making — long horizons, partial
171
  observability, dense visual signal, and 5v5 multi-agent dynamics. This dataset turns them into
172
  trainable rendered video.
 
178
  <div class="font-heading text-sm font-semibold tracking-tight">
179
  {m.title}
180
  </div>
181
+ <p class="mt-1 text-xs/relaxed text-muted-foreground">
182
  {m.body}
183
  </p>
184
  </div>
 
199
  Video
200
  </div>
201
  <div class="mt-1 font-heading text-sm font-semibold">1280×720 · 32 fps</div>
202
+ <p class="mt-1 text-xs/relaxed text-muted-foreground">
203
  Near-lossless H.264, one stream per player POV. 10 streams per round, all tick-aligned.
204
  </p>
205
  </div>
 
208
  Inputs
209
  </div>
210
  <div class="mt-1 font-heading text-sm font-semibold">Keyboard + mouse</div>
211
+ <p class="mt-1 text-xs/relaxed text-muted-foreground">
212
  Per-tick keystrokes, mouse delta, fire/jump/use, weapon switches — the actions a policy
213
  has to predict.
214
  </p>
 
218
  World state
219
  </div>
220
  <div class="mt-1 font-heading text-sm font-semibold">Per-tick game state</div>
221
+ <p class="mt-1 text-xs/relaxed text-muted-foreground">
222
  Positions, velocities, camera intrinsics, health, armor, ammo, weapons — for all 10
223
  players, every tick.
224
  </p>
 
228
  G-buffers <span class="text-amber-600 dark:text-amber-400">· soon</span>
229
  </div>
230
  <div class="mt-1 font-heading text-sm font-semibold">Luminance · depth · vertex IDs</div>
231
+ <p class="mt-1 text-xs/relaxed text-muted-foreground">
232
  Per-pixel auxiliary channels for self-supervised pretraining and dense prediction heads.
233
  </p>
234
  </div>
 
338
  BibTeX
339
  </h2>
340
  <pre
341
+ class="mt-3 overflow-x-auto rounded-md border bg-card p-4 text-left font-mono text-xs/relaxed text-foreground/90">{`@misc{blanchon2026cs2dataset,
342
  author = {Julien Blanchon},
343
  title = {Counter Strike 2 Dataset},
344
  year = {2026},