@@ -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,
};
}