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
1 change: 1 addition & 0 deletions collection/app/home.css
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@
.frame-lockup {
display: flex;
flex-direction: column;
margin: 0;
font-size: 2rem;
line-height: 1;
}
Expand Down
2 changes: 2 additions & 0 deletions collection/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AppIndex from "@/components/AppIndex";
import Footer from "@/components/Footer";
import Hero from "@/components/Hero";
import JsonLd from "@/components/JsonLd";
import Manifesto from "@/components/Manifesto";
import Nav from "@/components/Nav";
import Rules from "@/components/Rules";
Expand All @@ -11,6 +12,7 @@ export default function Home() {
// chrome to jump to, and <main> claimed the header and footer as content.
return (
<>
<JsonLd />
<Nav />
<main id="content">
<Hero />
Expand Down
14 changes: 0 additions & 14 deletions collection/app/robots.ts

This file was deleted.

23 changes: 0 additions & 23 deletions collection/app/sitemap.ts

This file was deleted.

4 changes: 2 additions & 2 deletions collection/components/AppIndex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ export default function AppIndex() {

<span className="frame-name">
<Mark size={28} className="frame-mark" />
<span className="type-heading frame-lockup">
<h3 className="type-heading frame-lockup">
<span className="frame-lockup-collection">Spiral</span>
<span className="frame-lockup-app">{short}</span>
</span>
</h3>
</span>

<span className="frame-proof">{PROOF[app.slug] ?? app.tagline}</span>
Expand Down
13 changes: 13 additions & 0 deletions collection/components/JsonLd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { homeJsonLd } from "@/lib/jsonLd";

/** Home-page JSON-LD. Escapes `<` so a future string cannot break the parser. */
export default function JsonLd() {
const json = JSON.stringify(homeJsonLd()).replace(/</g, "\\u003c");
return (
<script
type="application/ld+json"
// biome-ignore lint/security/noDangerouslySetInnerHtml: static JSON-LD, no user input
dangerouslySetInnerHTML={{ __html: json }}
/>
);
}
106 changes: 106 additions & 0 deletions collection/lib/jsonLd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* JSON-LD for the home page. Every string is already on the site (layout
* metadata, the hero, the app catalogue, the rules board, or the manifesto).
* No ratings, review counts, or other figures we do not have.
*/
import { apps } from "@/lib/apps";
import { WHOP_CHECKOUT_URL } from "@/lib/whop";

const SITE = "https://spiralcc.tech";

/** Same sentence as `app/layout.tsx` metadata.description. */
const COLLECTION_DESCRIPTION =
"Spiral Collection — Wallpaper, Slim, and Resume. One $9.99 license. No telemetry, no background processes. Mac, and Windows where each app ships.";

/**
* Live apps the home page and checkout actually sell. Clean has a page but
* is not released — it is not in this list and has no Offer.
*/
const LISTED = ["wallpaper", "slim", "resume"] as const;

const OFFER = {
"@type": "Offer",
name: "Spiral Collection license",
description: "One $9.99 license. Unlocks Wallpaper, Slim, and Resume.",
price: "9.99",
priceCurrency: "USD",
url: WHOP_CHECKOUT_URL,
availability: "https://schema.org/InStock",
};

function operatingSystem(slug: string): string {
// Slim's only shipped binary is the macOS wizard. The tagline already
// states that scripts cover the other browsers and platforms.
if (slug === "slim") return "macOS";
return "macOS, Windows";
}

export function homeJsonLd(): Record<string, unknown> {
const listedApps = LISTED.map((slug) => {
const app = apps.find((entry) => entry.slug === slug);
if (!app) throw new Error(`JSON-LD: catalogue is missing "${slug}".`);
return app;
});

return {
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebSite",
"@id": `${SITE}/#website`,
url: `${SITE}/`,
name: "Spiral",
description: COLLECTION_DESCRIPTION,
publisher: { "@id": `${SITE}/#org` },
},
{
"@type": "Organization",
"@id": `${SITE}/#org`,
name: "Spiral",
url: `${SITE}/`,
email: "cohencool@icloud.com",
},
{
"@type": "SoftwareApplication",
"@id": `${SITE}/#collection`,
name: "Spiral Collection",
url: `${SITE}/`,
description: COLLECTION_DESCRIPTION,
applicationCategory: "DesktopApplication",
operatingSystem: "macOS, Windows",
offers: OFFER,
featureList: [
"One $9.99 license",
"No telemetry",
"No background processes",
"Nothing to sign in to",
"Source you can read",
],
publisher: { "@id": `${SITE}/#org` },
},
{
"@type": "ItemList",
"@id": `${SITE}/#apps`,
name: "Spiral Collection apps",
numberOfItems: listedApps.length,
itemListElement: listedApps.map((app, i) => ({
"@type": "ListItem",
position: i + 1,
item: {
"@type": "SoftwareApplication",
"@id": `${SITE}${app.page}#app`,
name: app.name,
url: `${SITE}${app.page}`,
description: app.tagline,
applicationCategory: "DesktopApplication",
operatingSystem: operatingSystem(app.slug),
softwareVersion: app.version,
downloadUrl: app.downloads?.mac.url,
offers: OFFER,
publisher: { "@id": `${SITE}/#org` },
},
})),
},
],
};
}
9 changes: 9 additions & 0 deletions collection/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Spiral Collection — https://spiralcc.tech
# Allow crawlers. /thanks is a post-purchase download page (noindex).

User-agent: *
Allow: /
Disallow: /thanks
Disallow: /thanks/

Sitemap: https://spiralcc.tech/sitemap.xml
38 changes: 38 additions & 0 deletions collection/public/sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://spiralcc.tech/</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://spiralcc.tech/wallpaper/</loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://spiralcc.tech/slim/</loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://spiralcc.tech/resume/</loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://spiralcc.tech/clean/</loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://spiralcc.tech/privacy/</loc>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://spiralcc.tech/work/</loc>
<changefreq>weekly</changefreq>
<priority>0.6</priority>
</url>
</urlset>