Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,19 @@ the static assets. It never deploys pull requests.

## Downloads and service status

Each platform has separate direct asset links. The checked-in links remain usable
Each platform expands from a compact download row to show its direct asset links.
Native details and summary elements support keyboard navigation and work without
JavaScript. Opening another platform closes the previous one in browsers that
support named details groups. The checked-in links remain usable
without JavaScript. With JavaScript, the page reads the latest published client
release from GitHub and replaces links with that release's available assets.
A failed or rate-limited lookup keeps the known release links and labels the
lookup as unavailable. Android's AAB is clearly labeled as a publishing bundle.
Tester installations use the separate Google Play opt-in link.
lookup as unavailable. The APK link appears when the published release includes
an APK. Tester installations use the separate Google Play opt-in link. Keep
platform rows to names, architecture and download links. Put installation
requirements and signing details in the GitHub release installation instructions.
The website links to checksums, the manifest and release notes. These links use
full-width rows on mobile.

`/status/` reads a cached `/status.json` snapshot. A systemd timer probes API
health and sync readiness once per minute, independently of site visitors.
Expand Down
55 changes: 43 additions & 12 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
<main id="main">
<section class="hero container" aria-labelledby="hero-title">
<div class="hero-copy">
<h1 id="hero-title">E-book reader and<br>library manager</h1>
<h1 id="hero-title">Your books, your data,<br>every device.</h1>
<p class="hero-description">Papyrus is a free, open-source app for organizing physical and digital books, reading e-books, and syncing your library across devices.</p>
<div class="hero-actions">
<a class="button button-primary" href="https://app.papyrus-reader.com">Try in browser <span
Expand All @@ -114,7 +114,7 @@ <h1 id="hero-title">E-book reader and<br>library manager</h1>
</figure>
</section>
<section class="product container" id="about" aria-labelledby="about-title">
<h2 id="about-title">Library and reading features</h2>
<h2 id="about-title">Features</h2>
<div class="feature-panels">
<article class="feature-panel">
<div class="feature-header">
Expand Down Expand Up @@ -156,17 +156,48 @@ <h3>Storage options</h3>
</div>
</section>
<section class="downloads" id="downloads" aria-labelledby="downloads-title">
<div class="container">
<h2 id="downloads-title">Download Papyrus</h2>
<p class="release-label" id="release-label" aria-live="polite">Release 0.0.2+2</p>
<div class="download-grid">
<article class="platform-card"><h3>Windows</h3><p>For Windows x64. The installer is unsigned, so Windows may show a download warning.</p><div class="platform-links"><a class="button button-primary" data-download="installer" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-windows-x64-setup.exe">Windows installer (.exe) <span aria-hidden="true">↓</span></a><a class="text-link" data-download="windows_archive" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-windows-x64.zip">Portable ZIP <span aria-hidden="true">↓</span></a></div></article>
<article class="platform-card"><h3>Linux</h3><p>For Ubuntu 24.04 x64. The AppImage also works on Debian 13.</p><div class="platform-links"><a class="button button-primary" data-download="deb" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-linux-x64.deb">Ubuntu package (.deb) <span aria-hidden="true">↓</span></a><a class="text-link" data-download="appimage" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-linux-x64.AppImage">AppImage <span aria-hidden="true">↓</span></a><a class="text-link" data-download="linux_archive" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-linux-x64.tar.gz">Portable archive (.tar.gz) <span aria-hidden="true">↓</span></a></div></article>
<article class="platform-card"><h3>Android</h3><p>Available to invited Google Play testers. The AAB is a publishing bundle and cannot be installed directly.</p><div class="platform-links"><a class="button button-primary" href="https://play.google.com/apps/internaltest/4701535216306698776">Get it on Google Play <span aria-hidden="true">↗</span></a><a class="text-link" data-download="android" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-android-universal.aab">Android App Bundle (.aab) <span aria-hidden="true">↓</span></a></div></article>
<article class="platform-card"><h3>Web</h3><p>Open Papyrus in your browser, or download the web build for self-hosting.</p><div class="platform-links"><a class="button button-primary" href="https://app.papyrus-reader.com">Open web app <span aria-hidden="true">↗</span></a><a class="text-link" data-download="web" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-web.zip">Web archive (.zip) <span aria-hidden="true">↓</span></a></div></article>
<div class="container download-layout">
<div class="download-heading">
<h2 id="downloads-title">Download Papyrus</h2>
<p class="release-label" id="release-label" aria-live="polite">Release 0.0.2+2</p>
</div>
<div class="download-options">
<details class="platform-downloads" name="platform-downloads">
<summary><span>Windows <small>x64</small></span><svg class="download-chevron" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></summary>
<div class="platform-links">
<a class="platform-download" data-download="installer" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-windows-x64-setup.exe">Windows installer (.exe) <span aria-hidden="true">↓</span></a>
<a class="platform-download" data-download="windows_archive" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-windows-x64.zip">Portable ZIP <span aria-hidden="true">↓</span></a>
</div>
</details>
<details class="platform-downloads" name="platform-downloads">
<summary><span>Linux <small>x64</small></span><svg class="download-chevron" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></summary>
<div class="platform-links">
<a class="platform-download" data-download="deb" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-linux-x64.deb">Ubuntu package (.deb) <span aria-hidden="true">↓</span></a>
<a class="platform-download" data-download="appimage" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-linux-x64.AppImage">AppImage <span aria-hidden="true">↓</span></a>
<a class="platform-download" data-download="linux_archive" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-linux-x64.tar.gz">Portable archive (.tar.gz) <span aria-hidden="true">↓</span></a>
</div>
</details>
<details class="platform-downloads" name="platform-downloads">
<summary><span>Android</span><svg class="download-chevron" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></summary>
<div class="platform-links">
<a class="platform-download" href="https://play.google.com/apps/internaltest/4701535216306698776">Google Play (internal testing) <span aria-hidden="true">↗</span></a>
<a class="platform-download" data-download="android_apk" hidden>Android APK <span aria-hidden="true">↓</span></a>
<a class="platform-download" data-download="android" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-android-universal.aab">Android App Bundle (.aab) <span aria-hidden="true">↓</span></a>
</div>
</details>
<details class="platform-downloads" name="platform-downloads">
<summary><span>Web</span><svg class="download-chevron" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></summary>
<div class="platform-links">
<a class="platform-download" href="https://app.papyrus-reader.com">Open web app <span aria-hidden="true">↗</span></a>
<a class="platform-download" data-download="web" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-web.zip">Web archive (.zip) <span aria-hidden="true">↓</span></a>
</div>
</details>
</div>
<div class="release-resources">
<a class="text-link" data-download="checksums" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/SHA256SUMS">SHA-256 checksums <span aria-hidden="true">↓</span></a>
<a class="text-link" data-download="manifest" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/release-manifest.json">Release manifest <span aria-hidden="true">↓</span></a>
<a class="text-link" id="release-notes" href="https://github.com/PapyrusReader/client/releases/tag/v0.0.2%2B2">Release notes <span aria-hidden="true">↗</span></a>
</div>
<div class="release-resources"><a class="text-link" data-download="instructions" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/papyrus-v0.0.2%2B2-INSTALL.md">Installation instructions <span aria-hidden="true">↓</span></a><a class="text-link" data-download="checksums" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/SHA256SUMS">SHA-256 checksums <span aria-hidden="true">↓</span></a><a class="text-link" data-download="manifest" href="https://github.com/PapyrusReader/client/releases/download/v0.0.2%2B2/release-manifest.json">Release manifest <span aria-hidden="true">↓</span></a><a class="text-link" id="release-notes" href="https://github.com/PapyrusReader/client/releases/tag/v0.0.2%2B2">Release notes ↗</a></div>
<p class="download-note">macOS and iOS downloads are not available yet.</p>
</div>
</section>
<section class="open-source container" aria-labelledby="open-source-title">
Expand Down
2 changes: 1 addition & 1 deletion js/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
const suffixes = {
installer: 'windows-x64-setup.exe', windows_archive: 'windows-x64.zip',
deb: 'linux-x64.deb', appimage: 'linux-x64.AppImage', linux_archive: 'linux-x64.tar.gz',
android: 'android-universal.aab', web: 'web.zip', instructions: 'INSTALL.md',
android: 'android-universal.aab', android_apk: 'android-universal.apk', web: 'web.zip', instructions: 'INSTALL.md',
checksums: 'SHA256SUMS', manifest: 'release-manifest.json',
};
function releaseDownloads(release) {
Expand Down
7 changes: 7 additions & 0 deletions js/tests/services.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,10 @@ test('status snapshots report monitored results and reject stale or incomplete d
assert.throws(() => serviceStatus({ ...snapshot, checked_at: 'invalid' }, now));
assert.throws(() => serviceStatus({ ...snapshot, services: { api: 'operational' } }, now));
});

test('APK downloads appear only when a release actually publishes one', () => {
const value = release();
assert.equal(releaseDownloads(value).links.android_apk, undefined);
value.assets.push({ name: 'papyrus-v0.0.3+4-android-universal.apk', browser_download_url: 'https://github.com/PapyrusReader/client/releases/download/v0.0.3%2B4/papyrus-v0.0.3%2B4-android-universal.apk' });
assert.equal(releaseDownloads(value).links.android_apk, value.assets[1].browser_download_url);
});
39 changes: 24 additions & 15 deletions scss/_site.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,20 @@ h3 { font-size: 25px; letter-spacing: -.025em; }
.feature-panels-secondary { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 20px; }
.open-source-copy > p, .section-description { color: var(--muted); margin-top: 24px; max-width: 540px; }
.downloads { background: var(--surface); border-block: 1px solid var(--border); padding-block: 74px; }
.download-layout { display: grid; grid-template-columns: 1.1fr 1fr; gap: clamp(40px, 8vw, 120px); }
.download-options { padding-top: 2px; }
.download-link { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding-block: 20px; border-bottom: 1px solid var(--border); text-decoration: none; }
.download-link:first-child { padding-top: 0; }
.download-link strong { display: block; font-size: 19px; font-weight: 600; }
.download-link small { display: block; color: var(--muted); margin-top: 4px; font-size: 14px; }
.download-link > span:last-child { font-size: 22px; }
.download-layout { display: grid; grid-template-columns: 1.1fr 1fr; gap: 36px clamp(40px, 8vw, 120px); }
.download-options { min-width: 0; }
.platform-downloads { border-bottom: 1px solid var(--border); }
.platform-downloads > summary { display: flex; align-items: center; justify-content: space-between; gap: 20px; min-height: 64px; padding-block: 18px; list-style: none; cursor: pointer; font-size: 19px; font-weight: 600; }
.platform-downloads > summary::-webkit-details-marker { display: none; }
.platform-downloads > summary small { color: var(--muted); font-size: 14px; font-weight: 400; margin-left: 6px; }
.platform-downloads > summary:hover, .platform-downloads[open] > summary { color: var(--accent); }
.platform-downloads > summary:focus-visible { outline: 3px solid var(--focus); outline-offset: 5px; border-radius: 4px; }
.download-chevron { flex: 0 0 20px; }
.platform-downloads[open] .download-chevron { transform: rotate(180deg); }
.platform-links { display: flex; flex-direction: column; padding-bottom: 16px; }
.platform-download { display: flex; align-items: center; justify-content: space-between; gap: 20px; min-height: 44px; padding: 9px 12px; margin-inline: -12px; border-radius: 6px; text-decoration: none; font-size: 15px; }
.platform-download:hover { background: var(--bg); }
.platform-download > span:last-child { flex-shrink: 0; }
.open-source { display: grid; grid-template-columns: 1.1fr 1fr; gap: clamp(40px, 8vw, 120px); padding-block: 94px; }
.open-source-copy > p { margin-top: 3px; }
.inline-links { display: flex; flex-wrap: wrap; gap: 15px 26px; margin-top: 25px; }
Expand All @@ -82,7 +89,7 @@ h3 { font-size: 25px; letter-spacing: -.025em; }
.navigation { flex-wrap: wrap; gap: 0; min-height: 78px; padding-block: 10px; }
.brand { width: 132px; }
.nav-actions { margin-left: auto; }
.theme-toggle { padding-inline: 10px; }
.theme-toggle, .theme-toggle:focus-visible { width: 44px; height: 44px; flex: 0 0 44px; padding: 0; justify-content: center; border-radius: 50%; }
.theme-toggle span { display: none; }
.menu-toggle { display: inline-flex; }
.nav-links { order: 3; width: 100%; flex-wrap: wrap; margin: 0; padding: 12px 0 8px; justify-content: space-between; }
Expand Down Expand Up @@ -125,12 +132,12 @@ h3 { font-size: 25px; letter-spacing: -.025em; }
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }

.release-label, .download-note, .status-note { color: var(--muted); font-size: 14px; margin-top: 20px; }
.download-grid, .status-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; margin-top: 32px; }
.platform-card, .service-card { padding: 28px; border: 1px solid var(--border); border-radius: 16px; background: var(--bg); }
.platform-card > p, .service-card > p { color: var(--muted); margin-top: 18px; }
.platform-links { display: flex; align-items: flex-start; flex-direction: column; gap: 18px; margin-top: 24px; }
.status-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; margin-top: 32px; }
.service-card { padding: 28px; border: 1px solid var(--border); border-radius: 16px; background: var(--bg); }
.service-card > p { color: var(--muted); margin-top: 18px; }
.platform-links [hidden], .release-resources [hidden] { display: none; }
.release-resources { display: flex; flex-wrap: wrap; gap: 16px 28px; margin-top: 30px; }
.release-resources { display: flex; flex-wrap: wrap; gap: 16px 28px; grid-column: 1 / -1; }
.release-resources .text-link { min-height: 44px; }
.status-page { padding-block: 72px 100px; }
.eyebrow { color: var(--accent); margin-bottom: 18px; }
.status-summary { display: flex; align-items: center; justify-content: space-between; gap: 24px; margin-top: 56px; }
Expand All @@ -144,8 +151,10 @@ h3 { font-size: 25px; letter-spacing: -.025em; }
.service-detail { font-size: 14px; }
button:disabled { opacity: .65; cursor: wait; }
@media (max-width: 720px) {
.download-grid, .status-grid { grid-template-columns: 1fr; }
.platform-card, .service-card { padding: 24px; }
.release-resources { flex-direction: column; gap: 4px; }
.release-resources .text-link { width: 100%; min-height: 48px; justify-content: space-between; }
.status-grid { grid-template-columns: 1fr; }
.service-card { padding: 24px; }
.status-summary { align-items: flex-start; flex-direction: column; }
.status-page { padding-block: 46px 62px; }
}
Loading