diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0aa649d13..4f47c68ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,14 @@ Legend:
? = Other stuff
```
+(20/10/26): Version 2.1.22-srcdslab
+-----------------------
+```
+01. + Player drawer: the SteamID, Steam3 and Community values link to
+ the player's Steam Community profile
+02. ? Default theme version 2.0.4
+```
+
(20/10/26): Version 2.1.21-srcdslab
-----------------------
```
diff --git a/web/tests/e2e/specs/responsive/drawer.spec.ts b/web/tests/e2e/specs/responsive/drawer.spec.ts
index 077487cb3..a90759592 100644
--- a/web/tests/e2e/specs/responsive/drawer.spec.ts
+++ b/web/tests/e2e/specs/responsive/drawer.spec.ts
@@ -376,10 +376,14 @@ test.describe('responsive: drawer', () => {
// `dt + dd` selector lands on the
immediately after
// the matched . The contains the value text and a
// copy button (`[data-copy]`) — the assertion is that there
- // is NO `` descendant (which would mean an auto-detected
- // link slipped in).
+ // is NO `tel:` / `sms:` anchor (which would mean an auto-detected
+ // link slipped in). The value itself IS an anchor to the Steam
+ // Community profile, which must survive.
const steam3Value = idGrid.locator('dt:has-text("Steam3") + dd');
await expect(steam3Value).toBeVisible();
- await expect(steam3Value.locator('a')).toHaveCount(0);
+ await expect(steam3Value.locator('a[href^="tel:"], a[href^="sms:"]')).toHaveCount(0);
+ await expect(
+ steam3Value.locator('[data-testid="drawer-steam-profile-link"]'),
+ ).toHaveAttribute('href', /^https:\/\/steamcommunity\.com\/profiles\/7\d{16}$/);
});
});
diff --git a/web/themes/default/css/theme.css b/web/themes/default/css/theme.css
index b24744e8b..df08244a2 100644
--- a/web/themes/default/css/theme.css
+++ b/web/themes/default/css/theme.css
@@ -1936,6 +1936,12 @@ dialog::backdrop {
has been dropped (#1208 review finding 2). */
.drawer .drawer__ids dd,
.drawer .drawer__ban dd { min-width: 0; overflow-wrap: anywhere; }
+/* Steam identity values in the drawer link to the Steam Community
+ profile. Keep the mono value readable; the accent + underline only
+ show on hover / focus so the grid doesn't read as a wall of links. */
+.drawer .drawer__id-link { color: inherit; text-decoration: underline dotted; text-underline-offset: 2px; }
+.drawer .drawer__id-link:hover,
+.drawer .drawer__id-link:focus-visible { color: var(--accent); text-decoration: underline solid; }
.drawer a[href^="tel:"],
.drawer a[href^="sms:"] { color: inherit; text-decoration: none; pointer-events: none; }
@media (max-width: 768px) {
diff --git a/web/themes/default/js/theme.js b/web/themes/default/js/theme.js
index 3d3a1c1c0..03bcd874a 100644
--- a/web/themes/default/js/theme.js
+++ b/web/themes/default/js/theme.js
@@ -770,6 +770,15 @@
? player.country : '';
if (countryCode) idRows.push(['Country', countryDisplayName(countryCode) + ' (' + countryCode + ')']);
+ // The three Steam identity rows link to the player's Steam
+ // Community profile. Only a 17-digit Steam64 builds the URL, so a
+ // malformed authid (or an IP-only ban) renders as plain text.
+ const communityId = String(player.community_id || '');
+ const profileUrl = /^7\d{16}$/.test(communityId)
+ ? 'https://steamcommunity.com/profiles/' + communityId
+ : '';
+ const steamRows = ['SteamID', 'Steam3', 'Community'];
+
const idHtml = idRows.length === 0 ? '' :
''
+ idRows.map((row) =>
@@ -778,7 +787,12 @@
+ (row[0] === 'Country'
? '
'
: '')
- + escapeHtml(row[1])
+ + (profileUrl && steamRows.indexOf(row[0]) !== -1
+ ? ''
+ + escapeHtml(row[1])
+ + ''
+ : escapeHtml(row[1]))
+ ''
diff --git a/web/themes/default/theme.conf.php b/web/themes/default/theme.conf.php
index ba5de2b5f..e780534e2 100644
--- a/web/themes/default/theme.conf.php
+++ b/web/themes/default/theme.conf.php
@@ -23,7 +23,7 @@
// Theme version — independent of the panel's `SB_VERSION`. Bump
// when the theme's chrome / tokens change in a way fork maintainers
// should know about.
-define('theme_version', '2.0.3');
+define('theme_version', '2.0.4');
// Upstream link for the picker's "more info" affordance.
define('theme_link', 'https://github.com/srcdslab/sourcebans-pp');