diff --git a/ogre/src/__tests__/render.test.ts b/ogre/src/__tests__/render.test.ts
--- a/ogre/src/__tests__/render.test.ts
+++ b/ogre/src/__tests__/render.test.ts
@@ -43,8 +43,13 @@ const savePng = (filename: string, buffer: Uint8Array) => {
writeFileSync(join(outputDir, filename), buffer);
};
+const saveSvg = (filename: string, svg: string) => {
+ writeFileSync(join(outputDir, filename), svg);
+};
+
const renderAndSave = async
(component: VNode
, filename: string) => {
- const { png } = await renderCard(component as VNode);
+ const { svg, png } = await renderCard(component as VNode);
+ saveSvg(filename.replace(".png", ".svg"), svg);
savePng(filename, png);
};
diff --git a/ogre/src/components/cards/repository.tsx b/ogre/src/components/cards/repository.tsx
--- a/ogre/src/components/cards/repository.tsx
+++ b/ogre/src/components/cards/repository.tsx
@@ -8,10 +8,10 @@ import { TYPOGRAPHY } from "../shared/constants";
import type { RepositoryCardData } from "../../validation";
function repoNameFontSize(name: string): number {
- // Available width ~1000px (1104px card content minus language circles area).
+ // Available width ~756px
// Inter 600 average char width is ~0.58× the font size.
const maxSize = TYPOGRAPHY.repoName.fontSize;
- const fitted = Math.floor(1000 / (name.length * 0.58));
+ const fitted = Math.floor(756 / (name.length * 0.58));
return Math.min(maxSize, Math.max(fitted, 48));
}
@@ -19,13 +19,21 @@ export function RepositoryCard(data: RepositoryCardData) {
const fontSize = repoNameFontSize(data.repoName);
return (
-
+
+
+
-
-
+
+
{data.repoName}
-
+
diff --git a/ogre/src/components/shared/language-circles.tsx b/ogre/src/components/shared/language-circles.tsx
--- a/ogre/src/components/shared/language-circles.tsx
+++ b/ogre/src/components/shared/language-circles.tsx
@@ -2,55 +2,45 @@ import type { Language } from "../../validation";
interface LanguageCirclesProps {
languages: Language[];
+ width: number;
}
-const MAX_RADIUS = 380;
-
-function percentageToThickness(percentage: number): number {
- return (percentage / 100) * MAX_RADIUS;
-}
+export function LanguageCircles({ languages, width }: LanguageCirclesProps) {
+ const MAX_RADIUS = width || 100;
-export function LanguageCircles({ languages }: LanguageCirclesProps) {
const sortedLanguages = [...languages]
.sort((a, b) => b.percentage - a.percentage)
- .slice(0, 5)
- .reverse();
+ .slice(0, 5);
- let cumulativeRadius = 0;
+ let cumulativePercentage = 0;
+ const circles: { color: string; radius: number }[] = [];
- return (
-
- {sortedLanguages.map((lang, i) => {
- const thickness = percentageToThickness(lang.percentage);
- const contentSize = cumulativeRadius * 2;
-
- cumulativeRadius += thickness;
+ for (const lang of sortedLanguages) {
+ // Radius decreases as we go inward, but ring area is proportional to percentage
+ // Using sqrt to make area (πr²) proportional to remaining percentage
+ const radius = Math.max(
+ 1,
+ Math.round(MAX_RADIUS * Math.sqrt(1 - cumulativePercentage / 100) * 100) /
+ 100,
+ );
+ circles.push({ color: lang.color, radius });
+ cumulativePercentage += lang.percentage;
+ }
- return (
-
- );
- })}
-
+ return (
+
);
}
diff --git a/ogre/wrangler.jsonc b/ogre/wrangler.jsonc
--- a/ogre/wrangler.jsonc
+++ b/ogre/wrangler.jsonc
@@ -6,6 +6,9 @@ "compatibility_date": "2026-03-07",
"observability": {
"enabled": true,
},
+ "limits": {
+ "cpu_ms": 300000
+ },
"routes": [
{
"pattern": "ogre.tangled.network",
@@ -18,8 +21,34 @@ },
"rules": [
{
"type": "Data",
- "globs": ["**/*.woff"],
+ "globs": ["**/*.wasm", "**/*.woff"],
"fallthrough": true,
},
],
+ "env": {
+ "dev": {
+ "name": "ogre-dev",
+ "vars": {
+ "ENVIRONMENT": "development"
+ },
+ "routes": [
+ {
+ "pattern": "ogre-dev.tangled.network",
+ "custom_domain": true
+ }
+ ]
+ },
+ "production": {
+ "name": "ogre",
+ "vars": {
+ "ENVIRONMENT": "production"
+ },
+ "routes": [
+ {
+ "pattern": "ogre.tangled.network",
+ "custom_domain": true
+ }
+ ]
+ }
+ }
}