Home page: the reader's typeface, screenshots, no source or run sections (#216)

Inter, the same file ipx-web serves, in place of Archivo. Screenshots of a scratch iPX subscribed
to public podcasts (99% Invisible playing; Ologies on a phone), never production's. The Run it
section and the links to the source are out for now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 17:54:59 +00:00
parent 2ee2382c9b
commit 579fe27b15
6 changed files with 36 additions and 42 deletions

View File

@@ -1,10 +1,12 @@
#!/bin/sh #!/bin/sh
# ipxreader.app: the page, its stylesheet and font, and the logo and icon from web/, into # ipxreader.app: the page and its stylesheet, and the typeface, logo and icon from web/, into
# site/dist, which Cloudflare Pages serves. # site/dist, which Cloudflare Pages serves.
set -e set -e
here=$(dirname "$0") here=$(dirname "$0")
rm -rf "$here/dist" rm -rf "$here/dist"
mkdir -p "$here/dist/fonts" mkdir -p "$here/dist/fonts"
cp "$here/index.html" "$here/site.css" "$here/_headers" "$here/dist/" cp "$here/index.html" "$here/site.css" "$here/_headers" "$here/dist/"
cp "$here/fonts/archivo.woff2" "$here/dist/fonts/" # The reader's own typeface, the same file ipx-web serves.
cp "$here/../web/InterVariable.woff2" "$here/dist/fonts/inter.woff2"
cp "$here/../web/logo.svg" "$here/../web/favicon.png" "$here/dist/" cp "$here/../web/logo.svg" "$here/../web/favicon.png" "$here/dist/"
cp -r "$here/shots" "$here/dist/"

Binary file not shown.

View File

@@ -7,7 +7,7 @@
<meta name="description" content="iPX checks your feeds, downloads new episodes and gives everyone at home their own list, on your own server. The heir to iPodderX, free and open source."> <meta name="description" content="iPX checks your feeds, downloads new episodes and gives everyone at home their own list, on your own server. The heir to iPodderX, free and open source.">
<meta name="color-scheme" content="light"> <meta name="color-scheme" content="light">
<link rel="icon" href="/favicon.png"> <link rel="icon" href="/favicon.png">
<link rel="preload" href="/fonts/archivo.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/site.css"> <link rel="stylesheet" href="/site.css">
</head> </head>
<body> <body>
@@ -15,7 +15,6 @@
<a class="home" href="/"><img src="/logo.svg" alt="" width="36" height="36">iPX</a> <a class="home" href="/"><img src="/logo.svg" alt="" width="36" height="36">iPX</a>
<nav aria-label="Elsewhere"> <nav aria-label="Elsewhere">
<a href="https://directory.ipxreader.app">Directory</a> <a href="https://directory.ipxreader.app">Directory</a>
<a href="https://git.sdf1.net/rays/ipx">Source</a>
</nav> </nav>
</header> </header>
@@ -25,8 +24,7 @@
<p class="lede">A podcatcher for the whole household, running on your own server.</p> <p class="lede">A podcatcher for the whole household, running on your own server.</p>
<p class="intro">It checks your feeds, downloads new episodes, and gives everyone at home a list of their own to listen from in the browser, all sharing one copy of each file.</p> <p class="intro">It checks your feeds, downloads new episodes, and gives everyone at home a list of their own to listen from in the browser, all sharing one copy of each file.</p>
<p class="acts"> <p class="acts">
<a class="btn primary" href="#run">Run it on your server</a> <a class="btn primary" href="https://directory.ipxreader.app">Browse the directory</a>
<a class="btn" href="https://directory.ipxreader.app">Browse the directory</a>
</p> </p>
</section> </section>
@@ -56,6 +54,16 @@
<figcaption>Some of what people listen to and read on iPX servers today.</figcaption> <figcaption>Some of what people listen to and read on iPX servers today.</figcaption>
</figure> </figure>
<section class="shots" aria-labelledby="shots">
<h2 id="shots">In the browser, and on your phone</h2>
<figure class="screens">
<img class="desk" src="/shots/desktop.webp" width="2000" height="1250" loading="lazy"
alt="iPX in a desktop browser: the feed list down the side, 99% Invisible's episodes in a table, the newest one's notes below it, and the player along the bottom.">
<img class="phone" src="/shots/phone.webp" width="600" height="1298" loading="lazy"
alt="iPX on a phone: Ologies with Alie Ward and its latest episodes.">
</figure>
</section>
<section class="does" aria-labelledby="does"> <section class="does" aria-labelledby="does">
<h2 id="does">What it does</h2> <h2 id="does">What it does</h2>
<dl> <dl>
@@ -78,22 +86,6 @@
</dl> </dl>
</section> </section>
<section class="run" id="run" aria-labelledby="run-h">
<h2 id="run-h">Run it</h2>
<p>You need a machine that stays on and Docker. These three steps start iPX with everything kept beside it in SQLite.</p>
<ol>
<li><p>Get the source.</p>
<pre><code>git clone https://git.sdf1.net/rays/ipx.git
cd ipx</code></pre></li>
<li><p>Build iPX and its web page.</p>
<pre><code>docker build -t ipx .
docker build -f Dockerfile.web -t ipx-web .</code></pre></li>
<li><p>Start them, then open port 8099 on that machine and sign in as <b>admin</b> with the password <b>ipodderx</b>. Change it in Settings straight away.</p>
<pre><code>docker compose up -d</code></pre></li>
</ol>
<p>The <a href="https://git.sdf1.net/rays/ipx#run-it">README</a> covers Postgres, signing in through Cloudflare Access or Authentik, and every setting.</p>
</section>
<section class="then" aria-labelledby="then"> <section class="then" aria-labelledby="then">
<h2 id="then">Since 2004</h2> <h2 id="then">Since 2004</h2>
<p>iPodderX was one of the first podcatchers, a Mac app by Ray Slakinski and August Trometer, from the year podcasts got their name. iPX is its engine rewritten in Rust for a server, with a page modelled on the original window: the feed list down the side, the episodes in a table, the player along the bottom.</p> <p>iPodderX was one of the first podcatchers, a Mac app by Ray Slakinski and August Trometer, from the year podcasts got their name. iPX is its engine rewritten in Rust for a server, with a page modelled on the original window: the feed list down the side, the episodes in a table, the player along the bottom.</p>
@@ -101,7 +93,7 @@ docker build -f Dockerfile.web -t ipx-web .</code></pre></li>
</main> </main>
<footer> <footer>
<p>iPX is free and open source under the MIT licence. <a href="https://git.sdf1.net/rays/ipx">Read the source</a> or <a href="https://git.sdf1.net/rays/ipx/issues">report a problem</a>.</p> <p>iPX is free and open source under the MIT licence.</p>
</footer> </footer>
</body> </body>
</html> </html>

BIN
site/shots/desktop.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
site/shots/phone.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -1,7 +1,7 @@
/* ipxreader.app. The palette is the iPodderX logo's: the radio's navy body, the sky of its /* ipxreader.app. The palette is the iPodderX logo's: the radio's navy body, the sky of its
screen, the blue of its tuning scale, and the orange needle, which is the needle alone. One screen, the blue of its tuning scale, and the orange needle, which is the needle alone. The
typeface, Archivo, its width doing the work: expanded for the name, condensed on the dial. */ typeface is the reader's own, Inter, the same file (web/InterVariable.woff2). */
@font-face{font-family:Archivo;src:url(/fonts/archivo.woff2) format("woff2");font-weight:100 900;font-stretch:62% 125%;font-display:swap} @font-face{font-family:Inter;src:url(/fonts/inter.woff2) format("woff2");font-weight:100 900;font-display:swap}
:root{ :root{
--navy:#0c2238; /* the radio */ --navy:#0c2238; /* the radio */
--ink:#16304a; /* body text, a step off the navy */ --ink:#16304a; /* body text, a step off the navy */
@@ -15,20 +15,20 @@
} }
*{box-sizing:border-box} *{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth} html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{margin:0;background:var(--page);color:var(--ink);font:400 1.0625rem/1.6 Archivo,system-ui,sans-serif;font-stretch:100%} body{margin:0;background:var(--page);color:var(--ink);font:400 1.0625rem/1.6 Inter,system-ui,sans-serif}
a{color:var(--scale);text-underline-offset:.18em} a{color:var(--scale);text-underline-offset:.18em}
a:focus-visible,.btn:focus-visible{outline:3px solid var(--scale);outline-offset:3px;border-radius:6px} a:focus-visible,.btn:focus-visible{outline:3px solid var(--scale);outline-offset:3px;border-radius:6px}
main,.bar,footer{max-width:72rem;margin:0 auto;padding-inline:1.5rem} main,.bar,footer{max-width:72rem;margin:0 auto;padding-inline:1.5rem}
.bar{display:flex;align-items:center;justify-content:space-between;padding-block:1.25rem} .bar{display:flex;align-items:center;justify-content:space-between;padding-block:1.25rem}
.home{display:flex;align-items:center;gap:.6rem;color:var(--navy);text-decoration:none;font-weight:800;font-stretch:112%;font-size:1.25rem} .home{display:flex;align-items:center;gap:.6rem;color:var(--navy);text-decoration:none;font-weight:800;font-size:1.25rem}
.home img{border-radius:9px;display:block} .home img{border-radius:9px;display:block}
.bar nav{display:flex;gap:1.5rem} .bar nav{display:flex;gap:1.5rem}
.bar nav a{color:var(--navy);text-decoration:none;font-weight:600} .bar nav a{color:var(--navy);text-decoration:none;font-weight:600}
.bar nav a:hover{text-decoration:underline} .bar nav a:hover{text-decoration:underline}
.hero{padding-block:3rem 2.5rem;max-width:46rem} .hero{padding-block:3rem 2.5rem;max-width:46rem}
h1{margin:0;color:var(--navy);font-weight:850;font-stretch:125%;font-size:clamp(6.5rem,24vw,15rem);line-height:.82;letter-spacing:-.045em} h1{margin:0;color:var(--navy);font-weight:900;font-size:clamp(6.5rem,24vw,15rem);line-height:.82;letter-spacing:-.065em}
.lede{margin:1.75rem 0 0;color:var(--navy);font-weight:650;font-size:clamp(1.45rem,3.4vw,2rem);line-height:1.2;letter-spacing:-.01em} .lede{margin:1.75rem 0 0;color:var(--navy);font-weight:650;font-size:clamp(1.45rem,3.4vw,2rem);line-height:1.2;letter-spacing:-.01em}
.intro{margin:1rem 0 0;max-width:38rem;color:var(--muted);font-size:1.125rem} .intro{margin:1rem 0 0;max-width:38rem;color:var(--muted);font-size:1.125rem}
.acts{display:flex;flex-wrap:wrap;gap:.75rem;margin:2rem 0 0} .acts{display:flex;flex-wrap:wrap;gap:.75rem;margin:2rem 0 0}
@@ -43,7 +43,7 @@ h1{margin:0;color:var(--navy);font-weight:850;font-stretch:125%;font-size:clamp(
.dial{margin:1rem 0 0} .dial{margin:1rem 0 0}
.scale{background:var(--navy);border-radius:1.25rem;overflow:hidden} .scale{background:var(--navy);border-radius:1.25rem;overflow:hidden}
.track{position:relative;--at:45.8%} .track{position:relative;--at:45.8%}
.freq{position:relative;list-style:none;margin:0;padding:1.1rem 4% 0;display:flex;justify-content:space-between;color:var(--tick);font-stretch:70%;font-weight:600;font-size:.95rem;font-variant-numeric:tabular-nums} .freq{position:relative;list-style:none;margin:0;padding:1.1rem 4% 0;display:flex;justify-content:space-between;color:var(--tick);font-weight:600;font-size:.95rem;font-variant-numeric:tabular-nums}
.freq li{width:0;display:flex;justify-content:center} /* each number centred on its tick */ .freq li{width:0;display:flex;justify-content:center} /* each number centred on its tick */
.freq::after{content:"";position:absolute;left:4%;right:4%;top:2.6rem;height:1.6rem; .freq::after{content:"";position:absolute;left:4%;right:4%;top:2.6rem;height:1.6rem;
background: background:
@@ -51,7 +51,7 @@ h1{margin:0;color:var(--navy);font-weight:850;font-stretch:125%;font-size:clamp(
repeating-linear-gradient(90deg,rgba(143,194,234,.55) 0 1px,transparent 1px 2%) 0 100%/100% 45%; repeating-linear-gradient(90deg,rgba(143,194,234,.55) 0 1px,transparent 1px 2%) 0 100%/100% 45%;
background-repeat:no-repeat} background-repeat:no-repeat}
.stations{list-style:none;margin:2.6rem 0 0;padding:0 1% 1.6rem;display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:start; .stations{list-style:none;margin:2.6rem 0 0;padding:0 1% 1.6rem;display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:start;
color:#cfe3f4;font-stretch:65%;font-weight:550;font-size:.92rem;line-height:1.15;text-align:center} color:#cfe3f4;font-weight:550;font-size:.84rem;line-height:1.2;letter-spacing:-.01em;text-align:center}
.stations li{padding-inline:.15rem} .stations li{padding-inline:.15rem}
.stations li:nth-child(even){margin-top:1.7rem} .stations li:nth-child(even){margin-top:1.7rem}
.needle{position:absolute;top:2.35rem;height:2.7rem;left:var(--at);width:3px;margin-left:-1.5px;background:var(--needle);border-radius:2px; .needle{position:absolute;top:2.35rem;height:2.7rem;left:var(--at);width:3px;margin-left:-1.5px;background:var(--needle);border-radius:2px;
@@ -62,18 +62,19 @@ h1{margin:0;color:var(--navy);font-weight:850;font-stretch:125%;font-size:clamp(
@media (prefers-reduced-motion:reduce){.needle,.stations .tuned{animation-duration:0s;animation-delay:0s}} @media (prefers-reduced-motion:reduce){.needle,.stations .tuned{animation-duration:0s;animation-delay:0s}}
figcaption{margin:.7rem 0 0;color:var(--muted);font-size:.9rem} figcaption{margin:.7rem 0 0;color:var(--muted);font-size:.9rem}
h2{margin:0 0 1.5rem;color:var(--navy);font-weight:800;font-stretch:112%;font-size:clamp(1.75rem,4vw,2.4rem);line-height:1.1;letter-spacing:-.02em} /* The app as it looks: the desktop window, and the phone over its corner. */
.does,.run,.then{padding-block:4rem 0} .shots{padding-block:4.5rem 0}
.screens{position:relative;margin:0 0 3rem;padding-right:clamp(0px,12%,9rem)}
.screens img{display:block;height:auto}
.desk{width:100%;border-radius:.9rem;border:1px solid var(--rule);box-shadow:0 1.5rem 3rem -1.5rem rgba(12,34,56,.45)}
.phone{position:absolute;right:0;bottom:-3rem;width:24%;max-width:15rem;border-radius:1.6rem;border:.4rem solid var(--navy);box-shadow:0 1.5rem 3rem -1rem rgba(12,34,56,.5)}
h2{margin:0 0 1.5rem;color:var(--navy);font-weight:800;font-size:clamp(1.75rem,4vw,2.4rem);line-height:1.1;letter-spacing:-.02em}
.does,.then{padding-block:4rem 0}
.does dl{margin:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2.25rem 3.5rem} .does dl{margin:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2.25rem 3.5rem}
.does dt{color:var(--navy);font-weight:750;font-size:1.2rem;line-height:1.25} .does dt{color:var(--navy);font-weight:750;font-size:1.2rem;line-height:1.25}
.does dd{margin:.45rem 0 0;max-width:34rem} .does dd{margin:.45rem 0 0;max-width:34rem}
.run>p{max-width:40rem}
.run ol{list-style:none;counter-reset:step;margin:2rem 0;padding:0;display:grid;gap:1.75rem;max-width:44rem}
.run li{counter-increment:step;display:grid;grid-template-columns:3rem minmax(0,1fr);column-gap:1rem}
.run li::before{content:counter(step);grid-row:span 2;color:var(--scale);font-weight:800;font-stretch:125%;font-size:2.4rem;line-height:1}
.run li p{margin:0 0 .6rem}
pre{margin:0;padding:1rem 1.15rem;background:var(--navy);color:var(--sky);border-radius:.75rem;overflow-x:auto;font:500 .92rem/1.55 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.then p{max-width:40rem} .then p{max-width:40rem}
footer{margin-top:5rem;padding-block:1.75rem 3rem;border-top:1px solid var(--rule);color:var(--muted);font-size:.95rem} footer{margin-top:5rem;padding-block:1.75rem 3rem;border-top:1px solid var(--rule);color:var(--muted);font-size:.95rem}
@@ -84,8 +85,8 @@ footer p{margin:0}
.bar nav{gap:1rem} .bar nav{gap:1rem}
.hero{padding-block:1.5rem 2rem} .hero{padding-block:1.5rem 2rem}
.does dl{grid-template-columns:minmax(0,1fr);gap:1.75rem} .does dl{grid-template-columns:minmax(0,1fr);gap:1.75rem}
.run li{grid-template-columns:2.25rem minmax(0,1fr)} .screens{padding-right:0;margin-bottom:0}
.run li::before{font-size:1.9rem} .phone{position:static;width:62%;margin:1.5rem auto 0}
.dial{margin-inline:-1rem} .dial{margin-inline:-1rem}
.scale{border-radius:0} .scale{border-radius:0}
figcaption{padding-inline:1rem} figcaption{padding-inline:1rem}
@@ -95,5 +96,4 @@ footer p{margin:0}
.stations li:nth-child(-n+3),.stations li:nth-child(n+10){display:none} .stations li:nth-child(-n+3),.stations li:nth-child(n+10){display:none}
.stations li:nth-child(even){margin-top:0} .stations li:nth-child(even){margin-top:0}
.stations li:nth-child(odd){margin-top:1.7rem} .stations li:nth-child(odd){margin-top:1.7rem}
pre{white-space:pre-wrap;overflow-wrap:anywhere}
} }