From ad3fe7c586896f5598064f7bfed34010c241985f Mon Sep 17 00:00:00 2001 From: HorizonCode Date: Sun, 1 Nov 2026 14:32:56 +0100 Subject: [PATCH] fix(theme-editor): fix player card widget overflow, add banner variant, and improve widget selection badge --- .../theme/ThemeElementInspector.svelte | 52 ++++++++++++- .../components/theme/ThemeV2Elements.svelte | 69 +++++++++++++++++- src/lib/themePreview.ts | 73 ++++++++++--------- src/lib/themeV2.ts | 7 +- 4 files changed, 159 insertions(+), 42 deletions(-) diff --git a/src/lib/components/theme/ThemeElementInspector.svelte b/src/lib/components/theme/ThemeElementInspector.svelte index 153d47f..6fd9e3a 100644 --- a/src/lib/components/theme/ThemeElementInspector.svelte +++ b/src/lib/components/theme/ThemeElementInspector.svelte @@ -2231,7 +2231,7 @@ element.widget = val as ThemeV2WidgetKind; if (val === 'player-card') { element.width = 380; - element.height = 180; + element.height = 220; } else if (val === 'avatar') { element.width = 72; element.height = 72; @@ -2296,12 +2296,18 @@ if (val === 'minimal') { element.width = 280; element.height = 64; + } else if (val === 'banner') { + element.width = 340; + element.height = 76; + element.showStats = false; } else if (val === 'compact') { element.width = 340; - element.height = 120; + element.height = 140; + element.showStats = true; } else if (val === 'full') { element.width = 380; - element.height = 200; + element.height = 220; + element.showStats = true; } } else if (element.widget === 'recent-activity') { if (val === 'minimal') { @@ -2356,6 +2362,14 @@ : element.variant === 'icon-only' ? 'Icon-Only (Circle)' : 'Primary (Capsule)'} + {:else if element.widget === 'player-card'} + {element.variant === 'compact' + ? 'Compact (2 Stats)' + : element.variant === 'minimal' + ? 'Minimal (Pill)' + : element.variant === 'banner' + ? 'Banner (Profile & Level Only)' + : 'Full (4 Stats)'} {:else} {element.variant === 'compact' ? 'Compact' @@ -2373,6 +2387,11 @@ + {:else if element.widget === 'player-card'} + + + + {:else} @@ -2382,6 +2401,33 @@ + {#if element.widget === 'player-card' && element.variant !== 'minimal'} +
+ +
+
+ { + element.showStats = checked === true; + if (!checked) { + element.variant = 'banner'; + element.height = Math.min(element.height, 80); + } else { + element.variant = 'full'; + element.height = Math.max(element.height, 220); + } + }} + /> + +
+
+
+ {/if} + {#if element.widget === 'avatar'}
diff --git a/src/lib/components/theme/ThemeV2Elements.svelte b/src/lib/components/theme/ThemeV2Elements.svelte index cdc852b..2b08434 100644 --- a/src/lib/components/theme/ThemeV2Elements.svelte +++ b/src/lib/components/theme/ThemeV2Elements.svelte @@ -2027,7 +2027,7 @@ {#if element.variant === 'minimal'}
{:else if element.variant === 'compact'}
@@ -2125,9 +2125,56 @@
+ {:else if element.variant === 'banner' || element.showStats === false || element.height < 140} +
+
+ + + + + + + {#if country} + + {countryCodeToEmoji(country)} + + {/if} +
+ +
+ {playerName} +
+ + Lv.{levelVal} + +
+
+
+ + {levelProgress}% + +
+
+
{:else}
@@ -2514,7 +2561,21 @@ - {element.type} + {element.type === 'widget' + ? element.widget === 'player-card' + ? 'Player Card' + : element.widget === 'recent-activity' + ? 'Recent Activity' + : element.widget === 'launch-button' + ? 'Launch Button' + : element.widget === 'telemetry' + ? 'Telemetry Bar' + : element.widget === 'header' + ? 'Header Title' + : element.widget === 'avatar' + ? 'Player Avatar' + : 'Widget' + : element.type} {/if} diff --git a/src/lib/themePreview.ts b/src/lib/themePreview.ts index 9d8318c..8a7fce6 100644 --- a/src/lib/themePreview.ts +++ b/src/lib/themePreview.ts @@ -1662,7 +1662,7 @@ const drawWidgetElement = async (context: PreviewContext, element: ThemeV2Widget ctx.fillText(plays, cell2X + cellW - 8, bottomY + 14); ctx.restore(); } else { - // full + // full or banner fillRoundedRect( ctx, element.x, @@ -1683,9 +1683,12 @@ const drawWidgetElement = async (context: PreviewContext, element: ThemeV2Widget 1 ); - const avatarSize = 48; + const showStats = + element.variant !== 'banner' && element.showStats !== false && element.height >= 140; + const isBanner = element.variant === 'banner' || !showStats; + const avatarSize = isBanner ? Math.min(48, Math.max(32, element.height - 24)) : 48; const avatarX = element.x + 16; - const avatarY = element.y + 16; + const avatarY = isBanner ? element.y + (element.height - avatarSize) / 2 : element.y + 16; fillRoundedRect(ctx, avatarX, avatarY, avatarSize, avatarSize, 12, 'rgba(20, 20, 28, 0.9)'); strokeRoundedRect( ctx, @@ -1730,42 +1733,44 @@ const drawWidgetElement = async (context: PreviewContext, element: ThemeV2Widget ctx.font = '500 10px monospace'; ctx.fillText(`${levelProgress}%`, barX + barW + 8, barY - 2); - const divY = avatarY + avatarSize + 16; - ctx.strokeStyle = 'rgba(255, 255, 255, 0.06)'; - ctx.beginPath(); - ctx.moveTo(element.x + 16, divY); - ctx.lineTo(element.x + element.width - 16, divY); - ctx.stroke(); + if (showStats) { + const divY = avatarY + avatarSize + 16; + ctx.strokeStyle = 'rgba(255, 255, 255, 0.06)'; + ctx.beginPath(); + ctx.moveTo(element.x + 16, divY); + ctx.lineTo(element.x + element.width - 16, divY); + ctx.stroke(); - const gridY = divY + 12; - const gap = 8; - const cellW = (element.width - 32 - gap) / 2; - const cellH = Math.max(34, (element.height - (gridY - element.y) - 14 - gap) / 2); + const gridY = divY + 12; + const gap = 8; + const cellW = (element.width - 32 - gap) / 2; + const cellH = Math.max(34, (element.height - (gridY - element.y) - 14 - gap) / 2); - const stats = [ - { label: 'Rank', value: rank, valueColor: '#ffffff' }, - { label: 'Performance', value: pp, valueColor: accent }, - { label: 'Accuracy', value: acc, valueColor: '#ffffff' }, - { label: 'Plays', value: plays, valueColor: '#ffffff' }, - ]; + const stats = [ + { label: 'Rank', value: rank, valueColor: '#ffffff' }, + { label: 'Performance', value: pp, valueColor: accent }, + { label: 'Accuracy', value: acc, valueColor: '#ffffff' }, + { label: 'Plays', value: plays, valueColor: '#ffffff' }, + ]; - stats.forEach((st, idx) => { - const col = idx % 2; - const row = Math.floor(idx / 2); - const cx = element.x + 16 + col * (cellW + gap); - const cy = gridY + row * (cellH + gap); + stats.forEach((st, idx) => { + const col = idx % 2; + const row = Math.floor(idx / 2); + const cx = element.x + 16 + col * (cellW + gap); + const cy = gridY + row * (cellH + gap); - fillRoundedRect(ctx, cx, cy, cellW, cellH, 10, 'rgba(255, 255, 255, 0.05)'); - ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; - ctx.font = canvasFont(context, bodyFont, 9, 500); - ctx.textAlign = 'left'; - ctx.textBaseline = 'top'; - ctx.fillText(st.label, cx + 10, cy + 6); + fillRoundedRect(ctx, cx, cy, cellW, cellH, 10, 'rgba(255, 255, 255, 0.05)'); + ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; + ctx.font = canvasFont(context, bodyFont, 9, 500); + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + ctx.fillText(st.label, cx + 10, cy + 6); - ctx.fillStyle = st.valueColor; - ctx.font = canvasFont(context, bodyFont, 12, 700); - ctx.fillText(st.value, cx + 10, cy + cellH - 18); - }); + ctx.fillStyle = st.valueColor; + ctx.font = canvasFont(context, bodyFont, 12, 700); + ctx.fillText(st.value, cx + 10, cy + cellH - 18); + }); + } ctx.restore(); } } else if (element.widget === 'recent-activity') { diff --git a/src/lib/themeV2.ts b/src/lib/themeV2.ts index 5fbd89a..a033c23 100644 --- a/src/lib/themeV2.ts +++ b/src/lib/themeV2.ts @@ -534,7 +534,8 @@ export type ThemeV2WidgetVariant = | 'icon-only' | 'circle' | 'rounded' - | 'square'; + | 'square' + | 'banner'; export type ThemeV2WidgetElement = ThemeV2ElementBase & { type: 'widget'; @@ -543,6 +544,7 @@ export type ThemeV2WidgetElement = ThemeV2ElementBase & { showCountry?: boolean; showLevel?: boolean; showOnlineRing?: boolean; + showStats?: boolean; }; export type ThemeV2Element = @@ -1262,6 +1264,7 @@ const normalizeElement = (raw: unknown, index: number): ThemeV2Element | undefin 'circle', 'rounded', 'square', + 'banner', ] as const, widget === 'launch-button' ? 'primary' : widget === 'avatar' ? 'circle' : 'full' ); @@ -1269,6 +1272,7 @@ const normalizeElement = (raw: unknown, index: number): ThemeV2Element | undefin const showLevel = typeof source.showLevel === 'boolean' ? source.showLevel : false; const showOnlineRing = typeof source.showOnlineRing === 'boolean' ? source.showOnlineRing : true; + const showStats = typeof source.showStats === 'boolean' ? source.showStats : true; return { ...base, type, @@ -1277,6 +1281,7 @@ const normalizeElement = (raw: unknown, index: number): ThemeV2Element | undefin showCountry, showLevel, showOnlineRing, + showStats, }; }