x12i-downloads 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/graphify.mdc +21 -0
- package/.openai/hosting.json +1 -0
- package/.vinext/fonts/geist-8ac0455e797f/geist-001175b1.woff2 +0 -0
- package/.vinext/fonts/geist-8ac0455e797f/geist-52306abf.woff2 +0 -0
- package/.vinext/fonts/geist-8ac0455e797f/geist-875ccdd4.woff2 +0 -0
- package/.vinext/fonts/geist-8ac0455e797f/geist-98bbbccb.woff2 +0 -0
- package/.vinext/fonts/geist-8ac0455e797f/geist-ff2310f5.woff2 +0 -0
- package/.vinext/fonts/geist-8ac0455e797f/style.css +45 -0
- package/.vinext/fonts/geist-mono-00e989178794/geist-mono-013b2f2f.woff2 +0 -0
- package/.vinext/fonts/geist-mono-00e989178794/geist-mono-0638449e.woff2 +0 -0
- package/.vinext/fonts/geist-mono-00e989178794/geist-mono-44745446.woff2 +0 -0
- package/.vinext/fonts/geist-mono-00e989178794/geist-mono-44e03052.woff2 +0 -0
- package/.vinext/fonts/geist-mono-00e989178794/geist-mono-971fb274.woff2 +0 -0
- package/.vinext/fonts/geist-mono-00e989178794/geist-mono-f6b33328.woff2 +0 -0
- package/.vinext/fonts/geist-mono-00e989178794/style.css +54 -0
- package/README.md +54 -0
- package/app/api/cron/update-stats/route.ts +23 -0
- package/app/api/stats/route.ts +14 -0
- package/app/chatgpt-auth.ts +86 -0
- package/app/globals.css +301 -0
- package/app/layout.tsx +44 -0
- package/app/page.tsx +61 -0
- package/build/sites-vite-plugin.ts +45 -0
- package/components/animated-counter.tsx +70 -0
- package/components/cumulative-chart.tsx +82 -0
- package/components/downloads-dashboard.tsx +90 -0
- package/db/index.ts +13 -0
- package/db/schema.ts +4 -0
- package/drizzle/meta/_journal.json +5 -0
- package/drizzle.config.ts +7 -0
- package/eslint.config.mjs +18 -0
- package/examples/d1/app/api/notes/route.ts +58 -0
- package/examples/d1/db/schema.ts +9 -0
- package/graphify-out/.graphify_labels.json +25 -0
- package/graphify-out/.graphify_labels.json.sig +1 -0
- package/graphify-out/.graphify_root +1 -0
- package/graphify-out/GRAPH_REPORT.md +145 -0
- package/graphify-out/cache/ast/v0.9.33/0569aed6b68b30e30dcbaf649a48ee821f274b5b12aa100f2c226d15244b3a9b.json +1 -0
- package/graphify-out/cache/ast/v0.9.33/159ff1acf176e1a9d83c92e1132a5a9812fcbc84768f811b48ba88a5a46259a7.json +1 -0
- package/graphify-out/cache/ast/v0.9.33/2723bc60c1710800853b62fed8b65a07463fa4805580dc215cc84be4ead3f018.json +1 -0
- package/graphify-out/cache/ast/v0.9.33/7ce918b99db50a4df6ce9af15ad0386b77f2856b743e7efa1462dded67d5ed03.json +1 -0
- package/graphify-out/cache/ast/v0.9.33/7fddf7ae1399ea8c8f341e27419bbc339cf0f817f3422b5c6e3bc0701286abbe.json +1 -0
- package/graphify-out/cache/ast/v0.9.33/d77de58cd43e8cec3904e803fc046f584784a062f1520ec24e05212603265829.json +1 -0
- package/graphify-out/cache/ast/v0.9.33/d90342d6a3152d909250981198325960406a3887839590300e7f0de09f793116.json +1 -0
- package/graphify-out/cache/stat-index.json +1 -0
- package/graphify-out/graph.html +320 -0
- package/graphify-out/graph.json +6777 -0
- package/graphify-out/manifest.json +192 -0
- package/lib/config.ts +24 -0
- package/lib/npm-stats-calc.mjs +50 -0
- package/lib/npm-stats-live.ts +547 -0
- package/lib/npm-stats-shared.ts +51 -0
- package/next.config.ts +7 -0
- package/package.json +44 -0
- package/postcss.config.mjs +7 -0
- package/public/favicon.svg +6 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/window.svg +1 -0
- package/public/x12i-stats.json +47 -0
- package/scripts/build-verified.sh +27 -0
- package/scripts/install-ci.sh +189 -0
- package/scripts/sites-env.sh +52 -0
- package/scripts/update-stats.mjs +290 -0
- package/scripts/validate-artifact.sh +37 -0
- package/test-npm.mjs +13 -0
- package/tests/npm-stats-calc.test.mjs +51 -0
- package/tests/rendered-html.test.mjs +33 -0
- package/tsconfig.json +34 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.ts +76 -0
- package/worker/index.ts +243 -0
- package/wrangler.jsonc +34 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: graphify knowledge graph context
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
This project has a graphify knowledge graph at graphify-out/.
|
|
7
|
+
|
|
8
|
+
**MANDATORY: Before using Read, Grep, Glob, or Bash to explore the codebase, you MUST run graphify first:**
|
|
9
|
+
- `graphify query "<question>"` — scoped subgraph for any codebase or architecture question
|
|
10
|
+
- `graphify path "<A>" "<B>"` — dependency path between two symbols
|
|
11
|
+
- `graphify explain "<concept>"` — all nodes related to a concept
|
|
12
|
+
|
|
13
|
+
This applies to YOU and to every subagent you spawn. Include this rule explicitly in every subagent prompt that involves code exploration. Do not skip graphify because files are "already known" or because you are executing a plan — the graph surfaces cross-file dependencies and INFERRED edges that grep and Read cannot find.
|
|
14
|
+
|
|
15
|
+
Only use Read/Grep/Glob directly when:
|
|
16
|
+
1. graphify has already oriented you and you need to modify or debug specific lines
|
|
17
|
+
2. `graphify-out/graph.json` does not exist yet
|
|
18
|
+
|
|
19
|
+
- If `graphify-out/wiki/index.md` exists, navigate it instead of reading raw files
|
|
20
|
+
- Read `graphify-out/GRAPH_REPORT.md` only for broad architecture review when query/path/explain do not surface enough context
|
|
21
|
+
- After modifying code files, run `graphify update .` to keep the graph current (AST-only, no API cost)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* cyrillic-ext */
|
|
2
|
+
@font-face {
|
|
3
|
+
font-family: 'Geist';
|
|
4
|
+
font-style: normal;
|
|
5
|
+
font-weight: 100 900;
|
|
6
|
+
font-display: swap;
|
|
7
|
+
src: url(/Users/ami/Documents/prometheus/x12i/x12i-stat/.vinext/fonts/geist-8ac0455e797f/geist-ff2310f5.woff2) format('woff2');
|
|
8
|
+
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
9
|
+
}
|
|
10
|
+
/* cyrillic */
|
|
11
|
+
@font-face {
|
|
12
|
+
font-family: 'Geist';
|
|
13
|
+
font-style: normal;
|
|
14
|
+
font-weight: 100 900;
|
|
15
|
+
font-display: swap;
|
|
16
|
+
src: url(/Users/ami/Documents/prometheus/x12i/x12i-stat/.vinext/fonts/geist-8ac0455e797f/geist-875ccdd4.woff2) format('woff2');
|
|
17
|
+
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
18
|
+
}
|
|
19
|
+
/* vietnamese */
|
|
20
|
+
@font-face {
|
|
21
|
+
font-family: 'Geist';
|
|
22
|
+
font-style: normal;
|
|
23
|
+
font-weight: 100 900;
|
|
24
|
+
font-display: swap;
|
|
25
|
+
src: url(/Users/ami/Documents/prometheus/x12i/x12i-stat/.vinext/fonts/geist-8ac0455e797f/geist-52306abf.woff2) format('woff2');
|
|
26
|
+
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
|
27
|
+
}
|
|
28
|
+
/* latin-ext */
|
|
29
|
+
@font-face {
|
|
30
|
+
font-family: 'Geist';
|
|
31
|
+
font-style: normal;
|
|
32
|
+
font-weight: 100 900;
|
|
33
|
+
font-display: swap;
|
|
34
|
+
src: url(/Users/ami/Documents/prometheus/x12i/x12i-stat/.vinext/fonts/geist-8ac0455e797f/geist-001175b1.woff2) format('woff2');
|
|
35
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
36
|
+
}
|
|
37
|
+
/* latin */
|
|
38
|
+
@font-face {
|
|
39
|
+
font-family: 'Geist';
|
|
40
|
+
font-style: normal;
|
|
41
|
+
font-weight: 100 900;
|
|
42
|
+
font-display: swap;
|
|
43
|
+
src: url(/Users/ami/Documents/prometheus/x12i/x12i-stat/.vinext/fonts/geist-8ac0455e797f/geist-98bbbccb.woff2) format('woff2');
|
|
44
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
45
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* cyrillic-ext */
|
|
2
|
+
@font-face {
|
|
3
|
+
font-family: 'Geist Mono';
|
|
4
|
+
font-style: normal;
|
|
5
|
+
font-weight: 100 900;
|
|
6
|
+
font-display: swap;
|
|
7
|
+
src: url(/Users/ami/Documents/prometheus/x12i/x12i-stat/.vinext/fonts/geist-mono-00e989178794/geist-mono-f6b33328.woff2) format('woff2');
|
|
8
|
+
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
9
|
+
}
|
|
10
|
+
/* cyrillic */
|
|
11
|
+
@font-face {
|
|
12
|
+
font-family: 'Geist Mono';
|
|
13
|
+
font-style: normal;
|
|
14
|
+
font-weight: 100 900;
|
|
15
|
+
font-display: swap;
|
|
16
|
+
src: url(/Users/ami/Documents/prometheus/x12i/x12i-stat/.vinext/fonts/geist-mono-00e989178794/geist-mono-44e03052.woff2) format('woff2');
|
|
17
|
+
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
18
|
+
}
|
|
19
|
+
/* symbols2 */
|
|
20
|
+
@font-face {
|
|
21
|
+
font-family: 'Geist Mono';
|
|
22
|
+
font-style: normal;
|
|
23
|
+
font-weight: 100 900;
|
|
24
|
+
font-display: swap;
|
|
25
|
+
src: url(/Users/ami/Documents/prometheus/x12i/x12i-stat/.vinext/fonts/geist-mono-00e989178794/geist-mono-0638449e.woff2) format('woff2');
|
|
26
|
+
unicode-range: U+2000-2001, U+2004-2008, U+200A, U+23B8-23BD, U+2500-259F;
|
|
27
|
+
}
|
|
28
|
+
/* vietnamese */
|
|
29
|
+
@font-face {
|
|
30
|
+
font-family: 'Geist Mono';
|
|
31
|
+
font-style: normal;
|
|
32
|
+
font-weight: 100 900;
|
|
33
|
+
font-display: swap;
|
|
34
|
+
src: url(/Users/ami/Documents/prometheus/x12i/x12i-stat/.vinext/fonts/geist-mono-00e989178794/geist-mono-971fb274.woff2) format('woff2');
|
|
35
|
+
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
|
36
|
+
}
|
|
37
|
+
/* latin-ext */
|
|
38
|
+
@font-face {
|
|
39
|
+
font-family: 'Geist Mono';
|
|
40
|
+
font-style: normal;
|
|
41
|
+
font-weight: 100 900;
|
|
42
|
+
font-display: swap;
|
|
43
|
+
src: url(/Users/ami/Documents/prometheus/x12i/x12i-stat/.vinext/fonts/geist-mono-00e989178794/geist-mono-44745446.woff2) format('woff2');
|
|
44
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
45
|
+
}
|
|
46
|
+
/* latin */
|
|
47
|
+
@font-face {
|
|
48
|
+
font-family: 'Geist Mono';
|
|
49
|
+
font-style: normal;
|
|
50
|
+
font-weight: 100 900;
|
|
51
|
+
font-display: swap;
|
|
52
|
+
src: url(/Users/ami/Documents/prometheus/x12i/x12i-stat/.vinext/fonts/geist-mono-00e989178794/geist-mono-013b2f2f.woff2) format('woff2');
|
|
53
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
54
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @x12i Total Downloads
|
|
2
|
+
|
|
3
|
+
A React dashboard that discovers every public package in the `@x12i` npm scope and calculates cumulative downloads since April 1, 2026.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Discovers public `@x12i/*` packages anonymously through npm's public registry.
|
|
8
|
+
- Filters npm's fuzzy search results to exact `@x12i/` package names.
|
|
9
|
+
- Removes duplicates before calculating downloads.
|
|
10
|
+
- Serves a generated verified snapshot immediately, so visitors make no npm API calls.
|
|
11
|
+
- Includes a controlled update command with retry/backoff to avoid anonymous npm rate-limit bursts.
|
|
12
|
+
- Aggregates exact daily package ranges into cumulative 14-day chart points.
|
|
13
|
+
- Verifies the chart total against the independently accumulated package total.
|
|
14
|
+
- Replaces the published snapshot only after all package ranges load and the integrity check passes.
|
|
15
|
+
- Omits all horizontal-axis labels as requested.
|
|
16
|
+
|
|
17
|
+
## Run locally
|
|
18
|
+
|
|
19
|
+
Requirements: Node.js 22.13 or newer.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install
|
|
23
|
+
npm run dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Open the local URL printed by the development server.
|
|
27
|
+
|
|
28
|
+
## Production build
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm run build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Refresh the statistics
|
|
35
|
+
|
|
36
|
+
Run this before publishing a new snapshot or from a scheduled workflow:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm run update:stats
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The updater discovers all current public packages, downloads every daily range, verifies the aggregate, and atomically replaces `public/x12i-stats.json` only after success. Daily package-range work is resumable through an ignored local cache, so an interrupted or throttled run continues instead of starting over.
|
|
43
|
+
|
|
44
|
+
## Main source files
|
|
45
|
+
|
|
46
|
+
- `app/page.tsx` — page entry point
|
|
47
|
+
- `components/downloads-dashboard.tsx` — verified snapshot display and empty-state UI
|
|
48
|
+
- `components/cumulative-chart.tsx` — responsive accessible SVG chart
|
|
49
|
+
- `public/x12i-stats.json` — generated verified snapshot rendered by React
|
|
50
|
+
- `scripts/update-stats.mjs` — anonymous npm discovery and snapshot calculation
|
|
51
|
+
- `lib/npm-stats-shared.ts` — shared result types and runtime validation
|
|
52
|
+
- `app/globals.css` — responsive visual styling
|
|
53
|
+
|
|
54
|
+
No npm login, token, database or environment variables are required.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { fetchLiveStats } from "@/lib/npm-stats-live";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Local / non-Worker fallback. In production the Worker handles this path
|
|
6
|
+
* and persists results to STATS_KV asynchronously (see worker/index.ts).
|
|
7
|
+
*/
|
|
8
|
+
export async function GET() {
|
|
9
|
+
try {
|
|
10
|
+
const stats = await fetchLiveStats();
|
|
11
|
+
return NextResponse.json(
|
|
12
|
+
{ success: true, accepted: false, stats },
|
|
13
|
+
{ headers: { "Cache-Control": "no-store" } },
|
|
14
|
+
);
|
|
15
|
+
} catch (err: unknown) {
|
|
16
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17
|
+
return NextResponse.json({ success: false, error: message }, { status: 500 });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function POST() {
|
|
22
|
+
return GET();
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import statsJson from "@/public/x12i-stats.json";
|
|
3
|
+
import { isNpmStatsResult } from "@/lib/npm-stats-shared";
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
if (isNpmStatsResult(statsJson)) {
|
|
7
|
+
return NextResponse.json(statsJson, {
|
|
8
|
+
headers: {
|
|
9
|
+
"Cache-Control": "public, max-age=3600, s-maxage=86400, stale-while-revalidate=604800",
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return NextResponse.json({ error: "Invalid stats" }, { status: 500 });
|
|
14
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { headers } from "next/headers";
|
|
2
|
+
import { redirect } from "next/navigation";
|
|
3
|
+
|
|
4
|
+
export type ChatGPTUser = {
|
|
5
|
+
displayName: string;
|
|
6
|
+
email: string;
|
|
7
|
+
fullName: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const USER_EMAIL_HEADER = "oai-authenticated-user-email";
|
|
11
|
+
const USER_FULL_NAME_HEADER = "oai-authenticated-user-full-name";
|
|
12
|
+
const USER_FULL_NAME_ENCODING_HEADER =
|
|
13
|
+
"oai-authenticated-user-full-name-encoding";
|
|
14
|
+
const PERCENT_ENCODED_UTF8 = "percent-encoded-utf-8";
|
|
15
|
+
const SIGN_IN_PATH = "/signin-with-chatgpt";
|
|
16
|
+
const SIGN_OUT_PATH = "/signout-with-chatgpt";
|
|
17
|
+
const CALLBACK_PATH = "/callback";
|
|
18
|
+
|
|
19
|
+
export async function getChatGPTUser(): Promise<ChatGPTUser | null> {
|
|
20
|
+
const requestHeaders = await headers();
|
|
21
|
+
const email = requestHeaders.get(USER_EMAIL_HEADER);
|
|
22
|
+
if (!email) return null;
|
|
23
|
+
|
|
24
|
+
const encodedFullName = requestHeaders.get(USER_FULL_NAME_HEADER);
|
|
25
|
+
const fullName =
|
|
26
|
+
encodedFullName &&
|
|
27
|
+
requestHeaders.get(USER_FULL_NAME_ENCODING_HEADER) === PERCENT_ENCODED_UTF8
|
|
28
|
+
? safeDecodeURIComponent(encodedFullName)
|
|
29
|
+
: null;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
displayName: fullName ?? email,
|
|
33
|
+
email,
|
|
34
|
+
fullName,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function requireChatGPTUser(
|
|
39
|
+
returnTo: string,
|
|
40
|
+
): Promise<ChatGPTUser> {
|
|
41
|
+
const user = await getChatGPTUser();
|
|
42
|
+
if (user) return user;
|
|
43
|
+
|
|
44
|
+
redirect(chatGPTSignInPath(returnTo));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function chatGPTSignInPath(returnTo: string): string {
|
|
48
|
+
const safeReturnTo = safeRelativeReturnPath(returnTo);
|
|
49
|
+
return `${SIGN_IN_PATH}?return_to=${encodeURIComponent(safeReturnTo)}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function chatGPTSignOutPath(returnTo = "/"): string {
|
|
53
|
+
const safeReturnTo = safeRelativeReturnPath(returnTo);
|
|
54
|
+
return `${SIGN_OUT_PATH}?return_to=${encodeURIComponent(safeReturnTo)}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function safeRelativeReturnPath(value: string): string {
|
|
58
|
+
if (!value.startsWith("/") || value.startsWith("//")) return "/";
|
|
59
|
+
|
|
60
|
+
let url: URL;
|
|
61
|
+
try {
|
|
62
|
+
url = new URL(value, "https://app.local");
|
|
63
|
+
} catch {
|
|
64
|
+
return "/";
|
|
65
|
+
}
|
|
66
|
+
if (url.origin !== "https://app.local") return "/";
|
|
67
|
+
if (isReservedAuthPath(url.pathname)) return "/";
|
|
68
|
+
|
|
69
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isReservedAuthPath(pathname: string): boolean {
|
|
73
|
+
return (
|
|
74
|
+
pathname === SIGN_IN_PATH ||
|
|
75
|
+
pathname === SIGN_OUT_PATH ||
|
|
76
|
+
pathname === CALLBACK_PATH
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function safeDecodeURIComponent(value: string): string | null {
|
|
81
|
+
try {
|
|
82
|
+
return decodeURIComponent(value);
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
package/app/globals.css
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--background: #0f172a;
|
|
5
|
+
--surface: #111c30;
|
|
6
|
+
--surface-border: #253149;
|
|
7
|
+
--foreground: #f8fafc;
|
|
8
|
+
--muted: #94a3b8;
|
|
9
|
+
--muted-dark: #64748b;
|
|
10
|
+
--accent: #38bdf8;
|
|
11
|
+
--accent-soft: rgba(56, 189, 248, 0.12);
|
|
12
|
+
--grid: #334155;
|
|
13
|
+
--danger: #f87171;
|
|
14
|
+
--anim-duration: 2s;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
* {
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
html {
|
|
22
|
+
min-width: 320px;
|
|
23
|
+
background: var(--background);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body {
|
|
27
|
+
min-height: 100vh;
|
|
28
|
+
margin: 0;
|
|
29
|
+
background:
|
|
30
|
+
radial-gradient(circle at 50% 0%, rgba(56, 189, 248, 0.08), transparent 32rem),
|
|
31
|
+
var(--background);
|
|
32
|
+
color: var(--foreground);
|
|
33
|
+
font-family: var(--font-geist-sans), Inter, ui-sans-serif, system-ui, sans-serif;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
button,
|
|
37
|
+
input,
|
|
38
|
+
textarea,
|
|
39
|
+
select {
|
|
40
|
+
font: inherit;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.dashboard-shell {
|
|
44
|
+
display: grid;
|
|
45
|
+
min-height: 100vh;
|
|
46
|
+
place-items: center;
|
|
47
|
+
padding: 2rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.dashboard-card {
|
|
51
|
+
width: min(100%, 44rem);
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
border: 1px solid var(--surface-border);
|
|
54
|
+
border-radius: 1.75rem;
|
|
55
|
+
background: rgba(15, 23, 42, 0.82);
|
|
56
|
+
box-shadow: 0 32px 90px rgba(2, 6, 23, 0.48);
|
|
57
|
+
backdrop-filter: blur(16px);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.dashboard-content {
|
|
61
|
+
padding: 2.25rem 2.5rem 2.5rem;
|
|
62
|
+
text-align: center;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.eyebrow {
|
|
66
|
+
margin: 0.5rem 0 0;
|
|
67
|
+
color: var(--muted);
|
|
68
|
+
font-size: 0.72rem;
|
|
69
|
+
font-weight: 700;
|
|
70
|
+
letter-spacing: 0.18em;
|
|
71
|
+
text-transform: uppercase;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.title {
|
|
75
|
+
margin: 0;
|
|
76
|
+
font-size: clamp(2rem, 7vw, 2.75rem);
|
|
77
|
+
font-weight: 850;
|
|
78
|
+
letter-spacing: -0.045em;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.metric-block {
|
|
82
|
+
margin: 2.5rem 0 2rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.total {
|
|
86
|
+
min-height: 1.05em;
|
|
87
|
+
color: var(--accent);
|
|
88
|
+
font-size: clamp(3.5rem, 12vw, 5.25rem);
|
|
89
|
+
font-variant-numeric: tabular-nums;
|
|
90
|
+
font-weight: 900;
|
|
91
|
+
letter-spacing: -0.065em;
|
|
92
|
+
line-height: 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.total-pending {
|
|
96
|
+
color: var(--muted);
|
|
97
|
+
font-size: clamp(2.25rem, 8vw, 3.5rem);
|
|
98
|
+
letter-spacing: -0.045em;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.metric-description {
|
|
102
|
+
margin: 0.65rem 0 0;
|
|
103
|
+
color: var(--muted-dark);
|
|
104
|
+
font-size: 0.875rem;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.status-row {
|
|
108
|
+
display: flex;
|
|
109
|
+
min-height: 1.5rem;
|
|
110
|
+
align-items: center;
|
|
111
|
+
justify-content: center;
|
|
112
|
+
gap: 0.5rem;
|
|
113
|
+
margin-top: 0.4rem;
|
|
114
|
+
color: var(--muted-dark);
|
|
115
|
+
font-size: 0.75rem;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.status-row.error {
|
|
119
|
+
color: var(--danger);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.spinner {
|
|
123
|
+
width: 0.75rem;
|
|
124
|
+
height: 0.75rem;
|
|
125
|
+
flex: none;
|
|
126
|
+
border: 2px solid rgba(56, 189, 248, 0.2);
|
|
127
|
+
border-top-color: var(--accent);
|
|
128
|
+
border-radius: 999px;
|
|
129
|
+
animation: spin 0.85s linear infinite;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.retry-button {
|
|
133
|
+
margin-left: 0.25rem;
|
|
134
|
+
border: 0;
|
|
135
|
+
border-bottom: 1px solid currentColor;
|
|
136
|
+
padding: 0;
|
|
137
|
+
background: transparent;
|
|
138
|
+
color: inherit;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
font-weight: 700;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.retry-button:focus-visible {
|
|
144
|
+
outline: 2px solid var(--accent);
|
|
145
|
+
outline-offset: 4px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* ── Chart ─────────────────────────────────────────────── */
|
|
149
|
+
|
|
150
|
+
.chart-frame {
|
|
151
|
+
position: relative;
|
|
152
|
+
width: 100%;
|
|
153
|
+
height: 16rem;
|
|
154
|
+
margin-top: 1rem;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.chart-frame svg {
|
|
158
|
+
display: block;
|
|
159
|
+
width: 100%;
|
|
160
|
+
height: 100%;
|
|
161
|
+
overflow: visible;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.chart-empty {
|
|
165
|
+
display: grid;
|
|
166
|
+
height: 100%;
|
|
167
|
+
place-items: center;
|
|
168
|
+
color: var(--muted-dark);
|
|
169
|
+
font-size: 0.8rem;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.chart-grid {
|
|
173
|
+
stroke: var(--grid);
|
|
174
|
+
stroke-width: 1;
|
|
175
|
+
vector-effect: non-scaling-stroke;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.chart-y-label {
|
|
179
|
+
fill: var(--muted);
|
|
180
|
+
font-size: 12px;
|
|
181
|
+
font-variant-numeric: tabular-nums;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.chart-line {
|
|
185
|
+
fill: none;
|
|
186
|
+
stroke: var(--accent);
|
|
187
|
+
stroke-linecap: round;
|
|
188
|
+
stroke-linejoin: round;
|
|
189
|
+
stroke-width: 3;
|
|
190
|
+
vector-effect: non-scaling-stroke;
|
|
191
|
+
/* Draw animation — uses a very large dasharray; the offset animates to 0 */
|
|
192
|
+
stroke-dasharray: 2000;
|
|
193
|
+
stroke-dashoffset: 2000;
|
|
194
|
+
animation: draw-line var(--anim-duration) ease-out forwards;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.chart-area {
|
|
198
|
+
fill: url(#area-gradient);
|
|
199
|
+
opacity: 0;
|
|
200
|
+
animation: fade-area var(--anim-duration) ease-out forwards;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* ── npm link ─────────────────────────────────────────── */
|
|
204
|
+
|
|
205
|
+
.npm-link {
|
|
206
|
+
display: inline-flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
gap: 0.4rem;
|
|
209
|
+
margin-top: 1.5rem;
|
|
210
|
+
padding: 0.55rem 1.15rem;
|
|
211
|
+
border: 1px solid var(--surface-border);
|
|
212
|
+
border-radius: 0.65rem;
|
|
213
|
+
background: var(--accent-soft);
|
|
214
|
+
color: var(--accent);
|
|
215
|
+
font-size: 0.82rem;
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
text-decoration: none;
|
|
218
|
+
transition: background 0.2s, border-color 0.2s, transform 0.15s;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.npm-link:hover {
|
|
222
|
+
border-color: var(--accent);
|
|
223
|
+
background: rgba(56, 189, 248, 0.18);
|
|
224
|
+
transform: translateY(-1px);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.npm-link:active {
|
|
228
|
+
transform: translateY(0);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.npm-link:focus-visible {
|
|
232
|
+
outline: 2px solid var(--accent);
|
|
233
|
+
outline-offset: 3px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.npm-link-icon {
|
|
237
|
+
width: 0.95rem;
|
|
238
|
+
height: 0.95rem;
|
|
239
|
+
flex: none;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* ── Keyframes ────────────────────────────────────────── */
|
|
243
|
+
|
|
244
|
+
@keyframes spin {
|
|
245
|
+
to { transform: rotate(360deg); }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@keyframes draw-line {
|
|
249
|
+
to { stroke-dashoffset: 0; }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@keyframes fade-area {
|
|
253
|
+
0% { opacity: 0; }
|
|
254
|
+
40% { opacity: 0; }
|
|
255
|
+
100% { opacity: 1; }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* ── Responsive ───────────────────────────────────────── */
|
|
259
|
+
|
|
260
|
+
@media (max-width: 640px) {
|
|
261
|
+
.dashboard-shell {
|
|
262
|
+
align-items: stretch;
|
|
263
|
+
padding: 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.dashboard-card {
|
|
267
|
+
min-height: 100vh;
|
|
268
|
+
border: 0;
|
|
269
|
+
border-radius: 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.dashboard-content {
|
|
273
|
+
padding: 3rem 1.25rem 2rem;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.metric-block {
|
|
277
|
+
margin-top: 3rem;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.chart-frame {
|
|
281
|
+
height: 14rem;
|
|
282
|
+
margin-top: 2rem;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@media (prefers-reduced-motion: reduce) {
|
|
287
|
+
.spinner {
|
|
288
|
+
animation-duration: 2s;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.chart-line {
|
|
292
|
+
stroke-dasharray: none;
|
|
293
|
+
stroke-dashoffset: 0;
|
|
294
|
+
animation: none;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.chart-area {
|
|
298
|
+
opacity: 1;
|
|
299
|
+
animation: none;
|
|
300
|
+
}
|
|
301
|
+
}
|
package/app/layout.tsx
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Geist, Geist_Mono } from "next/font/google";
|
|
3
|
+
import { BRAND_NAME, PACKAGE_SCOPE } from "@/lib/config";
|
|
4
|
+
import "./globals.css";
|
|
5
|
+
|
|
6
|
+
const geistSans = Geist({
|
|
7
|
+
variable: "--font-geist-sans",
|
|
8
|
+
subsets: ["latin"],
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const geistMono = Geist_Mono({
|
|
12
|
+
variable: "--font-geist-mono",
|
|
13
|
+
subsets: ["latin"],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const scopeDisplay = PACKAGE_SCOPE.replace(/\/$/, "");
|
|
17
|
+
|
|
18
|
+
export const metadata: Metadata = {
|
|
19
|
+
title: `${scopeDisplay} Total Downloads`,
|
|
20
|
+
description: `Live cumulative downloads across every public ${scopeDisplay} npm package.`,
|
|
21
|
+
other: {
|
|
22
|
+
"codex-preview": "development",
|
|
23
|
+
},
|
|
24
|
+
icons: {
|
|
25
|
+
icon: "/favicon.svg",
|
|
26
|
+
shortcut: "/favicon.svg",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default function RootLayout({
|
|
31
|
+
children,
|
|
32
|
+
}: Readonly<{
|
|
33
|
+
children: React.ReactNode;
|
|
34
|
+
}>) {
|
|
35
|
+
return (
|
|
36
|
+
<html lang="en">
|
|
37
|
+
<body
|
|
38
|
+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
39
|
+
>
|
|
40
|
+
{children}
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
43
|
+
);
|
|
44
|
+
}
|