+
#{seed.seed} {seed.teamName}
-
+
{seed.divisionWinner ? 'Division winner' : 'Wildcard'}
@@ -165,7 +210,7 @@ export function LeagueStandings() {
['INTs', statLeaders.defINT],
] as const).map(([label, leaders]) => (
-
{label.toUpperCase()}
+
{label.toUpperCase()}
{leaders.map((leader, index) => (
@@ -173,7 +218,7 @@ export function LeagueStandings() {
{index + 1}.
-
{leader.teamName}
+
{leader.teamName}
{leader.value}
diff --git a/apps/web/src/features/standings/standingsSignalSvg.test.tsx b/apps/web/src/features/standings/standingsSignalSvg.test.tsx
new file mode 100644
index 00000000..633fe9b0
--- /dev/null
+++ b/apps/web/src/features/standings/standingsSignalSvg.test.tsx
@@ -0,0 +1,52 @@
+import { renderToStaticMarkup } from 'react-dom/server';
+import { describe, expect, it } from 'vitest';
+import { StandingsSignalSvg, StreakSignalSvg } from './standingsSignalSvg';
+
+describe('StandingsSignalSvg', () => {
+ it('renders fire variant for streak W4+', () => {
+ const markup = renderToStaticMarkup(
);
+
+ expect(markup).toContain('data-standings-signal="fire"');
+ expect(markup).toContain('Hot streak (W4+)');
+ });
+
+ it('renders ice variant for streak L3+', () => {
+ const markup = renderToStaticMarkup(
);
+
+ expect(markup).toContain('data-standings-signal="ice"');
+ expect(markup).toContain('Cold streak (L3+)');
+ });
+
+ it('renders nothing for short streaks', () => {
+ expect(renderToStaticMarkup(
)).toBe('');
+ expect(renderToStaticMarkup(
)).toBe('');
+ });
+
+ it('renders locked playoff seed signal', () => {
+ const markup = renderToStaticMarkup(
);
+
+ expect(markup).toContain('data-standings-signal="seed_locked"');
+ expect(markup).toContain('Playoff seed locked');
+ });
+
+ it('renders bubble playoff seed signal', () => {
+ const markup = renderToStaticMarkup(
);
+
+ expect(markup).toContain('data-standings-signal="seed_bubble"');
+ expect(markup).toContain('Playoff bubble');
+ });
+
+ it('renders out-of-picture seed signal', () => {
+ const markup = renderToStaticMarkup(
);
+
+ expect(markup).toContain('data-standings-signal="seed_out"');
+ expect(markup).toContain('Outside playoff picture');
+ });
+
+ it('renders division-leader laurel signal', () => {
+ const markup = renderToStaticMarkup(
);
+
+ expect(markup).toContain('data-standings-signal="division_leader"');
+ expect(markup).toContain('Division leader');
+ });
+});
diff --git a/apps/web/src/features/standings/standingsSignalSvg.tsx b/apps/web/src/features/standings/standingsSignalSvg.tsx
new file mode 100644
index 00000000..3963b56a
--- /dev/null
+++ b/apps/web/src/features/standings/standingsSignalSvg.tsx
@@ -0,0 +1,117 @@
+export type StandingsSignalKind =
+ | 'fire'
+ | 'ice'
+ | 'seed_locked'
+ | 'seed_bubble'
+ | 'seed_out'
+ | 'division_leader';
+
+interface StandingsSignalSvgProps {
+ kind: StandingsSignalKind;
+ title: string;
+ size?: number;
+}
+
+export function StandingsSignalSvg({ kind, title, size = 16 }: StandingsSignalSvgProps) {
+ return (
+
+
+
+ );
+}
+
+export function StreakSignalSvg({ streak }: { streak: number }) {
+ if (streak >= 4) return
;
+ if (streak <= -3) return
;
+ return null;
+}
+
+function renderSignal(kind: StandingsSignalKind) {
+ switch (kind) {
+ case 'fire':
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ );
+ case 'ice':
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ case 'seed_locked':
+ return (
+
+
+
+
+ );
+ case 'seed_bubble':
+ return (
+
+ );
+ case 'seed_out':
+ return (
+
+
+
+
+
+ );
+ case 'division_leader':
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}