diff --git a/apps/web/app/icon.svg b/apps/web/app/icon.svg new file mode 100644 index 0000000..f90e0da --- /dev/null +++ b/apps/web/app/icon.svg @@ -0,0 +1,11 @@ + diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 7b86657..afb6ba4 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -2,10 +2,51 @@ import type { Metadata } from "next"; import { GeistPixelSquare } from "geist/font/pixel"; import "./globals.css"; +const title = "Kyora — unlocking the full potential of coding agents"; +const description = + "Queryable runtime state, multi-model code review, and councils of other model families — on the coding subscriptions you already pay for."; + export const metadata: Metadata = { - title: "kyora — unlocking the full potential of coding agents", - description: - "Queryable runtime state, multi-model code review, and councils of other model families — on the coding subscriptions you already pay for.", + metadataBase: new URL("https://kyora.sh"), + title: { + default: title, + template: "%s — Kyora", + }, + description, + applicationName: "Kyora", + keywords: [ + "coding agents", + "AI code review", + "multi-model code review", + "runtime observability", + "MCP server", + "Claude Code", + "Codex", + "agent tooling", + ], + authors: [{ name: "Elia Hilse", url: "https://x.com/eliahilse" }], + creator: "Elia Hilse", + alternates: { canonical: "/" }, + openGraph: { + type: "website", + url: "https://kyora.sh", + siteName: "Kyora", + title, + description, + images: [{ url: "/og", width: 1200, height: 630, alt: title }], + }, + twitter: { + card: "summary_large_image", + title, + description, + creator: "@eliahilse", + images: ["/og"], + }, + robots: { + index: true, + follow: true, + googleBot: { index: true, follow: true, "max-image-preview": "large" }, + }, }; export default function RootLayout({ diff --git a/apps/web/app/og/route.tsx b/apps/web/app/og/route.tsx new file mode 100644 index 0000000..65ea7c5 --- /dev/null +++ b/apps/web/app/og/route.tsx @@ -0,0 +1,163 @@ +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { ImageResponse } from "@takumi-rs/image-response"; + +export const dynamic = "force-static"; +export const revalidate = false; + +const GLYPHS = " .·:-=+*ox#%@"; +const COLS = 50; +const ROWS = 27; +const CELL = 24; + +function hash(x: number, y: number): number { + const n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453123; + return n - Math.floor(n); +} + +function noise(x: number, y: number): number { + const ix = Math.floor(x); + const iy = Math.floor(y); + const fx = x - ix; + const fy = y - iy; + const ux = fx * fx * (3 - 2 * fx); + const uy = fy * fy * (3 - 2 * fy); + const a = hash(ix, iy); + const b = hash(ix + 1, iy); + const c = hash(ix, iy + 1); + const d = hash(ix + 1, iy + 1); + return (a + (b - a) * ux) * (1 - uy) + (c + (d - c) * ux) * uy; +} + +function fbm(x: number, y: number): number { + let value = 0; + let amplitude = 0.5; + let px = x; + let py = y; + for (let i = 0; i < 5; i++) { + value += amplitude * noise(px, py); + px *= 2.02; + py *= 2.02; + amplitude *= 0.5; + } + return value; +} + +/** Same domain-warped field as the live background, sampled once per character cell. */ +function fieldAt(col: number, row: number): number { + const x = (col / COLS) * 6.4; + const y = (row / ROWS) * 3.4; + const qx = fbm(x, y); + const qy = fbm(x + 5.2, y + 1.3); + const rx = fbm(x + 3.4 * qx + 1.7, y + 3.4 * qy + 9.2); + const ry = fbm(x + 3.4 * qx + 8.3, y + 3.4 * qy + 2.8); + const field = fbm(x + 3.4 * rx, y + 3.4 * ry); + + const dx = (col / COLS - 0.5) * 1.9; + const dy = row / ROWS - 0.5; + const distance = Math.sqrt(dx * dx + dy * dy); + const clearing = Math.min(1, Math.max(0, (distance - 0.26) / 0.38)); + + return Math.min(1, Math.max(0, (field - 0.34) * 2.6)) * clearing; +} + +const FONT_PATH = "geist/dist/fonts/geist-pixel/GeistPixel-Square.woff2"; + +/** geist's exports map blocks deep paths under Node, so read the file directly. */ +function pixelFont(): Uint8Array | null { + for (const base of ["node_modules", "../../node_modules", "../node_modules"]) { + const candidate = join(process.cwd(), base, FONT_PATH); + if (existsSync(candidate)) return new Uint8Array(readFileSync(candidate)); + } + return null; +} + +export function GET() { + const cells = []; + for (let row = 0; row < ROWS; row++) { + for (let col = 0; col < COLS; col++) { + const intensity = fieldAt(col, row); + if (intensity < 0.06) continue; + const index = Math.min(GLYPHS.length - 1, Math.floor(intensity * GLYPHS.length)); + const glyph = GLYPHS[index]!; + if (glyph === " ") continue; + cells.push( +