Add game detail view UI and data

Introduce a full-screen game detail view and supporting data/model changes. Adds extensive CSS for .game-detail-view and related elements (header image, overlay, content layout, screenshots, achievements, features, actions, and scrollbars). Update renderDetailsPanel to output enriched detail markup (screenshots carousel, achievements UI, features badges, play/install button and contextual actions). Change library action handling to call openDetails for card activation. Extend normalizeLibraryItem with convertArray, screenshots and longDescription support, and populate mock items with screenshots and longDescription to enable the new detail view.
This commit is contained in:
2026-05-16 20:52:41 +12:00
parent a04ae7803b
commit c868a3c2d5
4 changed files with 539 additions and 31 deletions
+359
View File
@@ -540,3 +540,362 @@
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
} }
} }
/* Game Detail View Styles */
.game-detail-view {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
overflow: hidden;
background: linear-gradient(180deg, rgba(17, 24, 48, 0.95), rgba(8, 12, 27, 0.98));
}
.game-detail-header-image {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 40%;
background-size: cover;
background-position: center;
background-color: rgba(79, 216, 255, 0.08);
z-index: 1;
}
.game-detail-header-overlay {
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(7, 10, 20, 0.3), rgba(7, 10, 20, 0.95));
z-index: 1;
}
.game-detail-content {
position: relative;
z-index: 2;
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.game-detail-header {
padding: 28px 40px 20px;
display: grid;
grid-template-columns: 1fr auto;
gap: 40px;
align-items: start;
}
.game-detail-title-section h1 {
margin: 0;
font-size: 48px;
line-height: 1.1;
letter-spacing: -0.02em;
max-width: 600px;
}
.game-detail-title-section .library-section-kicker {
display: block;
margin-bottom: 12px;
}
.game-detail-meta {
display: flex;
gap: 24px;
margin-top: 12px;
font-size: 14px;
color: var(--nebula-color-muted);
flex-wrap: wrap;
}
.game-detail-play-button-container {
display: flex;
gap: 12px;
}
.game-detail-play-button {
min-width: 160px;
padding: 16px 32px;
font-size: 18px;
font-weight: 900;
text-transform: uppercase;
letter-spacing: 0.08em;
border: none;
border-radius: 12px;
background: linear-gradient(135deg, var(--nebula-color-accent), #78f0ff);
color: #04101d;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
box-shadow: 0 12px 32px rgba(79, 216, 255, 0.34);
transition: all 0.2s ease;
white-space: nowrap;
}
.game-detail-play-button .play-icon {
font-size: 20px;
}
.game-detail-play-button.is-focused {
transform: scale(1.08);
box-shadow:
0 0 0 2px rgba(79, 216, 255, 0.3),
0 16px 40px rgba(79, 216, 255, 0.42);
}
.game-detail-main {
display: grid;
grid-template-columns: 1fr 320px;
gap: 40px;
padding: 0 40px 40px;
}
.game-detail-left-column {
display: flex;
flex-direction: column;
gap: 32px;
max-width: 700px;
}
.game-detail-right-column {
display: flex;
flex-direction: column;
gap: 24px;
}
.game-detail-section-title {
margin: 0 0 16px 0;
font-size: 18px;
font-weight: 900;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--nebula-color-accent);
}
.game-detail-description {
display: flex;
flex-direction: column;
gap: 12px;
}
.game-detail-description p {
margin: 0;
color: var(--nebula-color-muted);
line-height: 1.6;
}
.game-detail-description .long-description {
color: var(--nebula-color-muted);
opacity: 0.85;
}
.game-detail-info {
display: flex;
flex-direction: column;
gap: 12px;
}
.game-detail-info-list {
display: grid;
gap: 12px;
}
.game-detail-info-list div {
display: grid;
grid-template-columns: 120px 1fr;
gap: 12px;
padding: 12px;
border-radius: var(--nebula-radius-md);
background: rgba(79, 216, 255, 0.05);
border: 1px solid rgba(79, 216, 255, 0.08);
}
.game-detail-info-list dt {
font-size: 12px;
font-weight: 900;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--nebula-color-accent);
}
.game-detail-info-list dd {
margin: 0;
color: var(--nebula-color-muted);
word-break: break-word;
}
/* Screenshots Section */
.game-detail-screenshots {
display: flex;
flex-direction: column;
gap: 16px;
}
.screenshots-carousel {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
}
.screenshot-item {
aspect-ratio: 16 / 9;
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(79, 216, 255, 0.12);
background: rgba(10, 16, 34, 0.6);
}
.screenshot-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Achievements Section */
.game-detail-achievements {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
border-radius: var(--nebula-radius-lg);
background: linear-gradient(135deg, rgba(79, 216, 255, 0.08), rgba(157, 79, 224, 0.08));
border: 1px solid rgba(79, 216, 255, 0.12);
}
.achievements-container {
display: flex;
flex-direction: column;
gap: 12px;
}
.achievements-progress {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
align-items: center;
}
.achievement-stat {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.achievement-number {
font-size: 20px;
font-weight: 900;
color: var(--nebula-color-accent);
}
.achievement-label {
font-size: 11px;
font-weight: 800;
text-transform: uppercase;
color: var(--nebula-color-muted);
margin-top: 4px;
}
.achievement-bar {
height: 8px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.08);
overflow: hidden;
}
.achievement-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--nebula-color-accent), #78f0ff);
border-radius: 4px;
}
/* Features and Badges */
.game-detail-features {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px;
border-radius: var(--nebula-radius-lg);
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.07);
}
.features-grid {
display: grid;
gap: 10px;
}
.feature-badge {
display: inline-block;
padding: 8px 12px;
border-radius: 8px;
font-size: 12px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.04em;
background: rgba(79, 216, 255, 0.14);
color: var(--nebula-color-accent);
border: 1px solid rgba(79, 216, 255, 0.22);
}
.feature-badge.verified {
background: rgba(79, 255, 136, 0.12);
color: #9effbc;
border-color: rgba(79, 255, 136, 0.24);
}
/* Actions */
.game-detail-actions {
display: grid;
gap: 10px;
padding: 16px;
border-radius: var(--nebula-radius-lg);
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.05);
}
.library-detail-button {
padding: 12px 16px;
border-radius: var(--nebula-radius-md);
font-weight: 900;
font-size: 14px;
text-align: left;
}
.library-detail-button.is-focused {
border-color: var(--nebula-color-accent);
box-shadow:
0 0 0 2px rgba(79, 216, 255, 0.14),
0 0 26px rgba(79, 216, 255, 0.34);
transform: scale(1.03) translateZ(0);
}
/* Scrollbar styling for content area */
.game-detail-content::-webkit-scrollbar {
width: 8px;
}
.game-detail-content::-webkit-scrollbar-track {
background: transparent;
}
.game-detail-content::-webkit-scrollbar-thumb {
background: rgba(79, 216, 255, 0.24);
border-radius: 4px;
}
.game-detail-content::-webkit-scrollbar-thumb:hover {
background: rgba(79, 216, 255, 0.36);
}
/* Update the library-details-panel for full-screen mode */
.library-details-panel.is-open {
display: flex;
}
.game-detail-view {
z-index: 31;
}
+1 -1
View File
@@ -158,7 +158,7 @@ export const createLibraryView = ({ state, renderView }) => {
if (action === "card") { if (action === "card") {
runtime.focusedId = element.dataset.itemId; runtime.focusedId = element.dataset.itemId;
return launchFocusedItem(); return openDetails();
} }
if (action === "launch") return launchFocusedItem(); if (action === "launch") return launchFocusedItem();
+146 -30
View File
@@ -255,45 +255,161 @@ export const renderGrid = (items, focusedId) => {
export const renderDetailsPanel = (item) => { export const renderDetailsPanel = (item) => {
if (!item) return ""; if (!item) return "";
const achievements = item.achievementsSupported const achievements = item.achievementsSupported
? `${item.achievementsUnlocked ?? 0} / ${item.achievementsTotal ?? "?"}` ? `${item.achievementsUnlocked ?? 0} / ${item.achievementsTotal ?? "?"}`
: "Not supported"; : "Not supported";
const screenshotsHtml = item.screenshots && item.screenshots.length > 0
? `
<div class="game-detail-screenshots">
<h3 class="game-detail-section-title">Screenshots</h3>
<div class="screenshots-carousel">
${item.screenshots.map((screenshot, index) => `
<div class="screenshot-item" data-screenshot-index="${index}">
<img src="${escapeHtml(screenshot)}" alt="Screenshot ${index + 1}" />
</div>
`).join("")}
</div>
</div>
`
: '';
const achievementsHtml = item.achievementsSupported
? `
<div class="game-detail-achievements">
<h3 class="game-detail-section-title">Achievements</h3>
<div class="achievements-container">
<div class="achievements-progress">
<div class="achievement-stat">
<span class="achievement-number">${item.achievementsUnlocked ?? 0}</span>
<span class="achievement-label">Unlocked</span>
</div>
<div class="achievement-bar">
<div class="achievement-bar-fill" style="width: ${item.achievementsTotal ? Math.round((item.achievementsUnlocked ?? 0) / item.achievementsTotal * 100) : 0}%"></div>
</div>
<div class="achievement-stat">
<span class="achievement-number">${item.achievementsTotal ?? "?"}</span>
<span class="achievement-label">Total</span>
</div>
</div>
</div>
</div>
`
: '';
const featuresHtml = `
<div class="game-detail-features">
<h3 class="game-detail-section-title">Features</h3>
<div class="features-grid">
${item.supportsController ? '<span class="feature-badge">Controller Support</span>' : ''}
${item.steamDeckVerified ? '<span class="feature-badge verified">Steam Deck Verified</span>' : ''}
${item.multiplayer ? '<span class="feature-badge">Multiplayer</span>' : ''}
${item.coOp ? '<span class="feature-badge">Co-op</span>' : ''}
</div>
</div>
`;
return ` return `
<div class="library-details-backdrop" data-action="close-details"></div> <div class="library-details-backdrop" data-action="close-details"></div>
<section class="library-details-card"> <section class="game-detail-view" data-focus-root>
<div class="library-details-art ${item.bannerImage || item.coverImage ? "has-image" : ""}" style="${ <div class="game-detail-header-image" style="${
item.bannerImage || item.coverImage item.bannerImage || item.coverImage
? `--library-detail-art: url('${escapeHtml(item.bannerImage || item.coverImage)}');` ? `background-image: url('${escapeHtml(item.bannerImage || item.coverImage)}');`
: `--library-accent: ${escapeHtml(item.accent)};` : `background: linear-gradient(135deg, ${escapeHtml(item.accent)}, ${escapeHtml(item.accent)}cc);`
}"> }">
<span>${escapeHtml(initialsForTitle(item.title))}</span> <div class="game-detail-header-overlay"></div>
</div> </div>
<div class="library-details-body">
<p class="library-section-kicker">${escapeHtml(providerLabel(item.source))} · ${escapeHtml(typeLabel(item.type))}</p> <div class="game-detail-content" data-focus-root>
<h2>${escapeHtml(item.title)}</h2> <div class="game-detail-header">
<p>${escapeHtml(item.description)}</p> <div class="game-detail-title-section">
<dl class="library-details-list"> <p class="library-section-kicker">${escapeHtml(providerLabel(item.source))} · ${escapeHtml(typeLabel(item.type))}</p>
<div><dt>Install location</dt><dd>${escapeHtml(item.installPath || "Not installed")}</dd></div> <h1 class="game-detail-title">${escapeHtml(item.title)}</h1>
<div><dt>Last played</dt><dd>${escapeHtml(formatShortDate(item.lastPlayed))}</dd></div> <div class="game-detail-meta">
<div><dt>Playtime</dt><dd>${escapeHtml(formatPlaytime(item.playtimeMinutes))}</dd></div> <span>${item.installed ? '✓ Installed' : 'Not installed'}</span>
<div><dt>Achievements</dt><dd>${escapeHtml(achievements)}</dd></div> ${item.lastPlayed ? `<span>Last played: ${escapeHtml(formatShortDate(item.lastPlayed))}</span>` : ''}
</dl> ${item.playtimeMinutes > 0 ? `<span>${escapeHtml(formatPlaytime(item.playtimeMinutes))}</span>` : ''}
<div class="library-details-actions"> </div>
${["Launch", "Install", "Uninstall", "Hide", "Open Folder"] </div>
.map(
(label, index) => ` <div class="game-detail-play-button-container">
<button <button
class="library-detail-button focusable" class="game-detail-play-button focusable"
data-focusable="true" data-focusable="true"
data-row="${20 + index}" data-row="3"
data-col="8" data-col="2"
data-action="${escapeHtml(label.toLowerCase().replaceAll(" ", "-"))}" data-action="launch"
data-focus-key="library-detail-${index}" data-focus-key="game-detail-play-button"
>${escapeHtml(label)}</button> >
`, <span class="play-icon">▶</span>
) <span class="play-text">${item.installed ? 'PLAY' : 'INSTALL'}</span>
.join("")} </button>
</div>
</div>
<div class="game-detail-main">
<div class="game-detail-left-column">
<div class="game-detail-description">
<h3 class="game-detail-section-title">About</h3>
<p>${escapeHtml(item.description)}</p>
${item.longDescription ? `<p class="long-description">${escapeHtml(item.longDescription)}</p>` : ''}
</div>
<div class="game-detail-info">
<h3 class="game-detail-section-title">Details</h3>
<dl class="game-detail-info-list">
<div>
<dt>Genres</dt>
<dd>${escapeHtml(item.genre.join(', '))}</dd>
</div>
<div>
<dt>Source</dt>
<dd>${escapeHtml(providerLabel(item.source))}</dd>
</div>
${item.installed ? `<div>
<dt>Install Location</dt>
<dd>${escapeHtml(item.installPath)}</dd>
</div>` : ''}
${item.installedAt ? `<div>
<dt>Installed</dt>
<dd>${escapeHtml(formatShortDate(item.installedAt))}</dd>
</div>` : ''}
${item.playtimeMinutes > 0 ? `<div>
<dt>Playtime</dt>
<dd>${escapeHtml(formatPlaytime(item.playtimeMinutes))}</dd>
</div>` : ''}
</dl>
</div>
${screenshotsHtml}
</div>
<div class="game-detail-right-column">
${featuresHtml}
${achievementsHtml}
<div class="game-detail-actions">
${["Hide", "Open Folder"]
.filter(label => {
if (label === "Open Folder" && !item.installed) return false;
return true;
})
.map(
(label, index) => `
<button
class="library-detail-button focusable"
data-focusable="true"
data-row="${8 + index}"
data-col="2"
data-action="${escapeHtml(label.toLowerCase().replaceAll(" ", "-"))}"
data-focus-key="game-detail-action-${index}"
>${escapeHtml(label)}</button>
`,
)
.join("")}
</div>
</div>
</div> </div>
</div> </div>
</section> </section>
+33
View File
@@ -99,6 +99,7 @@ export const normalizeLibraryItem = (raw, index = 0, convertFileSrc = null) => {
const source = sourceFromBackend(raw?.platformSource ?? raw?.source); const source = sourceFromBackend(raw?.platformSource ?? raw?.source);
const type = typeFromBackend(raw?.appKind ?? raw?.type); const type = typeFromBackend(raw?.appKind ?? raw?.type);
const convert = (path) => (path && convertFileSrc ? convertFileSrc(path) : path || null); const convert = (path) => (path && convertFileSrc ? convertFileSrc(path) : path || null);
const convertArray = (paths) => (Array.isArray(paths) ? paths.map(convert).filter(Boolean) : []);
const installed = raw?.installed ?? Boolean(raw?.installPath || raw?.install_path); const installed = raw?.installed ?? Boolean(raw?.installPath || raw?.install_path);
return { return {
@@ -120,9 +121,11 @@ export const normalizeLibraryItem = (raw, index = 0, convertFileSrc = null) => {
coverImage: convert(raw?.coverImage ?? raw?.cover_image), coverImage: convert(raw?.coverImage ?? raw?.cover_image),
bannerImage: convert(raw?.bannerImage ?? raw?.heroImage ?? raw?.hero_image), bannerImage: convert(raw?.bannerImage ?? raw?.heroImage ?? raw?.hero_image),
iconImage: convert(raw?.iconImage ?? raw?.icon_image), iconImage: convert(raw?.iconImage ?? raw?.icon_image),
screenshots: convertArray(raw?.screenshots ?? []),
description: description:
raw?.description || raw?.description ||
"Scanned from your local library. Metadata can be enriched later by Steam, GOG, Epic, emulator, and local metadata providers.", "Scanned from your local library. Metadata can be enriched later by Steam, GOG, Epic, emulator, and local metadata providers.",
longDescription: raw?.longDescription ?? null,
lastPlayed: raw?.lastPlayed ?? null, lastPlayed: raw?.lastPlayed ?? null,
installedAt: raw?.installedAt ?? raw?.createdAt ?? null, installedAt: raw?.installedAt ?? raw?.createdAt ?? null,
playtimeMinutes: Number(raw?.playtimeMinutes ?? 0), playtimeMinutes: Number(raw?.playtimeMinutes ?? 0),
@@ -150,6 +153,7 @@ export const createMockLibraryItems = () =>
installPath: "C:/Games/Starfall Protocol", installPath: "C:/Games/Starfall Protocol",
executablePath: "C:/Games/Starfall Protocol/starfall.exe", executablePath: "C:/Games/Starfall Protocol/starfall.exe",
description: "Pilot a relic fighter through collapsing gates in a neon campaign built for controller play.", description: "Pilot a relic fighter through collapsing gates in a neon campaign built for controller play.",
longDescription: "Experience a fast-paced action-RPG where you pilot experimental fighter craft through a collapsing interdimensional gateway. Built from the ground up with controller support, Starfall Protocol features real-time combat, intricate level design, and a gripping sci-fi narrative. Perfect for couch co-op sessions or solo playthroughs.",
lastPlayed: "2026-05-15T21:15:00Z", lastPlayed: "2026-05-15T21:15:00Z",
installedAt: "2026-05-01T08:30:00Z", installedAt: "2026-05-01T08:30:00Z",
playtimeMinutes: 1874, playtimeMinutes: 1874,
@@ -160,6 +164,11 @@ export const createMockLibraryItems = () =>
achievementsTotal: 54, achievementsTotal: 54,
multiplayer: true, multiplayer: true,
coOp: true, coOp: true,
screenshots: [
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EStarfall Protocol - Screenshot 1%3C/text%3E%3C/svg%3E",
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EStarfall Protocol - Screenshot 2%3C/text%3E%3C/svg%3E",
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%231f7aff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EStarfall Protocol - Screenshot 3%3C/text%3E%3C/svg%3E",
],
accent: "#4fd8ff", accent: "#4fd8ff",
}, },
{ {
@@ -172,6 +181,7 @@ export const createMockLibraryItems = () =>
installPath: "D:/Games/Iron Vault Tactics", installPath: "D:/Games/Iron Vault Tactics",
executablePath: "D:/Games/Iron Vault Tactics/ivt.exe", executablePath: "D:/Games/Iron Vault Tactics/ivt.exe",
description: "A turn-based tactics sandbox with long-form campaigns, mod support, and couch co-op skirmishes.", description: "A turn-based tactics sandbox with long-form campaigns, mod support, and couch co-op skirmishes.",
longDescription: "Command your squad through dynamic turn-based tactical battles in Iron Vault Tactics. Features mod support, deep unit customization, engaging campaign narratives, and intense couch co-op multiplayer. Each decision matters in this tactical masterpiece.",
lastPlayed: "2026-05-10T11:00:00Z", lastPlayed: "2026-05-10T11:00:00Z",
installedAt: "2026-04-18T18:00:00Z", installedAt: "2026-04-18T18:00:00Z",
playtimeMinutes: 942, playtimeMinutes: 942,
@@ -179,6 +189,11 @@ export const createMockLibraryItems = () =>
achievementsSupported: false, achievementsSupported: false,
multiplayer: true, multiplayer: true,
coOp: true, coOp: true,
screenshots: [
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%2339ffd2' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EIron Vault Tactics - Screenshot 1%3C/text%3E%3C/svg%3E",
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EIron Vault Tactics - Screenshot 2%3C/text%3E%3C/svg%3E",
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EIron Vault Tactics - Screenshot 3%3C/text%3E%3C/svg%3E",
],
accent: "#39ffd2", accent: "#39ffd2",
}, },
{ {
@@ -191,6 +206,7 @@ export const createMockLibraryItems = () =>
installPath: "C:/Program Files/Nebula Paint", installPath: "C:/Program Files/Nebula Paint",
executablePath: "C:/Program Files/Nebula Paint/paint.exe", executablePath: "C:/Program Files/Nebula Paint/paint.exe",
description: "A TV-friendly concept art tool for quick capture, markup, and launcher artwork editing.", description: "A TV-friendly concept art tool for quick capture, markup, and launcher artwork editing.",
longDescription: "Nebula Paint Studio is a professional-grade digital painting application optimized for controller input and TV display. Create stunning concept art, edit game launcher artwork, and collaborate seamlessly with frame-perfect precision.",
lastPlayed: "2026-05-11T06:20:00Z", lastPlayed: "2026-05-11T06:20:00Z",
installedAt: "2026-03-12T09:00:00Z", installedAt: "2026-03-12T09:00:00Z",
playtimeMinutes: 223, playtimeMinutes: 223,
@@ -198,6 +214,10 @@ export const createMockLibraryItems = () =>
achievementsSupported: false, achievementsSupported: false,
multiplayer: false, multiplayer: false,
coOp: false, coOp: false,
screenshots: [
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ENebula Paint Studio - Screenshot 1%3C/text%3E%3C/svg%3E",
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ENebula Paint Studio - Screenshot 2%3C/text%3E%3C/svg%3E",
],
accent: "#9d4fe0", accent: "#9d4fe0",
}, },
{ {
@@ -210,6 +230,7 @@ export const createMockLibraryItems = () =>
installPath: null, installPath: null,
executablePath: null, executablePath: null,
description: "Wishlist entry from a linked store. Install support will be routed through the Epic integration later.", description: "Wishlist entry from a linked store. Install support will be routed through the Epic integration later.",
longDescription: "Emberline is an indie action game with stunning visuals and engaging gameplay. Add it to your library to stay updated on new releases, sales, and updates.",
lastPlayed: null, lastPlayed: null,
installedAt: null, installedAt: null,
playtimeMinutes: 0, playtimeMinutes: 0,
@@ -220,6 +241,10 @@ export const createMockLibraryItems = () =>
achievementsTotal: 32, achievementsTotal: 32,
multiplayer: false, multiplayer: false,
coOp: false, coOp: false,
screenshots: [
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%231f7aff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EEmberline - Screenshot 1%3C/text%3E%3C/svg%3E",
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EEmberline - Screenshot 2%3C/text%3E%3C/svg%3E",
],
accent: "#1f7aff", accent: "#1f7aff",
}, },
{ {
@@ -232,6 +257,7 @@ export const createMockLibraryItems = () =>
installPath: "D:/Emulation/RetroCore", installPath: "D:/Emulation/RetroCore",
executablePath: "D:/Emulation/RetroCore/retrocore.exe", executablePath: "D:/Emulation/RetroCore/retrocore.exe",
description: "A controller-native emulator hub prepared for future ROM library scanning and save sync.", description: "A controller-native emulator hub prepared for future ROM library scanning and save sync.",
longDescription: "RetroCore Station is your gateway to classic gaming. Play thousands of retro games with full controller support, save synchronization, and achievement tracking across multiple emulation systems.",
lastPlayed: "2026-05-14T04:10:00Z", lastPlayed: "2026-05-14T04:10:00Z",
installedAt: "2026-05-03T15:10:00Z", installedAt: "2026-05-03T15:10:00Z",
playtimeMinutes: 517, playtimeMinutes: 517,
@@ -241,6 +267,11 @@ export const createMockLibraryItems = () =>
achievementsTotal: 210, achievementsTotal: 210,
multiplayer: true, multiplayer: true,
coOp: true, coOp: true,
screenshots: [
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%23ffb84f' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ERetroCore Station - Screenshot 1%3C/text%3E%3C/svg%3E",
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ERetroCore Station - Screenshot 2%3C/text%3E%3C/svg%3E",
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ERetroCore Station - Screenshot 3%3C/text%3E%3C/svg%3E",
],
accent: "#ffb84f", accent: "#ffb84f",
}, },
{ {
@@ -253,6 +284,7 @@ export const createMockLibraryItems = () =>
installPath: null, installPath: null,
executablePath: null, executablePath: null,
description: "Tooling placeholder for future mod SDK detection, dependency checks, and per-game utilities.", description: "Tooling placeholder for future mod SDK detection, dependency checks, and per-game utilities.",
longDescription: "Orbit Mod Tools provides a comprehensive suite of utilities for game modding, including SDK management, dependency resolution, and per-game optimization tools. Essential for serious mod creators.",
lastPlayed: null, lastPlayed: null,
installedAt: null, installedAt: null,
playtimeMinutes: 0, playtimeMinutes: 0,
@@ -260,6 +292,7 @@ export const createMockLibraryItems = () =>
achievementsSupported: false, achievementsSupported: false,
multiplayer: false, multiplayer: false,
coOp: false, coOp: false,
screenshots: [],
accent: "#ff6b9a", accent: "#ff6b9a",
}, },
].map((item, index) => normalizeLibraryItem(item, index)); ].map((item, index) => normalizeLibraryItem(item, index));