From 3dd969aa529eb855284cd9176c07afc93de8b67a Mon Sep 17 00:00:00 2001 From: Louis Tai <142078052+louistaii@users.noreply.github.com> Date: Mon, 10 Nov 2025 22:26:28 +0800 Subject: [PATCH 1/7] Remove unused variables and functions --- client/src/ResultsPage.tsx | 23 ----------------------- client/src/config.ts | 5 +++-- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/client/src/ResultsPage.tsx b/client/src/ResultsPage.tsx index fcd687e..7455726 100644 --- a/client/src/ResultsPage.tsx +++ b/client/src/ResultsPage.tsx @@ -408,28 +408,6 @@ function getStatsByGameMode(matches: Match[] = [], puuid: string) { return result; } -function getLatestUniquePlayers(matches: Match[] = [], puuid: string) { - const seen = new Set(); - const players: { puuid: string; summonerName: string; championName: string; championImageUrl?: string }[] = []; - - for (const match of matches || []) { - for (const p of match.info?.participants || []) { - if (p.puuid !== puuid && !seen.has(p.puuid)) { - seen.add(p.puuid); - const summonerName = (p as any).riotIdGameName || (p as any).summonerName || `Player${players.length + 1}`; - players.push({ - puuid: p.puuid, - summonerName: summonerName, - championName: p.championName, - championImageUrl: p.championImageUrl - }); - if (players.length === 10) return players; - } - } - } - return players; -} - const ResultsPage: React.FC = ({ playerData }) => { const [currentCardIndex, setCurrentCardIndex] = useState(0); const [showShareModal, setShowShareModal] = useState(false); @@ -499,7 +477,6 @@ const ResultsPage: React.FC = ({ playerData }) => { const puuid = playerData.account?.puuid || ''; const statsByMode = getStatsByGameMode(playerData.matches, puuid); - const latestPlayers = getLatestUniquePlayers(playerData.matches, puuid); // Chat functionality functions const handleSendMessage = async () => { diff --git a/client/src/config.ts b/client/src/config.ts index e8442b6..c90372e 100644 --- a/client/src/config.ts +++ b/client/src/config.ts @@ -2,7 +2,6 @@ // Automatically detects if running locally or on production const isProduction = process.env.NODE_ENV === 'production'; -const isDevelopment = process.env.NODE_ENV === 'development'; // In production (Vercel), use relative URLs (same domain) // In development, use localhost:5000 @@ -14,7 +13,9 @@ export const getApiUrl = (endpoint: string): string => { }; // Export for use in components -export default { +const config = { API_BASE_URL, getApiUrl, }; + +export default config; From 47fe9fc083ba87c5eb2a188be624a8e4e0c5c406 Mon Sep 17 00:00:00 2001 From: Louis Tai <142078052+louistaii@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:02:37 +0800 Subject: [PATCH 2/7] Update readme an fix code consistency --- README.md | 411 ++++++++++++++++++++++++++++++------- api/bedrock-test.js | 2 +- api/chat.js | 2 +- api/matchmaking.js | 2 +- client/src/ResultsPage.tsx | 6 +- 5 files changed, 338 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index e91a4d2..bf7f190 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,359 @@ -# ShapeSplitter - League of Legends Personality Analyzer +# Shape Split - League of Legends Digital Twin Platform -A League of Legends player statistics and personality analysis web application. Analyze players' personalities based on their gameplay patterns, find compatible duo partners, and chat with an AI-powered digital twin. +Shape Split creates digital twins of League of Legends summoners for coaching, interaction, gameplay improvement, and comprehensive personality insights. The platform analyzes gameplay patterns to generate psychological profiles, enabling players to better understand their playstyle, find compatible duo partners, and interact with an AI-powered version of themselves. -## 🚀 Deploy to Vercel (Recommended) +## Overview -This project is optimized for Vercel serverless deployment! +Shape Split transforms League of Legends match data into actionable personality insights by combining psychometric analysis with artificial intelligence. The platform fetches player statistics from the Riot Games API, processes gameplay patterns through a custom personality analysis engine, and generates an interactive digital twin powered by AWS Bedrock's Claude AI model. -**Quick Deploy:** -1. Push to GitHub -2. Import to Vercel -3. Add `RIOT_API_KEY` environment variable -4. Deploy! 🎉 +## Architecture -**📖 Complete Guide:** See [READY_TO_DEPLOY.md](READY_TO_DEPLOY.md) for detailed instructions. +### Project Structure -**📋 Step-by-Step:** See [DEPLOYMENT_CHECKLIST.md](DEPLOYMENT_CHECKLIST.md) - -## Project Structure - -- `client/` - React frontend application (TypeScript) -- `api/` - Serverless API functions (deployed to Vercel) - - `_lib/` - Shared modules for API functions -- `server/` - Express server (for local development only, not deployed) - -## ✨ Features - -- 🔍 **Player Search** - Search any League player by Riot ID -- 🧠 **Personality Analysis** - Analyze gameplay patterns using Big Five personality traits -- 🎭 **Archetype Matching** - Match players to 12 Jungian archetypes -- 💘 **Duo Compatibility** - Find perfect duo queue partners -- 🤖 **AI Chat** - Chat with your digital twin (powered by AWS Bedrock/Claude) -- 📊 **Champion Mastery** - View top champions and play patterns - -## Getting Started - -### Prerequisites - -- Node.js (v14 or higher) -- npm -- Riot Games API key from https://developer.riotgames.com/ - -### Installation +``` +ShapeSplitter/ +├── client/ # React TypeScript frontend +│ ├── src/ +│ │ ├── App.tsx # Main application component +│ │ ├── SearchPage.tsx # Player search interface +│ │ ├── LoadingPage.tsx # Real-time data fetching progress +│ │ ├── ResultsPage.tsx # Personality analysis dashboard +│ │ └── PlayerDataContext.tsx # Global state management +│ └── build/ # Production build output +│ +├── api/ # Vercel serverless functions +│ ├── search.js # Player data retrieval endpoint +│ ├── chat.js # Digital twin chat interface +│ ├── matchmaking.js # Duo compatibility analysis +│ ├── regions.js # Regional routing configuration +│ └── _lib/ # Shared utilities +│ ├── leagueDataFetcher.js # Riot API client +│ ├── personalityAnalyzer.js # Personality calculation engine +│ ├── prompts.js # AI system prompts +│ └── utils.js # Helper functions +│ +└── server/ # Express server (local development only) +``` -Install dependencies for all parts of the application: +### Technology Stack + +**Frontend** +- React with TypeScript for type-safe component development +- React Router for client-side navigation +- Context API for global state management +- CSS3 for responsive UI design + +**Backend** +- Node.js serverless functions hosted on Vercel +- Riot Games API for player statistics and match history +- AWS Bedrock (Claude 3.5 Sonnet) for conversational AI +- Data Dragon CDN for champion and profile icon assets + +**Data Processing** +- Custom psychometric analysis engine +- Statistical variance and aggregation calculations +- Big Five personality trait mapping +- Jungian archetype classification + +## Core Features + +### 1. Player Search and Data Retrieval + +The search endpoint (`/api/search`) fetches comprehensive player data through the Riot Games API: + +**Data Collection Process:** +1. Account lookup via Riot ID (gameName + tagLine) +2. Summoner profile retrieval (level, profile icon) +3. Ranked statistics (tier, rank, LP, win rate) +4. Champion mastery scores (top 10 champions with mastery points) +5. Match history collection (up to 100 recent matches) +6. Match detail analysis (targeting 25 valid ranked/normal games) + +**Smart Match Filtering:** +- Valid queue types: Ranked Solo/Duo (420), Ranked Flex (440), Normal Draft (400), Normal Blind (430), Normal Quickplay (490) +- Early termination when target valid games are reached +- Rate limiting (1200ms delay) to comply with Riot API restrictions +- Parallel asset loading for champion images and icons + +### 2. Personality Analysis Engine + +The personality analyzer (`personalityAnalyzer.js`) implements a sophisticated psychometric model that maps gameplay patterns to personality traits. + +#### Statistical Feature Extraction + +From match data, the engine calculates: + +**Combat Metrics:** +- Average KDA (Kills/Deaths/Assists ratio) +- KDA variance (consistency indicator) +- Death variance (emotional stability indicator) +- Kill participation rate (teamfight involvement) +- First blood rate (early game aggression) +- Aggression index: `(kills + killParticipation) / (deaths + assists)` + +**Strategic Metrics:** +- Vision score per minute (map awareness) +- Objective focus (dragons, barons, turrets per game) +- Champion diversity (unique champions / total games) +- Assist ratio (assists / kills) +- Primary role consistency + +#### Big Five Personality Trait Mapping + +The engine maps gameplay features to five personality dimensions (0-100 scale): + +**1. Openness to Experience (Creativity & Variety)** +- Champion diversity: +40 points per diversity ratio +- Mid lane players: +15 points (creative roaming opportunities) +- ADC/Support players: -10 points (structured playstyle) + +**2. Conscientiousness (Discipline & Consistency)** +- Deaths above 3: -2 points per excess death +- KDA variance: -2.5 points per unit +- Death variance: -2 points per unit +- High KDA bonus: +5 points per KDA unit (capped at +20) +- ADC role: +15 points (precision farming required) + +**3. Extraversion (Aggression & Engagement)** +- Kill participation: +0.4 points per percentage +- Aggression index: +20 points per unit +- First blood rate: +25 points per occurrence +- Jungle/Mid roles: +15 points (active map presence) +- Top lane: -15 points (isolated gameplay) + +**4. Agreeableness (Teamwork & Support)** +- Assist ratio: +15 points per unit (capped at +25) +- Vision per minute: +7 points per unit (capped at +15) +- Low assists penalty: -30 points when ratio < 0.5 +- Support role: +25 points +- Jungle role: +10 points +- Mid lane: -10 points +- Top lane: -5 points + +**5. Emotional Stability (Consistency & Composure)** +- KDA variance: -3 points per unit +- Death variance: -2.5 points per unit +- Objective focus: +8 points per objective +- KDA consistency bonus: +10 points (KDA > 2.0 with variance < 2.0) +- Jungle role: +10 points (macro control) + +All traits are normalized to 0-100 range after calculation. + +#### Jungian Archetype Matching + +The system maps players to 12 Jungian archetypes using Euclidean distance in 5-dimensional trait space: + +- **Innocent**: High Agreeableness (80), Conscientiousness (70) - Support champions like Lulu, Soraka +- **Orphan**: Balanced across all traits (50-75) - Team players like Amumu, Maokai +- **Hero**: High Extraversion (90), Conscientiousness (75) - Frontline warriors like Garen, Darius +- **Caregiver**: Maximum Agreeableness (95) - Selfless supports like Janna, Nami +- **Explorer**: High Openness (85), Extraversion (80) - Adventurous picks like Taliyah, Bard +- **Rebel**: Maximum Extraversion (95), low Agreeableness (45) - Aggressive soloists like Yasuo, Draven +- **Lover**: High Extraversion (85), Agreeableness (90) - Duo synergy champions like Xayah, Rakan +- **Creator**: Maximum Openness (90), high Conscientiousness (70) - Strategic innovators like Viktor, Heimerdinger +- **Jester**: High Extraversion (90), low Conscientiousness (45) - Chaotic tricksters like Teemo, Shaco +- **Sage**: Balanced high traits (60-75) - Disciplined masters like Ryze, Orianna +- **Magician**: High Openness (75), Extraversion (75), Conscientiousness (75) - Versatile playmakers like Twisted Fate, Zed +- **Ruler**: High Conscientiousness (80), Emotional Stability (75) - Macro controllers like Galio, Shen + +Similarity score: `max(0, 100 - (euclideanDistance / 173 * 100))` + +### 3. AWS Bedrock Integration + +The chat endpoint (`/api/chat`) creates an interactive digital twin using AWS Bedrock's Claude 3.5 Sonnet model. + +#### System Architecture + +**Authentication:** +- Bearer token authentication via `BEDROCK_API_KEY` environment variable +- Regional endpoint routing: `https://bedrock-runtime.{AWS_REGION}.amazonaws.com/model/{modelId}/invoke` + +**Model Configuration:** +- Model: `anthropic.claude-sonnet-4-5-20250929-v1:0` +- Max tokens: 300 (concise responses) +- Temperature: 0.7 (balanced creativity and consistency) +- Anthropic version: `bedrock-2023-05-31` + +#### Digital Twin Context Building + +The system constructs a rich context from player data: + +```javascript +Player: {gameName}#{tagLine} +Level: {summonerLevel} +Rank: {tier} {rank} {leaguePoints}LP ({winRate}% WR, {wins}W/{losses}L) +Top Champions: {champion1} ({masteryPoints}k pts), {champion2}, {champion3} +Archetype: {archetypeName} ({similarity}% match) +Personality Traits: Openness: {score}%, Conscientiousness: {score}%, ... +Primary Role: {role} +Average KDA: {kda} +Aggression Level: {aggressionIndex}% +Recent Performance: {wins}W/{losses}L in last {totalGames} games +``` -```bash -npm run install-all +#### Conversation Management + +- System prompt establishes the AI as the player's digital twin +- Maintains last 10 messages of conversation history +- Context injection before each user message +- Fallback responses when API fails + +### 4. Duo Compatibility Analysis + +The matchmaking endpoint (`/api/matchmaking`) analyzes compatibility between two players using AI-powered assessment. + +**Process Flow:** +1. Fetch complete data for both players (if not already available) +2. Generate personality profiles for both +3. Construct comparative context with: + - Big Five trait differences + - Archetype compatibility + - Role synergy potential + - Playstyle complementarity +4. Send to Claude for natural language compatibility analysis + +**AI Compatibility Output:** +```json +{ + "score": 0-100, + "level": "Excellent|Very Good|Good|Fair|Challenging", + "recommendation": "2-3 sentence personalized advice", + "strengths": ["synergy1", "synergy2", "synergy3"], + "challenges": ["challenge1", "challenge2"] +} ``` -### Environment Variables +## Deployment + +### Vercel Deployment (Recommended) + +Shape Split is optimized for Vercel's serverless platform: + +1. **Push to GitHub:** + ```bash + git push origin main + ``` + +2. **Import to Vercel:** + - Connect your GitHub repository + - Vercel auto-detects the configuration + +3. **Configure Environment Variables:** + ``` + RIOT_API_KEY=your_riot_api_key + BEDROCK_API_KEY=your_aws_bedrock_key # Optional for chat features + AWS_REGION=us-east-1 # Optional, default: us-east-1 + ``` + +4. **Deploy:** + - Automatic deployment on every push + - Serverless functions in `/api` are automatically detected + - Client built from `/client` directory + +### Local Development + +**Prerequisites:** +- Node.js v14 or higher +- npm package manager +- Riot Games API key (https://developer.riotgames.com/) + +**Setup:** + +1. Clone and install dependencies: + ```bash + git clone + cd ShapeSplitter + npm run install-all + ``` + +2. Create `.env` file: + ```bash + RIOT_API_KEY=your_riot_api_key_here + BEDROCK_API_KEY=your_bedrock_api_key # Optional + AWS_REGION=us-east-1 # Optional + ``` + +3. Run with Vercel CLI (recommended): + ```bash + npm install -g vercel + vercel dev + ``` + + Or run with Express server: + ```bash + npm run dev + ``` + +## API Endpoints + +### GET/POST `/api/search` +Fetch and analyze player data. + +**Parameters:** +- `gameName`: Riot ID game name +- `tagLine`: Riot ID tag (e.g., "NA1") +- `region`: Server region (default: "na1") + +**Response:** Complete player profile with personality analysis + +### POST `/api/chat` +Interact with player's digital twin. + +**Body:** +```json +{ + "message": "User message", + "playerData": { /* player context */ }, + "chatHistory": [ /* previous messages */ ] +} +``` -Create a `.env` file in the root directory (see `.env.example`): +**Response:** AI-generated response as digital twin -```bash -RIOT_API_KEY=your_riot_api_key_here +### POST `/api/matchmaking` +Analyze duo compatibility. -# Optional - for AI features -BEDROCK_API_KEY=your_bedrock_api_key -AWS_REGION=us-east-1 +**Body:** +```json +{ + "player1Data": { /* first player data */ }, + "player2GameName": "string", + "player2TagLine": "string", + "region": "na1" +} ``` -### Development - -**Option 1: With Vercel CLI (Recommended)** -```bash -npm install -g vercel -vercel dev -``` +**Response:** Compatibility score and analysis -**Option 2: With Express Server** -```bash -npm run dev -``` +### GET `/api/regions` +List available regions and routing configurations. -This will start: -- Server on port 5000 -- Client on port 3000 (React development server) +## Performance Optimizations -### Building +- Rate limiting (1200ms) for Riot API compliance +- Early termination when target valid games reached +- Parallel asset loading for champion images +- Smart batch processing for match history +- Conversation history limited to last 10 messages +- Cached Data Dragon version and champion data -Build the client for production: +## Development Scripts ```bash -npm run build +npm run dev # Start both client and server +npm run server # Express server only (port 5000) +npm run client # React development server (port 3000) +npm run build # Production build +npm run vercel-build # Vercel deployment build +npm run install-all # Install all dependencies ``` -## Available Scripts - -- `npm run dev` - Start both client and server in development mode -- `npm run server` - Start only the Express server -- `npm run client` - Start only the React client -- `npm run build` - Build client for production -- `npm run vercel-build` - Build for Vercel deployment -- `npm run install-all` - Install dependencies for all packages -- `npm run build` - Build client for production -- `npm run install-all` - Install dependencies for root, server, and client - -## Technologies Used +## Supported Regions -- **Frontend**: React, TypeScript -- **Backend**: Node.js -- **API**: Riot Games API for League of Legends data +**Americas:** na1, br1, la1, la2 +**Asia:** kr, jp1 +**Europe:** euw1, eun1, tr1, ru +**Southeast Asia:** oc1, ph2, sg2, th2, tw2, vn2 ## License diff --git a/api/bedrock-test.js b/api/bedrock-test.js index 6e9ed48..4938cd4 100644 --- a/api/bedrock-test.js +++ b/api/bedrock-test.js @@ -12,7 +12,7 @@ module.exports = async (req, res) => { const apiKey = process.env.BEDROCK_API_KEY; const awsRegion = process.env.AWS_REGION; - const modelId = "anthropic.claude-3-5-sonnet-20240620-v1:0"; + const modelId = "anthropic.claude-sonnet-4-5-20250929-v1:0"; const url = `https://bedrock-runtime.${awsRegion}.amazonaws.com/model/${modelId}/invoke`; const payload = { diff --git a/api/chat.js b/api/chat.js index cc6d0db..0953ab0 100644 --- a/api/chat.js +++ b/api/chat.js @@ -22,7 +22,7 @@ module.exports = async (req, res) => { const apiKey = process.env.BEDROCK_API_KEY; const awsRegion = process.env.AWS_REGION; - const modelId = "anthropic.claude-3-5-sonnet-20240620-v1:0"; + const modelId = "anthropic.claude-sonnet-4-5-20250929-v1:0"; const url = `https://bedrock-runtime.${awsRegion}.amazonaws.com/model/${modelId}/invoke`; const playerContext = buildPlayerContext(playerData); diff --git a/api/matchmaking.js b/api/matchmaking.js index cc79afa..38f9124 100644 --- a/api/matchmaking.js +++ b/api/matchmaking.js @@ -117,7 +117,7 @@ async function calculateCompatibilityWithAI(player1Personality, player2Personali try { const apiKey = process.env.BEDROCK_API_KEY; const awsRegion = process.env.AWS_REGION; - const modelId = "anthropic.claude-3-5-sonnet-20240620-v1:0"; + const modelId = "anthropic.claude-sonnet-4-5-20250929-v1:0"; const url = `https://bedrock-runtime.${awsRegion}.amazonaws.com/model/${modelId}/invoke`; const player1Context = buildPlayerCompatibilityContext(player1Data, player1Personality, 'Player 1'); diff --git a/client/src/ResultsPage.tsx b/client/src/ResultsPage.tsx index 7455726..2050627 100644 --- a/client/src/ResultsPage.tsx +++ b/client/src/ResultsPage.tsx @@ -41,17 +41,17 @@ const MatchmakingCard: React.FC = ({ playerData }) => { // Region options (same as SearchPage) const regions = [ + { value: 'kr', label: 'Korea' }, { value: 'na1', label: 'North America' }, { value: 'euw1', label: 'Europe West' }, { value: 'eun1', label: 'Europe Nordic & East' }, - { value: 'kr', label: 'Korea' }, - { value: 'jp1', label: 'Japan' }, { value: 'br1', label: 'Brazil' }, { value: 'la1', label: 'Latin America North' }, { value: 'la2', label: 'Latin America South' }, { value: 'oc1', label: 'Oceania' }, { value: 'tr1', label: 'Turkey' }, - { value: 'ru', label: 'Russia' } + { value: 'ru', label: 'Russia' }, + { value: 'jp1', label: 'Japan' } ]; const handleSearch = async () => { From 05cfb68e44b3eeafb54f381d99ec07b380a842ec Mon Sep 17 00:00:00 2001 From: Louis Tai <142078052+louistaii@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:11:58 +0800 Subject: [PATCH 3/7] Add how it works page --- client/src/App.css | 339 ++++++++++++++++++++++++++++++++++ client/src/App.tsx | 4 +- client/src/HowItWorksPage.tsx | 195 +++++++++++++++++++ 3 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 client/src/HowItWorksPage.tsx diff --git a/client/src/App.css b/client/src/App.css index dab977c..1131bae 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -2921,4 +2921,343 @@ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } +} + +/* How It Works Page Styles */ +.how-it-works-page { + padding: 2rem 2rem 4rem; +} + +.how-it-works-container { + max-width: 1400px; + margin: 0 auto; + width: 100%; +} + +.how-it-works-header { + text-align: center; + margin-bottom: 4rem; + margin-top: 2rem; +} + +.how-it-works-title { + font-size: 4rem; + font-family: 'Bebas Neue', sans-serif; + letter-spacing: 0.3em; + color: white; + text-shadow: 0 2px 4px rgba(0,0,0,0.3); + margin-bottom: 1rem; + background: linear-gradient(135deg, #ffffff 0%, rgba(255, 255, 255, 0.7) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.how-it-works-subtitle { + font-size: 1.2rem; + color: rgba(255, 255, 255, 0.7); + font-family: 'Rajdhani', sans-serif; + max-width: 800px; + margin: 0 auto; + line-height: 1.6; +} + +.steps-container { + display: grid; + gap: 2rem; + margin-bottom: 4rem; +} + +.step-card { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 16px; + padding: 2rem; + transition: all 0.3s ease; + display: flex; + gap: 2rem; + align-items: flex-start; + position: relative; + overflow: hidden; +} + +.step-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, transparent 100%); + opacity: 0; + transition: opacity 0.3s ease; + pointer-events: none; +} + +.step-card:hover { + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.3); + transform: translateY(-4px); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.step-card:hover::before { + opacity: 1; +} + +.step-number { + font-size: 5rem; + font-family: 'Bebas Neue', sans-serif; + color: rgba(255, 255, 255, 0.1); + font-weight: bold; + line-height: 1; + min-width: 120px; + text-align: center; + transition: all 0.3s ease; +} + +.step-card:hover .step-number { + color: rgba(255, 255, 255, 0.2); + transform: scale(1.1); +} + +.step-content { + flex: 1; +} + +.step-title { + font-size: 2rem; + font-family: 'Rajdhani', sans-serif; + color: white; + margin-bottom: 1rem; + font-weight: 600; +} + +.step-description { + font-size: 1.1rem; + color: rgba(255, 255, 255, 0.7); + line-height: 1.6; + margin-bottom: 1.5rem; + font-family: 'Rajdhani', sans-serif; +} + +.step-details { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +.detail-item { + display: flex; + align-items: center; + gap: 0.5rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 0.75rem 1.25rem; + border-radius: 8px; + font-size: 0.95rem; + color: rgba(255, 255, 255, 0.9); + font-family: 'Rajdhani', sans-serif; + transition: all 0.3s ease; +} + +.detail-item:hover { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.2); + transform: translateY(-2px); +} + +.detail-icon { + font-size: 1.2rem; +} + +.technical-section { + margin-bottom: 4rem; +} + +.technical-title { + font-size: 2.5rem; + font-family: 'Bebas Neue', sans-serif; + letter-spacing: 0.2em; + color: white; + text-align: center; + margin-bottom: 2.5rem; +} + +.technical-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1.5rem; +} + +.technical-card { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + padding: 2rem; + text-align: center; + transition: all 0.3s ease; +} + +.technical-card:hover { + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.2); + transform: translateY(-4px); + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2); +} + +.technical-icon { + font-size: 3rem; + margin-bottom: 1rem; +} + +.technical-card h4 { + font-size: 1.5rem; + font-family: 'Rajdhani', sans-serif; + color: white; + margin-bottom: 1rem; + font-weight: 600; +} + +.technical-card p { + font-size: 0.95rem; + color: rgba(255, 255, 255, 0.7); + line-height: 1.6; + font-family: 'Rajdhani', sans-serif; +} + +.metrics-section { + margin-bottom: 4rem; +} + +.metrics-title { + font-size: 2.5rem; + font-family: 'Bebas Neue', sans-serif; + letter-spacing: 0.2em; + color: white; + text-align: center; + margin-bottom: 2.5rem; +} + +.metrics-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.5rem; +} + +.metric-item { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + transition: all 0.3s ease; +} + +.metric-item:hover { + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.2); + transform: translateY(-2px); +} + +.metric-label { + font-size: 1.2rem; + font-family: 'Rajdhani', sans-serif; + color: white; + font-weight: 600; +} + +.metric-value { + font-size: 0.95rem; + color: rgba(255, 255, 255, 0.7); + font-family: 'Rajdhani', sans-serif; + line-height: 1.5; +} + +.how-it-works-cta { + text-align: center; + padding: 3rem 2rem; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 16px; + margin-top: 2rem; +} + +.how-it-works-cta h2 { + font-size: 2.5rem; + font-family: 'Bebas Neue', sans-serif; + letter-spacing: 0.2em; + color: white; + margin-bottom: 2rem; +} + +.cta-button { + display: inline-block; + background: rgba(255, 255, 255, 0.1); + border: 2px solid rgba(255, 255, 255, 0.3); + color: white; + padding: 1rem 3rem; + font-size: 1.2rem; + font-family: 'Rajdhani', sans-serif; + font-weight: 600; + text-decoration: none; + border-radius: 8px; + transition: all 0.3s ease; + letter-spacing: 0.1em; +} + +.cta-button:hover { + background: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.5); + transform: translateY(-2px); + box-shadow: 0 4px 24px rgba(255, 255, 255, 0.1); +} + +/* Responsive Design for How It Works */ +@media (max-width: 768px) { + .how-it-works-title { + font-size: 2.5rem; + letter-spacing: 0.2em; + } + + .how-it-works-subtitle { + font-size: 1rem; + } + + .step-card { + flex-direction: column; + padding: 1.5rem; + } + + .step-number { + font-size: 3rem; + min-width: auto; + text-align: left; + } + + .step-title { + font-size: 1.5rem; + } + + .step-description { + font-size: 1rem; + } + + .technical-title, + .metrics-title, + .how-it-works-cta h2 { + font-size: 2rem; + } + + .technical-grid, + .metrics-grid { + grid-template-columns: 1fr; + } + + .cta-button { + padding: 0.875rem 2rem; + font-size: 1rem; + } } \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index ad3eb26..f5daecd 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -4,6 +4,7 @@ import { PlayerDataProvider, PlayerDataContext } from './PlayerDataContext'; import SearchPage from './SearchPage'; import LoadingPage from './LoadingPage'; import ResultsPage from './ResultsPage'; +import HowItWorksPage from './HowItWorksPage'; import './App.css'; function AppRoutes() { @@ -16,6 +17,7 @@ function AppRoutes() { } /> } /> } /> + } /> ); } @@ -39,7 +41,7 @@ function App() { diff --git a/client/src/HowItWorksPage.tsx b/client/src/HowItWorksPage.tsx new file mode 100644 index 0000000..203d9a5 --- /dev/null +++ b/client/src/HowItWorksPage.tsx @@ -0,0 +1,195 @@ +import React from 'react'; +import './App.css'; + +const HowItWorksPage: React.FC = () => { + return ( +
+
+
+

How It Works

+

+ Shape Split analyzes your League of Legends gameplay to create a comprehensive digital twin +

+
+ +
+ {/* Step 1 */} +
+
01
+
+

Search Your Summoner

+

+ Enter your Riot ID and select your region. We'll fetch your account data, ranked statistics, + champion mastery, and up to 100 recent matches from the Riot Games API. +

+
+
+ 📊 + Account & Ranked Stats +
+
+ 🏆 + Champion Mastery +
+
+ 🎮 + Match History Analysis +
+
+
+
+ + {/* Step 2 */} +
+
02
+
+

Personality Analysis

+

+ Our advanced algorithm analyzes 25+ ranked and normal games, extracting patterns from your + KDA, vision score, role preference, champion diversity, aggression index, and objective control. +

+
+
+ 🧠 + Big Five Traits (0-100 scale) +
+
+ 🎭 + Jungian Archetype Match +
+
+ 📈 + Playstyle Metrics +
+
+
+
+ + {/* Step 3 */} +
+
03
+
+

Digital Twin Creation

+

+ Powered by AWS Bedrock and Claude AI, your digital twin learns your gameplay personality, + champion preferences, and performance patterns to provide personalized insights. +

+
+
+ 🤖 + AI-Powered Chat +
+
+ 💡 + Personalized Insights +
+
+ 🎯 + Performance Coaching +
+
+
+
+ + {/* Step 4 */} +
+
04
+
+

Duo Compatibility

+

+ Find your perfect duo queue partner! Compare personality traits, playstyles, and roles with + other summoners. Get AI-generated compatibility scores and recommendations. +

+
+
+ 💘 + Compatibility Score (0-100) +
+
+ + Synergy Analysis +
+
+ 🎪 + Team Dynamic Insights +
+
+
+
+
+ + {/* Technical Details Section */} +
+

The Science Behind It

+
+
+
🔬
+

Personality Mapping

+

+ We use the Big Five personality model (Openness, Conscientiousness, Extraversion, + Agreeableness, Emotional Stability) mapped to gameplay patterns through statistical analysis. +

+
+
+
📐
+

Archetype Matching

+

+ 12 Jungian archetypes are matched using Euclidean distance in 5-dimensional trait space, + providing similarity scores and descriptions. +

+
+
+
🤖
+

AWS Bedrock AI

+

+ Claude 3.5 Sonnet powers conversational insights, analyzing your complete player context + to provide coaching and answer questions about your gameplay. +

+
+
+
+

Real-Time Processing

+

+ Serverless architecture on Vercel ensures fast, scalable analysis. Rate-limited API calls + comply with Riot Games restrictions while maximizing data collection. +

+
+
+
+ + {/* Example Metrics */} +
+

What We Analyze

+
+
+ Combat Metrics + KDA, Deaths, Kill Participation, First Blood Rate +
+
+ Strategic Metrics + Vision Score, Objective Focus, Champion Diversity +
+
+ Consistency + KDA Variance, Death Variance, Role Preference +
+
+ Playstyle + Aggression Index, Assist Ratio, Primary Role +
+
+
+ + {/* CTA */} +
+

Ready to Meet Your Digital Twin?

+ + Get Started + +
+
+
+ ); +}; + +export default HowItWorksPage; From 578a5c982d191549605f4e199feb20bf90ff4b99 Mon Sep 17 00:00:00 2001 From: Louis Tai <142078052+louistaii@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:17:06 +0800 Subject: [PATCH 4/7] Fix routing issue --- vercel.json | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/vercel.json b/vercel.json index 2866234..e5b89e0 100644 --- a/vercel.json +++ b/vercel.json @@ -2,6 +2,16 @@ "version": 2, "buildCommand": "cd client && npm install && npm run build", "outputDirectory": "client/build", + "rewrites": [ + { + "source": "/api/:path*", + "destination": "/api/:path*" + }, + { + "source": "/(.*)", + "destination": "/index.html" + } + ], "routes": [ { "src": "/api/health", @@ -32,18 +42,6 @@ "src": "/api/chat", "dest": "/api/chat.js", "methods": ["POST"] - }, - { - "src": "/static/(.*)", - "dest": "/client/build/static/$1" - }, - { - "src": "/(.*\\.(js|css|ico|png|jpg|jpeg|gif|svg|json|txt|woff|woff2|ttf|eot))", - "dest": "/client/build/$1" - }, - { - "src": "/(.*)", - "dest": "/client/build/index.html" } ] } From 44d264175d4185f1df5c66f666450da9a181315b Mon Sep 17 00:00:00 2001 From: Louis Tai <142078052+louistaii@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:22:02 +0800 Subject: [PATCH 5/7] Fix how it works route --- .vercelignore | 3 +-- client/src/HowItWorksPage.tsx | 2 +- vercel.json | 22 ++++++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.vercelignore b/.vercelignore index efe58c7..ca06557 100644 --- a/.vercelignore +++ b/.vercelignore @@ -10,8 +10,7 @@ node_modules/ .env.local .env*.local -# Build outputs -client/build/ +# Build outputs (but NOT client/build since that's our outputDirectory) .next/ dist/ diff --git a/client/src/HowItWorksPage.tsx b/client/src/HowItWorksPage.tsx index 203d9a5..0c66043 100644 --- a/client/src/HowItWorksPage.tsx +++ b/client/src/HowItWorksPage.tsx @@ -142,7 +142,7 @@ const HowItWorksPage: React.FC = () => {
🤖

AWS Bedrock AI

- Claude 3.5 Sonnet powers conversational insights, analyzing your complete player context + Claude 4.5 Sonnet powers conversational insights, analyzing your complete player context to provide coaching and answer questions about your gameplay.

diff --git a/vercel.json b/vercel.json index e5b89e0..589f8ca 100644 --- a/vercel.json +++ b/vercel.json @@ -2,16 +2,6 @@ "version": 2, "buildCommand": "cd client && npm install && npm run build", "outputDirectory": "client/build", - "rewrites": [ - { - "source": "/api/:path*", - "destination": "/api/:path*" - }, - { - "source": "/(.*)", - "destination": "/index.html" - } - ], "routes": [ { "src": "/api/health", @@ -42,6 +32,18 @@ "src": "/api/chat", "dest": "/api/chat.js", "methods": ["POST"] + }, + { + "src": "/static/(.*)", + "dest": "/static/$1" + }, + { + "src": "/(.*\\.(js|css|ico|png|jpg|jpeg|gif|svg|json|txt|woff|woff2|ttf|eot))", + "dest": "/$1" + }, + { + "src": "/(.*)", + "dest": "/index.html" } ] } From 8b7bc7d47e3ec7a6022dc3414ded89f5bffed44b Mon Sep 17 00:00:00 2001 From: Louis Tai <142078052+louistaii@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:38:24 +0800 Subject: [PATCH 6/7] Fix loading page --- client/src/LoadingPage.tsx | 51 +++----------------------------------- 1 file changed, 4 insertions(+), 47 deletions(-) diff --git a/client/src/LoadingPage.tsx b/client/src/LoadingPage.tsx index b9f4080..8c736b9 100644 --- a/client/src/LoadingPage.tsx +++ b/client/src/LoadingPage.tsx @@ -6,49 +6,11 @@ interface LoadingPageProps { } const LoadingPage: React.FC = ({ profileIconUrl, loadingStatus }) => { - const getProgressPercentage = () => { - const stats = getStatsFromStatus(); - // Base progress on ranked games found out of target (25) - const targetRankedGames = 25; - const rankedProgress = Math.min((stats.ranked / targetRankedGames) * 100, 100); - - // If we have valid games analyzed, use that for final progress - if (stats.valid > 0) { - return Math.min((stats.valid / stats.target) * 100, 100); - } - - // Otherwise use ranked games progress - return Math.round(rankedProgress); - }; - const getCurrentStep = () => { if (loadingStatus.length === 0) return 'Initializing...'; return loadingStatus[loadingStatus.length - 1]; }; - const getStatsFromStatus = () => { - const lastStatus = loadingStatus[loadingStatus.length - 1] || ''; - - // Extract game counts from status messages - const rankedMatch = lastStatus.match(/🏆 (\d+) ranked/); - const normalMatch = lastStatus.match(/🎮 (\d+) normal/); - const otherMatch = lastStatus.match(/📊 (\d+) other/); - const validMatch = lastStatus.match(/✅ (\d+)\/(\d+) valid games/); - const matchIdsMatch = lastStatus.match(/📥 (\d+)\/(\d+)/); - - return { - ranked: rankedMatch ? parseInt(rankedMatch[1]) : 0, - normal: normalMatch ? parseInt(normalMatch[1]) : 0, - other: otherMatch ? parseInt(otherMatch[1]) : 0, - valid: validMatch ? parseInt(validMatch[1]) : 0, - target: validMatch ? parseInt(validMatch[2]) : 25, - fetched: matchIdsMatch ? parseInt(matchIdsMatch[1]) : 0, - maxFetch: matchIdsMatch ? parseInt(matchIdsMatch[2]) : 100 - }; - }; - - const progress = getProgressPercentage(); - return (
@@ -62,15 +24,10 @@ const LoadingPage: React.FC = ({ profileIconUrl, loadingStatus
- {/* Progress Bar */} -
-
-
-
- {progress}% Complete + {/* Loading Message */} +
+

Please wait a few minutes...

+

We're analyzing 25+ games to build your personality profile

{/* Current Step - Only Latest Message */} From ec022841ee17aa6af2f27cc91f5b5d280307528f Mon Sep 17 00:00:00 2001 From: Louis Tai <142078052+louistaii@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:51:39 +0800 Subject: [PATCH 7/7] Revert to claude 3.5 --- README.md | 2 +- api/chat.js | 2 +- api/matchmaking.js | 2 +- client/src/HowItWorksPage.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bf7f190..ac55988 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ The chat endpoint (`/api/chat`) creates an interactive digital twin using AWS Be - Regional endpoint routing: `https://bedrock-runtime.{AWS_REGION}.amazonaws.com/model/{modelId}/invoke` **Model Configuration:** -- Model: `anthropic.claude-sonnet-4-5-20250929-v1:0` +- Model: `anthropic.claude-3-5-sonnet-20240620-v1:0` - Max tokens: 300 (concise responses) - Temperature: 0.7 (balanced creativity and consistency) - Anthropic version: `bedrock-2023-05-31` diff --git a/api/chat.js b/api/chat.js index 0953ab0..cc6d0db 100644 --- a/api/chat.js +++ b/api/chat.js @@ -22,7 +22,7 @@ module.exports = async (req, res) => { const apiKey = process.env.BEDROCK_API_KEY; const awsRegion = process.env.AWS_REGION; - const modelId = "anthropic.claude-sonnet-4-5-20250929-v1:0"; + const modelId = "anthropic.claude-3-5-sonnet-20240620-v1:0"; const url = `https://bedrock-runtime.${awsRegion}.amazonaws.com/model/${modelId}/invoke`; const playerContext = buildPlayerContext(playerData); diff --git a/api/matchmaking.js b/api/matchmaking.js index 38f9124..cc79afa 100644 --- a/api/matchmaking.js +++ b/api/matchmaking.js @@ -117,7 +117,7 @@ async function calculateCompatibilityWithAI(player1Personality, player2Personali try { const apiKey = process.env.BEDROCK_API_KEY; const awsRegion = process.env.AWS_REGION; - const modelId = "anthropic.claude-sonnet-4-5-20250929-v1:0"; + const modelId = "anthropic.claude-3-5-sonnet-20240620-v1:0"; const url = `https://bedrock-runtime.${awsRegion}.amazonaws.com/model/${modelId}/invoke`; const player1Context = buildPlayerCompatibilityContext(player1Data, player1Personality, 'Player 1'); diff --git a/client/src/HowItWorksPage.tsx b/client/src/HowItWorksPage.tsx index 0c66043..203d9a5 100644 --- a/client/src/HowItWorksPage.tsx +++ b/client/src/HowItWorksPage.tsx @@ -142,7 +142,7 @@ const HowItWorksPage: React.FC = () => {
🤖

AWS Bedrock AI

- Claude 4.5 Sonnet powers conversational insights, analyzing your complete player context + Claude 3.5 Sonnet powers conversational insights, analyzing your complete player context to provide coaching and answer questions about your gameplay.