Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Git City 3d Github Visualization

ASecurity

Build and extend Git City — a 3D pixel art city where GitHub profiles become interactive buildings using Next.js, Three.js, and Supabase.

81 stars
0 votes
0 copies
0 views
Added 9/19/2026
developmenttypescriptgoshellbashsqlreactnextjsgitapidatabase

Works with

cliapi

Security Analysis

A96/100
mediumInstalls packages at runtime which could introduce malicious dependencies

Scanned 9/19/2026

Install to Claude Code

$npx -y skills add reason-machines/trending-skills --skill git-city-3d-github-visualization --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Git City 3d Github Visualization?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Git City 3d Github Visualization
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/reason-machines-git-city-3d-github-visualization/badge)](https://www.skillsdirectory.com/skills/reason-machines-git-city-3d-github-visualization)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: git-city-3d-github-visualization
description: Build and extend Git City — a 3D pixel art city where GitHub profiles become interactive buildings using Next.js, Three.js, and Supabase.
triggers:
  - "add a feature to git city"
  - "work on the git city project"
  - "modify the 3d building renderer"
  - "add achievements to git city"
  - "customize git city buildings"
  - "set up git city locally"
  - "add a new building decoration"
  - "extend the github visualization"
---

# Git City — 3D GitHub Profile Visualization

> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.

Git City transforms GitHub profiles into a 3D pixel art city. Each user becomes a unique building: height from contributions, width from repos, window brightness from stars. Built with Next.js 16 (App Router), React Three Fiber, and Supabase.

## Quick Setup

```bash
git clone https://github.com/srizzon/git-city.git
cd git-city
npm install

# Copy env template
cp .env.example .env.local   # Linux/macOS
copy .env.example .env.local  # Windows CMD
Copy-Item .env.example .env.local  # PowerShell

npm run dev
# → http://localhost:3001
```

## Environment Variables

Fill in `.env.local` after copying:

```bash
# Supabase — Project Settings → API
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

# GitHub — Settings → Developer settings → Personal access tokens
GITHUB_TOKEN=github_pat_your_token_here

# Optional: comma-separated GitHub logins for /admin/ads access
ADMIN_GITHUB_LOGINS=your_github_login
```

**Finding Supabase values:** Dashboard → Project Settings → API  
**Finding GitHub token:** github.com → Settings → Developer settings → Personal access tokens (fine-grained recommended)

## Project Structure

```
git-city/
├── app/                    # Next.js App Router pages
│   ├── page.tsx            # Main city view
│   ├── [username]/         # User profile pages
│   ├── compare/            # Side-by-side compare mode
│   └── admin/              # Admin panel
├── components/
│   ├── city/               # 3D city scene components
│   │   ├── Building.tsx    # Individual building mesh
│   │   ├── CityScene.tsx   # Main R3F canvas/scene
│   │   └── LODManager.tsx  # Level-of-detail system
│   ├── ui/                 # 2D overlay UI components
│   └── profile/            # Profile page components
├── lib/
│   ├── github.ts           # GitHub API helpers
│   ├── supabase/           # Supabase client + server utils
│   ├── buildings.ts        # Building metric calculations
│   └── achievements.ts     # Achievement logic
├── hooks/                  # Custom React hooks
├── types/                  # TypeScript type definitions
└── public/                 # Static assets
```

## Core Concepts

### Building Metrics Mapping

Buildings are generated from GitHub profile data:

```typescript
// lib/buildings.ts pattern
interface BuildingMetrics {
  height: number;      // Based on total contributions
  width: number;       // Based on public repo count
  windowBrightness: number;  // Based on total stars received
  windowPattern: number[];   // Based on recent activity pattern
}

function calculateBuildingMetrics(profile: GitHubProfile): BuildingMetrics {
  const height = Math.log10(profile.totalContributions + 1) * 10;
  const width = Math.min(Math.ceil(profile.publicRepos / 10), 8);
  const windowBrightness = Math.min(profile.totalStars / 1000, 1);
  
  return { height, width, windowBrightness, windowPattern: [] };
}
```

### 3D Building Component (React Three Fiber)

```tsx
// components/city/Building.tsx pattern
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';

interface BuildingProps {
  position: [number, number, number];
  metrics: BuildingMetrics;
  username: string;
  isSelected?: boolean;
  onClick?: () => void;
}

export function Building({ position, metrics, username, isSelected, onClick }: BuildingProps) {
  const meshRef = useRef<THREE.Mesh>(null);
  
  // Animate selected building
  useFrame((state) => {
    if (meshRef.current && isSelected) {
      meshRef.current.rotation.y = Math.sin(state.clock.elapsedTime) * 0.05;
    }
  });

  return (
    <group position={position} onClick={onClick}>
      {/* Main building body */}
      <mesh ref={meshRef}>
        <boxGeometry args={[metrics.width, metrics.height, metrics.width]} />
        <meshStandardMaterial color="#1a1a2e" />
      </mesh>
      
      {/* Windows as instanced meshes for performance */}
      <WindowInstances metrics={metrics} />
    </group>
  );
}
```

### Instanced Meshes for Performance

Git City uses instanced rendering for windows — critical for a city with many buildings:

```tsx
// components/city/WindowInstances.tsx pattern
import { useRef, useEffect } from 'react';
import { InstancedMesh, Matrix4, Color } from 'three';

export function WindowInstances({ metrics }: { metrics: BuildingMetrics }) {
  const meshRef = useRef<InstancedMesh>(null);
  
  useEffect(() => {
    if (!meshRef.current) return;
    
    const matrix = new Matrix4();
    const color = new Color();
    let index = 0;
    
    // Calculate window positions based on building dimensions
    for (let floor = 0; floor < metrics.height; floor++) {
      for (let col = 0; col < metrics.width; col++) {
        const isLit = metrics.windowPattern[index] > 0.5;
        
        matrix.setPosition(col * 1.1 - metrics.width / 2, floor * 1.2, 0.51);
        meshRef.current.setMatrixAt(index, matrix);
        meshRef.current.setColorAt(
          index,
          color.set(isLit ? '#FFD700' : '#1a1a2e')
        );
        index++;
      }
    }
    
    meshRef.current.instanceMatrix.needsUpdate = true;
    if (meshRef.current.instanceColor) {
      meshRef.current.instanceColor.needsUpdate = true;
    }
  }, [metrics]);

  const windowCount = Math.floor(metrics.height) * metrics.width;
  
  return (
    <instancedMesh ref={meshRef} args={[undefined, undefined, windowCount]}>
      <planeGeometry args={[0.4, 0.5]} />
      <meshBasicMaterial />
    </instancedMesh>
  );
}
```

### GitHub API Integration

```typescript
// lib/github.ts pattern
import { Octokit } from '@octokit/rest';

const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });

export async function fetchGitHubProfile(username: string) {
  const [userResponse, reposResponse] = await Promise.all([
    octokit.users.getByUsername({ username }),
    octokit.repos.listForUser({ username, per_page: 100, sort: 'updated' }),
  ]);

  const totalStars = reposResponse.data.reduce(
    (sum, repo) => sum + (repo.stargazers_count ?? 0),
    0
  );

  return {
    username: userResponse.data.login,
    avatarUrl: userResponse.data.avatar_url,
    publicRepos: userResponse.data.public_repos,
    followers: userResponse.data.followers,
    totalStars,
  };
}

export async function fetchContributionData(username: string): Promise<number> {
  // Use GitHub GraphQL for contribution calendar data
  const query = `
    query($username: String!) {
      user(login: $username) {
        contributionsCollection {
          contributionCalendar {
            totalContributions
            weeks {
              contributionDays {
                contributionCount
                date
              }
            }
          }
        }
      }
    }
  `;
  
  const response = await fetch('https://api.github.com/graphql', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ query, variables: { username } }),
  });
  
  const data = await response.json();
  return data.data.user.contributionsCollection.contributionCalendar.totalContributions;
}
```

### Supabase Integration

```typescript
// lib/supabase/server.ts pattern — server-side client
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';

export function createClient() {
  const cookieStore = cookies();
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll: () => cookieStore.getAll(),
        setAll: (cookiesToSet) => {
          cookiesToSet.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options)
          );
        },
      },
    }
  );
}

// lib/supabase/client.ts — browser client
import { createBrowserClient } from '@supabase/ssr';

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}
```

### Achievement System

```typescript
// lib/achievements.ts pattern
export interface Achievement {
  id: string;
  name: string;
  description: string;
  icon: string;
  condition: (stats: UserStats) => boolean;
}

export const ACHIEVEMENTS: Achievement[] = [
  {
    id: 'first-commit',
    name: 'First Commit',
    description: 'Made your first contribution',
    icon: '🌱',
    condition: (stats) => stats.totalContributions >= 1,
  },
  {
    id: 'thousand-commits',
    name: 'Commit Crusher',
    description: '1,000+ total contributions',
    icon: '⚡',
    condition: (stats) => stats.totalContributions >= 1000,
  },
  {
    id: 'star-collector',
    name: 'Star Collector',
    description: 'Earned 100+ stars across repos',
    icon: '⭐',
    condition: (stats) => stats.totalStars >= 100,
  },
  {
    id: 'open-sourcer',
    name: 'Open Sourcer',
    description: '20+ public repositories',
    icon: '📦',
    condition: (stats) => stats.publicRepos >= 20,
  },
];

export function calculateAchievements(stats: UserStats): Achievement[] {
  return ACHIEVEMENTS.filter((achievement) => achievement.condition(stats));
}
```

### Adding a New Building Decoration

```typescript
// types/decorations.ts
export type DecorationSlot = 'crown' | 'aura' | 'roof' | 'face';

export interface Decoration {
  id: string;
  slot: DecorationSlot;
  name: string;
  price: number;
  component: React.ComponentType<DecorationProps>;
}

// components/city/decorations/Crown.tsx
export function CrownDecoration({ position, buildingWidth }: DecorationProps) {
  return (
    <group position={[position[0], position[1], position[2]]}>
      <mesh>
        <coneGeometry args={[buildingWidth / 3, 2, 4]} />
        <meshStandardMaterial color="#FFD700" metalness={0.8} roughness={0.2} />
      </mesh>
    </group>
  );
}

// Register in decoration registry
export const DECORATIONS: Decoration[] = [
  {
    id: 'golden-crown',
    slot: 'crown',
    name: 'Golden Crown',
    price: 500,
    component: CrownDecoration,
  },
];
```

### Camera / Flight Controls

```tsx
// components/city/CameraController.tsx pattern
import { useThree, useFrame } from '@react-three/fiber';
import { useRef } from 'react';
import * as THREE from 'three';

export function CameraController() {
  const { camera } = useThree();
  const targetRef = useRef(new THREE.Vector3());
  const velocityRef = useRef(new THREE.Vector3());

  useFrame((_, delta) => {
    // Smooth lerp camera toward target
    camera.position.lerp(targetRef.current, delta * 2);
  });

  // Expose flyTo function via context or ref
  const flyTo = (position: THREE.Vector3) => {
    targetRef.current.copy(position).add(new THREE.Vector3(0, 10, 20));
  };

  return null;
}
```

### Server Actions (Next.js App Router)

```typescript
// app/actions/kudos.ts
'use server';

import { createClient } from '@/lib/supabase/server';
import { revalidatePath } from 'next/cache';

export async function sendKudos(toUsername: string) {
  const supabase = createClient();
  const { data: { user } } = await supabase.auth.getUser();
  
  if (!user) throw new Error('Must be logged in to send kudos');

  const { error } = await supabase.from('kudos').insert({
    from_user_id: user.id,
    to_username: toUsername,
    created_at: new Date().toISOString(),
  });

  if (error) throw error;
  
  revalidatePath(`/${toUsername}`);
}
```

### Profile Page Route

```tsx
// app/[username]/page.tsx pattern
import { fetchGitHubProfile } from '@/lib/github';
import { createClient } from '@/lib/supabase/server';
import { calculateAchievements } from '@/lib/achievements';
import { BuildingPreview } from '@/components/profile/BuildingPreview';

interface Props {
  params: { username: string };
}

export default async function ProfilePage({ params }: Props) {
  const { username } = params;
  
  const [githubProfile, supabase] = await Promise.all([
    fetchGitHubProfile(username),
    createClient(),
  ]);

  const { data: cityProfile } = await supabase
    .from('profiles')
    .select('*, decorations(*)')
    .eq('username', username)
    .single();

  const achievements = calculateAchievements({
    totalContributions: githubProfile.totalContributions,
    totalStars: githubProfile.totalStars,
    publicRepos: githubProfile.publicRepos,
  });

  return (
    <main>
      <BuildingPreview profile={githubProfile} cityProfile={cityProfile} />
      <AchievementGrid achievements={achievements} />
    </main>
  );
}

export async function generateMetadata({ params }: Props) {
  return {
    title: `${params.username} — Git City`,
    description: `View ${params.username}'s building in Git City`,
    openGraph: {
      images: [`/api/og/${params.username}`],
    },
  };
}
```

## Common Development Patterns

### LOD (Level of Detail) System

```typescript
// Simplified LOD pattern used in the city
import { useThree } from '@react-three/fiber';

export function useLOD(buildingPosition: THREE.Vector3) {
  const { camera } = useThree();
  const distance = camera.position.distanceTo(buildingPosition);
  
  if (distance < 50) return 'high';    // Full detail + animated windows
  if (distance < 150) return 'medium'; // Simplified windows
  return 'low';                        // Box only
}
```

### Fetching with SWR in City View

```tsx
import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(r => r.json());

export function useCityBuildings() {
  const { data, error, isLoading } = useSWR('/api/buildings', fetcher, {
    refreshInterval: 30000, // refresh every 30s for live activity feed
  });
  
  return { buildings: data, error, isLoading };
}
```

## Key API Routes

| Route | Purpose |
|-------|---------|
| `GET /api/buildings` | Fetch all city buildings with positions |
| `GET /api/profile/[username]` | GitHub + city profile data |
| `POST /api/kudos` | Send kudos to a user |
| `GET /api/og/[username]` | Generate OG share card image |
| `POST /api/webhook/stripe` | Stripe payment webhook |
| `GET /admin/ads` | Admin panel (requires `ADMIN_GITHUB_LOGINS`) |

## Troubleshooting

**3D scene not rendering**  
Check that `@react-three/fiber` and `three` versions are compatible. The canvas needs a height set on its container div.

**GitHub API rate limits**  
Use a fine-grained token with appropriate scopes. The app caches GitHub responses in Supabase to avoid repeated API calls.

**Supabase auth not working locally**  
Configure the GitHub OAuth provider in your Supabase project and ensure your local callback URL (`http://localhost:3001/auth/callback`) is allowlisted.

**Buildings not appearing**  
Check that Supabase Row Level Security policies allow reads on the `profiles` and `buildings` tables for anonymous users.

**Window shimmer/flicker**  
This is usually a Z-fighting issue. Add a tiny offset (`0.001`) to window mesh positions along the normal axis.

**Performance issues with many buildings**  
Ensure instanced meshes are used for windows and the LOD system is active. Avoid creating new `THREE.Material` instances inside render loops — define them outside components or use `useMemo`.

## Stack Reference

| Layer | Technology |
|-------|-----------|
| Framework | Next.js 16 (App Router, Turbopack) |
| 3D Rendering | Three.js + @react-three/fiber + drei |
| Database | Supabase (PostgreSQL + RLS) |
| Auth | Supabase GitHub OAuth |
| Payments | Stripe |
| Styling | Tailwind CSS v4 + Silkscreen font |
| Hosting | Vercel |
| License | AGPL-3.0 |

Attribution

reason-machinesreason-machines
View sourceMore from reason-machines →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

281612 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2132 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →