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
4 changes: 2 additions & 2 deletions deploy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ ARG GIT_SHA=dev
WORKDIR /src
COPY scripts/build-site.mjs scripts/
COPY site site
# _headers and _redirects are for Cloudflare Workers; nginx.conf does the same.
RUN node scripts/build-site.mjs && rm dist/_headers dist/_redirects && echo "$GIT_SHA" > dist/version.txt
# _headers is for Cloudflare Workers; nginx.conf sends the same headers.
RUN node scripts/build-site.mjs && rm dist/_headers && echo "$GIT_SHA" > dist/version.txt

FROM nginxinc/nginx-unprivileged:1.28-alpine
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
Expand Down
4 changes: 2 additions & 2 deletions deploy/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ server {
location = /booking/404.html { internal; }
location = /404.html { internal; }

# Planner's landing page is exactly /planner; the trailing-slash form redirects to it.
location = /planner/ { return 301 /planner; }
# Every page lives at its address with no trailing slash; the slash form redirects to it.
location ~ ^/(apps|download|pricing|security|planner|notes|studio|engineering)/$ { return 301 /$1; }

location / { try_files $uri $uri/index.html $uri/ =404; }
error_page 404 /404.html;
Expand Down
20 changes: 10 additions & 10 deletions scripts/build-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ const PREVIEWS = {
const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
const building = '<span class="st info"><i class="sd"></i>Building</span>';
const planned = '<span class="st planned"><i class="sd"></i>Planned</span>';
const NAV = [["Apps", "/apps/"], ["Pricing", "/pricing/"], ["Security", "/security/"]];
const NAV = [["Apps", "/apps"], ["Pricing", "/pricing"], ["Security", "/security"]];

// Theme: the button and menu from orbyn.dev Designs "Theme switch".
const THEMES = [["system", "System", "i-laptop", "Follows this device"], ["light", "Light", "i-sun", ""], ["dark", "Dark", "i-moon", ""], ["dim", "Dim", "i-moon", "Softer dark"]];
Expand All @@ -178,9 +178,9 @@ function nav(current) {
`<div class="mrow foot">${building}Nothing is out yet</div></nav></div></header>`;
}

// The Planner landing page lives at exactly https://orbyn.dev/planner (no trailing slash).
// /planner/ redirects there; every link and the canonical address use the exact form.
const appPath = (key) => (key === "planner" ? "/planner" : `/${key}/`);
// Every page lives at its address with no trailing slash (orbyn.dev/planner, orbyn.dev/apps).
// The trailing-slash form redirects there: nginx.conf, and Workers' drop-trailing-slash setting.
const appPath = (key) => `/${key}`;
const appIcon = (a, sm = true) => `<span class="appi${sm ? " sm" : ""} ${a.cls}" aria-hidden="true">${ic(a.icon)}</span>`;

function appCard(a, { withPreview = true } = {}) {
Expand Down Expand Up @@ -236,7 +236,7 @@ pages.push({ file: "index.html", path: "/", title: "Orbyn", current: "",
<span class="eyebrow t3">One account · four apps</span>
<h1>Private apps that work offline and that agents can work in safely.</h1>
<p><span class="desk-only">Orbyn is a family of local-first apps that sync end to end under one account. Plan your week, keep your notes, make films and build software.</span><span class="phone-only">A family of local-first apps under one account.</span></p>
<div class="acts"><a class="btn lg" href="/apps/">See the apps</a><a class="ghost lg" href="/security/">How we protect your data</a></div>
<div class="acts"><a class="btn lg" href="/apps">See the apps</a><a class="ghost lg" href="/security">How we protect your data</a></div>
<span class="note t4">Nothing is out yet. Every app is still being built.</span>
</section>
<div class="pvhero desk-only" role="group" aria-label="Orbyn Planner, Notes, Studio and Engineering on one account">
Expand All @@ -254,7 +254,7 @@ ${APPS.map((a) => `<div class="pvstack phone-only"><span class="pvsh">${appIcon(
<div class="gap34"></div>
${promises()}` });

pages.push({ file: "apps/index.html", path: "/apps/", title: "Apps · Orbyn", current: "Apps",
pages.push({ file: "apps/index.html", path: "/apps", title: "Apps · Orbyn", current: "Apps",
description: "Four apps on one account: Orbyn Planner, Notes, Studio and Engineering. Every app is still being built.",
body: `<section class="hero sub">
<h1>Apps</h1>
Expand All @@ -265,7 +265,7 @@ pages.push({ file: "apps/index.html", path: "/apps/", title: "Apps · Orbyn", cu
<div class="cards c2" id="catalog">${APPS.map((a) => appCard(a)).join("")}</div>
<div class="legend t3">${building}Being built, not out yet${planned}On the roadmap, not started</div>` });

pages.push({ file: "download/index.html", path: "/download/", title: "Download · Orbyn", current: "",
pages.push({ file: "download/index.html", path: "/download", title: "Download · Orbyn", current: "",
description: "Nothing to download yet. Every Orbyn app is still being built.",
body: `<section class="hero sub dl">
<h1>Nothing to download yet</h1>
Expand All @@ -274,7 +274,7 @@ pages.push({ file: "download/index.html", path: "/download/", title: "Download
<div class="cards c4">${APPS.map((a) => `<div class="acard slim"><h2>${appIcon(a)}${a.name}</h2><span class="planned-for t3">Planned for ${PLATFORMS[a.key]}</span><div>${building}</div></div>`).join("")}</div>
<p class="foot t3">Phone and web versions are planned for Planner and Notes.</p>` });

pages.push({ file: "pricing/index.html", path: "/pricing/", title: "Pricing · Orbyn", current: "Pricing",
pages.push({ file: "pricing/index.html", path: "/pricing", title: "Pricing · Orbyn", current: "Pricing",
description: "Nothing is for sale yet. These are the plans Orbyn intends to launch with: every app free on one device, and one subscription to sync.",
body: `<section class="hero center pricing-hero">
<span class="desk-only">${planned}</span>
Expand All @@ -300,7 +300,7 @@ const SEC_ROWS = [
["Shared lists and projects", "Standard", "End-to-end when everyone sharing uses Advanced"],
["Calendars other apps use", "Standard", "Standard, so calendar apps and invitations keep working"],
];
pages.push({ file: "security/index.html", path: "/security/", title: "Security · Orbyn", current: "Security",
pages.push({ file: "security/index.html", path: "/security", title: "Security · Orbyn", current: "Security",
description: "How Orbyn protects your data. This is how Orbyn is being built. Nothing has launched yet, and details may change before it does.",
body: `<div class="sec-page">
<section class="sec-hero">${building}<h1>How Orbyn protects your data</h1><p>This is how Orbyn is being built. Nothing has launched yet, and details may change before it does.</p></section>
Expand Down Expand Up @@ -365,7 +365,7 @@ ${promises()}
const notFound = (line, button, path, file) => ({ file, path, title: "Not found · Orbyn", current: "",
description: "This page doesn’t exist.",
body: `<div class="public"><span class="t3 mono">404</span><h1>This page doesn’t exist</h1><p>${line}</p>
<div class="acts"><a class="btn" href="/apps/">${button}</a><a class="ghost" href="/">Go home</a></div></div>` });
<div class="acts"><a class="btn" href="/apps">${button}</a><a class="ghost" href="/">Go home</a></div></div>` });
pages.push(notFound("The link may be wrong, or the page may have moved.", "See the apps", "/404", "404.html"));
pages.push(notFound("Booking lives inside Orbyn Planner. It’s planned, not built yet.", "See all apps", "/booking", "booking/404.html"));

Expand Down
6 changes: 2 additions & 4 deletions scripts/serve-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ async function file(p) {

createServer(async (req, res) => {
const path = normalize(decodeURIComponent(new URL(req.url, "http://x").pathname)).replace(/^(\.\.[/\\])+/, "");
if (path === "/planner/") { res.writeHead(301, { Location: "/planner" }); return res.end(); }
if (/^\/(apps|download|pricing|security|notes|studio|engineering)$/.test(path)) {
res.writeHead(301, { Location: path + "/" }); return res.end();
}
const slash = path.match(/^(\/(?:apps|download|pricing|security|planner|notes|studio|engineering))\/$/);
if (slash) { res.writeHead(301, { Location: slash[1] }); return res.end(); }
let found = path.startsWith("/_headers") ? null : await file(join(dist, path));
let status = 200;
if (!found) {
Expand Down
2 changes: 0 additions & 2 deletions site/public/_redirects

This file was deleted.

2 changes: 1 addition & 1 deletion wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
"assets": {
"directory": "./dist",
"not_found_handling": "404-page",
"html_handling": "auto-trailing-slash"
"html_handling": "drop-trailing-slash"
}
}
Loading