Improve website copy and layout hierarchy

This commit is contained in:
Jeen
2026-06-02 00:52:49 +02:00
parent bf515bddc8
commit 32a1d71ca0
5 changed files with 241 additions and 64 deletions
+2 -2
View File
@@ -7,8 +7,8 @@
"name": "ARMSX2 iOS",
"bundleIdentifier": "com.armsx2.ios",
"developerName": "ARMSX2",
"subtitle": "PlayStation 2 emulation, built for iOS.",
"localizedDescription": "ARMSX2 brings PlayStation 2 emulation to iOS with an ARM64-focused build, controller-friendly play, and local-network support for compatible titles.\n\nA JIT-capable setup is required. Performance and compatibility vary by device, iOS version, game, and sideloading environment. You must provide your own BIOS and game backups.",
"subtitle": "Modern PlayStation 2 emulation for iOS.",
"localizedDescription": "ARMSX2 brings PlayStation 2 emulation to iOS devices. Based on the open-source PCSX2 project, this ARM64-focused iOS build helps you revisit and preserve your own legally obtained PS2 game library on modern mobile hardware.\n\nFeatures:\n- Customizable touchscreen controls and external controller support\n- Graphics settings, performance options, save states, and memory card support\n- Local-network support for compatible online titles\n\nImportant iOS requirements:\nA JIT-capable setup is required for playable performance. Compatibility varies by device, iOS version, game, and sideloading environment, including LiveContainer, SideStore, or Feather.\n\nDisclaimer:\nARMSX2 does not include games or BIOS files. Users must provide their own legally obtained BIOS and game backups, such as ISO files. ARMSX2 is not affiliated with Sony Interactive Entertainment or PlayStation.\n\nCredits:\nThe upstream ARMSX2 project was founded by jpolo1224 and MoonPower, with work from contributors including Zeruth and Dib1998.",
"iconURL": "https://j1coding.github.io/Armsx2-Repo/assets/icon.png",
"screenshotURLs": [
"https://j1coding.github.io/Armsx2-Repo/assets/screenshots/01-library.png",
+230 -53
View File
@@ -6,7 +6,7 @@
<meta name="color-scheme" content="dark">
<meta
name="description"
content="ARMSX2 iOS source for LiveContainer, SideStore, and Feather."
content="Modern PlayStation 2 emulation for iOS."
>
<title>ARMSX2 iOS</title>
<link rel="icon" href="assets/icon.png">
@@ -126,6 +126,17 @@
text-decoration: none;
}
.top-links {
display: flex;
flex-wrap: wrap;
justify-content: end;
gap: 8px 16px;
}
.top-links a:hover {
color: var(--text);
}
.visually-hidden {
position: absolute;
width: 1px;
@@ -241,6 +252,20 @@
backdrop-filter: blur(24px);
}
.manual-source {
display: grid;
gap: 8px;
}
.panel-label {
margin: 0;
color: var(--soft);
font-size: 0.74rem;
font-weight: 850;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.source-url {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
@@ -407,18 +432,43 @@
box-shadow: 0 22px 60px rgba(0, 0, 0, 0.32);
}
.screenshot-frame figcaption {
margin-top: 10px;
color: var(--soft);
font-size: 0.88rem;
font-weight: 700;
text-align: center;
}
.content-grid {
display: grid;
gap: 28px;
padding-top: 32px;
}
.priority-grid {
display: grid;
gap: 28px;
padding-top: 32px;
}
.copy-block {
color: var(--muted);
font-size: 1rem;
line-height: 1.75;
}
.copy-block h3 {
margin: 18px 0 8px;
color: var(--text);
font-size: 0.95rem;
line-height: 1.25;
}
.copy-block h3:first-child {
margin-top: 0;
}
.copy-block p {
margin-block-end: 14px;
}
@@ -427,6 +477,7 @@
margin-block-end: 0;
}
.detail-list,
.change-list,
.requirement-list {
display: grid;
@@ -436,6 +487,7 @@
list-style: none;
}
.detail-list li,
.change-list li,
.requirement-list li {
position: relative;
@@ -444,6 +496,7 @@
line-height: 1.55;
}
.detail-list li::before,
.change-list li::before,
.requirement-list li::before {
position: absolute;
@@ -530,8 +583,12 @@
@media (max-width: 720px) {
.topbar {
align-items: center;
flex-direction: row;
align-items: flex-start;
flex-direction: column;
}
.top-links {
justify-content: start;
}
.identity {
@@ -582,14 +639,11 @@
overflow: visible;
}
.content-grid {
.content-grid,
.priority-grid {
grid-template-columns: minmax(0, 1.1fr) minmax(310px, 0.9fr);
gap: 48px;
}
.description-section {
grid-column: 1 / -1;
}
}
@media (min-width: 940px) {
@@ -616,7 +670,11 @@
<div class="page">
<header class="topbar" aria-label="Page">
<strong id="source-name">ARMSX2 iOS</strong>
<nav class="top-links" aria-label="Page sections">
<a href="#requirements">Requirements</a>
<a href="#features">Features</a>
<a href="#integrity">Checksums</a>
</nav>
</header>
<main>
@@ -638,17 +696,6 @@
</div>
<aside class="install-panel" aria-label="Install options">
<div class="source-url">
<code id="source-url">Loading apps.json</code>
<button class="source-copy" type="button" data-action="copy" aria-label="Copy install link" title="Copy install link">
<span class="visually-hidden">Copy install link</span>
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none">
<path d="M8 8.5A2.5 2.5 0 0 1 10.5 6h6A2.5 2.5 0 0 1 19 8.5v6a2.5 2.5 0 0 1-2.5 2.5h-6A2.5 2.5 0 0 1 8 14.5v-6Z" stroke="currentColor" stroke-width="1.8"/>
<path d="M6 13.5h-.5A2.5 2.5 0 0 1 3 11V5.5A2.5 2.5 0 0 1 5.5 3H11a2.5 2.5 0 0 1 2.5 2.5V6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="metrics" aria-label="Latest version">
<div class="metric">
<span>Version</span>
@@ -674,14 +721,48 @@
</div>
</div>
<div class="manual-source" aria-label="Manual source URL">
<p class="panel-label">Manual source URL</p>
<div class="source-url">
<code id="source-url">Loading apps.json</code>
<button class="source-copy" type="button" data-action="copy" aria-label="Copy install link" title="Copy install link">
<span class="visually-hidden">Copy install link</span>
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none">
<path d="M8 8.5A2.5 2.5 0 0 1 10.5 6h6A2.5 2.5 0 0 1 19 8.5v6a2.5 2.5 0 0 1-2.5 2.5h-6A2.5 2.5 0 0 1 8 14.5v-6Z" stroke="currentColor" stroke-width="1.8"/>
<path d="M6 13.5h-.5A2.5 2.5 0 0 1 3 11V5.5A2.5 2.5 0 0 1 5.5 3H11a2.5 2.5 0 0 1 2.5 2.5V6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
</svg>
</button>
</div>
</div>
<p class="note">
New here? Start with LiveContainer.
LiveContainer is recommended for first-time installs.
</p>
<p class="status" id="status" aria-live="polite"></p>
</aside>
</section>
<section class="section" aria-labelledby="screenshots-title">
<div class="priority-grid">
<section class="section" id="requirements" aria-labelledby="requirements-title">
<p class="section-kicker">Before You Install</p>
<h2 id="requirements-title">Requirements</h2>
<ul class="requirement-list">
<li id="minimum-os-requirement">Minimum iOS version loads from apps.json.</li>
<li>A <a href="https://livecontainer.github.io/docs/guides/jit-support" target="_blank" rel="noopener noreferrer">JIT-capable setup</a> is needed for playable performance.</li>
<li>LiveContainer automatic import requires LiveContainer 3.7.0 or later.</li>
<li>ARMSX2 does not include games or BIOS files.</li>
<li>You need your own legally obtained PS2 BIOS and game backups, such as ISO files.</li>
</ul>
</section>
<section class="section" id="features" aria-labelledby="features-title">
<p class="section-kicker">Core Features</p>
<h2 id="features-title">Built for PS2 on iOS</h2>
<div class="copy-block" id="feature-list"></div>
</section>
</div>
<section class="section" id="screenshots-preview" aria-labelledby="screenshots-title">
<div class="section-header">
<div>
<p class="section-kicker">Preview</p>
@@ -699,21 +780,10 @@
<div class="copy-block" id="whats-new"></div>
</section>
<section class="section" aria-labelledby="requirements-title">
<p class="section-kicker">Before You Install</p>
<h2 id="requirements-title">Requirements</h2>
<ul class="requirement-list">
<li id="minimum-os-requirement">Minimum iOS version loads from apps.json.</li>
<li>A <a href="https://livecontainer.github.io/docs/guides/jit-support" target="_blank" rel="noopener noreferrer">JIT-capable setup</a> is needed for playable performance.</li>
<li>LiveContainer automatic import requires LiveContainer 3.7.0 or later.</li>
<li>You&rsquo;ll need your own PS2 BIOS file and dumped game disc images, such as ISO files.</li>
</ul>
</section>
<section class="section description-section" aria-labelledby="description-title">
<p class="section-kicker">About</p>
<h2 id="description-title">Description</h2>
<div class="copy-block" id="description"></div>
<section class="section legal-section" aria-labelledby="legal-title">
<p class="section-kicker">Trust</p>
<h2 id="legal-title">Legal &amp; Credits</h2>
<div class="copy-block" id="legal-details"></div>
<div class="integrity" id="integrity">
<div class="integrity-row" id="integrity-summary">
@@ -743,7 +813,8 @@
screenshots: document.getElementById("screenshots"),
whatsNewVersion: document.getElementById("whats-new-version"),
whatsNew: document.getElementById("whats-new"),
description: document.getElementById("description"),
features: document.getElementById("feature-list"),
legalDetails: document.getElementById("legal-details"),
integritySummary: document.getElementById("integrity-summary"),
status: document.getElementById("status"),
};
@@ -783,24 +854,121 @@
}
};
const renderParagraphs = (targetElement, textValue) => {
clearElement(targetElement);
const paragraphs = textValue
?.split(/\r?\n\s*\r?\n/)
.map((paragraph) => paragraph.trim())
.filter(Boolean) || [];
if (!paragraphs.length) {
targetElement.textContent = "Details are temporarily unavailable.";
const appendFormattedParagraph = (targetElement, paragraphLines) => {
if (!paragraphLines.length) {
return;
}
for (const paragraph of paragraphs) {
const paragraphElement = document.createElement("p");
paragraphElement.textContent = paragraph;
paragraphElement.textContent = paragraphLines.join(" ");
targetElement.append(paragraphElement);
};
const appendFormattedList = (targetElement, listItems) => {
if (!listItems.length) {
return;
}
const listElement = document.createElement("ul");
listElement.className = "detail-list";
for (const listItem of listItems) {
const itemElement = document.createElement("li");
itemElement.textContent = listItem;
listElement.append(itemElement);
}
targetElement.append(listElement);
};
const formattedBlocks = (textValue) => textValue
?.split(/\r?\n\s*\r?\n/)
.map((block) => block.trim())
.filter(Boolean)
.map((block) => {
const lines = block
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean);
const headingMatch = lines[0]?.match(/^([^:\n]{1,80}):$/);
return {
heading: headingMatch ? headingMatch[1] : null,
lines: headingMatch ? lines.slice(1) : lines,
};
}) || [];
const renderFormattedBlocks = (targetElement, blocks, emptyMessage, options = {}) => {
clearElement(targetElement);
if (!blocks.length) {
targetElement.textContent = emptyMessage;
return;
}
for (const block of blocks) {
let lineIndex = 0;
if (block.heading && options.showHeadings !== false) {
const headingElement = document.createElement("h3");
headingElement.textContent = block.heading;
targetElement.append(headingElement);
}
let paragraphLines = [];
let listItems = [];
const flushParagraph = () => {
appendFormattedParagraph(targetElement, paragraphLines);
paragraphLines = [];
};
const flushList = () => {
appendFormattedList(targetElement, listItems);
listItems = [];
};
for (; lineIndex < block.lines.length; lineIndex += 1) {
const listItemMatch = block.lines[lineIndex].match(/^-\s+(.+)$/);
if (listItemMatch) {
flushParagraph();
listItems.push(listItemMatch[1]);
continue;
}
flushList();
paragraphLines.push(block.lines[lineIndex]);
}
flushParagraph();
flushList();
}
};
const blockByHeading = (blocks, heading) =>
blocks.find((block) => block.heading?.toLowerCase() === heading.toLowerCase());
const renderFeatureHighlights = (targetElement, blocks) => {
const featuresBlock = blockByHeading(blocks, "Features");
renderFormattedBlocks(
targetElement,
featuresBlock ? [featuresBlock] : [],
"Feature details are temporarily unavailable.",
{ showHeadings: false },
);
};
const renderLegalDetails = (targetElement, blocks) => {
const legalBlocks = ["Disclaimer", "Credits"]
.map((heading) => blockByHeading(blocks, heading))
.filter(Boolean);
renderFormattedBlocks(
targetElement,
legalBlocks,
"Legal and credit details are temporarily unavailable.",
);
};
const renderWhatsNew = (targetElement, releaseNotes) => {
@@ -844,17 +1012,22 @@
return;
}
const screenshotCaptions = ["Library", "Settings", "Gameplay"];
screenshotUrls.forEach((screenshotUrl, screenshotIndex) => {
const screenshotFrame = document.createElement("figure");
const screenshotImage = document.createElement("img");
const screenshotCaption = document.createElement("figcaption");
screenshotFrame.className = "screenshot-frame";
screenshotImage.src = absoluteUrl(screenshotUrl);
screenshotImage.alt = `${appName} screenshot ${screenshotIndex + 1}`;
screenshotImage.loading = screenshotIndex === 0 ? "eager" : "lazy";
screenshotImage.decoding = "async";
screenshotCaption.textContent = screenshotCaptions[screenshotIndex] || `Screenshot ${screenshotIndex + 1}`;
screenshotFrame.append(screenshotImage);
screenshotFrame.append(screenshotCaption);
elements.screenshots.append(screenshotFrame);
});
};
@@ -880,12 +1053,12 @@
document.title = sourceApp.name;
document.querySelector("meta[name='description']").content = sourceApp.subtitle
|| "ARMSX2 iOS source for LiveContainer, SideStore, and Feather.";
|| "Modern PlayStation 2 emulation for iOS.";
elements.appIcon.src = absoluteUrl(sourceApp.iconURL || "assets/icon.png");
elements.appTitle.textContent = sourceApp.name;
elements.sourceName.textContent = sourcePayload.name || sourceApp.name;
elements.appSubtitle.textContent = sourceApp.subtitle || "PlayStation 2 emulation, built for iOS.";
elements.appSubtitle.textContent = sourceApp.subtitle || "Modern PlayStation 2 emulation for iOS.";
elements.heroDescription.textContent = firstParagraph(sourceApp.localizedDescription)
|| "Add ARMSX2 iOS to LiveContainer, SideStore, or Feather to install the latest build.";
elements.sourceUrl.textContent = canonicalSourceUrl;
@@ -899,9 +1072,12 @@
? `Version ${currentVersion.version}`
: "Latest version";
const appDescriptionBlocks = formattedBlocks(sourceApp.localizedDescription);
renderScreenshots(sourceApp.screenshotURLs, sourceApp.name);
renderWhatsNew(elements.whatsNew, currentVersion.localizedDescription);
renderParagraphs(elements.description, sourceApp.localizedDescription);
renderFeatureHighlights(elements.features, appDescriptionBlocks);
renderLegalDetails(elements.legalDetails, appDescriptionBlocks);
};
const renderIntegrity = (checksumPayload) => {
@@ -965,7 +1141,8 @@
elements.heroDescription.textContent = "Try again in a moment or copy the install link manually.";
elements.whatsNewVersion.textContent = "Release notes unavailable";
elements.whatsNew.textContent = "apps.json could not be loaded.";
elements.description.textContent = "apps.json could not be loaded.";
elements.features.textContent = "apps.json could not be loaded.";
elements.legalDetails.textContent = "apps.json could not be loaded.";
elements.status.textContent = `Install details could not be loaded: ${message}`;
return;
}
+2 -2
View File
@@ -1,7 +1,7 @@
{
"app": {
"subtitle": "PlayStation 2 emulation, built for iOS.",
"localizedDescription": "ARMSX2 brings PlayStation 2 emulation to iOS with an ARM64-focused build, controller-friendly play, and local-network support for compatible titles.\n\nA JIT-capable setup is required. Performance and compatibility vary by device, iOS version, game, and sideloading environment. You must provide your own BIOS and game backups."
"subtitle": "Modern PlayStation 2 emulation for iOS.",
"localizedDescription": "ARMSX2 brings PlayStation 2 emulation to iOS devices. Based on the open-source PCSX2 project, this ARM64-focused iOS build helps you revisit and preserve your own legally obtained PS2 game library on modern mobile hardware.\n\nFeatures:\n- Customizable touchscreen controls and external controller support\n- Graphics settings, performance options, save states, and memory card support\n- Local-network support for compatible online titles\n\nImportant iOS requirements:\nA JIT-capable setup is required for playable performance. Compatibility varies by device, iOS version, game, and sideloading environment, including LiveContainer, SideStore, or Feather.\n\nDisclaimer:\nARMSX2 does not include games or BIOS files. Users must provide their own legally obtained BIOS and game backups, such as ISO files. ARMSX2 is not affiliated with Sony Interactive Entertainment or PlayStation.\n\nCredits:\nThe upstream ARMSX2 project was founded by jpolo1224 and MoonPower, with work from contributors including Zeruth and Dib1998."
},
"screenshots": {
"directory": "assets/screenshots"
+2 -2
View File
@@ -7,8 +7,8 @@
"name": "ARMSX2 iOS",
"bundleIdentifier": "com.armsx2.ios",
"developerName": "ARMSX2",
"subtitle": "PlayStation 2 emulation, built for iOS.",
"localizedDescription": "ARMSX2 brings PlayStation 2 emulation to iOS with an ARM64-focused build, controller-friendly play, and local-network support for compatible titles.\n\nA JIT-capable setup is required. Performance and compatibility vary by device, iOS version, game, and sideloading environment. You must provide your own BIOS and game backups.",
"subtitle": "Modern PlayStation 2 emulation for iOS.",
"localizedDescription": "ARMSX2 brings PlayStation 2 emulation to iOS devices. Based on the open-source PCSX2 project, this ARM64-focused iOS build helps you revisit and preserve your own legally obtained PS2 game library on modern mobile hardware.\n\nFeatures:\n- Customizable touchscreen controls and external controller support\n- Graphics settings, performance options, save states, and memory card support\n- Local-network support for compatible online titles\n\nImportant iOS requirements:\nA JIT-capable setup is required for playable performance. Compatibility varies by device, iOS version, game, and sideloading environment, including LiveContainer, SideStore, or Feather.\n\nDisclaimer:\nARMSX2 does not include games or BIOS files. Users must provide their own legally obtained BIOS and game backups, such as ISO files. ARMSX2 is not affiliated with Sony Interactive Entertainment or PlayStation.\n\nCredits:\nThe upstream ARMSX2 project was founded by jpolo1224 and MoonPower, with work from contributors including Zeruth and Dib1998.",
"iconURL": "https://j1coding.github.io/Armsx2-Repo/assets/icon.png",
"screenshotURLs": [
"https://j1coding.github.io/Armsx2-Repo/assets/screenshots/01-library.png",
+2 -2
View File
@@ -94,9 +94,9 @@ const sourceApp = (ipaFileManifests, generatorOptions, metadataPayload, screensh
name: "ARMSX2 iOS",
bundleIdentifier,
developerName: "ARMSX2",
subtitle: metadataPayload.app.subtitle ?? "PS2 emulation built for ARM64 iOS.",
subtitle: metadataPayload.app.subtitle ?? "Modern PlayStation 2 emulation for iOS.",
localizedDescription: metadataPayload.app.localizedDescription
?? "ARMSX2 brings PlayStation 2 emulation to iOS with an ARM64-focused build, controller-friendly play, and local-network support for compatible titles.",
?? "ARMSX2 brings PlayStation 2 emulation to iOS devices. Based on the open-source PCSX2 project, this ARM64-focused iOS build helps you revisit and preserve your own legally obtained PS2 game library on modern mobile hardware.",
iconURL: publicAssetUrl(generatorOptions.baseUrl, "assets/icon.png"),
screenshotURLs: screenshotFiles.map((screenshotFile) => publicAssetUrl(generatorOptions.baseUrl, screenshotFile)),
tintColor: "#2F6FAD",