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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 2 additions & 1 deletion site/app/admin/status/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Metadata } from "next";
import Nav from "@/components/Nav";
import Health from "@/components/Health";
import { m } from "@/i18n";

// Behind Cloudflare Access; not indexed or linked from the site.
export const metadata: Metadata = {
title: "questlog health, every number",
title: m.meta.adminStatus.title,
robots: { index: false, follow: false },
};

Expand Down
15 changes: 7 additions & 8 deletions site/app/builder/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,31 @@ import type { Metadata } from "next";
import Nav from "@/components/Nav";
import Footer from "@/components/Footer";
import ProfileBuilder from "@/components/builder/ProfileBuilder";
import { m, rich } from "@/i18n";

export const metadata: Metadata = {
title: "questlog profile builder: your GitHub README with pixel art cards",
description:
"Write your GitHub profile README, add pixel art cards for your stats, languages and streak, and see it the way GitHub will show it, in light and dark.",
title: m.meta.builder.title,
description: m.meta.builder.description,
alternates: { canonical: "/builder" },
openGraph: {
url: "/builder",
title: "questlog profile builder",
description: "Your GitHub profile README with pixel art cards, in four steps.",
title: m.meta.builder.ogTitle,
description: m.meta.builder.ogDescription,
},
};

export default function BuilderPage() {
return (
<>
<a className="skip" href="#top">
Skip to content
{m.site.skip}
</a>
<Nav still />
<main id="top">
<ProfileBuilder />
<noscript>
<p style={{ width: "min(100% - 32px, 40rem)", margin: "0 auto 3rem" }}>
The builder needs JavaScript. The <a href="/wiki#getting-started">wiki</a> shows how to write the card lines by
hand.
{rich(m.site.noscript.builder, { wiki: (c) => <a href="/wiki#getting-started">{c}</a> })}
</p>
</noscript>
</main>
Expand Down
27 changes: 27 additions & 0 deletions site/app/cards/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Metadata } from "next";
import Nav from "@/components/Nav";
import Footer from "@/components/Footer";
import AllCards from "@/components/AllCards";
import { m } from "@/i18n";

export const metadata: Metadata = {
title: m.meta.cards.title,
description: m.meta.cards.description,
alternates: { canonical: "/cards" },
openGraph: { url: "/cards", title: m.meta.cards.ogTitle, description: m.meta.cards.ogDescription },
};

export default function CardsPage() {
return (
<>
<a className="skip" href="#top">
{m.site.skip}
</a>
<Nav />
<main id="top">
<AllCards />
</main>
<Footer />
</>
);
}
12 changes: 12 additions & 0 deletions site/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -491,3 +491,15 @@ img[src*="/streak"] {
.nav.nav-still {
position: absolute;
}

.nav-gh {
display: inline-flex;
align-items: center;
gap: 0.45em;
}

.gh-mark {
width: 1.05em;
height: 1.05em;
flex: none;
}
20 changes: 11 additions & 9 deletions site/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,39 @@ import type { Metadata, Viewport } from "next";
import localFont from "next/font/local";
import "./globals.css";
import { HOST } from "@/components/data";
import { lang, m } from "@/i18n";

const outfit = localFont({ src: "./fonts/Outfit-Variable.ttf", weight: "100 900", variable: "--font-outfit", display: "swap" });
const nodesto = localFont({ src: "./fonts/NodestoCapsCondensed-Bold.otf", variable: "--font-nodesto", display: "swap" });
const silk = localFont({ src: "./fonts/Silkscreen-Bold.woff2", weight: "700", variable: "--font-mark", display: "swap" });
const tiny = localFont({ src: "./fonts/Tiny5-Regular.ttf", variable: "--font-tiny", display: "swap" });

const meta = m.meta.home;

export const metadata: Metadata = {
metadataBase: new URL(HOST),
alternates: { canonical: "/" },
openGraph: {
type: "website",
url: "/",
siteName: "questlog",
title: "questlog: pixel art GitHub profile cards",
description: "Pixel art cards for your GitHub profile README: stats, languages and streak in 16 painted scenes.",
images: [{ url: "/demo/stats?scene=castle-sky", width: 1600, height: 640, alt: "A questlog Character Sheet" }],
siteName: meta.siteName,
title: meta.ogTitle,
description: meta.ogDescription,
images: [{ url: "/demo/stats?scene=castle-sky", width: 1600, height: 640, alt: meta.ogImageAlt }],
},
title: "questlog: pixel art GitHub profile cards",
description:
"Pixel art cards for your GitHub profile README: a character sheet of your stats, a spellbook of your languages and a fire for your streak, in 16 painted scenes.",
title: meta.title,
description: meta.description,
icons: { icon: "/static/favicon.svg" },
};

const FAILED_SVG = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 256'><rect width='640' height='256' rx='12' fill='#17141f'/><rect x='1' y='1' width='638' height='254' rx='11' fill='none' stroke='#f5dea0' stroke-opacity='.35' stroke-width='2'/><text x='320' y='120' fill='#f2e6c9' font-family='system-ui,sans-serif' font-size='26' text-anchor='middle'>This card did not load.</text><text x='320' y='158' fill='#f2e6c9' fill-opacity='.7' font-family='system-ui,sans-serif' font-size='22' text-anchor='middle'>Reload the page to try again.</text></svg>";
const FAILED_SVG = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 256'><rect width='640' height='256' rx='12' fill='#17141f'/><rect x='1' y='1' width='638' height='254' rx='11' fill='none' stroke='#f5dea0' stroke-opacity='.35' stroke-width='2'/><text x='320' y='120' fill='#f2e6c9' font-family='system-ui,sans-serif' font-size='26' text-anchor='middle'>${m.site.failedCard.title}</text><text x='320' y='158' fill='#f2e6c9' fill-opacity='.7' font-family='system-ui,sans-serif' font-size='22' text-anchor='middle'>${m.site.failedCard.hint}</text></svg>`;
const FAILED_CARD = `document.addEventListener("error",function(e){var i=e.target;if(!i||i.tagName!=="IMG"||i.src.indexOf("data:")===0)return;try{var u=new URL(i.src);if(u.origin!==location.origin||!/\\/(stats|languages|streak)\\/|\\/static\\/cards\\//.test(u.pathname+"/"))return}catch(_){return}i.src="data:image/svg+xml,"+encodeURIComponent(${JSON.stringify(FAILED_SVG)})},true)`;

export const viewport: Viewport = { themeColor: "#0f0d14", colorScheme: "dark" };

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${outfit.variable} ${nodesto.variable} ${tiny.variable} ${silk.variable}`}>
<html lang={lang} className={`${outfit.variable} ${nodesto.variable} ${tiny.variable} ${silk.variable}`}>
<body>
{/* a card that fails to load says so in plain words instead of showing a broken image */}
<script dangerouslySetInnerHTML={{ __html: FAILED_CARD }} />
Expand Down
18 changes: 8 additions & 10 deletions site/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import CardStack from "@/components/home/CardStack";
import Marquee from "@/components/home/Marquee";
import Gallery from "@/components/home/Gallery";
import Decorations from "@/components/home/Decorations";
import { snap } from "@/components/data";
import { DEMO_USER, snap } from "@/components/data";
import { m } from "@/i18n";
import s from "./page.module.css";

const HERO = "/stats?user=BaranDev&scene=castle&scale=3";
Expand All @@ -17,30 +18,27 @@ export default function Home() {
{/* old /#demo links go to the builder */}
<script dangerouslySetInnerHTML={{ __html: `if(location.hash==="#demo")location.replace("/builder")` }} />
<a className="skip" href="#top">
Skip to content
{m.site.skip}
</a>
<Nav />
<main id="top" style={{ overflowX: "clip" }}>
<section className={s.hero}>
<div className={s.heroCopy}>
<h1 className={s.h1}>Pixel art cards for your GitHub profile</h1>
<p className={s.heroLine}>
Your stats, your top languages and your streak, drawn in one of 16 painted scenes. Add them to your README
with one line: no token, no setup.
</p>
<h1 className={s.h1}>{m.home.hero.title}</h1>
<p className={s.heroLine}>{m.home.hero.line}</p>
<div className={s.ctas}>
<a className="btn btn-primary" href="/builder">
Make my cards
{m.home.hero.make}
</a>
<a className="btn btn-ghost" href="/wiki">
Read the wiki
{m.home.hero.wiki}
</a>
</div>
</div>
<div className={s.heroArt}>
<picture className="pix">
<source media="(prefers-color-scheme: dark)" srcSet={snap(`${HERO}&time=night`)} />
<img src={snap(HERO)} width={960} height={384} alt="BaranDev's Character Sheet in the castle scene" />
<img src={snap(HERO)} width={960} height={384} alt={m.home.hero.alt(DEMO_USER)} />
</picture>
</div>
</section>
Expand Down
3 changes: 2 additions & 1 deletion site/app/snapshots.json/route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { THUMBS } from "@/components/builder/cards";
import { ALL_CARDS } from "@/components/combos";

// The card addresses the pages build in the browser, written out at build time for scripts/snap_cards.py; the
// pages' other cards are found in their HTML.
export const dynamic = "force-static";

export function GET() {
return Response.json(THUMBS);
return Response.json([...THUMBS, ...ALL_CARDS]);
}
11 changes: 6 additions & 5 deletions site/app/status/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,27 @@ import type { Metadata } from "next";
import Nav from "@/components/Nav";
import Footer from "@/components/Footer";
import Health from "@/components/Health";
import { m, rich } from "@/i18n";

export const metadata: Metadata = {
title: "questlog health: is the service up, and how busy is it",
description: "Live health of questlog: whether cards are served, whether stats arrive from GitHub, and whether new users get in right now.",
title: m.meta.status.title,
description: m.meta.status.description,
alternates: { canonical: "/status" },
openGraph: { url: "/status", title: "questlog health", description: "Live health of the questlog card service." },
openGraph: { url: "/status", title: m.meta.status.ogTitle, description: m.meta.status.ogDescription },
};

export default function StatusPage() {
return (
<>
<a className="skip" href="#top">
Skip to content
{m.site.skip}
</a>
<Nav />
<main id="top">
<Health />
<noscript>
<p style={{ width: "min(100% - 32px, 40rem)", margin: "0 auto 3rem" }}>
This page needs JavaScript. The same is at <a href="/health">/health</a>.
{rich(m.site.noscript.status, { health: (c) => <a href="/health">{c}</a> })}
</p>
</noscript>
</main>
Expand Down
Loading
Loading