From 7e4af9a4fe3c48188ba8254a5f70d8a87e760162 Mon Sep 17 00:00:00 2001 From: Srijan Guchhait <62981066+qwertystars@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:59:54 +0530 Subject: [PATCH 1/3] frontend only --- SIMPLIFIED_FIX.sql | 243 --- backup-db.sh | 95 - check-leaderboard.js | 38 - google-apps-script-updated.js | 110 - package-lock.json | 1898 +++++++---------- package.json | 21 +- scripts/create_admins.cjs | 70 - scripts/create_admins.js | 65 - scripts/update_missing_institution.sql | 54 - setup_scores.sql | 93 - src/App.jsx | 27 +- src/components/AnimatedSection.jsx | 53 + src/components/BuildTeam.jsx | 279 --- src/components/CustomCursor.jsx | 144 ++ src/components/ParticleBackground.jsx | 171 ++ src/components/Preloader.jsx | 10 +- src/components/icons/index.jsx | 156 ++ src/index.css | 33 +- src/pages/admin.jsx | 1027 --------- src/pages/dashboard.jsx | 1635 -------------- src/pages/home.jsx | 907 ++++---- src/pages/login.jsx | 296 --- src/pages/member.jsx | 734 ------- src/pages/otp.jsx | 154 -- src/pages/register.jsx | 503 ----- src/styles/admin.css | 331 --- src/styles/admin_mobile_fix.css | 14 - src/styles/animations.css | 505 +++++ src/styles/auth.css | 85 - src/styles/build-team.css | 206 -- src/styles/custom-cursor.css | 224 ++ src/styles/dashboard.css | 814 ------- src/styles/design-tokens.css | 225 ++ src/styles/home.css | 1368 +++++++----- src/styles/login.css | 665 ------ src/styles/member.css | 322 --- src/styles/otp.css | 236 -- src/styles/preloader.css | 150 +- src/styles/register.css | 727 ------- src/supabaseClient.js | 6 - src/utils/authRouting.js | 23 - src/utils/buildTeam.js | 30 - src/utils/selectProblem.js | 42 - supabase/.gitignore | 8 - supabase/.temp/cli-latest | 1 - supabase/.temp/gotrue-version | 1 - supabase/.temp/pooler-url | 1 - supabase/.temp/postgres-version | 1 - supabase/.temp/project-ref | 1 - supabase/.temp/rest-version | 1 - supabase/.temp/storage-migration | 1 - supabase/.temp/storage-version | 1 - supabase/config.toml | 393 ---- supabase/functions/build-team/deno.json | 5 - supabase/functions/build-team/index.ts | 254 --- supabase/functions/register-team/.npmrc | 3 - supabase/functions/register-team/deno.json | 3 - supabase/functions/register-team/index.ts | 147 -- supabase/functions/select-problem/deno.json | 5 - supabase/functions/select-problem/index.ts | 121 -- .../20251208183150_initial_schema.sql | 52 - .../20251211181257_add_team_size_column.sql | 4 - ...20251212040905_create_scorecards_table.sql | 41 - .../20251212215000_add_institution_field.sql | 24 - .../20251214000000_add_receipt_link.sql | 6 - ...16000000_add_member_login_and_qr_codes.sql | 68 - ...20251216120000_add_member_email_column.sql | 23 - ...20251217000000_create_leaderboard_view.sql | 25 - .../20251217000001_add_scorecard_history.sql | 134 -- .../20251218000000_unique_emails.sql | 28 - .../20251220000000_add_is_vit_chennai.sql | 11 - ...20000001_update_institution_constraint.sql | 21 - ...51221000000_enhanced_security_policies.sql | 143 -- ...0251221000001_fix_function_search_path.sql | 155 -- ...51221000002_enable_password_protection.sql | 24 - ...251226080000_add_member_is_vit_chennai.sql | 19 - ...20251229090000_add_team_problem_fields.sql | 8 - ...statements_and_select_problem_function.sql | 90 - ...251229171500_fix_select_problem_atomic.sql | 65 - ...120000_create_scorecard_on_team_insert.sql | 28 - .../20260102000000_add_app_settings.sql | 30 - .../20260103000000_change_iot4_to_cyber4.sql | 15 - .../20260103001000_move_iot4_to_cyber4.sql | 28 - .../20260103002000_connect_cybersecurity4.sql | 17 - .../20260103003000_insert_cybersecurity4.sql | 24 - .../20260107090000_fix_leaderboard_rls.sql | 39 - supabase/seed_test_scores.sql | 46 - tmp_get_team.cjs | 31 - tmp_invoke_register.cjs | 33 - tmp_invoke_register.js | 33 - vercel.json | 8 - wait | 1 - 92 files changed, 3624 insertions(+), 13386 deletions(-) delete mode 100644 SIMPLIFIED_FIX.sql delete mode 100644 backup-db.sh delete mode 100644 check-leaderboard.js delete mode 100644 google-apps-script-updated.js delete mode 100644 scripts/create_admins.cjs delete mode 100644 scripts/create_admins.js delete mode 100644 scripts/update_missing_institution.sql delete mode 100644 setup_scores.sql create mode 100644 src/components/AnimatedSection.jsx delete mode 100644 src/components/BuildTeam.jsx create mode 100644 src/components/CustomCursor.jsx create mode 100644 src/components/ParticleBackground.jsx create mode 100644 src/components/icons/index.jsx delete mode 100644 src/pages/admin.jsx delete mode 100644 src/pages/dashboard.jsx delete mode 100644 src/pages/login.jsx delete mode 100644 src/pages/member.jsx delete mode 100644 src/pages/otp.jsx delete mode 100644 src/pages/register.jsx delete mode 100644 src/styles/admin.css delete mode 100644 src/styles/admin_mobile_fix.css create mode 100644 src/styles/animations.css delete mode 100644 src/styles/auth.css delete mode 100644 src/styles/build-team.css create mode 100644 src/styles/custom-cursor.css delete mode 100644 src/styles/dashboard.css create mode 100644 src/styles/design-tokens.css delete mode 100644 src/styles/login.css delete mode 100644 src/styles/member.css delete mode 100644 src/styles/otp.css delete mode 100644 src/styles/register.css delete mode 100644 src/supabaseClient.js delete mode 100644 src/utils/authRouting.js delete mode 100644 src/utils/buildTeam.js delete mode 100644 src/utils/selectProblem.js delete mode 100644 supabase/.gitignore delete mode 100644 supabase/.temp/cli-latest delete mode 100644 supabase/.temp/gotrue-version delete mode 100644 supabase/.temp/pooler-url delete mode 100644 supabase/.temp/postgres-version delete mode 100644 supabase/.temp/project-ref delete mode 100644 supabase/.temp/rest-version delete mode 100644 supabase/.temp/storage-migration delete mode 100644 supabase/.temp/storage-version delete mode 100644 supabase/config.toml delete mode 100644 supabase/functions/build-team/deno.json delete mode 100644 supabase/functions/build-team/index.ts delete mode 100644 supabase/functions/register-team/.npmrc delete mode 100644 supabase/functions/register-team/deno.json delete mode 100644 supabase/functions/register-team/index.ts delete mode 100644 supabase/functions/select-problem/deno.json delete mode 100644 supabase/functions/select-problem/index.ts delete mode 100644 supabase/migrations/20251208183150_initial_schema.sql delete mode 100644 supabase/migrations/20251211181257_add_team_size_column.sql delete mode 100644 supabase/migrations/20251212040905_create_scorecards_table.sql delete mode 100644 supabase/migrations/20251212215000_add_institution_field.sql delete mode 100644 supabase/migrations/20251214000000_add_receipt_link.sql delete mode 100644 supabase/migrations/20251216000000_add_member_login_and_qr_codes.sql delete mode 100644 supabase/migrations/20251216120000_add_member_email_column.sql delete mode 100644 supabase/migrations/20251217000000_create_leaderboard_view.sql delete mode 100644 supabase/migrations/20251217000001_add_scorecard_history.sql delete mode 100644 supabase/migrations/20251218000000_unique_emails.sql delete mode 100644 supabase/migrations/20251220000000_add_is_vit_chennai.sql delete mode 100644 supabase/migrations/20251220000001_update_institution_constraint.sql delete mode 100644 supabase/migrations/20251221000000_enhanced_security_policies.sql delete mode 100644 supabase/migrations/20251221000001_fix_function_search_path.sql delete mode 100644 supabase/migrations/20251221000002_enable_password_protection.sql delete mode 100644 supabase/migrations/20251226080000_add_member_is_vit_chennai.sql delete mode 100644 supabase/migrations/20251229090000_add_team_problem_fields.sql delete mode 100644 supabase/migrations/20251229120000_create_problem_statements_and_select_problem_function.sql delete mode 100644 supabase/migrations/20251229171500_fix_select_problem_atomic.sql delete mode 100644 supabase/migrations/20251230120000_create_scorecard_on_team_insert.sql delete mode 100644 supabase/migrations/20260102000000_add_app_settings.sql delete mode 100644 supabase/migrations/20260103000000_change_iot4_to_cyber4.sql delete mode 100644 supabase/migrations/20260103001000_move_iot4_to_cyber4.sql delete mode 100644 supabase/migrations/20260103002000_connect_cybersecurity4.sql delete mode 100644 supabase/migrations/20260103003000_insert_cybersecurity4.sql delete mode 100644 supabase/migrations/20260107090000_fix_leaderboard_rls.sql delete mode 100644 supabase/seed_test_scores.sql delete mode 100644 tmp_get_team.cjs delete mode 100644 tmp_invoke_register.cjs delete mode 100644 tmp_invoke_register.js delete mode 100644 vercel.json delete mode 100644 wait diff --git a/SIMPLIFIED_FIX.sql b/SIMPLIFIED_FIX.sql deleted file mode 100644 index aa0ee62..0000000 --- a/SIMPLIFIED_FIX.sql +++ /dev/null @@ -1,243 +0,0 @@ --- ===================================================== --- SIMPLIFIED FIX FOR ALL SUPABASE ADVISOR ISSUES --- This version skips the problematic profiles table --- ===================================================== - --- ===================================================== --- PART 1: Fix Function Search Path Issues (Security) --- ===================================================== - -CREATE OR REPLACE FUNCTION get_score_delta(p_team_id UUID) -RETURNS INTEGER -LANGUAGE plpgsql -SECURITY DEFINER -SET search_path = public, pg_temp -AS $$ -DECLARE - current_score INTEGER; - previous_score INTEGER; -BEGIN - SELECT total_score INTO current_score - FROM scorecards - WHERE team_id = p_team_id; - - SELECT total_score INTO previous_score - FROM scorecard_history - WHERE team_id = p_team_id - ORDER BY recorded_at DESC - OFFSET 1 - LIMIT 1; - - RETURN COALESCE(current_score - previous_score, 0); -END; -$$; - -CREATE OR REPLACE FUNCTION record_scorecard_change() -RETURNS TRIGGER -LANGUAGE plpgsql -SECURITY DEFINER -SET search_path = public, pg_temp -AS $$ -BEGIN - INSERT INTO scorecard_history ( - team_id, - innovation_score, - implementation_score, - presentation_score, - impact_score, - total_score, - snapshot_type - ) VALUES ( - NEW.team_id, - NEW.innovation_score, - NEW.implementation_score, - NEW.presentation_score, - NEW.impact_score, - NEW.total_score, - CASE - WHEN TG_OP = 'INSERT' THEN 'initial' - ELSE 'update' - END - ); - - RETURN NEW; -END; -$$; - -CREATE OR REPLACE FUNCTION update_scorecard_timestamp() -RETURNS TRIGGER -LANGUAGE plpgsql -SECURITY DEFINER -SET search_path = public, pg_temp -AS $$ -BEGIN - NEW.updated_at = TIMEZONE('utc'::text, NOW()); - RETURN NEW; -END; -$$; - -CREATE OR REPLACE FUNCTION is_team_owner(team_uuid uuid) -RETURNS boolean -LANGUAGE plpgsql -SECURITY DEFINER -SET search_path = public, pg_temp -AS $$ -BEGIN - RETURN EXISTS ( - SELECT 1 FROM teams - WHERE id = team_uuid - AND ( - lead_email = auth.jwt() ->> 'email' - OR user_id = auth.uid() - ) - ); -END; -$$; - -CREATE OR REPLACE FUNCTION check_email_uniqueness(email_to_check TEXT) -RETURNS BOOLEAN -LANGUAGE plpgsql -SECURITY DEFINER -SET search_path = public, pg_temp -AS $$ -BEGIN - IF EXISTS (SELECT 1 FROM teams WHERE lead_email = email_to_check) THEN - RETURN FALSE; - END IF; - - IF EXISTS (SELECT 1 FROM team_members WHERE member_email = email_to_check) THEN - RETURN FALSE; - END IF; - - RETURN TRUE; -END; -$$; - --- ===================================================== --- PART 2: Fix Multiple Permissive Policies --- ===================================================== - --- Fix TEAMS table -DROP POLICY IF EXISTS "Teams are viewable by everyone" ON teams; -DROP POLICY IF EXISTS "Public can view team names and scores only" ON teams; -DROP POLICY IF EXISTS "Leaders can view their own team details" ON teams; - -CREATE POLICY "Optimized teams select policy" - ON teams FOR SELECT - USING (true); - --- Fix SCORECARDS table -DROP POLICY IF EXISTS "Teams can view their own scorecard" ON scorecards; -DROP POLICY IF EXISTS "Everyone can view all scorecards (for leaderboard)" ON scorecards; -DROP POLICY IF EXISTS "Public can view scores for leaderboard" ON scorecards; - -CREATE POLICY "Optimized scorecards select policy" - ON scorecards FOR SELECT - USING (true); - -DROP POLICY IF EXISTS "Service role can manage scorecards" ON scorecards; -DROP POLICY IF EXISTS "Only service can insert scorecards" ON scorecards; -DROP POLICY IF EXISTS "Only service can update scorecards" ON scorecards; -DROP POLICY IF EXISTS "Only service can delete scorecards" ON scorecards; - -CREATE POLICY "Service role manages scorecards" - ON scorecards FOR ALL - USING (auth.role() = 'service_role') - WITH CHECK (auth.role() = 'service_role'); - --- Fix SCORECARD_HISTORY table -DROP POLICY IF EXISTS "Everyone can view scorecard history" ON scorecard_history; -DROP POLICY IF EXISTS "Service role can manage scorecard history" ON scorecard_history; - -CREATE POLICY "Public can view scorecard history" - ON scorecard_history FOR SELECT - USING (true); - -CREATE POLICY "Service role manages scorecard history" - ON scorecard_history FOR ALL - USING (auth.role() = 'service_role') - WITH CHECK (auth.role() = 'service_role'); - --- Fix TEAM_MEMBERS table -DROP POLICY IF EXISTS "Members are viewable by everyone" ON team_members; -DROP POLICY IF EXISTS "Members can view their own team" ON team_members; -DROP POLICY IF EXISTS "Authenticated users can add members" ON team_members; -DROP POLICY IF EXISTS "Only service can add members" ON team_members; -DROP POLICY IF EXISTS "Only service can update members" ON team_members; -DROP POLICY IF EXISTS "Only service can delete members" ON team_members; - -CREATE POLICY "Team members can view their team" - ON team_members FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM teams - WHERE teams.id = team_members.team_id - AND (teams.lead_email = auth.jwt() ->> 'email' - OR teams.user_id = auth.uid()) - ) - OR member_email = auth.jwt() ->> 'email' - ); - -CREATE POLICY "Service role manages team members" - ON team_members FOR ALL - USING (auth.role() = 'service_role') - WITH CHECK (auth.role() = 'service_role'); - --- ===================================================== --- PART 3: Fix Duplicate Indexes --- ===================================================== - -DO $$ -DECLARE - idx_name TEXT; -BEGIN - FOR idx_name IN - SELECT indexname - FROM pg_indexes - WHERE tablename = 'scorecard_history' - AND indexname LIKE '%_team_%' - AND indexname != 'idx_scorecard_history_team_time' - LOOP - EXECUTE format('DROP INDEX IF EXISTS %I', idx_name); - END LOOP; -END $$; - -CREATE INDEX IF NOT EXISTS idx_scorecard_history_team_time - ON scorecard_history(team_id, recorded_at DESC); - --- ===================================================== --- PART 4: Optimize Indexes --- ===================================================== - -CREATE INDEX IF NOT EXISTS idx_teams_lead_email ON teams(lead_email); -CREATE INDEX IF NOT EXISTS idx_teams_user_id ON teams(user_id); -CREATE INDEX IF NOT EXISTS idx_team_members_email ON team_members(member_email); -CREATE INDEX IF NOT EXISTS idx_team_members_team_id ON team_members(team_id); -CREATE INDEX IF NOT EXISTS idx_scorecards_team_id ON scorecards(team_id); - --- ===================================================== --- PART 5: Recreate Essential Team Policies --- ===================================================== - -DROP POLICY IF EXISTS "Authenticated users can create teams" ON teams; -DROP POLICY IF EXISTS "Users can update own team" ON teams; -DROP POLICY IF EXISTS "Leaders can update their own team" ON teams; -DROP POLICY IF EXISTS "Only service can delete teams" ON teams; - -CREATE POLICY "Authenticated users can create teams" - ON teams FOR INSERT - WITH CHECK (auth.role() = 'authenticated' AND auth.uid() = user_id); - -CREATE POLICY "Leaders can update their own team" - ON teams FOR UPDATE - USING (auth.uid() = user_id OR lead_email = auth.jwt() ->> 'email') - WITH CHECK (auth.uid() = user_id OR lead_email = auth.jwt() ->> 'email'); - -CREATE POLICY "Only service can delete teams" - ON teams FOR DELETE - USING (auth.role() = 'service_role'); - --- ===================================================== --- DONE! All main issues fixed. --- The profiles table warnings can be ignored if you don't use that feature. --- ===================================================== diff --git a/backup-db.sh b/backup-db.sh deleted file mode 100644 index f45f5ec..0000000 --- a/backup-db.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# CHANGE ONLY THIS -PASSWORD="YOUR_PASSWORD_HERE" # MUST Change - -# ─────────────────────────────────────────────── - -DATABASE_URL="postgresql://postgres:${PASSWORD}@db.zmcrdozxxclgzpltwpme.supabase.co:5432/postgres" - -TS="$(date +%Y%m%d_%H%M%S)" -OUT_DIR="./backups" -mkdir -p "$OUT_DIR" - -SQL_OUT="$OUT_DIR/backup_${TS}.sql" -SQLITE_OUT="$OUT_DIR/backup_${TS}.db" - -# 1) FULL POSTGRES SQL BACKUP (schema + data, directly restorable) -pg_dump "$DATABASE_URL" \ - --format=plain \ - --no-owner \ - --no-privileges \ - --file "$SQL_OUT" - -# 2) SQLITE .db SNAPSHOT (data-only, portable) -python3 - <=6.9.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/win32-x64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", @@ -486,47 +908,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@fast-csv/format": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", - "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", - "license": "MIT", - "dependencies": { - "@types/node": "^14.0.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isboolean": "^3.0.3", - "lodash.isequal": "^4.5.0", - "lodash.isfunction": "^3.0.9", - "lodash.isnil": "^4.0.0" - } - }, - "node_modules/@fast-csv/format/node_modules/@types/node": { - "version": "14.18.63", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", - "license": "MIT" - }, - "node_modules/@fast-csv/parse": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", - "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", - "license": "MIT", - "dependencies": { - "@types/node": "^14.0.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.groupby": "^4.6.0", - "lodash.isfunction": "^3.0.9", - "lodash.isnil": "^4.0.0", - "lodash.isundefined": "^3.0.1", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/@fast-csv/parse/node_modules/@types/node": { - "version": "14.18.63", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", - "license": "MIT" - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -636,6 +1017,286 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.53.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", @@ -664,86 +1325,6 @@ "win32" ] }, - "node_modules/@supabase/auth-js": { - "version": "2.87.1", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.87.1.tgz", - "integrity": "sha512-6RDeOf5TVoaXFtEstN188ykp3pXLZaU9qoAWfx8dc50FFAAqt+kcFJ96V0IvSmcpb4mDAWcpTJ7BegmVDn/WIw==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/functions-js": { - "version": "2.87.1", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.87.1.tgz", - "integrity": "sha512-rWmYo4gRD0XAjMhYDlz7IH67bp4TIQ1UE4VqwIQtl1gGPwtLDq6wcRnu7jLKlXx0Gtrknw/eoiHYG9//XrCTzQ==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/postgrest-js": { - "version": "2.87.1", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.87.1.tgz", - "integrity": "sha512-Yzu5eL3iGmZW0C/8x+vEojAOou63FI9oVw8HI8YOq63+5yM8g8aGh7Y1E2vbXFb7+gHGsPqLnaC6dPhrYt7qBA==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.87.1", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.87.1.tgz", - "integrity": "sha512-XvLtEznxmYZXA7LYuy5zbSXpSYjDLJq2wQeRh3MzON2OR4U8Kq+RtPz2E2Wi8HEzvBfsc+nNu1TG8LQ9+3DRkA==", - "license": "MIT", - "dependencies": { - "@types/phoenix": "^1.6.6", - "@types/ws": "^8.18.1", - "tslib": "2.8.1", - "ws": "^8.18.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/storage-js": { - "version": "2.87.1", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.87.1.tgz", - "integrity": "sha512-0Uc8tNV4yzkNNmp1inpXru0RB4a7ECq05G2S6BDvSpMxTxJrDVJ4vVDwyhqB8ZZ+O9+8prHaQYoByQeuDnwpFQ==", - "license": "MIT", - "dependencies": { - "iceberg-js": "^0.8.1", - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.87.1", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.87.1.tgz", - "integrity": "sha512-tVgqZqnHZVum584KuUKSQZgcy6ZkhVd6gG8QWg2QfIXH9HmXdamauxdVsLXwaNPJxEdOyfAfwIyi5XUsiVYWtg==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.87.1", - "@supabase/functions-js": "2.87.1", - "@supabase/postgrest-js": "2.87.1", - "@supabase/realtime-js": "2.87.1", - "@supabase/storage-js": "2.87.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -803,27 +1384,13 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/node": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.0.tgz", - "integrity": "sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/phoenix": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", - "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", - "license": "MIT" - }, "node_modules/@types/react": { "version": "19.2.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -838,15 +1405,6 @@ "@types/react": "^19.2.0" } }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@vitejs/plugin-react": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz", @@ -874,6 +1432,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -924,75 +1483,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/archiver": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", - "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", - "license": "MIT", - "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "license": "MIT", - "dependencies": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/archiver-utils/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/archiver-utils/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1000,12 +1490,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT" - }, "node_modules/babel-plugin-react-compiler": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", @@ -1020,26 +1504,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "dev": true, "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -1052,49 +1517,11 @@ "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "license": "Unlicense", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", - "license": "MIT", - "dependencies": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", - "license": "MIT" - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1121,6 +1548,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1135,56 +1563,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-indexof-polyfill": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", - "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", - "engines": { - "node": ">=0.2.0" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1216,18 +1594,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", - "license": "MIT/X11", - "dependencies": { - "traverse": ">=0.3.0 <0.4" - }, - "engines": { - "node": "*" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1265,25 +1631,11 @@ "dev": true, "license": "MIT" }, - "node_modules/compress-commons": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", - "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", - "license": "MIT", - "dependencies": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, "node_modules/convert-source-map": { @@ -1306,37 +1658,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/crc32-stream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", - "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", - "license": "MIT", - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1359,12 +1680,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1390,57 +1705,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "license": "BSD-3-Clause", - "dependencies": { - "readable-stream": "^2.0.2" - } - }, - "node_modules/duplexer2/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/duplexer2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/duplexer2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.266", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", @@ -1448,15 +1712,6 @@ "dev": true, "license": "ISC" }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -1528,6 +1783,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -1706,39 +1962,6 @@ "node": ">=0.10.0" } }, - "node_modules/exceljs": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", - "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", - "license": "MIT", - "dependencies": { - "archiver": "^5.0.0", - "dayjs": "^1.8.34", - "fast-csv": "^4.3.1", - "jszip": "^3.10.1", - "readable-stream": "^3.6.0", - "saxes": "^5.0.1", - "tmp": "^0.2.0", - "unzipper": "^0.10.11", - "uuid": "^8.3.0" - }, - "engines": { - "node": ">=8.3.0" - } - }, - "node_modules/fast-csv": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", - "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", - "license": "MIT", - "dependencies": { - "@fast-csv/format": "4.3.5", - "@fast-csv/parse": "4.3.6" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1829,32 +2052,19 @@ "dev": true, "license": "ISC" }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "node_modules/fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.6" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/gensync": { @@ -1867,27 +2077,6 @@ "node": ">=6.9.0" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1914,12 +2103,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1943,38 +2126,9 @@ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", "dev": true, "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" - } - }, - "node_modules/iceberg-js": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", - "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" + "dependencies": { + "hermes-estree": "0.25.1" + } }, "node_modules/ignore": { "version": "5.3.2", @@ -1986,12 +2140,6 @@ "node": ">= 4" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -2019,23 +2167,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2059,12 +2190,6 @@ "node": ">=0.10.0" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2139,48 +2264,6 @@ "node": ">=6" } }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/jszip/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/jszip/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/jszip/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2191,48 +2274,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lazystream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/lazystream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/lazystream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2247,21 +2288,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/listenercount": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", - "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", - "license": "ISC" - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2278,73 +2304,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "license": "MIT" - }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", - "license": "MIT" - }, - "node_modules/lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", - "license": "MIT" - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", - "license": "MIT" - }, - "node_modules/lodash.groupby": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", - "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", - "license": "MIT" - }, - "node_modules/lodash.isfunction": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", - "license": "MIT" - }, - "node_modules/lodash.isnil": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", - "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isundefined": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", - "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2352,18 +2311,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", - "license": "MIT" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "license": "MIT" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2378,6 +2325,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2386,27 +2334,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2440,26 +2367,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -2467,24 +2374,6 @@ "dev": true, "license": "MIT" }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2535,12 +2424,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2564,15 +2447,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2596,6 +2470,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2642,12 +2517,6 @@ "node": ">= 0.8.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2663,6 +2532,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -2672,6 +2542,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2727,50 +2598,6 @@ "react-dom": ">=18" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdir-glob": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", - "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.1.0" - } - }, - "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2781,19 +2608,6 @@ "node": ">=4" } }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/rollup": { "version": "4.53.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", @@ -2836,38 +2650,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -2890,12 +2672,6 @@ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2929,15 +2705,6 @@ "node": ">=0.10.0" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -2964,22 +2731,6 @@ "node": ">=8" } }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -2997,36 +2748,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "license": "MIT", - "engines": { - "node": ">=14.14" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", - "license": "MIT/X11", - "engines": { - "node": "*" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3044,55 +2765,9 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/unzipper": { - "version": "0.10.14", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", - "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", - "license": "MIT", - "dependencies": { - "big-integer": "^1.6.17", - "binary": "~0.3.0", - "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", - "duplexer2": "~0.1.4", - "fstream": "^1.0.12", - "graceful-fs": "^4.2.2", - "listenercount": "~1.0.1", - "readable-stream": "~2.3.6", - "setimmediate": "~1.0.4" - } - }, - "node_modules/unzipper/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/unzipper/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/unzipper/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } + "optional": true }, "node_modules/update-browserslist-db": { "version": "1.2.2", @@ -3135,27 +2810,13 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/vite": { "version": "7.2.6", "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz", "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -3225,22 +2886,6 @@ } } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3267,39 +2912,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "license": "MIT" - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -3320,47 +2932,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zip-stream": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", - "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", - "license": "MIT", - "dependencies": { - "archiver-utils": "^3.0.4", - "compress-commons": "^4.1.2", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/zip-stream/node_modules/archiver-utils": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", - "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", - "license": "MIT", - "dependencies": { - "glob": "^7.2.3", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/zod": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index eafa44f..8679454 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,32 @@ { "name": "vortex-app", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", - "description": "This Repository consists of a web app which handles attendee registration, team formation, project submission, real-time scorekeeping, and administrative tools", + "description": "V-Vortex - National Level Hackathon at VIT Chennai. A stunning showcase website for the ultimate innovation challenge.", "main": "index.js", "scripts": { "dev": "vite", "build": "vite build", "lint": "eslint .", - "preview": "vite preview", - "test": "echo \"Error: no test specified\" && exit 1" + "preview": "vite preview" }, "repository": { "type": "git", "url": "git+https://github.com/V-Vortex-VIT/V-Vortex-Hackathon.git" }, - "keywords": [], - "author": "", + "keywords": [ + "hackathon", + "vit-chennai", + "v-vortex" + ], + "author": "V-Vortex Team", "license": "ISC", "bugs": { "url": "https://github.com/V-Vortex-VIT/V-Vortex-Hackathon/issues" }, "homepage": "https://github.com/V-Vortex-VIT/V-Vortex-Hackathon#readme", "dependencies": { - "@supabase/supabase-js": "^2.87.1", - "exceljs": "^4.3.0", - "dotenv": "^17.2.3", - "node-fetch": "^2.7.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.10.1" @@ -44,4 +43,4 @@ "globals": "^16.5.0", "vite": "^7.2.4" } -} +} \ No newline at end of file diff --git a/scripts/create_admins.cjs b/scripts/create_admins.cjs deleted file mode 100644 index 17c957d..0000000 --- a/scripts/create_admins.cjs +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env node -// CommonJS version of scripts/create_admins.js for projects using "type": "module". -// Usage: -// 1. Add SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY to .env -// 2. npm install dotenv (if not installed) -// 3. node scripts/create_admins.cjs - -const dotenv = require('dotenv'); -dotenv.config(); - -const SUPABASE_URL = process.env.SUPABASE_URL || process.env.VITE_SUPABASE_URL || process.env.VITE_SUPABASE_URL; -const SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_SERVICE_ROLE || process.env.SUPABASE_SERVICE_ROLE_KEY; - -if (!SUPABASE_URL || !SERVICE_ROLE_KEY) { - console.error('Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY environment variables.'); - console.error('Set them (or add to a .env) then re-run the script.'); - process.exit(1); -} - -const admins = ['admin1','admin2','admin3','admin4','admin5']; - -async function createUser(username) { - const email = `${username}@v-vortex.in`; - const password = `${username}231225`; - - const url = SUPABASE_URL.replace(/\/$/, '') + '/auth/v1/admin/users'; - - const body = { - email, - password, - email_confirm: true, - user_metadata: { full_name: username }, - app_metadata: { role: 'admin' } - }; - - // Use global fetch if available (Node 18+), otherwise try node-fetch - const fetchFn = (typeof fetch !== 'undefined') ? fetch : require('node-fetch'); - - const res = await fetchFn(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${SERVICE_ROLE_KEY}`, - apikey: SERVICE_ROLE_KEY - }, - body: JSON.stringify(body) - }); - - let data; - try { data = await res.json(); } catch(e){ data = null; } - - if (res.ok) { - console.log(`Created ${username} -> ${email} password: ${password}`); - return { ok: true, user: data }; - } else { - console.error(`Failed to create ${username}:`, data || res.statusText); - return { ok: false, error: data }; - } -} - -async function main(){ - console.log('Creating admin users...'); - for (const name of admins) { - // eslint-disable-next-line no-await-in-loop - await createUser(name); - } - console.log('Done. Verify users in Supabase Auth dashboard.'); -} - -main().catch(err=>{ console.error(err); process.exit(1); }); diff --git a/scripts/create_admins.js b/scripts/create_admins.js deleted file mode 100644 index 27b681f..0000000 --- a/scripts/create_admins.js +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env node -// Create five admin users via Supabase Admin REST API. -// Usage: -// 1. Set env vars: SUPABASE_URL (e.g. https://xyz.supabase.co) and SUPABASE_SERVICE_ROLE_KEY -// 2. node scripts/create_admins.js -// Default emails: admin1@v-vortex.in ... admin5@v-vortex.in - -const dotenv = require('dotenv'); -dotenv.config(); - -const SUPABASE_URL = process.env.SUPABASE_URL || process.env.VITE_SUPABASE_URL; -const SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_SERVICE_ROLE; - -if (!SUPABASE_URL || !SERVICE_ROLE_KEY) { - console.error('Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY environment variables.'); - console.error('Set them (or add to a .env) then re-run the script.'); - process.exit(1); -} - -const admins = ['admin1','admin2','admin3','admin4','admin5']; - -async function createUser(username) { - const email = `${username}@v-vortex.in`; - const password = `${username}231225`; - - const url = SUPABASE_URL.replace(/\/$/, '') + '/auth/v1/admin/users'; - - const body = { - email, - password, - email_confirm: true, - user_metadata: { full_name: username }, - app_metadata: { role: 'admin' } - }; - - const res = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${SERVICE_ROLE_KEY}`, - apikey: SERVICE_ROLE_KEY - }, - body: JSON.stringify(body) - }); - - const data = await res.json().catch(()=>null); - if (res.ok) { - console.log(`Created ${username} -> ${email} password: ${password}`); - return { ok: true, user: data }; - } else { - console.error(`Failed to create ${username}:`, data || res.statusText); - return { ok: false, error: data }; - } -} - -async function main(){ - console.log('Creating admin users...'); - for (const name of admins) { - // eslint-disable-next-line no-await-in-loop - await createUser(name); - } - console.log('Done. Verify users in Supabase Auth dashboard.'); -} - -main().catch(err=>{ console.error(err); process.exit(1); }); diff --git a/scripts/update_missing_institution.sql b/scripts/update_missing_institution.sql deleted file mode 100644 index 5378903..0000000 --- a/scripts/update_missing_institution.sql +++ /dev/null @@ -1,54 +0,0 @@ --- Script: Find and update missing `institution` values (EventHub IDs) --- Usage: --- 1) Run the SELECT queries to list affected rows and their IDs. --- 2) For each affected `teams.id` or `team_members.id`, replace --- and run the corresponding UPDATE statement. --- 3) Optionally wrap updates in a transaction. - --- --- Find affected teams (non-VIT leaders missing institution) --- -SELECT id, team_name, lead_name, lead_email, is_vit_chennai, institution, lead_reg_no -FROM teams -WHERE (is_vit_chennai = false OR is_vit_chennai IS NULL) - AND (institution IS NULL OR TRIM(institution) = ''); - --- --- Find affected team_members (non-VIT members missing institution) --- -SELECT id, team_id, member_name, member_email, is_vit_chennai, institution, member_reg_no -FROM team_members -WHERE (is_vit_chennai = false OR is_vit_chennai IS NULL) - AND (institution IS NULL OR TRIM(institution) = ''); - --- --- UPDATE templates --- --- Replace with the actual EventHub Unique ID and / - --- Update a team by id: --- BEGIN; --- UPDATE teams --- SET institution = '' --- WHERE id = '' --- AND (institution IS NULL OR TRIM(institution) = ''); --- COMMIT; - --- Update a team_member by id: --- BEGIN; --- UPDATE team_members --- SET institution = '' --- WHERE id = '' --- AND (institution IS NULL OR TRIM(institution) = ''); --- COMMIT; - --- --- Bulk update example if you want to set the same EventHub ID for all affected rows --- --- NOTE: Use with caution — ensure this is correct for every affected row before running. --- BEGIN; --- UPDATE teams --- SET institution = '' --- WHERE (is_vit_chennai = false OR is_vit_chennai IS NULL) --- AND (institution IS NULL OR TRIM(institution) = ''); - --- UPDATE team_members --- SET institution = '' --- WHERE (is_vit_chennai = false OR is_vit_chennai IS NULL) --- AND (institution IS NULL OR TRIM(institution) = ''); --- COMMIT; - --- --- Verify updates --- --- Re-run the SELECTs above to confirm there are no remaining missing institutions. diff --git a/setup_scores.sql b/setup_scores.sql deleted file mode 100644 index b749750..0000000 --- a/setup_scores.sql +++ /dev/null @@ -1,93 +0,0 @@ --- Run this in Supabase SQL Editor to set up scores - --- Step 1: Create the leaderboard view with exact format needed by frontend -CREATE OR REPLACE VIEW leaderboard_view AS -SELECT - ROW_NUMBER() OVER (ORDER BY COALESCE(s.total_score, 0) DESC, t.team_name ASC) as position, - t.team_name, - COALESCE(s.total_score, 0) as score, - 0 as delta, -- Delta changes - will be calculated from historical data in future - t.id as team_id, - s.innovation_score, - s.implementation_score, - s.presentation_score, - s.impact_score, - COALESCE(s.total_score, 0) as total_score -FROM teams t -LEFT JOIN scorecards s ON t.id = s.team_id -ORDER BY COALESCE(s.total_score, 0) DESC, t.team_name ASC; - --- Step 2: Grant permissions -GRANT SELECT ON leaderboard_view TO authenticated; -GRANT SELECT ON leaderboard_view TO anon; - --- Step 3: Add test scores to all existing teams (OPTIONAL - for testing) --- Comment out if you don't want random scores -DO $$ -DECLARE - team_record RECORD; - innovation INT; - implementation INT; - presentation INT; - impact INT; -BEGIN - FOR team_record IN SELECT id, team_name FROM teams LOOP - -- Generate random scores between 65-95 for realistic data - innovation := FLOOR(RANDOM() * 30 + 65)::integer; - implementation := FLOOR(RANDOM() * 30 + 65)::integer; - presentation := FLOOR(RANDOM() * 30 + 65)::integer; - impact := FLOOR(RANDOM() * 30 + 65)::integer; - - INSERT INTO scorecards ( - team_id, - innovation_score, - implementation_score, - presentation_score, - impact_score, - judge_comments - ) - VALUES ( - team_record.id, - innovation, - implementation, - presentation, - impact, - 'Test scores - Team: ' || team_record.team_name - ) - ON CONFLICT (team_id) DO NOTHING; - END LOOP; - - RAISE NOTICE 'Test scores added successfully!'; -END $$; - --- Step 4: Verify the leaderboard -SELECT * FROM leaderboard_view LIMIT 10; - --- Step 5: (OPTIONAL) Add some historical data to see delta changes --- This simulates score updates to show delta in action --- Uncomment to create fake score progression: - -/* --- Wait a moment then update some scores to create history -DO $$ -DECLARE - team_record RECORD; -BEGIN - -- Update scores for top 3 teams to simulate progression - FOR team_record IN - SELECT id FROM scorecards ORDER BY total_score DESC LIMIT 3 - LOOP - -- Small score increase (simulate improvement) - UPDATE scorecards - SET - innovation_score = LEAST(100, innovation_score + FLOOR(RANDOM() * 10)::integer), - implementation_score = LEAST(100, implementation_score + FLOOR(RANDOM() * 10)::integer) - WHERE team_id = team_record.id; - END LOOP; - - RAISE NOTICE 'Score updates applied - deltas should now show changes!'; -END $$; - --- Check the leaderboard again to see deltas -SELECT position, team_name, score, delta FROM leaderboard_view LIMIT 10; -*/ diff --git a/src/App.jsx b/src/App.jsx index 622c44a..2ab80f1 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,17 +1,14 @@ import { useState } from "react"; -import { BrowserRouter, Routes, Route, useNavigate } from "react-router-dom"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; import Preloader from "./components/Preloader"; import PageTransition from "./components/PageTransition"; -import "./styles/dashboard.css"; - +import CustomCursor from "./components/CustomCursor"; import Home from "./pages/home"; -import Login from "./pages/login"; -import Register from "./pages/register"; -import OTP from "./pages/otp"; -import TeamDashboard from "./pages/dashboard"; -import Member from "./pages/member"; -import Admin from "./pages/admin"; + +import "./styles/design-tokens.css"; +import "./styles/animations.css"; +import "./styles/custom-cursor.css"; export default function App() { const [introDone, setIntroDone] = useState(false); @@ -19,7 +16,7 @@ export default function App() { return ( - + {transition} {!introDone ? ( @@ -27,17 +24,9 @@ export default function App() { ) : ( } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - + } /> )} - ); } diff --git a/src/components/AnimatedSection.jsx b/src/components/AnimatedSection.jsx new file mode 100644 index 0000000..32b2ab4 --- /dev/null +++ b/src/components/AnimatedSection.jsx @@ -0,0 +1,53 @@ +import { useEffect, useRef, useState } from 'react'; + +export default function AnimatedSection({ + children, + className = '', + animation = 'reveal-up', + delay = 0, + threshold = 0.1, + triggerOnce = true +}) { + const sectionRef = useRef(null); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + if (triggerOnce) { + observer.unobserve(entry.target); + } + } else if (!triggerOnce) { + setIsVisible(false); + } + }, + { threshold, rootMargin: '0px 0px -50px 0px' } + ); + + if (sectionRef.current) { + observer.observe(sectionRef.current); + } + + return () => { + if (sectionRef.current) { + observer.unobserve(sectionRef.current); + } + }; + }, [threshold, triggerOnce]); + + // Build class names - CSS handles all animation states + const animationClass = `anim-${animation}`; + const visibleClass = isVisible ? 'is-visible' : ''; + + return ( +
+ {children} +
+ ); +} diff --git a/src/components/BuildTeam.jsx b/src/components/BuildTeam.jsx deleted file mode 100644 index 674cb72..0000000 --- a/src/components/BuildTeam.jsx +++ /dev/null @@ -1,279 +0,0 @@ -import { useState } from "react"; -import { supabase } from "../supabaseClient"; -import { buildTeam } from "../utils/buildTeam"; -import "../styles/build-team.css"; - -export default function BuildTeam({ teamId, onTeamBuilt, currentTeamName, hasMembers, team, teamMembers }) { - const [teamName, setTeamName] = useState(""); - const [teamSize, setTeamSize] = useState(2); - const [members, setMembers] = useState([ - { name: "", email: "", isVitChennai: "yes", regNo: "", eventHubId: "" }, - ]); - const [isSubmitting, setIsSubmitting] = useState(false); - const [error, setError] = useState(""); - - // Check if team is already built — show team details instead of generic message - if (hasMembers) { - const displayTeamName = currentTeamName || team?.team_name || "Unnamed Team"; - const leadName = team?.lead_name || "Team Leader"; - const leadEmail = team?.lead_email || "-"; - - return ( -
-
-

✅ Team Already Built

-

Your team has already been built. Below are the team details.

- -
-
- -
{displayTeamName}
-
- -
- -
{leadName} — {leadEmail}
-
- -
-

Team Members

- {(teamMembers || []).length === 0 ? ( -
No additional members found.
- ) : ( - (teamMembers || []).map((m, idx) => ( -
- {m.member_name || m.name || `Member ${idx + 1}`} -
{m.member_email || m.email || "-"}
-
- )) - )} -
-
-
-
- ); - } - - const handleTeamSizeChange = (size) => { - setTeamSize(size); - const memberCount = size - 1; // Subtract 1 for the team leader - - if (memberCount < members.length) { - setMembers(members.slice(0, memberCount)); - } else { - const newMembers = [...members]; - for (let i = members.length; i < memberCount; i++) { - newMembers.push({ name: "", email: "", isVitChennai: "yes", regNo: "", eventHubId: "" }); - } - setMembers(newMembers); - } - }; - - const handleMemberChange = (index, field, value) => { - const newMembers = [...members]; - newMembers[index][field] = value; - - // Clear the opposite field when toggling - if (field === "isVitChennai") { - if (value === "yes") { - newMembers[index].eventHubId = ""; - } else { - newMembers[index].regNo = ""; - } - } - - setMembers(newMembers); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - setError(""); - setIsSubmitting(true); - - try { - // Validate team name - if (!teamName.trim()) { - throw new Error("Please enter a team name"); - } - - // Validate all members - for (let i = 0; i < members.length; i++) { - const member = members[i]; - if (!member.name.trim()) { - throw new Error(`Please enter name for Member ${i + 1}`); - } - if (!member.email.trim()) { - throw new Error(`Please enter email for Member ${i + 1}`); - } - if (member.isVitChennai === "yes" && !member.regNo.trim()) { - throw new Error(`Please enter registration number for Member ${i + 1}`); - } - if (member.isVitChennai === "no" && !member.eventHubId.trim()) { - throw new Error(`Please enter EventHub ID for Member ${i + 1}`); - } - } - - // Call the build-team edge function via helper (sends Authorization header) - await buildTeam({ - teamId, - teamName, - teamSize, - members: members.map((m) => ({ - name: m.name, - email: m.email, - isVitChennai: m.isVitChennai === "yes", - regNo: m.isVitChennai === "yes" ? m.regNo : null, - eventHubId: m.isVitChennai === "no" ? m.eventHubId : null, - })), - }); - - // Success! - alert("✅ Team successfully built! Your team is now complete."); - if (onTeamBuilt) onTeamBuilt(); - } catch (err) { - console.error("Build team error:", err); - setError(err.message || "Failed to build team. Please try again."); - } finally { - setIsSubmitting(false); - } - }; - - return ( -
-
-

🔨 Build Your Team

-

- Set your team name, choose team size (2-5 members including you), and add your team members. -

- - {error && ( -
- ⚠️ {error} -
- )} - -
- {/* Team Name */} -
- - setTeamName(e.target.value)} - required - /> -
- - {/* Team Size */} -
- -
- {[2, 3, 4, 5].map((size) => ( - - ))} -
-
- - {/* Team Members */} -
-

Team Members (excluding you)

- - {members.map((member, index) => ( -
-
Member {index + 1}
- -
- - handleMemberChange(index, "name", e.target.value)} - required - /> -
- -
- - handleMemberChange(index, "email", e.target.value)} - required - /> -
- -
- -
- - -
-
- - {member.isVitChennai === "yes" ? ( -
- - handleMemberChange(index, "regNo", e.target.value)} - required - /> -
- ) : ( -
- - handleMemberChange(index, "eventHubId", e.target.value)} - required - /> -
- )} -
- ))} -
- - -
-
-
- ); -} diff --git a/src/components/CustomCursor.jsx b/src/components/CustomCursor.jsx new file mode 100644 index 0000000..0643c7c --- /dev/null +++ b/src/components/CustomCursor.jsx @@ -0,0 +1,144 @@ +import { useEffect, useRef, useState } from 'react'; + +export default function CustomCursor() { + const cursorRef = useRef(null); + const ringRef = useRef(null); + const trailRefs = useRef([]); + const [isHovering, setIsHovering] = useState(false); + const [isClicking, setIsClicking] = useState(false); + const [isHidden, setIsHidden] = useState(false); + const mousePos = useRef({ x: 0, y: 0 }); + const ringPos = useRef({ x: 0, y: 0 }); + const trailPositions = useRef([]); + const rafId = useRef(null); + + useEffect(() => { + // Check for touch device + const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; + if (isTouchDevice) return; + + // Initialize trail positions + trailPositions.current = Array(5).fill({ x: 0, y: 0 }); + + const handleMouseMove = (e) => { + mousePos.current = { x: e.clientX, y: e.clientY }; + + // Move main cursor immediately + if (cursorRef.current) { + cursorRef.current.style.left = `${e.clientX}px`; + cursorRef.current.style.top = `${e.clientY}px`; + } + }; + + const handleMouseDown = () => setIsClicking(true); + const handleMouseUp = () => setIsClicking(false); + + const handleMouseEnter = () => setIsHidden(false); + const handleMouseLeave = () => setIsHidden(true); + + // Check for hoverable elements + const handleElementHover = (e) => { + const target = e.target; + const isInteractive = + target.tagName === 'A' || + target.tagName === 'BUTTON' || + target.closest('a') || + target.closest('button') || + target.closest('[role="button"]') || + target.closest('[data-cursor="hover"]') || + target.classList.contains('nav-btn') || + target.classList.contains('cta') || + target.classList.contains('domain-card') || + target.classList.contains('round-card'); + + setIsHovering(isInteractive); + }; + + // Animation loop for smooth ring follow + const animate = () => { + // Smooth ring follow - reduced lerp for smoother movement + if (ringRef.current) { + ringPos.current.x += (mousePos.current.x - ringPos.current.x) * 0.08; + ringPos.current.y += (mousePos.current.y - ringPos.current.y) * 0.08; + ringRef.current.style.left = `${ringPos.current.x}px`; + ringRef.current.style.top = `${ringPos.current.y}px`; + } + + // Update trail + trailPositions.current = trailPositions.current.map((pos, i) => { + const prevPos = i === 0 ? mousePos.current : trailPositions.current[i - 1]; + return { + x: pos.x + (prevPos.x - pos.x) * (0.3 - i * 0.04), + y: pos.y + (prevPos.y - pos.y) * (0.3 - i * 0.04) + }; + }); + + trailRefs.current.forEach((trail, i) => { + if (trail && trailPositions.current[i]) { + trail.style.left = `${trailPositions.current[i].x}px`; + trail.style.top = `${trailPositions.current[i].y}px`; + } + }); + + rafId.current = requestAnimationFrame(animate); + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mousemove', handleElementHover); + document.addEventListener('mousedown', handleMouseDown); + document.addEventListener('mouseup', handleMouseUp); + document.addEventListener('mouseenter', handleMouseEnter); + document.addEventListener('mouseleave', handleMouseLeave); + + animate(); + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mousemove', handleElementHover); + document.removeEventListener('mousedown', handleMouseDown); + document.removeEventListener('mouseup', handleMouseUp); + document.removeEventListener('mouseenter', handleMouseEnter); + document.removeEventListener('mouseleave', handleMouseLeave); + if (rafId.current) cancelAnimationFrame(rafId.current); + }; + }, []); + + // Check for touch device + if (typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0)) { + return null; + } + + const cursorClasses = [ + 'vortex-cursor', + isHovering && 'cursor-hover', + isClicking && 'cursor-click', + isHidden && 'cursor-hidden' + ].filter(Boolean).join(' '); + + const ringClasses = [ + 'vortex-cursor-ring', + isHovering && 'cursor-hover', + isClicking && 'cursor-click', + isHidden && 'cursor-hidden' + ].filter(Boolean).join(' '); + + return ( + <> + {/* Trail dots */} + {[...Array(5)].map((_, i) => ( +
trailRefs.current[i] = el} + className="cursor-trail" + style={{ opacity: 0.5 - i * 0.1 }} + /> + ))} + + {/* Main cursor ring */} +
+ + {/* Main cursor dot */} +
+ + ); +} diff --git a/src/components/ParticleBackground.jsx b/src/components/ParticleBackground.jsx new file mode 100644 index 0000000..8743146 --- /dev/null +++ b/src/components/ParticleBackground.jsx @@ -0,0 +1,171 @@ +import { useEffect, useRef } from 'react'; + +export default function ParticleBackground() { + const canvasRef = useRef(null); + const animationRef = useRef(null); + const mouseRef = useRef({ x: null, y: null }); + const particlesRef = useRef([]); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + let width = window.innerWidth; + let height = window.innerHeight; + + const resize = () => { + width = window.innerWidth; + height = window.innerHeight; + canvas.width = width; + canvas.height = height; + }; + + resize(); + window.addEventListener('resize', resize); + + // Particle class + class Particle { + constructor() { + this.reset(); + } + + reset() { + this.x = Math.random() * width; + this.y = Math.random() * height; + this.size = Math.random() * 2 + 0.5; + this.speedX = (Math.random() - 0.5) * 0.5; + this.speedY = (Math.random() - 0.5) * 0.5; + this.opacity = Math.random() * 0.5 + 0.2; + + // Color variation + const colors = [ + { r: 139, g: 92, b: 246 }, // Violet + { r: 6, g: 182, b: 212 }, // Cyan + { r: 236, g: 72, b: 153 } // Pink + ]; + this.color = colors[Math.floor(Math.random() * colors.length)]; + } + + update() { + this.x += this.speedX; + this.y += this.speedY; + + // Mouse interaction + if (mouseRef.current.x !== null && mouseRef.current.y !== null) { + const dx = mouseRef.current.x - this.x; + const dy = mouseRef.current.y - this.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < 150) { + const force = (150 - distance) / 150; + this.x -= (dx / distance) * force * 2; + this.y -= (dy / distance) * force * 2; + } + } + + // Wrap around edges + if (this.x < 0) this.x = width; + if (this.x > width) this.x = 0; + if (this.y < 0) this.y = height; + if (this.y > height) this.y = 0; + } + + draw() { + ctx.beginPath(); + ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); + ctx.fillStyle = `rgba(${this.color.r}, ${this.color.g}, ${this.color.b}, ${this.opacity})`; + ctx.fill(); + } + } + + // Initialize particles - reduced count for better performance + const particleCount = Math.min(100, Math.floor((width * height) / 15000)); + particlesRef.current = Array.from({ length: particleCount }, () => new Particle()); + + // Draw connections between nearby particles + const drawConnections = () => { + for (let i = 0; i < particlesRef.current.length; i++) { + for (let j = i + 1; j < particlesRef.current.length; j++) { + const dx = particlesRef.current[i].x - particlesRef.current[j].x; + const dy = particlesRef.current[i].y - particlesRef.current[j].y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < 120) { + const opacity = (1 - distance / 120) * 0.15; + ctx.beginPath(); + ctx.strokeStyle = `rgba(139, 92, 246, ${opacity})`; + ctx.lineWidth = 0.5; + ctx.moveTo(particlesRef.current[i].x, particlesRef.current[i].y); + ctx.lineTo(particlesRef.current[j].x, particlesRef.current[j].y); + ctx.stroke(); + } + } + } + }; + + // Delta-time based animation for consistent speed across refresh rates + let lastTime = 0; + const targetFPS = 60; + const frameInterval = 1000 / targetFPS; + + // Animation loop with frame timing + const animate = (currentTime) => { + const deltaTime = currentTime - lastTime; + + if (deltaTime >= frameInterval) { + lastTime = currentTime - (deltaTime % frameInterval); + + ctx.clearRect(0, 0, width, height); + + particlesRef.current.forEach(particle => { + particle.update(); + particle.draw(); + }); + + drawConnections(); + } + + animationRef.current = requestAnimationFrame(animate); + }; + + // Mouse tracking + const handleMouseMove = (e) => { + mouseRef.current = { x: e.clientX, y: e.clientY }; + }; + + const handleMouseLeave = () => { + mouseRef.current = { x: null, y: null }; + }; + + window.addEventListener('mousemove', handleMouseMove); + window.addEventListener('mouseleave', handleMouseLeave); + + animate(); + + return () => { + window.removeEventListener('resize', resize); + window.removeEventListener('mousemove', handleMouseMove); + window.removeEventListener('mouseleave', handleMouseLeave); + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + }; + }, []); + + return ( + + ); +} diff --git a/src/components/Preloader.jsx b/src/components/Preloader.jsx index 3cd131a..e8830ce 100644 --- a/src/components/Preloader.jsx +++ b/src/components/Preloader.jsx @@ -27,11 +27,11 @@ export default function Preloader({ onFinished }) { e.stopPropagation(); return false; }; - + window.addEventListener('keydown', blockKeyboard, true); window.addEventListener('keyup', blockKeyboard, true); window.addEventListener('keypress', blockKeyboard, true); - + return () => { window.removeEventListener('keydown', blockKeyboard, true); window.removeEventListener('keyup', blockKeyboard, true); @@ -185,9 +185,9 @@ export default function Preloader({ onFinished }) { preload="auto" onEnded={handleEnd} onLoadedData={(e) => { - // smooth opacity fade-in once video data is ready + // smooth opacity fade-in once video data is ready via CSS class setTimeout(() => { - if (e && e.target) e.target.style.opacity = 1; + if (e && e.target) e.target.classList.add('video-loaded'); }, 100); }} // Disable all video controls @@ -200,7 +200,7 @@ export default function Preloader({ onFinished }) { onPause={(e) => { // If user somehow pauses, immediately resume if (videoRef.current && !finishedRef.current) { - videoRef.current.play().catch(() => {}); + videoRef.current.play().catch(() => { }); } }} src="/secondpart_fixed.mp4" diff --git a/src/components/icons/index.jsx b/src/components/icons/index.jsx new file mode 100644 index 0000000..895d017 --- /dev/null +++ b/src/components/icons/index.jsx @@ -0,0 +1,156 @@ +// Custom SVG Icons for V-Vortex +// Unique brand icons with consistent styling + +export const VortexIcon = ({ size = 24, className = '' }) => ( + + + + + + + + + + + + +); + +export const RocketIcon = ({ size = 24, className = '' }) => ( + + + + + + +); + +export const AIIcon = ({ size = 24, className = '' }) => ( + + + + + + + + + + + + +); + +export const ShieldIcon = ({ size = 24, className = '' }) => ( + + + + + +); + +export const HeartPulseIcon = ({ size = 24, className = '' }) => ( + + + + + +); + +export const CoinIcon = ({ size = 24, className = '' }) => ( + + + + + + + +); + +export const ChipIcon = ({ size = 24, className = '' }) => ( + + + + + + + + + + + + + +); + +export const CalendarIcon = ({ size = 24, className = '' }) => ( + + + + + + + + +); + +export const MapPinIcon = ({ size = 24, className = '' }) => ( + + + + + +); + +export const TrophyIcon = ({ size = 24, className = '' }) => ( + + + + + + + + + +); + +export const ArrowRightIcon = ({ size = 24, className = '' }) => ( + + + + +); + +export const SparkleIcon = ({ size = 24, className = '' }) => ( + + + + + +); + +export const LightningIcon = ({ size = 24, className = '' }) => ( + + + +); diff --git a/src/index.css b/src/index.css index 68dab2d..62c976d 100644 --- a/src/index.css +++ b/src/index.css @@ -6,38 +6,52 @@ } /* Force full black background everywhere */ -html, body { +html, +body { background: #000 !important; - height: 100%; + min-height: 100%; overflow-x: hidden; font-family: "Share Tech Mono", monospace; color: #fcfbfb; } -:root{ +:root { --container-padding: 16px; /* responsive base font size */ font-size: clamp(14px, 1.6vw, 16px); } /* helpers: fluid container and touch-friendly controls */ -.container{ +.container { max-width: 1200px; margin: 0 auto; padding: 0 var(--container-padding); } -button, .navBtn, input[type="text"], input[type="email"], input[type="password"], .form-control{ +button, +.navBtn, +input[type="text"], +input[type="email"], +input[type="password"], +.form-control { min-height: 44px; font-size: 1rem; -webkit-tap-highlight-color: transparent; touch-action: manipulation; } -input, textarea, select { width: 100%; } +input, +textarea, +select { + width: 100%; +} /* make images responsive by default */ -img { max-width: 100%; height: auto; display: block; } +img { + max-width: 100%; + height: auto; + display: block; +} /* ---------------------- */ /* WRAPPER */ @@ -46,7 +60,8 @@ img { max-width: 100%; height: auto; display: block; } background-color: #000; width: 100%; min-height: 100vh; - background: #000; /* vortex canvas shows through */ + background: #000; + /* vortex canvas shows through */ display: flex; flex-direction: column; align-items: center; @@ -182,4 +197,4 @@ img { max-width: 100%; height: auto; display: block; } .aboutSection { width: 92%; } -} +} \ No newline at end of file diff --git a/src/pages/admin.jsx b/src/pages/admin.jsx deleted file mode 100644 index 42b7d21..0000000 --- a/src/pages/admin.jsx +++ /dev/null @@ -1,1027 +0,0 @@ -import { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { supabase } from "../supabaseClient"; -import "../styles/admin.css"; -import "../styles/admin_mobile_fix.css"; - -export default function AdminDashboard() { - const navigate = useNavigate(); - const [checking, setChecking] = useState(true); - const [isAdmin, setIsAdmin] = useState(false); - const [unauthorized, setUnauthorized] = useState(false); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [authLoading, setAuthLoading] = useState(false); - const [authError, setAuthError] = useState(""); - const [exporting, setExporting] = useState(false); - const [teams, setTeams] = useState([]); - const [teamQuery, setTeamQuery] = useState(''); - const [domainFilter, setDomainFilter] = useState(''); - const [leaderboard, setLeaderboard] = useState([]); - const [lbLoading, setLbLoading] = useState(false); - const [leaderboardPublic, setLeaderboardPublic] = useState(false); - const [lbToggleLoading, setLbToggleLoading] = useState(false); - const [selectedTeam, setSelectedTeam] = useState(null); - const [review1Open, setReview1Open] = useState(false); - const [review1Scores, setReview1Scores] = useState({}); - const [review2Open, setReview2Open] = useState(false); - const [review2Scores, setReview2Scores] = useState({}); - const [review3Open, setReview3Open] = useState(false); - const [review3Scores, setReview3Scores] = useState({}); - const [ideaOpen, setIdeaOpen] = useState(false); - const [ideaScores, setIdeaScores] = useState({}); - const [pitchOpen, setPitchOpen] = useState(false); - const [pitchScores, setPitchScores] = useState({}); - const [scoringOpen, setScoringOpen] = useState(false); - // Slider maximums are fixed constants and must not be edited from the admin UI. - const SLIDER_MAX = { - idea: { requirement_check: 20, solution_logic: 15, feasibility: 15 }, - review1: { architecture_flow: 20, current_progress: 20, qa_defense: 10 }, - review2: { mvp_functionality: 25, ui_usability: 15, code_quality: 10 }, - review3: { final_demo: 40, innovation: 30, business_impact: 30 }, - pitch: { pitch_vortex: 100 } - }; - - useEffect(() => { - const checkAdmin = async () => { - setChecking(true); - setUnauthorized(false); - try { - const { data: { user } } = await supabase.auth.getUser(); - const { data: { session } } = await supabase.auth.getSession(); - - if (!user || !session) { - // not signed in -> show login form - setIsAdmin(false); - setChecking(false); - return; - } - - const role = user?.app_metadata?.role; - if (role === "admin") { - setIsAdmin(true); - } else { - setUnauthorized(true); - setIsAdmin(false); - } - } catch (err) { - console.error("admin check error:", err); - } finally { - setChecking(false); - } - }; - - checkAdmin(); - }, [navigate]); - - // Load teams when admin is confirmed - useEffect(() => { - if (!isAdmin) return; - const loadTeams = async () => { - try { - const { data, error } = await supabase - .from('teams') - .select('id, team_name, lead_name, lead_email, domain, problem_statement, team_members(member_name)') - .order('team_name', { ascending: true }); - if (error) throw error; - setTeams(data || []); - } catch (err) { - console.error('Failed to load teams for admin:', err); - setTeams([]); - } - }; - - loadTeams(); - }, [isAdmin]); - - - - // Helper to refresh leaderboard from the DB - const refreshLeaderboard = async () => { - setLbLoading(true); - try { - // Try secure RPC function first, fallback to direct view access - let data, error; - ({ data, error } = await supabase.rpc('get_leaderboard_secure')); - - // If RPC fails (function doesn't exist or error), fallback to direct view - if (error) { - console.warn('RPC failed, trying direct view access:', error.message); - ({ data, error } = await supabase.from('leaderboard_view').select('*').order('position', { ascending: true })); - } - - if (error) throw error; - setLeaderboard(data || []); - } catch (err) { - console.error('Failed to refresh leaderboard:', err); - setLeaderboard([]); - } finally { - setLbLoading(false); - } - }; - -// ...existing code... - - // Load leaderboard when admin is confirmed - useEffect(() => { - if (!isAdmin) return; - refreshLeaderboard(); - }, [isAdmin]); - - // Load leaderboard visibility setting - useEffect(() => { - if (!isAdmin) return; - const loadSetting = async () => { - try { - const { data, error } = await supabase.from('app_settings').select('leaderboard_public').eq('id', 'main').single(); - if (!error && data) setLeaderboardPublic(!!data.leaderboard_public); - } catch (err) { - console.error('Failed to load leaderboard visibility setting:', err); - } - }; - loadSetting(); - }, [isAdmin]); - - // Toggle leaderboard visibility - const toggleLeaderboardPublic = async () => { - setLbToggleLoading(true); - try { - const newVal = !leaderboardPublic; - const { error } = await supabase.from('app_settings').update({ leaderboard_public: newVal, updated_at: new Date().toISOString() }).eq('id', 'main'); - if (error) throw error; - setLeaderboardPublic(newVal); - } catch (err) { - console.error('Failed to toggle leaderboard visibility:', err); - alert('Failed to update setting: ' + (err.message || err)); - } finally { - setLbToggleLoading(false); - } - }; - - // Sign in handler for admin access via URL only (no button on main pages) - const handleSignIn = async (e) => { - e.preventDefault(); - setAuthLoading(true); - setAuthError(""); - try { - const { error } = await supabase.auth.signInWithPassword({ email, password }); - if (error) { - setAuthError(error.message || "Sign-in failed"); - setAuthLoading(false); - return; - } - - // Re-check session/role - const { data: { session } } = await supabase.auth.getSession(); - const role = session?.user?.app_metadata?.role; - if (role === "admin") { - setIsAdmin(true); - setUnauthorized(false); - } else { - setUnauthorized(true); - } - } catch (err) { - console.error(err); - setAuthError("Unexpected error during sign-in"); - } finally { - setAuthLoading(false); - } - }; - - const handleSignOut = async () => { - await supabase.auth.signOut(); - setIsAdmin(false); - navigate("/"); - }; - - const handleExport = async () => { - setExporting(true); - try { - // Lazy-load exceljs to avoid adding it to the main bundle - const ExcelJS = (await import('exceljs')).default; - // Fetch teams with their members (exclude ids) - const { data, error } = await supabase - .from('teams') - .select('team_name, team_size, lead_name, lead_email, lead_reg_no, is_vit_chennai, institution, receipt_link, team_members(member_name, member_reg_no, member_email, institution)'); - - if (error) throw error; - - // Build XLSX rows (one row per member; if no members, include single row with empty member fields) - const header = [ - 'Team Name', 'Team Size', - 'Lead Name', 'Lead Email', 'Lead Reg No', 'Lead Is VIT Chennai', - 'Team Institution', 'Payment Link', - 'Member Name', 'Member Email', 'Member Reg No', 'Member Is VIT Chennai' - ]; - - const rows = []; - (data || []).forEach(team => { - const leadIsVit = !!team.is_vit_chennai || !!team.lead_reg_no; - const members = team.team_members || []; - if (members.length === 0) { - rows.push([ - team.team_name || '', team.team_size || '', - team.lead_name || '', team.lead_email || '', team.lead_reg_no || '', leadIsVit, - team.institution || '', team.receipt_link || '', '', '', '' - ]); - } else { - members.forEach(m => { - const memberIsVit = (m.institution === 'VIT Chennai') || !!m.member_reg_no; - rows.push([ - team.team_name || '', team.team_size || '', - team.lead_name || '', team.lead_email || '', team.lead_reg_no || '', leadIsVit, - team.institution || '', team.receipt_link || '', m.member_name || '', m.member_email || '', m.member_reg_no || '', !!memberIsVit - ]); - }); - } - }); - - // Use ExcelJS to generate the workbook in-memory - const workbook = new ExcelJS.Workbook(); - const worksheet = workbook.addWorksheet('Teams'); - worksheet.addRow(header); - rows.forEach(r => worksheet.addRow(r)); - - // Turn Payment Link column into clickable hyperlinks (column 8 after adding Team Institution) - const paymentCol = 8; - worksheet.eachRow((row, rowNumber) => { - if (rowNumber === 1) return; // skip header - const cell = row.getCell(paymentCol); - const val = cell.value; - if (typeof val === 'string' && val.trim()) { - cell.value = { text: val, hyperlink: val }; - cell.font = { color: { argb: 'FF0000FF' }, underline: true }; - } - }); - - const buffer = await workbook.xlsx.writeBuffer(); - const blob = new Blob([buffer], { type: 'application/octet-stream' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - const now = new Date().toISOString().slice(0,10); - a.download = `teams_export_${now}.xlsx`; - document.body.appendChild(a); - a.click(); - a.remove(); - URL.revokeObjectURL(url); - } catch (err) { - console.error('Export error', err); - alert('Failed to export teams: ' + (err.message || err)); - } finally { - setExporting(false); - } - }; - - const handleReview1Change = (teamId, key, value) => { - setReview1Scores(prev => { - const teamScores = prev[teamId] || { architecture_flow:0, current_progress:0, qa_defense:0, review_1_total:0 }; - const updated = { ...teamScores, [key]: Number(value) }; - const total = (Number(updated.architecture_flow||0) + Number(updated.current_progress||0) + Number(updated.qa_defense||0)); - return { ...prev, [teamId]: { ...updated, review_1_total: total } }; - }); - }; - - const handleReview2Change = (teamId, key, value) => { - setReview2Scores(prev => { - const teamScores = prev[teamId] || { mvp_functionality:0, ui_usability:0, code_quality:0, review_2_total:0 }; - const updated = { ...teamScores, [key]: Number(value) }; - const total = (Number(updated.mvp_functionality||0) + Number(updated.ui_usability||0) + Number(updated.code_quality||0)); - return { ...prev, [teamId]: { ...updated, review_2_total: total } }; - }); - }; - - const closeReview2 = () => setReview2Open(false); - - const handleReview3Change = (teamId, key, value) => { - setReview3Scores(prev => { - const teamScores = prev[teamId] || { final_demo:0, innovation:0, business_impact:0, review_3_total:0 }; - const updated = { ...teamScores, [key]: Number(value) }; - const total = (Number(updated.final_demo||0) + Number(updated.innovation||0) + Number(updated.business_impact||0)); - return { ...prev, [teamId]: { ...updated, review_3_total: total } }; - }); - }; - - const closeReview3 = () => setReview3Open(false); - - const handleIdeaChange = (teamId, key, value) => { - setIdeaScores(prev => { - const teamScores = prev[teamId] || { requirement_check:0, solution_logic:0, feasibility:0, idea_total:0 }; - const updated = { ...teamScores, [key]: Number(value) }; - const total = (Number(updated.requirement_check||0) + Number(updated.solution_logic||0) + Number(updated.feasibility||0)); - return { ...prev, [teamId]: { ...updated, idea_total: total } }; - }); - }; - - const closeIdea = () => setIdeaOpen(false); - - const closeReview1 = () => setReview1Open(false); - - if (checking) return

Checking credentials…

; - - if (!isAdmin) { - // If unauthorized user signed in - if (unauthorized) { - return ( -
- - -
-

Access Denied

-

You are signed in but do not have admin permissions.

-
-
- ); - } - - // Not signed in -> show login form (admins must access via URL) - return ( -
- - -
-

Admin Login

-
-
- - setEmail(e.target.value)} type="email" required style={{width:'100%', padding:8, borderRadius:6}} /> -
-
- - setPassword(e.target.value)} type="password" required style={{width:'100%', padding:8, borderRadius:6}} /> -
- {authError &&
{authError}
} - -
-
-
- ); - } - - // isAdmin === true -> render dashboard - return ( -
- - {/* TOP NAVBAR */} - - - {/* MAIN GRID */} -
- -
-

Active Users

-

128

-
- -
-

Pending Requests

-

7

-
- -
-

System Health

-

Optimal

-
- -
-

Security Alerts

-

0

-
- -
-

Scoring Settings

-

- Configure per-slider maximums used by all scoring panels. -

-
- -
-
- -
- - {/* LARGE PANEL BELOW */} -
-
-

Teams {teams.length} total

-
- setTeamQuery(e.target.value)} - style={{flex:1, padding:8, borderRadius:6, border:'1px solid rgba(255,255,255,0.06)'}} - /> -
- - - {(teamQuery || domainFilter) && ( - - )} -
-
-
- {teams.length === 0 &&
No teams found.
} - {teams - .filter(t => { - // text search filter - if (teamQuery) { - try { - if (!((t.team_name || '').toLowerCase().includes(teamQuery.toLowerCase()))) return false; - } catch (e) { /* ignore */ } - } - - // domain filter - if (domainFilter) { - if (domainFilter === 'null') { - if (t.domain) return false; - } else { - if (t.domain !== domainFilter) return false; - } - } - - return true; - }) - .map(t => ( -
setSelectedTeam(t)} style={{padding:10, borderRadius:6, cursor:'pointer', marginBottom:8, background: selectedTeam?.id === t.id ? 'rgba(0,255,157,0.07)' : 'transparent'}}> - {t.team_name || '(Unnamed)'} -
{t.lead_name || t.lead_email || ''}
-
- ))} -
- -
-

- Leaderboard -
- - -
-

-
- {leaderboard.length === 0 && !lbLoading &&
No leaderboard data.
} - {lbLoading &&
Loading…
} - {leaderboard.map((row, idx) => ( -
-
-
{row.position}
-
{row.team_name}
-
-
{row.score ?? '—'}
-
- ))} -
-
- -
- - {ideaOpen && selectedTeam && ( -
-
-
-

IdeaVortex — {selectedTeam.team_name}

-
- -
-
- -
Score each category using the sliders. Total is out of {Object.values(SLIDER_MAX.idea).reduce((a,b)=>a+Number(b||0),0)}.
- - {(() => { - const scores = ideaScores[selectedTeam.id] || { requirement_check:0, solution_logic:0, feasibility:0, idea_total:0 }; - const total = scores.idea_total ?? (Number(scores.requirement_check||0) + Number(scores.solution_logic||0) + Number(scores.feasibility||0)); - return ( -
-
-
Requirement Check
- handleIdeaChange(selectedTeam.id, 'requirement_check', e.target.value)} /> -
{scores.requirement_check} / {SLIDER_MAX.idea.requirement_check}
-
- -
-
Solution Logic
- handleIdeaChange(selectedTeam.id, 'solution_logic', e.target.value)} /> -
{scores.solution_logic} / {SLIDER_MAX.idea.solution_logic}
-
- -
-
Feasibility
- handleIdeaChange(selectedTeam.id, 'feasibility', e.target.value)} /> -
{scores.feasibility} / {SLIDER_MAX.idea.feasibility}
-
- -
-
- IdeaVortex Total: {total} / {Object.values(SLIDER_MAX.idea).reduce((a,b)=>a+Number(b||0),0)} -
Weighted: {Math.round(Number(total) * 0.6)}
-
-
- - -
-
-
- ); - })()} -
-
- )} - -
-

Team Details

- {!selectedTeam && ( -
Select a team from the left to view details.
- )} - - {selectedTeam && ( -
-
-

{selectedTeam.team_name}

-

Leader: {selectedTeam.lead_name || selectedTeam.lead_email}

-

Domain: {selectedTeam.domain ?? 'null'}

-

Problem Statement: {selectedTeam.problem_statement ?? 'null'}

-
-
Team Members
- {(selectedTeam.team_members || []).length === 0 &&
No members listed.
} -
    - {(selectedTeam.team_members || []).map((m, i) => ( -
  • {m.member_name || 'Unnamed Member'}
  • - ))} -
-
- -
-
{ if(selectedTeam) { setIdeaOpen(true); setIdeaScores(prev=>({ ...prev, [selectedTeam.id]: prev[selectedTeam.id] ?? { requirement_check:0, solution_logic:0, feasibility:0, idea_total:0 } })) } }} style={{cursor: selectedTeam ? 'pointer' : 'not-allowed', flex:'1 1 180px', minWidth:140, background:'rgba(255,255,255,0.02)', border:'1px solid rgba(255,255,255,0.03)', padding:12, borderRadius:8}}> -
IdeaVortex
-
- {selectedTeam ? 'Click to open' : 'Select a team'} -
-
- -
{ if(selectedTeam) { setReview1Open(true); setReview1Scores(prev=>({ ...prev, [selectedTeam.id]: prev[selectedTeam.id] ?? { architecture_flow:0, current_progress:0, qa_defense:0, review_1_total:0 } })) } }} style={{cursor: selectedTeam ? 'pointer' : 'not-allowed', flex:'1 1 140px', minWidth:140, background:'rgba(255,255,255,0.02)', border:'1px solid rgba(255,255,255,0.03)', padding:12, borderRadius:8}}> -
Review 1
-
- {selectedTeam ? 'Click to open' : 'Select a team'} -
-
- -
{ if(selectedTeam) { setReview2Open(true); setReview2Scores(prev=>({ ...prev, [selectedTeam.id]: prev[selectedTeam.id] ?? { mvp_functionality:0, ui_usability:0, code_quality:0, review_2_total:0 } })) } }} style={{cursor: selectedTeam ? 'pointer' : 'not-allowed', flex:'1 1 140px', minWidth:140, background:'rgba(255,255,255,0.02)', border:'1px solid rgba(255,255,255,0.03)', padding:12, borderRadius:8}}> -
Review 2
-
- {selectedTeam ? 'Click to open' : 'Select a team'} -
-
- -
{ if(selectedTeam) { setReview3Open(true); setReview3Scores(prev=>({ ...prev, [selectedTeam.id]: prev[selectedTeam.id] ?? { final_demo:0, innovation:0, business_impact:0, review_3_total:0 } })) } }} style={{cursor: selectedTeam ? 'pointer' : 'not-allowed', flex:'1 1 140px', minWidth:140, background:'rgba(255,255,255,0.02)', border:'1px solid rgba(255,255,255,0.03)', padding:12, borderRadius:8}}> -
Review 3
-
- {selectedTeam ? 'Click to open' : 'Select a team'} -
-
- -
{ - if (!selectedTeam) return; - setPitchOpen(true); - // load existing pitch score if any - try { - const { data, error } = await supabase.from('scorecards').select('pitch_vortex').eq('team_id', selectedTeam.id).single(); - if (!error && data) { - setPitchScores(prev => ({ ...prev, [selectedTeam.id]: data.pitch_vortex ?? 0 })); - } else { - setPitchScores(prev => ({ ...prev, [selectedTeam.id]: 0 })); - } - } catch (e) { - setPitchScores(prev => ({ ...prev, [selectedTeam.id]: 0 })); - } - }} style={{cursor: selectedTeam ? 'pointer' : 'not-allowed', flex:'1 1 180px', minWidth:140, background:'rgba(255,255,255,0.02)', border:'1px solid rgba(255,255,255,0.03)', padding:12, borderRadius:8}}> -
PitchVortex
-
- {selectedTeam ? 'Click to enter manual score' : 'Select a team'} -
-
-
- - {review1Open && selectedTeam && ( -
-
-
-

Review 1 — {selectedTeam.team_name}

-
- -
-
- -
Score each category using the sliders. Total is out of {Object.values(SLIDER_MAX.review1).reduce((a,b)=>a+Number(b||0),0)}.
- - {(() => { - const scores = review1Scores[selectedTeam.id] || { architecture_flow:0, current_progress:0, qa_defense:0, review_1_total:0 }; - const total = scores.review_1_total ?? (Number(scores.architecture_flow||0) + Number(scores.current_progress||0) + Number(scores.qa_defense||0)); - return ( -
-
-
Architecture / Flow
- handleReview1Change(selectedTeam.id, 'architecture_flow', e.target.value)} /> -
{scores.architecture_flow} / {SLIDER_MAX.review1.architecture_flow}
-
- -
-
Current Progress
- handleReview1Change(selectedTeam.id, 'current_progress', e.target.value)} /> -
{scores.current_progress} / {SLIDER_MAX.review1.current_progress}
-
- -
-
Q&A Defense
- handleReview1Change(selectedTeam.id, 'qa_defense', e.target.value)} /> -
{scores.qa_defense} / {SLIDER_MAX.review1.qa_defense}
-
- -
-
- Review 1 Total: {total} / {Object.values(SLIDER_MAX.review1).reduce((a,b)=>a+Number(b||0),0)} -
-
- - -
-
- -
- ); - })()} -
-
- )} - {review2Open && selectedTeam && ( -
-
-
-

Review 2 — {selectedTeam.team_name}

-
- -
-
- -
Score each category using the sliders. Total is out of {Object.values(SLIDER_MAX.review2).reduce((a,b)=>a+Number(b||0),0)}.
- - {(() => { - const scores = review2Scores[selectedTeam.id] || { mvp_functionality:0, ui_usability:0, code_quality:0, review_2_total:0 }; - const total = scores.review_2_total ?? (Number(scores.mvp_functionality||0) + Number(scores.ui_usability||0) + Number(scores.code_quality||0)); - return ( -
-
-
MVP Functionality
- handleReview2Change(selectedTeam.id, 'mvp_functionality', e.target.value)} /> -
{scores.mvp_functionality} / {SLIDER_MAX.review2.mvp_functionality}
-
- -
-
UI / Usability
- handleReview2Change(selectedTeam.id, 'ui_usability', e.target.value)} /> -
{scores.ui_usability} / {SLIDER_MAX.review2.ui_usability}
-
- -
-
Code Quality
- handleReview2Change(selectedTeam.id, 'code_quality', e.target.value)} /> -
{scores.code_quality} / {SLIDER_MAX.review2.code_quality}
-
- -
-
- Review 2 Total: {total} / {Object.values(SLIDER_MAX.review2).reduce((a,b)=>a+Number(b||0),0)} -
Weighted: {Math.round(Number(total) * 1.667)}
-
-
- - -
-
- -
- ); - })()} -
-
- )} - {review3Open && selectedTeam && ( -
-
-
-

Review 3 — {selectedTeam.team_name}

-
- -
-
- -
Score each category using the sliders. Total is out of {Object.values(SLIDER_MAX.review3).reduce((a,b)=>a+Number(b||0),0)}.
- - {(() => { - const scores = review3Scores[selectedTeam.id] || { final_demo:0, innovation:0, business_impact:0, review_3_total:0 }; - const total = scores.review_3_total ?? (Number(scores.final_demo||0) + Number(scores.innovation||0) + Number(scores.business_impact||0)); - const weighted = Math.round(Number(total) * 1.65); - return ( -
-
-
Final Demo
- handleReview3Change(selectedTeam.id, 'final_demo', e.target.value)} /> -
{scores.final_demo} / {SLIDER_MAX.review3.final_demo}
-
- -
-
Innovation
- handleReview3Change(selectedTeam.id, 'innovation', e.target.value)} /> -
{scores.innovation} / {SLIDER_MAX.review3.innovation}
-
- -
-
Business / Impact
- handleReview3Change(selectedTeam.id, 'business_impact', e.target.value)} /> -
{scores.business_impact} / {SLIDER_MAX.review3.business_impact}
-
- -
-
- Review 3 Total: {total} / {Object.values(SLIDER_MAX.review3).reduce((a,b)=>a+Number(b||0),0)} -
Weighted: {weighted}
-
-
- - -
-
- -
- ); - })()} -
-
- )} - {pitchOpen && selectedTeam && ( -
-
-
-

PitchVortex — {selectedTeam.team_name}

-
- -
-
- -
Enter a manual overall score for the Pitch round (0–{SLIDER_MAX.pitch.pitch_vortex}).
- - {(() => { - const current = pitchScores[selectedTeam.id] ?? 0; - return ( -
-
- setPitchScores(prev => ({ ...prev, [selectedTeam.id]: Number(e.target.value) }))} - style={{width:'100%', padding:8, borderRadius:6}} - /> -
Current: {current} / {SLIDER_MAX.pitch.pitch_vortex}
-
- -
- - -
-
- ); - })()} -
-
- )} -
-
- )} -
-
- - {scoringOpen && ( -
-
-
-

Scoring Guide — Metrics & Maximums

-
- -
-
- -
These slider maximums are fixed in the application. This guide lists each metric and its maximum points.
- -
-
- IdeaVortex — Requirement Check -
{SLIDER_MAX.idea.requirement_check}
-
-
- IdeaVortex — Solution Logic -
{SLIDER_MAX.idea.solution_logic}
-
-
- IdeaVortex — Feasibility -
{SLIDER_MAX.idea.feasibility}
-
- -
- -
- Review 1 — Architecture / Flow -
{SLIDER_MAX.review1.architecture_flow}
-
-
- Review 1 — Current Progress -
{SLIDER_MAX.review1.current_progress}
-
-
- Review 1 — Q&A Defense -
{SLIDER_MAX.review1.qa_defense}
-
- -
- -
- Review 2 — MVP Functionality -
{SLIDER_MAX.review2.mvp_functionality}
-
-
- Review 2 — UI / Usability -
{SLIDER_MAX.review2.ui_usability}
-
-
- Review 2 — Code Quality -
{SLIDER_MAX.review2.code_quality}
-
- -
- -
- Review 3 — Final Demo -
{SLIDER_MAX.review3.final_demo}
-
-
- Review 3 — Innovation -
{SLIDER_MAX.review3.innovation}
-
-
- Review 3 — Business / Impact -
{SLIDER_MAX.review3.business_impact}
-
- -
- -
- PitchVortex — Max (overall) -
{SLIDER_MAX.pitch.pitch_vortex}
-
- -
- -
-
-
-
- )} -
- ); -} diff --git a/src/pages/dashboard.jsx b/src/pages/dashboard.jsx deleted file mode 100644 index 127d76a..0000000 --- a/src/pages/dashboard.jsx +++ /dev/null @@ -1,1635 +0,0 @@ -import { useEffect, useState } from "react"; -import { useParams, useNavigate } from "react-router-dom"; -import { supabase } from "../supabaseClient"; -import BuildTeam from "../components/BuildTeam"; -import "../styles/dashboard.css"; -import logo from "/logo.jpg"; -import { selectProblem } from "../utils/selectProblem"; - -export default function TeamDashboard() { - const { teamId } = useParams(); - const navigate = useNavigate(); - - const [team, setTeam] = useState(null); - const [scorecard, setScorecard] = useState(null); - const [loading, setLoading] = useState(true); - const [time, setTime] = useState(""); - const [activeTab, setActiveTab] = useState("vortex"); - const [showSidebar, setShowSidebar] = useState(false); - const [teamMembers, setTeamMembers] = useState([]); - - // Leaderboard State - const [leaderboard, setLeaderboard] = useState([]); - const [leaderboardPublic, setLeaderboardPublic] = useState(false); - const [leaderboardLoading, setLeaderboardLoading] = useState(false); - - // Problem Statement Selection State - const [selectedDomain, setSelectedDomain] = useState(null); - const [selectedPS, setSelectedPS] = useState(null); - const [isSubmitted, setIsSubmitted] = useState(false); - const [isSubmittingSelection, setIsSubmittingSelection] = useState(false); - const [lastRequest, setLastRequest] = useState(null); - const [lastResponse, setLastResponse] = useState(null); - const [lastError, setLastError] = useState(null); - const [successInfo, setSuccessInfo] = useState(null); - - // Problem Statements Data - const [problemStatements, setProblemStatements] = useState({ - ai: [], - fintech: [], - cybersecurity: [], - healthcare: [], - iot: [] - }); - - const mapRow = (r) => ({ - id: r.id, - code: r.code || '', - title: r.title || '', - description: r.description || '', - totalSeats: r.total_seats ?? 10, - seatsBooked: r.seats_booked ?? 0 - }); - - // Load current problem statements from DB and subscribe to realtime updates - useEffect(() => { - let channel; - - const load = async () => { - try { - const { data, error } = await supabase.from('problem_statements').select('*'); - if (error) throw error; - - const grouped = { ai: [], fintech: [], cybersecurity: [], healthcare: [], iot: [] }; - data.forEach(r => { - const domain = r.domain || 'iot'; - grouped[domain] = grouped[domain] || []; - grouped[domain].push(mapRow(r)); - }); - setProblemStatements(grouped); - - // subscribe to changes - channel = supabase - .channel('public:problem_statements') - .on('postgres_changes', { event: '*', schema: 'public', table: 'problem_statements' }, (payload) => { - const eventType = payload.eventType || payload.event; - const newRow = payload.new; - const oldRow = payload.old; - - setProblemStatements(prev => { - const next = { ...prev }; - - const applyInsert = (row) => { - const d = row.domain || 'iot'; - next[d] = (next[d] || []).filter(Boolean); - next[d] = [...next[d], mapRow(row)]; - }; - - const applyUpdate = (row, old) => { - const d = row.domain || 'iot'; - next[d] = (next[d] || []).map(ps => ps.id === row.id ? mapRow(row) : ps); - if (old && old.domain && old.domain !== row.domain) { - next[old.domain] = (next[old.domain] || []).filter(ps => ps.id !== row.id); - } - }; - - const applyDelete = (old) => { - const d = old.domain || 'iot'; - next[d] = (next[d] || []).filter(ps => ps.id !== old.id); - }; - - if (eventType === 'INSERT') applyInsert(newRow); - else if (eventType === 'UPDATE') applyUpdate(newRow, oldRow); - else if (eventType === 'DELETE') applyDelete(oldRow); - - return next; - }); - }) - .subscribe(); - } catch (err) { - console.error('Failed to load problem statements:', err); - } - }; - - load(); - - return () => { - if (channel) supabase.removeChannel(channel); - }; - }, []); - - const domainNames = { - ai: 'AI/ML - Artificial Intelligence & Machine Learning', - fintech: 'FINTECH - Financial Technology & Digital Payments', - cybersecurity: 'CYBERSECURITY - Security & Privacy', - healthcare: 'HEALTHCARE - Medical Innovation & Digital Health', - iot: 'IOT & ROBOTICS - Internet of Things & Robotics Systems' - }; - - const domainShortNames = { - ai: 'AI/ML', - fintech: 'FINTECH', - cybersecurity: 'CYBERSECURITY', - healthcare: 'HEALTHCARE', - iot: 'IOT & ROBOTICS' - }; - - const domainIcons = { - ai: '🖥️', - fintech: '💰', - cybersecurity: '🛡️', - healthcare: '🏥', - iot: '🤖' - }; - - /* =============================== - AUTH + DATA FETCH - =============================== */ - useEffect(() => { - const init = async () => { - const { data: { user } } = await supabase.auth.getUser(); - if (!user) { - navigate("/login"); - return; - } - - try { - const queries = [ - supabase.from("teams").select("*").eq("id", teamId).single(), - supabase.from("scorecards").select("*").eq("team_id", teamId).single(), - supabase.from("team_members").select("*").eq("team_id", teamId) - ]; - - const results = await Promise.all(queries); - const teamData = results[0]?.data; - const scoreData = results[1]?.data; - const membersData = results[2]?.data; - - if (teamData) { - const userEmail = user.email?.toLowerCase(); - const isLeader = teamData.lead_email?.toLowerCase() === userEmail; - const isMember = membersData?.some(m => m.member_email?.toLowerCase() === userEmail); - - if (!isLeader && !isMember) { - alert("You don't have access to this team dashboard."); - navigate("/login"); - return; - } - } - - setTeam(teamData); - setScorecard(scoreData || null); - setTeamMembers(membersData || []); - } catch (err) { - console.error("Dashboard error:", err); - } finally { - setLoading(false); - } - }; - - init(); - }, [teamId, navigate]); - - // Helper to refresh the current team's scorecard - const refreshScorecard = async () => { - if (!teamId) return; - try { - const { data, error } = await supabase.from('scorecards').select('*').eq('team_id', teamId).single(); - if (error) { - // if no row found, clear scorecard - if (error.code === 'PGRST116' || /No rows? returned/.test(error.message || '')) { - setScorecard(null); - return; - } - throw error; - } - setScorecard(data || null); - } catch (err) { - console.error('Failed to refresh scorecard:', err); - } - }; - - // Helper to refresh the leaderboard - const refreshLeaderboard = async () => { - setLeaderboardLoading(true); - try { - // Check if leaderboard is public - const { data: settings } = await supabase.from('app_settings').select('leaderboard_public').eq('id', 'main').single(); - const isPublic = !!settings?.leaderboard_public; - setLeaderboardPublic(isPublic); - - if (isPublic) { - const { data, error } = await supabase.from('leaderboard_view').select('*').order('position', { ascending: true }); - if (error) throw error; - const norm = (data || []).map(r => ({ ...r, score: r.score ?? r.total_score ?? 0, delta: r.delta ?? 0 })); - setLeaderboard(norm); - } else { - setLeaderboard([]); - } - } catch (err) { - console.error('Failed to refresh leaderboard:', err); - setLeaderboard([]); - } finally { - setLeaderboardLoading(false); - } - }; - - // Load leaderboard on mount and when tab changes to leaderboard - useEffect(() => { - if (activeTab === 'leaderboard') { - refreshLeaderboard(); - } - }, [activeTab]); - - // Initial leaderboard load - useEffect(() => { - refreshLeaderboard(); - }, []); - - // Subscribe to realtime changes so scorecard updates automatically - useEffect(() => { - const channel = supabase - .channel('public:scorecard_updates') - .on('postgres_changes', { event: '*', schema: 'public', table: 'scorecards' }, () => { - refreshScorecard(); - }) - .on('postgres_changes', { event: '*', schema: 'public', table: 'scorecard_history' }, () => { - refreshScorecard(); - }) - .subscribe(); - - return () => { - supabase.removeChannel(channel); - }; - }, []); - - /* =============================== - LIVE CLOCK - =============================== */ - useEffect(() => { - const tick = () => { - const now = new Date(); - setTime( - `${String(now.getHours()).padStart(2, "0")}:${String( - now.getMinutes() - ).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}` - ); - }; - tick(); - const i = setInterval(tick, 1000); - return () => clearInterval(i); - }, []); - - // Debug: ensure initial load state - useEffect(() => { - if (!loading) console.debug('dashboard loaded'); - }, [loading]); - - /* =============================== - LOGOUT - =============================== */ - const handleLogout = async () => { - await supabase.auth.signOut(); - navigate("/"); - }; - - /* =============================== - PROBLEM STATEMENT HELPERS - =============================== */ - const getSeatStatus = (ps) => { - const seatsLeft = ps.totalSeats - ps.seatsBooked; - const isFull = seatsLeft === 0; - const seatPercentage = ps.totalSeats ? (seatsLeft / ps.totalSeats) * 100 : 0; - - let seatClass = 'seat-high'; - let seatColor = '#4ade80'; - let seatIcon = '✓'; - - if (isFull) { - seatClass = 'seat-full'; - seatColor = '#f87171'; - seatIcon = '✗'; - } else if (seatPercentage <= 25) { - seatClass = 'seat-low'; - seatColor = '#fb923c'; - seatIcon = '⚠'; - } else if (seatPercentage <= 50) { - seatClass = 'seat-medium'; - seatColor = '#facc15'; - seatIcon = '●'; - } - - return { seatsLeft, isFull, seatClass, seatColor, seatIcon, seatPercentage }; - }; - - const getDomainGradient = (domain) => { - const gradients = { - ai: 'linear-gradient(to bottom right, #9333ea, #a855f7)', - fintech: 'linear-gradient(to bottom right, #db2777, #ec4899)', - cybersecurity: 'linear-gradient(to bottom right, #ea580c, #f97316)', - healthcare: 'linear-gradient(to bottom right, #059669, #10b981)', - iot: 'linear-gradient(to bottom right, #0891b2, #06b6d4)' - }; - return gradients[domain]; - }; - - // Calculate total seats for each domain - const getDomainSeats = (domain) => { - const statements = problemStatements[domain]; - const totalSeats = statements.reduce((sum, ps) => sum + ps.totalSeats, 0); - const bookedSeats = statements.reduce((sum, ps) => sum + ps.seatsBooked, 0); - const availableSeats = totalSeats - bookedSeats; - const percentageAvailable = (availableSeats / totalSeats) * 100; - - return { - totalSeats, - bookedSeats, - availableSeats, - percentageAvailable, - isFull: availableSeats === 0 - }; - }; - - const getChallengeCount = (domain) => String(problemStatements[domain]?.length || 0).padStart(2, '0'); - - const handleDomainSelect = (domain) => { - if (isSubmitted || isSubmittingSelection) return; - const domainSeats = getDomainSeats(domain); - if (domainSeats.isFull) return; - setSelectedDomain(domain); - setSelectedPS(null); - }; - - const handlePSSelect = (ps) => { - if (isSubmitted || isSubmittingSelection) return; - const { isFull } = getSeatStatus(ps); - if (isFull) return; - setSelectedPS(ps); - }; - - // NOTE: `handleConfirmSubmit` is used directly for submission. - - const handleConfirmSubmit = async () => { - setIsSubmittingSelection(true); - - try { - console.debug("Submitting problem selection", { teamId: team.id, domain: selectedDomain, ps: selectedPS }); - - const payload = { - teamId: team.id, - domain: selectedDomain, - psId: selectedPS.id, - problemName: selectedPS.title, - problemDescription: selectedPS.description, - }; - - setLastRequest(payload); - setLastResponse(null); - setLastError(null); - - // Call edge function to persist selection (only team lead should be able to do this) - const resp = await selectProblem(payload); - setLastResponse(resp || { success: true }); - - // Refresh team data from DB - const { data: refreshedTeam, error: teamError } = await supabase.from("teams").select("*").eq("id", team.id).single(); - if (teamError) throw teamError; - setTeam(refreshedTeam); - - setIsSubmitted(true); - // show immediate success info to user - setSuccessInfo({ domain: selectedDomain, ps: selectedPS }); - // auto-hide after 6s - setTimeout(() => setSuccessInfo(null), 6000); - setIsSubmitted(true); - } catch (err) { - console.error("Failed to submit problem selection:", err); - setLastError(err.message || String(err)); - alert(err.message || "Failed to save selection. Please try again."); - } finally { - setIsSubmittingSelection(false); - } - }; - - const handleBackToDomains = () => { - setSelectedPS(null); - setSelectedDomain(null); - }; - - if (loading) { - return
SYNCING VORTEX DATA…
; - } - - return ( -
- - {/* ================= SIDEBAR ================= */} - - - {showSidebar &&
setShowSidebar(false)} /> } - - {/* ================= MAIN ================= */} -
- - {/* HEADER */} -
-
- - -
-
- {activeTab === "vortex" && "Vortex Hub"} - {activeTab === "buildTeam" && "Build Your Team"} - {activeTab === "nexus" && "Nexus Entry"} - {activeTab === "mission" && "IdeaVortex - Problem Statement Selector"} - {activeTab === "leaderboard" && "Leaderboard"} -
- -
- OPERATIONAL OBJECTIVE DECRYPTION -
-
-
- -
-
SYSTEM TIME
-
{time}
-
-
- - {/* ================= CONTENT ================= */} -
- - {/* ===== BUILD YOUR TEAM ===== */} - {activeTab === "buildTeam" && ( - 0} - team={team} - teamMembers={teamMembers} - onTeamBuilt={async () => { - // Check if leaderboard is public before fetching - const { data: settings } = await supabase.from('app_settings').select('leaderboard_public').eq('id', 'main').single(); - const isPublic = !!settings?.leaderboard_public; - setLeaderboardPublic(isPublic); - - const queries = [ - supabase.from("teams").select("*").eq("id", teamId).single(), - supabase.from("team_members").select("*").eq("team_id", teamId) - ]; - - if (isPublic) { - queries.push(supabase.from("leaderboard_view").select("*").order("position", { ascending: true })); - } - - const results = await Promise.all(queries); - const updatedTeam = results[0]?.data; - const updatedMembers = results[1]?.data; - const updatedLeaderboard = isPublic ? results[2]?.data : []; - - setTeam(updatedTeam); - setTeamMembers(updatedMembers || []); - const norm = (updatedLeaderboard || []).map(r => ({ ...r, score: r.score ?? r.total_score ?? 0, delta: r.delta ?? 0 })); - setLeaderboard(norm); - setActiveTab("vortex"); - }} - /> - )} - - {/* ===== VORTEX HUB ===== */} - {activeTab === "vortex" && ( -
- - {teamMembers.length === 0 && ( -
setActiveTab("buildTeam")}> -
🔨
-

- Build Your Team First! -

-

- Your team is not complete yet. Click here to add members and set your team name. -

-
- Go to Build Your Team → -
-
- )} - -
-
setActiveTab("nexus")}> -
-

Nexus Entry

-

Generate encrypted access credentials.

-
- -
setActiveTab("mission")}> -
💡
-

IdeaVortex

-

Select your domain and problem statement.

-
- -
setActiveTab("leaderboard")}> -
📊
-

Leaderboard

-

View live rankings and track your position.

-
-
- -
-
-
- {team.team_name.slice(0, 2).toUpperCase()} -
-
-

{team.team_name}

-

ELITE SQUAD · LIVE RANKING

-
-
- -
- CURRENT YIELD - {scorecard?.total_score ?? "—"} PTS -
-
- {team?.domain && team?.problem_statement && ( -
-
-
Assigned
-
{domainShortNames[team.domain] || team.domain} ·
-
{team.problem_statement}
-
-
- )} - {/* ===== SCORECARD (per-round breakdown) ===== */} - {scorecard && ( -
-
-

Scorecard

-
Per-round breakdown
-
- -
-
IdeaVortex
-
{scorecard.ideavortex ?? 0} PTS
- -
Review 1
-
{scorecard.review_1 ?? 0} PTS
- -
Review 2
-
{scorecard.review_2 ?? 0} PTS
- -
Review 3
-
{scorecard.review_3 ?? 0} PTS
- -
Pitch Vortex
-
{scorecard.pitch_vortex ?? 0} PTS
- -
Total
-
{scorecard.total_score ?? 0} PTS
-
-
- )} -
- )} - - {/* ===== NEXUS ENTRY ===== */} - {activeTab === "nexus" && ( -
-
-
-
🔒
-
- -
SQUAD: {team.team_name}
- -
- Authorized personnel must scan this encrypted vortex key - to gain access to the secure development environment. -
- -
- HCK-2024-{team.team_name.slice(0, 2).toUpperCase()}-{team.id}-EPSILON -
-
-
- )} - - {/* ===== IDEAVORTEX - PROBLEM STATEMENT SELECTOR ===== */} - {activeTab === "mission" && ( -
- - {/* Step 1: Domain Selection */} -
-
-
- 01 -

- CHOOSE YOUR DOMAIN -

-
-

- Select the arena where your innovation will make its mark. Each domain presents unique challenges and opportunities for disruption. -

-
- -
- {/* AI/ML Domain */} -
{ - if (isSubmitted) return; - handleDomainSelect('ai'); - }} - style={{ - position: 'relative', - background: 'linear-gradient(135deg, rgba(10, 10, 20, 0.9) 0%, rgba(20, 20, 35, 0.8) 100%)', - border: selectedDomain === 'ai' ? '2px solid #f0ff00' : '1px solid rgba(0, 245, 255, 0.2)', - borderRadius: '1rem', - padding: '2rem', - cursor: isSubmitted ? 'not-allowed' : 'pointer', - opacity: isSubmitted ? 0.5 : 1, - transition: 'all 0.4s ease', - boxShadow: selectedDomain === 'ai' ? '0 0 40px rgba(240, 255, 0, 0.4)' : 'none' - }} - > -
-
- 🖥️ -
-
-

AI/ML

-

Artificial Intelligence & Machine Learning

-
-
-

- Push the boundaries of what's possible with intelligent systems. Solve real-world problems using cutting-edge AI algorithms. -

-
-
- {getChallengeCount('ai')} - Challenges -
-
- {selectedDomain === 'ai' && ( -
- ✓ -
- )} -
- - {/* FINTECH Domain */} -
{ - if (isSubmitted) return; - handleDomainSelect('fintech'); - }} - style={{ - position: 'relative', - background: 'linear-gradient(135deg, rgba(10, 10, 20, 0.9) 0%, rgba(20, 20, 35, 0.8) 100%)', - border: selectedDomain === 'fintech' ? '2px solid #f0ff00' : '1px solid rgba(0, 245, 255, 0.2)', - borderRadius: '1rem', - padding: '2rem', - cursor: isSubmitted ? 'not-allowed' : 'pointer', - opacity: isSubmitted ? 0.5 : 1, - transition: 'all 0.4s ease', - boxShadow: selectedDomain === 'fintech' ? '0 0 40px rgba(240, 255, 0, 0.4)' : 'none' - }} - > -
-
- 💰 -
-
-

FINTECH

-

Financial Technology & Digital Payments

-
-
-

- Innovate payment flows, settlement systems and privacy-preserving financial services for the modern economy. -

-
-
- {getChallengeCount('fintech')} - Challenges -
-
- {selectedDomain === 'fintech' && ( -
- ✓ -
- )} -
- - {/* CYBERSECURITY Domain */} -
{ - if (isSubmitted) return; - handleDomainSelect('cybersecurity'); - }} - style={{ - position: 'relative', - background: 'linear-gradient(135deg, rgba(10, 10, 20, 0.9) 0%, rgba(20, 20, 35, 0.8) 100%)', - border: selectedDomain === 'cybersecurity' ? '2px solid #f0ff00' : '1px solid rgba(0, 245, 255, 0.2)', - borderRadius: '1rem', - padding: '2rem', - cursor: isSubmitted ? 'not-allowed' : 'pointer', - opacity: isSubmitted ? 0.5 : 1, - transition: 'all 0.4s ease', - boxShadow: selectedDomain === 'cybersecurity' ? '0 0 40px rgba(240, 255, 0, 0.4)' : 'none' - }} - > -
-
- 🛡️ -
-
-

CYBERSECURITY

-

Security & Privacy

-
-
-

- Build systems and defenses to protect users and infrastructure from evolving threats while preserving privacy. -

-
-
- {getChallengeCount('cybersecurity')} - Challenges -
-
- {selectedDomain === 'cybersecurity' && ( -
- ✓ -
- )} -
- - {/* HEALTHCARE Domain */} -
{ - if (isSubmitted) return; - handleDomainSelect('healthcare'); - }} - style={{ - position: 'relative', - background: 'linear-gradient(135deg, rgba(10, 10, 20, 0.9) 0%, rgba(20, 20, 35, 0.8) 100%)', - border: selectedDomain === 'healthcare' ? '2px solid #f0ff00' : '1px solid rgba(0, 245, 255, 0.2)', - borderRadius: '1rem', - padding: '2rem', - cursor: isSubmitted ? 'not-allowed' : 'pointer', - opacity: isSubmitted ? 0.5 : 1, - transition: 'all 0.4s ease', - boxShadow: selectedDomain === 'healthcare' ? '0 0 40px rgba(240, 255, 0, 0.4)' : 'none' - }} - > -
-
- 🏥 -
-
-

HEALTHCARE

-

Medical Innovation & Digital Health

-
-
-

- Design patient-centered digital health solutions, remote monitoring, and AI-assisted diagnostics with privacy-first principles. -

-
-
- {getChallengeCount('healthcare')} - Challenges -
-
- {selectedDomain === 'healthcare' && ( -
- ✓ -
- )} -
- - {/* IOT Domain */} -
{ - if (isSubmitted) return; - handleDomainSelect('iot'); - }} - style={{ - position: 'relative', - background: 'linear-gradient(135deg, rgba(10, 10, 20, 0.9) 0%, rgba(20, 20, 35, 0.8) 100%)', - border: selectedDomain === 'iot' ? '2px solid #f0ff00' : '1px solid rgba(0, 245, 255, 0.2)', - borderRadius: '1rem', - padding: '2rem', - cursor: isSubmitted ? 'not-allowed' : 'pointer', - opacity: isSubmitted ? 0.5 : 1, - transition: 'all 0.4s ease', - boxShadow: selectedDomain === 'iot' ? '0 0 40px rgba(240, 255, 0, 0.4)' : 'none' - }} - > -
-
- 🤖 -
-
-

IOT & ROBOTICS

-

Internet of Things & Robotics Systems

-
-
-

- Build the connected future. Design intelligent systems that bridge the physical and digital worlds. -

-
-
- {getChallengeCount('iot')} - Challenges -
-
- {selectedDomain === 'iot' && ( -
- ✓ -
- )} -
-
-
- - {/* Step 2: Problem Statement Selection */} - {selectedDomain && ( -
-
-
- 02 -

- SELECT PROBLEM STATEMENT -

-
-

- Choose your challenge wisely. Each statement presents a unique opportunity to innovate and make an impact. -

-
- -
- {problemStatements[selectedDomain]?.map((ps) => { - const { seatsLeft, isFull, seatClass, seatColor, seatIcon, seatPercentage } = getSeatStatus(ps); - - return ( -
{ - if (isFull || isSubmitted) return; - handlePSSelect(ps); - }} - style={{ - position: 'relative', - background: selectedPS?.id === ps.id - ? 'linear-gradient(135deg, rgba(240, 255, 0, 0.1) 0%, rgba(240, 255, 0, 0.05) 100%)' - : 'linear-gradient(135deg, rgba(15, 15, 30, 0.95) 0%, rgba(25, 25, 45, 0.8) 100%)', - border: selectedPS?.id === ps.id ? '1px solid #f0ff00' : '1px solid rgba(0, 245, 255, 0.15)', - borderLeft: selectedPS?.id === ps.id ? '4px solid #f0ff00' : '1px solid rgba(0, 245, 255, 0.15)', - borderRadius: '0.75rem', - padding: '1.5rem', - marginBottom: '1rem', - cursor: isFull || isSubmitted ? 'not-allowed' : 'pointer', - opacity: isFull || isSubmitted ? 0.5 : 1, - transition: 'all 0.3s ease' - }} - > -
-
- {ps.code.split(' ')[ps.code.split(' ').length - 1]} -
- -
-
-

- {ps.title} -

- -
- {seatIcon} -
-

- {seatsLeft}/{ps.totalSeats} -

-

SEATS

-
-
-
- - {isFull && ( -
- ⚠️ FULLY BOOKED -
- )} - -

- {ps.description} -

- - {!isFull && seatPercentage <= 25 && ( -
- ⏱️ - FILLING FAST - Only {seatsLeft} seat{seatsLeft === 1 ? '' : 's'} remaining! -
- )} -
- - {selectedPS?.id === ps.id && ( -
- ✓ -
- )} -
-
- ); - })} -
-
- )} - - {/* Step 3: Submit Section */} - {selectedDomain && selectedPS && ( -
-
-
-
-
- ✓ -
-

- SELECTION SUMMARY -

-
- -
-
-
- TEAM: - {team.team_name.toUpperCase()} -
-
-
- DOMAIN: - {domainShortNames[selectedDomain]} -
-
-
- CHALLENGE: - {selectedPS.code}: {selectedPS.title} -
-
-
- -
- ⚠️ -
-

IRREVERSIBLE ACTION

-

- Once submitted, your selection cannot be changed. Choose wisely, innovator. -

-
-
- - -
-
- )} - - {/* Confirmation modal removed: direct-submit UX enabled */} - - {/* Inline success banner (shows briefly after submission) */} - {successInfo && ( -
-
-
-
-
Selection saved
-
- {domainShortNames[successInfo.domain] || successInfo.domain} · {successInfo.ps?.code}: {successInfo.ps?.title} -
-
-
-
- )} - -
- )} - - {/* ===== LEADERBOARD ===== */} - {activeTab === "leaderboard" && ( -
- - {/* Team Stats Cards */} - {leaderboardPublic && leaderboard.length > 0 && (() => { - const currentTeam = leaderboard.find(r => r.team_id === teamId); - const alphaTeam = leaderboard[0]; - const gapToAlpha = currentTeam ? (alphaTeam?.score || 0) - (currentTeam?.score || 0) : 0; - - return ( -
- {/* Tactical Rank Card */} -
-
TACTICAL RANK
-
- - {currentTeam?.position || '—'} - - /{leaderboard.length} -
-
- - {/* Accumulated Data Card */} -
-
ACCUMULATED DATA
-
- {currentTeam?.score || 0} -
-
- - {/* Gap to Alpha Card */} -
-
GAP TO ALPHA
-
- - {gapToAlpha} - - PTS -
-
-
- ); - })()} - - {!leaderboardPublic ? ( -
-
🔒
-

Leaderboard Hidden

-

- The leaderboard is currently not available. Check back later when the organizers make it public. -

-
- ) : leaderboardLoading ? ( -
-
-

Loading leaderboard...

-
- ) : leaderboard.length === 0 ? ( -
-
📊
-

No leaderboard data available yet.

-
- ) : ( -
- {/* Table Header */} -
-
POSITION
-
SQUAD DESIGNATION
-
PAYLOAD
-
- - {/* Leaderboard Rows */} - {leaderboard.map((row, idx) => { - const isCurrentTeam = row.team_id === teamId; - const prevTeam = leaderboard[idx - 1]; - const delta = prevTeam ? row.score - (prevTeam?.score || 0) : 0; - - return ( -
- {isCurrentTeam && ( -
- )} - - {/* Position */} -
- - {row.position} - -
- - {/* Squad Designation */} -
-
- {row.team_name?.slice(0, 2).toUpperCase() || '??'} -
-
- - {row.team_name} - - - {isCurrentTeam ? 'YOUR SQUAD' : 'ACTIVE'} - -
-
- - {/* Payload */} -
-
- {row.score} -
- {row.position > 1 && ( -
- {delta >= 0 ? `+${delta}` : delta} PTS -
- )} -
-
- ); - })} -
- )} -
- )} - -
-
- {/* Dev debug panel removed to avoid lingering modal in UI */} -
- ); -} - diff --git a/src/pages/home.jsx b/src/pages/home.jsx index 044d2c5..67fb6f1 100644 --- a/src/pages/home.jsx +++ b/src/pages/home.jsx @@ -1,605 +1,440 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; +import AnimatedSection from "../components/AnimatedSection"; +import ParticleBackground from "../components/ParticleBackground"; +import { + VortexIcon, AIIcon, ShieldIcon, HeartPulseIcon, + CoinIcon, ChipIcon, CalendarIcon, MapPinIcon, + TrophyIcon, SparkleIcon, LightningIcon, ArrowRightIcon +} from "../components/icons/index.jsx"; import "../styles/home.css"; export default function Home({ setTransition }) { const router = useNavigate(); + const heroRef = useRef(null); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); const goTo = (path) => { - console.log("nav.goTo ->", path); - try { - if (setTransition) setTransition(null); - } catch (e) {} + if (setTransition) setTransition(null); router(path); window.scrollTo(0, 0); }; - // Problem statements stay blurred until manual removal of the blur feature - - const rounds = { - r1: { - title: "ROUND 1: CONCEPTUALIZATION", - desc: "The Ideathon will be conducted by the V-Vortex team via a dedicated platform. The problem statements will be displayed on January 7, 2026; with the problem statements for IOT and Robotics to be revealed sooner (by a week sooner) for better preparation.", - blocks: { - Rules: [ - "10–15 slides maximum", - "Clear problem & solution", - "Market & feasibility analysis", - "Timeline: 07th Jan 2026 - 08th Jan 2026", - "Mode: Online" - ], - Evaluation: [ - "Innovation (30%)", - "Feasibility (25%)", - "Market Impact (25%)", - "Presentation (20%)", - ], - }, - }, - r2: { - title: "ROUND 2: CONSTRUCTION", - desc: "The hackathon will commence offline at VIT Chennai on January 9, 2025 from 9 AM onwards. Participants are requested to report to MG Auditorium before 8:30 AM in order to facilitate smooth registrations!!!", - blocks: { - Rules: ["No pre-written code", "Any tech stack allowed", "Mentors available"], - Evaluation: ["Working Prototype (35%)", "Code Quality (25%)", "UX/UI (20%)"], - }, - }, - r3: { - title: "ROUND 3: VALIDATION", - desc: "On January 10, 2026; the finalists will be selected for a exclusive investor pitch with the director of V-Nest and a team of industry domain experts.", - blocks: { - Pitch: ["10 min pitch + Q&A", "Selected teams pitch to Industry Entrepreneurs for feedback, mentorship & opportunities."], - Rewards: ["₹ 15,000 Winner","₹ 7,000 First Runner-Up","₹ 5,000 Second Runner-Up","₹ 3,000 Special Mentions","Participation certificate For every participant in the finale", "Internships"], - }, - }, - }; + // Parallax effect for hero + useEffect(() => { + const handleMouseMove = (e) => { + setMousePosition({ + x: (e.clientX / window.innerWidth - 0.5) * 20, + y: (e.clientY / window.innerHeight - 0.5) * 20 + }); + }; + window.addEventListener('mousemove', handleMouseMove); + return () => window.removeEventListener('mousemove', handleMouseMove); + }, []); - const domains = { - iot: { - title: "IOT & ROBOTICS", - icon: "🔌", - problems: [ - { - name: "Sky-Glow Sentinel (Urban Light Pollution Mapping)", - desc: "Urban light pollution creates skyglow that obscures stars and disrupts natural biological cycles. The objective is to design a high-sensitivity Sky Quality Monitoring system capable of accurately measuring night-sky brightness in urban environments and mapping these measurements to quantify and visualize light pollution at ground level." - }, - { - name: "Decentralized Communication in Infrastructure-Denied Environments", - desc: "Modern communication systems fail in environments without internet, cellular networks, Wi-Fi, or cloud access. The objective is to develop a decentralized, peer-to-peer hardware communication network that enables reliable data exchange and maintains network functionality without any centralized infrastructure." - }, - { - name: "Smart Parking Occupancy Detection System", - desc: "In urban areas, drivers spend significant time searching for vacant parking spaces, leading to traffic congestion and fuel wastage. The objective is to design a low-cost IoT-based system that detects parking spot occupancy in real time and communicates availability information to users through a centralized interface." - } - ] + const rounds = [ + { + number: "01", + title: "IDEATION", + subtitle: "CONCEPTUALIZATION", + date: "07th - 08th Jan", + mode: "Online", + desc: "Teams submitted their innovative ideas through our dedicated platform. Problem statements were revealed on January 7, 2026.", + highlights: ["10-15 slides max", "Online submission", "48-hour window"] }, - aiml: { - title: "AI/ML", - icon: "🤖", - problems: [ - { - name: "AI-Generated Image Authenticity Detection", - desc: "Design a system that determines whether an image is AI-generated or real, remaining robust to compression, resizing, and post-processing, while providing confidence-aware and explainable authenticity assessments across diverse image sources." - }, - { - name: "AI-Powered Mind Map Search Engine", - desc: "Design a search system that retrieves information for a user query and organizes results into an interactive mind map, automatically revealing key concepts, subtopics, and relationships to support exploratory learning and research." - }, - { - name: "AI-Powered Mental Well-Being Risk Indicator (Non-Clinical)", - desc: "Design a non-clinical system that analyzes anonymized behavioral patterns over time to identify early mental well-being risk indicators, while preserving user privacy, avoiding medical diagnosis, and providing transparent, uncertainty-aware insights." - } - ] + { + number: "02", + title: "HACKATHON", + subtitle: "CONSTRUCTION", + date: "09th - 10th Jan", + mode: "Offline", + desc: "24-hour intensive hackathon at VIT Chennai. Teams built working prototypes with mentor guidance.", + highlights: ["No pre-written code", "Any tech stack", "Mentors available"] }, - fintech: { - title: "FINTECH", - icon: "💰", - problems: [ - { - name: "Unified Payment Orchestration & Automated Settlements", - desc: "Design and prototype a unified payment orchestration platform that allows merchants to accept payments across multiple channels through a single interface, while automating post-payment workflows such as settlements, refunds, splits, and conditional payouts using configurable logic. The system should reliably initiate, track, settle, and reconcile transactions in an auditable and extensible manner." - }, - { - name: "Privacy-Preserving Collaborative Fraud Intelligence Platform", - desc: "Design and prototype a real-time transaction monitoring and compliance intelligence platform that detects suspicious activity and assigns risk levels while enabling privacy-preserving collaboration across multiple independent entities. The platform must support shared fraud intelligence without exposing raw transaction data, provide explainable risk decisions, and ensure transparency, auditability, and data sovereignty." - }, - { - name: "Adaptive Pricing in Real-Time Digital Marketplaces", - desc: "Design a real-time adaptive pricing system for digital marketplaces that continuously adjusts prices under uncertain and evolving demand conditions to maximize long-term business value while maintaining fairness, customer trust, and regulatory compliance. The system should balance short-term revenue with long-term retention, handle delayed feedback, and avoid extreme or erratic price fluctuations." - } - ] + { + number: "03", + title: "PITCH", + subtitle: "VALIDATION", + date: "10th Jan", + mode: "Live", + desc: "Teams presented to industry experts and investors. Top teams received mentorship and opportunities.", + highlights: ["10 min pitch", "Q&A session", "Expert feedback"] } - }; + ]; + + const domains = [ + { name: "AI/ML", icon: AIIcon, color: "#8B5CF6", desc: "Machine Learning & Intelligence" }, + { name: "Cybersecurity", icon: ShieldIcon, color: "#06B6D4", desc: "Digital Defense & Privacy" }, + { name: "Healthcare", icon: HeartPulseIcon, color: "#EC4899", desc: "Medical Innovation" }, + { name: "Fintech", icon: CoinIcon, color: "#F59E0B", desc: "Financial Technology" }, + { name: "IoT & Robotics", icon: ChipIcon, color: "#10B981", desc: "Connected Systems" } + ]; + + const prizes = [ + { place: "1st", amount: "₹15,000", label: "Winner" }, + { place: "2nd", amount: "₹7,000", label: "Runner Up" }, + { place: "3rd", amount: "₹5,000", label: "2nd Runner Up" }, + { place: "★", amount: "₹3,000", label: "Special Mentions" } + ]; - const [activeRound, setActiveRound] = useState(null); - const [activeDomain, setActiveDomain] = useState(null); const DRIVE_LINK = 'https://drive.google.com/drive/folders/1_8MIetG3u4Y-5st4FfFWtrbDbe6VEPFd?usp=sharing'; - useEffect(() => { - function onKey(e) { - if (e.key === "Escape") { - closeModal(); - closeDomainModal(); - } - } - window.addEventListener("keydown", onKey); - return () => window.removeEventListener("keydown", onKey); - }, []); - - function openModal(key) { - setActiveRound(rounds[key]); - document.body.style.overflow = "hidden"; - } - - function closeModal(e) { - if (e && e.stopPropagation) e.stopPropagation(); - setActiveRound(null); - document.body.style.overflow = ""; - } - - function openDomainModal(key) { - setActiveDomain(domains[key]); - document.body.style.overflow = "hidden"; - } - - function closeDomainModal(e) { - if (e && e.stopPropagation) e.stopPropagation(); - setActiveDomain(null); - document.body.style.overflow = ""; - } - - const overlayStyle = { - position: "fixed", - inset: 0, - display: activeRound ? "flex" : "none", - alignItems: "center", - justifyContent: "center", - background: "linear-gradient(0deg, rgba(0,0,0,0.7), rgba(0,0,0,0.6))", - zIndex: 1000, - padding: "40px 20px", - overflowY: "auto", - }; - - const domainOverlayStyle = { - position: "fixed", - inset: 0, - display: activeDomain ? "flex" : "none", - alignItems: "center", - justifyContent: "center", - background: "linear-gradient(0deg, rgba(0,0,0,0.7), rgba(0,0,0,0.6))", - zIndex: 1000, - padding: "40px 20px", - overflowY: "auto", - }; - - const modalStyle = { - width: "100%", - maxWidth: 1100, - background: "linear-gradient(180deg, rgba(10,4,14,0.98) 0%, rgba(5,2,8,0.98) 100%)", - border: "2px solid #00e6ff", - borderRadius: 12, - padding: "28px", - boxShadow: "0 20px 60px rgba(0,0,0,0.8)", - color: "#9ffcff", - position: "relative", - }; - - const domainModalStyle = { - width: "100%", - maxWidth: 1100, - background: "linear-gradient(180deg, rgba(10,4,14,0.98) 0%, rgba(5,2,8,0.98) 100%)", - border: "2px solid #00e6ff", - borderRadius: 12, - padding: "28px", - boxShadow: "0 20px 60px rgba(0,0,0,0.8)", - color: "#9ffcff", - position: "relative", - maxHeight: "90vh", - overflowY: "auto" - }; - - const headerBarStyle = { - display: "flex", - alignItems: "center", - justifyContent: "space-between", - gap: 12, - marginBottom: 12, - }; - - const titleStyle = { - fontFamily: "'Courier New', Courier, monospace", - fontWeight: 700, - letterSpacing: 1.5, - fontSize: 22, - color: "#00e6ff", - margin: 0, - }; - - const closeBtnStyle = { - background: "#0b0b0b", - color: "#00e6ff", - border: "2px solid rgba(255,255,255,0.03)", - height: 30, - width: 30, - borderRadius: 6, - cursor: "pointer", - fontSize: 18, - lineHeight: "24px", - display: "flex", - alignItems: "center", - justifyContent: "center", - }; - - const bodyGridStyle = { - display: "grid", - gridTemplateColumns: "1fr 1fr", - gap: 24, - marginTop: 16, - alignItems: "start", - }; - - const blockTitleStyle = { - color: "#e8fefe", - fontWeight: 700, - marginBottom: 8, - letterSpacing: 0.6, - }; + return ( +
+ - const listStyle = { - margin: 0, - paddingLeft: 20, - color: "#bfeffb", - lineHeight: 1.9, - }; + {/* Animated gradient orbs */} +
+
+
+
+
- const problemCardStyle = { - background: "rgba(0, 230, 255, 0.05)", - border: "1px solid rgba(0, 230, 255, 0.2)", - borderRadius: 8, - padding: "20px", - marginBottom: "20px", - }; + {/* Navigation */} + - + {/* Hero Section */} +
+
+
+
- return ( -
-
+ +
+ + VIT Chennai Presents +
- +

+ INNOVATION UNLEASHED +

-
-
-

V-VORTEX

-

UNLEASH YOUR INNOVATION

- The ultimate national-level hackathon from VIT Chennai where champions - are forged, ideas become reality, and innovation knows no bounds. - Join us for 24 hours of pure adrenaline, groundbreaking solutions, - and the chance to etch your name in the hall of legends. + The ultimate national-level hackathon where champions were forged, + ideas became reality, and innovation knew no bounds.

-
- + -
-
-

📍 VENUE

-

VIT Chennai

+
+
+ +
+ 07-10 Jan 2026 + Event Held +
-
-

📅 DATES

-

07th - 08th Jan -- IdeaVortex

-

09th - 10th Jan -- HackVortex

-

10th Jan -- PitchVortex

+
+ +
+ VIT Chennai + Venue +
-
-

⚡LEVEL

-

National

+
+ +
+ ₹30,000+ + Prize Pool +
+ + +
+
+ Scroll to explore
-
-

⟨ BATTLE DOMAINS ⟩

+ {/* Domains Section */} +
+ + CHALLENGE AREAS +

Battle Domains

+

Choose your arena and demonstrate your expertise

+
+
-
window.open(DRIVE_LINK, '_blank')} style={{cursor: 'pointer'}}> -
🤖
-

AI/ML

-
-
window.open(DRIVE_LINK, '_blank')} style={{cursor: 'pointer'}}> -
🛡️
-

Cybersecurity

-
-
window.open(DRIVE_LINK, '_blank')} style={{cursor: 'pointer'}}> -
🏥
-

Healthcare

-
-
window.open(DRIVE_LINK, '_blank')} style={{cursor: 'pointer'}}> -
💰
-

Fintech

-
-
window.open(DRIVE_LINK, '_blank')} style={{cursor: 'pointer'}}> -
🔌
-

IoT & Robotics

-
+ {domains.map((domain, idx) => ( + window.open(DRIVE_LINK, '_blank')} + > +
+
+ +
+

{domain.name}

+

{domain.desc}

+
+ +
+ + ))}
-
-

⟨ PATH TO VICTORY ⟩

-
- {Object.keys(rounds).map((k, idx) => ( -
openModal(k)} - role="button" - tabIndex={0} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") openModal(k); - }} + {/* Rounds Section */} +
+ + THE JOURNEY +

The Path Taken

+

Three intense rounds of innovation

+
+ + +
+
+ {round.number} +
+
+
+ +
+
+
+ {round.date} + {round.mode} +
+

{round.title}

+ {round.subtitle} +
+ +

{round.desc}

+ +
    + {round.highlights.map((h, i) => ( +
  • + + {h} +
  • + ))} +
+
+ ))}
-
-

⟨ THE ARCHITECTS ⟩

+ {/* Prizes Section */} +
+ + REWARDS +

Winners Awarded

+

Winners received amazing prizes and recognition

+
+ +
+ {prizes.map((prize, idx) => ( + +
{prize.place}
+
{prize.amount}
+
{prize.label}
+
+ ))} +
-
-

▸ FACULTY COORDINATOR

-
-
-
DP
-

Dr. Pavithra Sekar

-

Faculty Coordinator

-
-
-
RP
-

Dr. Rama Parvathy

-

Faculty Coordinator

-
+ +
+ + Certificates for all Finalists
-
+
+ + Internship Opportunities +
+
+ + Mentorship Sessions +
+ +
-
-

▸ STUDENT COORDINATORS

-
-
-
SJ
-

Sugeeth Jayaraj S.A.

-

Student Coordinator

-

Feel free to reach out

-

+91 81226 54796

+ {/* Team Section */} +
+ + THE ARCHITECTS +

Our Team

+
+ + {/* Faculty Coordinators */} + +

Faculty Coordinators

+
+
+
DP
+

Dr. Pavithra Sekar

+ Faculty Coordinator
-
-
PM
-

Prasanna M

-

Student Coordinator

-

Need Help?

-

+91 97909 70726

+
+
RP
+

Dr. Rama Parvathy

+ Faculty Coordinator
-
- -

⟨ TEAM LEADS ⟩

-
-
MS

M. Shree

Guests, Sponsorship & Awards Committee

-
YG

Yashwant Gokul

Technical Support Committee

-
KD

L. Kevin Daniel

Web Development Committee

-
SJ

Sanjay

Security Committee

-
JK

Jaidev Karthikeyan

Reg & Marketing Committee

-
SV

Suprajha V M

Design & Social Media Committee

-
SN

Sanjana

Design & Social Media Committee

-
- -

⟨ DEV TEAM ⟩

-
-
-
IM
-

Ibhan Mukherjee

-

Frontend Developer

+ + + {/* Student Coordinators */} + +

Student Coordinators

+
+
+
SJ
+

Sugeeth Jayaraj S.A.

+ Student Coordinator + +91 81226 54796 +
+
+
PM
+

Prasanna M

+ Student Coordinator + +91 97909 70726 +
-
-
DP
-

Devangshu Pandey

-

Frontend Developer

+ + + {/* Team Leads */} + +

Team Leads

+
+ {[ + { initials: "MS", name: "M. Shree", role: "Sponsorship & Awards" }, + { initials: "YG", name: "Yashwant Gokul", role: "Technical Support" }, + { initials: "KD", name: "L. Kevin Daniel", role: "Web Development" }, + { initials: "SJ", name: "Sanjay", role: "Security" }, + { initials: "JK", name: "Jaidev Karthikeyan", role: "Registration & Marketing" }, + { initials: "SV", name: "Suprajha V M", role: "Design & Social Media" }, + { initials: "SN", name: "Sanjana", role: "Design & Social Media" } + ].map((member, idx) => ( +
+
{member.initials}
+
+

{member.name}

+ {member.role} +
+
+ ))}
-
-
SG
-

Srijan Guchhait

-

System Architect

+ + + {/* Dev Team */} + +

Dev Team

+
+ {[ + { initials: "IM", name: "Ibhan Mukherjee", role: "Frontend Developer" }, + { initials: "DP", name: "Devangshu Pandey", role: "Frontend Developer" }, + { initials: "SG", name: "Srijan Guchhait", role: "System Architect" } + ].map((member, idx) => ( +
+
{member.initials}
+

{member.name}

+ {member.role} +
+ ))}
-
+
-
-
-

Supported By

- + {/* Footer */} +
+
+ + + + + + + + + +
-

🌀 V-VORTEX 2026 • WHERE LEGENDS ARE BORN 🌀

-

VIT Chennai • National Level Hackathon

-
- - {/* Rounds Modal */} -
- {activeRound && ( -
e.stopPropagation()} - style={modalStyle} - > -
-

{activeRound.title}

- -
- -
- "{activeRound.desc}" -
- -
- -
- {Object.entries(activeRound.blocks).map(([heading, items]) => ( -
-

{heading.toUpperCase()}

-
    - {items.map((it, i) => ( -
  • - - {it} -
  • - ))} -
-
- ))} +
+
+

Supported By

+
- )} -
- {/* Domain Modal */} -
- {activeDomain && ( -
e.stopPropagation()} - style={domainModalStyle} - > -
-

- {activeDomain.icon} {activeDomain.title} - PROBLEM STATEMENTS -

- -
- -
- -
-

- Problem statements are in the below mentioned drive link. -

-

- https://drive.google.com/drive/folders/1_8MIetG3u4Y-5st4FfFWtrbDbe6VEPFd?usp=sharing -

+
+
+ + V-VORTEX 2026
+

Where Legends Are Born

+

VIT Chennai • National Level Hackathon

- )} -
+
+
); } - - - - - diff --git a/src/pages/login.jsx b/src/pages/login.jsx deleted file mode 100644 index 6e0a4a1..0000000 --- a/src/pages/login.jsx +++ /dev/null @@ -1,296 +0,0 @@ -import { useState, useEffect, useRef } from "react"; -import { useNavigate } from "react-router-dom"; -import { supabase } from "../supabaseClient"; -import "../styles/login.css"; -import logo from "/logo.jpg"; - -export default function Login({ setTransition }) { - const navigate = useNavigate(); - const [email, setEmail] = useState(""); - const [role, setRole] = useState("Team Leader"); - const [showModal, setShowModal] = useState(false); - const [notFoundInfo, setNotFoundInfo] = useState(null); - const [isMobile, setIsMobile] = useState(false); - - const modalRef = useRef(null); - - // Mobile detection - useEffect(() => { - const checkMobile = () => setIsMobile(window.innerWidth <= 768); - checkMobile(); - window.addEventListener("resize", checkMobile); - return () => window.removeEventListener("resize", checkMobile); - }, []); - - const handleModalOk = (e) => { - if (e && e.preventDefault) e.preventDefault(); - setShowModal(false); - navigate("/otp"); - }; - - const handleModalCancel = (e) => { - if (e && e.preventDefault) e.preventDefault(); - setShowModal(false); - }; - - useEffect(() => { - if (!showModal) return; - const onKey = (ev) => { - if (ev.key === "Escape") { - setShowModal(false); - } else if (ev.key === "Enter") { - handleModalOk(ev); - } - }; - window.addEventListener("keydown", onKey); - return () => window.removeEventListener("keydown", onKey); - }, [showModal, handleModalOk]); - - const handleSubmit = async (e) => { - e.preventDefault(); - - try { - // 1. Verify email exists (different checks for leader vs member) - let team = null; - const trimmedEmail = email.trim(); - - if (role === "Team Leader") { - const { data: t, error: teamError } = await supabase - .from('teams') - .select('id, team_name, lead_email') - .ilike('lead_email', trimmedEmail) - .single(); - - if (teamError || !t) { - setNotFoundInfo({ - type: 'leader', - email: trimmedEmail, - message: 'Team leader account not found. You can register as a Team Leader or verify your email with your team leader.' - }); - return; - } - - team = t; - } else { - // Team Member login - verify member exists - const { data: member, error: memberError } = await supabase - .from('team_members') - .select('team_id') - .ilike('member_email', trimmedEmail) - .single(); - - if (memberError || !member) { - setNotFoundInfo({ - type: 'member', - email: trimmedEmail, - message: 'Member account not found. Ask your team leader to add your email to their team, or register as a Team Leader.' - }); - return; - } - - const { data: t, error: teamError2 } = await supabase - .from('teams') - .select('id, team_name') - .eq('id', member.team_id) - .single(); - - if (teamError2 || !t) { - alert('❌ Team not found for this member.'); - return; - } - - team = t; - } - - // 2. Send OTP to email using Supabase Auth - const { error: otpError } = await supabase.auth.signInWithOtp({ - email: email, - options: { - shouldCreateUser: true, - } - }); - - if (otpError) { - alert(`❌ Failed to send OTP: ${otpError.message}`); - return; - } - - // 3. Store email and role in sessionStorage for OTP verification page - sessionStorage.setItem('loginEmail', email); - sessionStorage.setItem('teamId', team.id); - sessionStorage.setItem('role', role); - - // 4. Show success modal - setShowModal(true); - } catch (error) { - console.error('Login error:', error); - alert('❌ An error occurred. Please try again.'); - } - }; - - useEffect(() => { - const el = document.getElementById("system-time"); - if (!el) return; - - const update = () => { - const now = new Date(); - el.textContent = now.toLocaleTimeString("en-GB", { hour12: false }); - }; - - update(); - const interval = setInterval(update, 1000); - return () => clearInterval(interval); - }, []); - - return ( - <> - {/* TOP MARQUEE - FIXED */} -
-
- - ⚡ 24 HOURS TO LEGENDARY STATUS • CODE LIKE YOUR DREAMS DEPEND ON IT • BUILD THE IMPOSSIBLE • SLEEP IS FOR THE WEAK • YOUR SQUAD, YOUR LEGACY • BREAK LIMITS, NOT RULES • INNOVATION NEVER SLEEPS • - - -
-
- -
- {/* LOGIN BOX */} -
- {/* HEADER */} -
-
- V-VORTEX logo - V-VORTEX -
- -
-
-
-
-
-
- -
- ⟨ WARRIORS ASSEMBLE • THE ARENA AWAITS ⟩ -
- - {/* FORM */} -
- {notFoundInfo && ( -
- Notice: -

{notFoundInfo.message}

-
- - -
-
- )} - - -

– Your designation in the squad

- - - setEmail(e.target.value)} - autoComplete="email" - required - /> -

– Your official battle credentials

- - -
-
-
- - {/* STATUS BAR - FIXED BOTTOM */} -
-
-
- ARENA STATUS: LIVE & ELECTRIC -
-
- SYSTEM TIME: -
-
- LEGENDS IN THE MAKING: LOADING… -
-
- - {/* MODAL */} - {showModal && ( -
setShowModal(false)} - > -
e.stopPropagation()} - ref={modalRef} - role="dialog" - aria-modal="true" - aria-labelledby="modalVerifyTitle" - > -

Verify

-

- A battle code has been dispatched. Click OK to enter the auth gate. -

-
- - -
-
-
- )} - - ); -} diff --git a/src/pages/member.jsx b/src/pages/member.jsx deleted file mode 100644 index 9262705..0000000 --- a/src/pages/member.jsx +++ /dev/null @@ -1,734 +0,0 @@ -import { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { supabase } from "../supabaseClient"; -import "../styles/member.css"; -import logo from "/logo.jpg"; -import { selectProblem } from "../utils/selectProblem"; - -export default function HackVortexDashboard() { - const navigate = useNavigate(); - const [activePage, setActivePage] = useState("details"); - const [time, setTime] = useState("00:00:00"); - const [loading, setLoading] = useState(true); - const [memberData, setMemberData] = useState(null); - const [teamData, setTeamData] = useState(null); - const [showProblemModal, setShowProblemModal] = useState(false); - const [isAssigning, setIsAssigning] = useState(false); - - // Leaderboard State - const [leaderboard, setLeaderboard] = useState([]); - const [leaderboardPublic, setLeaderboardPublic] = useState(false); - const [leaderboardLoading, setLeaderboardLoading] = useState(false); - - // =============================== - // AUTHENTICATION & DATA FETCH - // =============================== - useEffect(() => { - const fetchMemberData = async () => { - try { - // Check if user is authenticated - const { data: { user }, error: authError } = await supabase.auth.getUser(); - - if (authError || !user) { - alert('❌ You must be logged in to access this page.'); - navigate('/login'); - return; - } - - // Fetch member data using authenticated email - const { data: member, error: memberError } = await supabase - .from('team_members') - .select('*') - .eq('member_email', user.email) - .single(); - - if (memberError || !member) { - alert('❌ Member data not found. Please contact support.'); - navigate('/login'); - return; - } - - setMemberData(member); - - // Fetch team data - const { data: team, error: teamError } = await supabase - .from('teams') - .select('*') - .eq('id', member.team_id) - .single(); - - if (teamError || !team) { - alert('❌ Team data not found.'); - navigate('/login'); - return; - } - - setTeamData(team); - setLoading(false); - - } catch (error) { - console.error('Error fetching member data:', error); - alert('❌ An error occurred. Please try again.'); - navigate('/login'); - } - }; - - fetchMemberData(); - }, [navigate]); - - // =============================== - // LIVE CLOCK - // =============================== - useEffect(() => { - const tick = () => { - const now = new Date(); - const h = String(now.getHours()).padStart(2, "0"); - const m = String(now.getMinutes()).padStart(2, "0"); - const s = String(now.getSeconds()).padStart(2, "0"); - setTime(`${h}:${m}:${s}`); - }; - - tick(); - const interval = setInterval(tick, 1000); - return () => clearInterval(interval); - }, []); - - // =============================== - // LEADERBOARD FETCH - // =============================== - const refreshLeaderboard = async () => { - setLeaderboardLoading(true); - try { - // Check if leaderboard is public - const { data: settings } = await supabase.from('app_settings').select('leaderboard_public').eq('id', 'main').single(); - const isPublic = !!settings?.leaderboard_public; - setLeaderboardPublic(isPublic); - - if (isPublic) { - const { data, error } = await supabase.from('leaderboard_view').select('*').order('position', { ascending: true }); - if (error) throw error; - const norm = (data || []).map(r => ({ ...r, score: r.score ?? r.total_score ?? 0, delta: r.delta ?? 0 })); - setLeaderboard(norm); - } else { - setLeaderboard([]); - } - } catch (err) { - console.error('Failed to refresh leaderboard:', err); - setLeaderboard([]); - } finally { - setLeaderboardLoading(false); - } - }; - - // Load leaderboard when tab changes to leaderboard - useEffect(() => { - if (activePage === 'leaderboard') { - refreshLeaderboard(); - } - }, [activePage]); - - // Initial leaderboard load - useEffect(() => { - refreshLeaderboard(); - }, []); - - // =============================== - // PAGE TITLES - // =============================== - const titles = { - details: { - title: "Member Identity", - subtitle: "Authenticated session", - }, - qr: { - title: "Access Gateway", - subtitle: "Team verification portal", - }, - problem: { - title: "Mission Logic", - subtitle: "Strategic challenge overview", - }, - leaderboard: { - title: "Leaderboard", - subtitle: "Live team rankings", - }, - }; - - // Small domains list used for leader problem assignment modal - const domains = { - iot: { - title: "IOT & ROBOTICS", - problems: [ - { name: "Sky-Glow Sentinel (Urban Light Pollution Mapping)", desc: "Design a high-sensitivity Sky Quality Monitoring system and mapping solution." }, - { name: "Smart Parking Occupancy Detection System", desc: "Low-cost IoT-based system to detect parking spot occupancy in real time." }, - ], - }, - aiml: { - title: "AI/ML", - problems: [ - { name: "AI-Generated Image Authenticity Detection", desc: "Determine whether an image is AI-generated or real with explainable confidence." }, - { name: "AI-Powered Mind Map Search Engine", desc: "Retrieve information and present it as an interactive mind map." }, - ], - }, - fintech: { - title: "FINTECH", - problems: [ - { name: "Unified Payment Orchestration & Automated Settlements", desc: "Prototype a unified payment orchestration platform with automated workflows." }, - ], - }, - }; - - // Get member initials for avatar - const getInitials = (name) => { - if (!name) return "??"; - const parts = name.split(" "); - if (parts.length === 1) return parts[0].substring(0, 2).toUpperCase(); - return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); - }; - - // Handle logout - const handleLogout = async () => { - const { error } = await supabase.auth.signOut(); - if (error) { - alert('❌ Logout failed. Please try again.'); - return; - } - navigate('/login'); - }; - - // Problem assignment modal handlers (leader only) - const openProblemModal = () => setShowProblemModal(true); - const closeProblemModal = () => setShowProblemModal(false); - - const handleAssignProblem = async (domainKey, problem) => { - if (!teamData || !teamData.id) { - alert('Team not loaded'); - return; - } - - setIsAssigning(true); - try { - await selectProblem({ - teamId: teamData.id, - domain: domainKey, - problemName: problem.name, - problemDescription: problem.desc, - }); - - // Refresh teamData - const { data: refreshedTeam, error: teamError } = await supabase.from('teams').select('*').eq('id', teamData.id).single(); - if (teamError) throw teamError; - setTeamData(refreshedTeam); - - alert('✅ Problem assigned to team successfully'); - closeProblemModal(); - } catch (err) { - console.error('Assign problem error:', err); - alert(err.message || 'Failed to assign problem'); - } finally { - setIsAssigning(false); - } - }; - - if (loading) { - return ( -
-
- Loading member data... -
-
- ); - } - - return ( - <> - {/* BACKGROUND */} -
- -
- {/* =============================== - SIDEBAR (DESKTOP) - =============================== */} - - - {/* =============================== - MAIN AREA - =============================== */} -
- {/* HEADER */} -
-
-

{titles[activePage].title}

-

{titles[activePage].subtitle}

-
- -
- System Pulse - {time} -
-
- - {/* CONTENT */} -
- {/* DETAILS PAGE */} - {activePage === "details" && ( -
-
-

{memberData?.member_name || 'Member Name'}

-

- {memberData?.member_role || 'Team Member'}
- {teamData?.team_name || 'Team Name'} -

-

- Email: {memberData?.member_email} -

-
-
- )} - - {/* QR PAGE */} - {activePage === "qr" && ( -
-
-

Gateway Access

- {memberData?.member_qr_code ? ( - Member QR Code - ) : ( -
QR
- )} -

- Team: {teamData?.team_name} -

- -
-
- )} - - {/* PROBLEM PAGE */} - {activePage === "problem" && ( -
-
-

{teamData?.problem_statement || 'Problem Statement'}

-

- {teamData?.problem_description || - 'Problem description will be displayed here once your team receives the challenge.'} -

- -
- - - {/* Leader-only: assign/select problem */} - {memberData?.member_email === teamData?.lead_email && ( - - )} -
-
- - {/* Problem selection modal (leader only) */} - {showProblemModal && ( -
-
e.stopPropagation()} style={{ width: '100%', maxWidth: 900, padding: 20 }}> -
-

Assign Problem Statement

- -
- -
- {Object.entries(domains).map(([key, d]) => ( -
-

{d.title}

-
- {d.problems.map((p, i) => ( -
- {p.name} -

{p.desc}

- -
- ))} -
-
- ))} -
-
-
- )} -
- )} - - {/* LEADERBOARD PAGE */} - {activePage === "leaderboard" && ( -
-
- - {/* Team Stats Cards */} - {leaderboardPublic && leaderboard.length > 0 && (() => { - const currentTeam = leaderboard.find(r => r.team_id === teamData?.id); - const alphaTeam = leaderboard[0]; - const gapToAlpha = currentTeam ? (alphaTeam?.score || 0) - (currentTeam?.score || 0) : 0; - - return ( -
- {/* Tactical Rank Card */} -
-
TACTICAL RANK
-
- - {currentTeam?.position || '—'} - - /{leaderboard.length} -
-
- - {/* Accumulated Data Card */} -
-
ACCUMULATED DATA
-
- {currentTeam?.score || 0} -
-
- - {/* Gap to Alpha Card */} -
-
GAP TO ALPHA
-
- - {gapToAlpha} - - PTS -
-
-
- ); - })()} - - {!leaderboardPublic ? ( -
-
🔒
-

Leaderboard Hidden

-

- The leaderboard is currently not available. Check back later when the organizers make it public. -

-
- ) : leaderboardLoading ? ( -
-
-

Loading leaderboard...

-
- ) : leaderboard.length === 0 ? ( -
-
📊
-

No leaderboard data available yet.

-
- ) : ( -
- {/* Table Header */} -
-
POSITION
-
SQUAD DESIGNATION
-
PAYLOAD
-
- - {/* Leaderboard Rows */} - {leaderboard.map((row, idx) => { - const isCurrentTeam = row.team_id === teamData?.id; - const prevTeam = leaderboard[idx - 1]; - const delta = prevTeam ? row.score - (prevTeam?.score || 0) : 0; - - return ( -
- {isCurrentTeam && ( -
- )} - - {/* Position */} -
- - {row.position} - -
- - {/* Squad Designation */} -
-
- {row.team_name?.slice(0, 2).toUpperCase() || '??'} -
-
- - {row.team_name} - - - {isCurrentTeam ? 'YOUR SQUAD' : 'ACTIVE'} - -
-
- - {/* Payload */} -
-
- {row.score} -
- {row.position > 1 && ( -
- {delta >= 0 ? `+${delta}` : delta} PTS -
- )} -
-
- ); - })} -
- )} -
-
- )} -
-
-
- - ); -} diff --git a/src/pages/otp.jsx b/src/pages/otp.jsx deleted file mode 100644 index 5bf597c..0000000 --- a/src/pages/otp.jsx +++ /dev/null @@ -1,154 +0,0 @@ -import { useState, useEffect } from "react"; -import { supabase } from "../supabaseClient"; -import "../styles/otp.css"; -import VortexBackground from "../components/VortexBackground"; -import logo from "/logo.jpg"; -import { useNavigate } from "react-router-dom"; - -export default function OTP({ setTransition }) { - const [otp, setOtp] = useState(""); - const [email, setEmail] = useState(""); - const navigate = useNavigate(); - - // Get email from sessionStorage - useEffect(() => { - const storedEmail = sessionStorage.getItem('loginEmail'); - if (!storedEmail) { - alert('❌ No login session found. Please login again.'); - navigate('/login'); - return; - } - setEmail(storedEmail); - }, [navigate]); - - const handleVerify = async (e) => { - e.preventDefault(); - - if (otp.length !== 6) { - alert("⚠ INVALID BATTLE CODE • MUST BE 6 DIGITS ⚠"); - return; - } - - try { - // Verify OTP with Supabase - const { data, error } = await supabase.auth.verifyOtp({ - email: email, - token: otp, - type: 'email' - }); - - if (error) { - alert(`❌ INVALID CODE: ${error.message}`); - return; - } - - // OTP verified successfully - alert("🔥 AUTH VERIFIED • WELCOME TO THE VORTEX CHAMPION 🔥"); - - // Clear login email from session storage - sessionStorage.removeItem('loginEmail'); - - const role = sessionStorage.getItem('role') || 'Team Leader'; - sessionStorage.removeItem('role'); - - // Get team ID (may be needed for dashboard) - const teamId = sessionStorage.getItem('teamId'); - sessionStorage.removeItem('teamId'); - - // Decide destination based on role - const destination = role === 'Team Member' ? '/member' : `/dashboard/${teamId}`; - - if (setTransition) { - setTransition( -
- Entering The Vortex... -
- ); - - setTimeout(() => { - setTransition(null); - navigate(destination); - }, 1200); - } else { - navigate(destination); - } - } catch (error) { - console.error('OTP verification error:', error); - alert('❌ An error occurred. Please try again.'); - } - }; - - // Time updater - useEffect(() => { - const el = document.getElementById("otp-system-time"); - if (!el) return; - - const tick = () => { - el.textContent = new Date().toLocaleTimeString("en-GB", { hour12: false }); - }; - - tick(); - const interval = setInterval(tick, 1000); - return () => clearInterval(interval); - }, []); - - return ( -
- - - {/* MARQUEE */} -
-
- ⚡ BATTLE CODE DEPLOYED • ENTER THE AUTH GATE • BECOME UNSTOPPABLE • - ⚡ BATTLE CODE DEPLOYED • ENTER THE AUTH GATE • BECOME UNSTOPPABLE • -
-
- - {/* OTP BOX */} -
-
-
- - V-VORTEX -
- -
-
-
-
-
-
- -
- ⟨ ENTER YOUR 6-DIGIT AUTHENTICATION CODE ⟩ -
- -
- - setOtp(e.target.value.replace(/\D/g, ""))} - /> - -

– Found in your mission control center

- - - -
navigate("/login")}> - ⟨ REGROUP • GO BACK ⟩ -
-
-
- - {/* STATUS BAR */} -
-
AUTH GATE: ACTIVE
-
SYSTEM TIME:
-
MISSION LOG: STANDBY...
-
-
- ); -} diff --git a/src/pages/register.jsx b/src/pages/register.jsx deleted file mode 100644 index e83b876..0000000 --- a/src/pages/register.jsx +++ /dev/null @@ -1,503 +0,0 @@ -import { useEffect, useRef, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { supabase } from "../supabaseClient"; -import "../styles/register.css"; -import logo from "/logo.jpg"; -import submitSfxFile from "/vortex_music.m4a"; - -export default function Register() { - const canvasRef = useRef(null); - const formWrapperRef = useRef(null); - const vortexMessageRef = useRef(null); - const submitSfxRef = useRef(null); - const timeoutsRef = useRef([]); - - const navigate = useNavigate(); - - const [sucked, setSucked] = useState(false); - const [vortexVisible, setVortexVisible] = useState(false); - const [isSubmitting, setIsSubmitting] = useState(false); - const [submitMessage, setSubmitMessage] = useState(""); - - const [role, setRole] = useState("team_leader"); - const [authEmail, setAuthEmail] = useState(""); - const [sendingOtp, setSendingOtp] = useState(false); - - const [isVitChennai, setIsVitChennai] = useState("yes"); - const [eventHubId, setEventHubId] = useState(""); - const [universityName, setUniversityName] = useState(""); - const [teamCode, setTeamCode] = useState(""); - - const stateRef = useRef({ - width: window.innerWidth, - height: window.innerHeight, - digits: [], - ARMS: 10, - POINTS_PER_ARM: 40, - SPIRAL_TIGHTNESS: 0.28, - baseSpeed: 0.00045, - speedFactor: 1, - targetSpeedFactor: 1, - rotationAngle: 0, - lastTime: 0, - }); - - useEffect(() => { - if (role === "team_leader" && isVitChennai === "yes") { - setEventHubId(""); - setUniversityName(""); - } - }, [isVitChennai, role]); - - useEffect(() => { - const storedIntent = sessionStorage.getItem("registerIntent"); - if (!storedIntent) return; - - try { - const parsed = JSON.parse(storedIntent); - if (parsed?.role === "team_leader" || parsed?.role === "team_member") { - setRole(parsed.role); - } - if (parsed?.email) { - setAuthEmail(parsed.email); - } - } catch (error) { - console.warn("Invalid register intent payload"); - sessionStorage.removeItem("registerIntent"); - } - }, []); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - const ctx = canvas.getContext("2d"); - const s = stateRef.current; - - function randomBinary() { - return Math.random() < 0.5 ? "0" : "1"; - } - - function initDigits() { - s.digits.length = 0; - const maxRadius = Math.min(s.width, s.height) * 0.9; - const minRadius = maxRadius * 0.06; - - for (let arm = 0; arm < s.ARMS; arm++) { - const armOffset = (Math.PI * 2 * arm) / s.ARMS; - for (let i = 0; i < s.POINTS_PER_ARM; i++) { - const t = i / (s.POINTS_PER_ARM - 1); - const radius = minRadius + t * maxRadius; - const angle = armOffset + radius * s.SPIRAL_TIGHTNESS; - const depth = t; - const jitter = (Math.random() - 0.5) * 8; - - s.digits.push({ - char: randomBinary(), - baseRadius: radius + jitter, - baseAngle: angle + (Math.random() - 0.5) * 0.06, - depth, - size: 9 + depth * 20, - baseOpacity: 0.28 + (1 - Math.pow(depth, 0.5)) * 0.85, - paletteIndex: Math.floor(Math.random() * 3), - }); - } - } - } - - function resize() { - s.width = canvas.width = window.innerWidth; - s.height = canvas.height = window.innerHeight; - initDigits(); - } - - resize(); - window.addEventListener("resize", resize); - - let rafId; - function draw(ts) { - if (!s.lastTime) s.lastTime = ts; - const dt = ts - s.lastTime; - s.lastTime = ts; - - s.speedFactor += (s.targetSpeedFactor - s.speedFactor) * 0.04; - s.rotationAngle += s.baseSpeed * s.speedFactor * dt; - - const cx = s.width / 2; - const cy = s.height / 2; - - ctx.fillStyle = "rgba(2, 6, 23, 0.18)"; - ctx.fillRect(0, 0, s.width, s.height); - - s.digits.forEach((d) => { - const pulse = Math.sin(ts * 0.0005 + d.baseRadius * 0.008) * 6; - const radius = d.baseRadius + pulse; - const angle = d.baseAngle + s.rotationAngle * (0.7 + 0.6 * (1 - d.depth)); - - const x = cx + Math.cos(angle) * radius; - const y = cy + Math.sin(angle) * radius; - - ctx.save(); - ctx.translate(x, y); - - const perspectiveScale = 0.75 + (1 - d.depth) * 0.6; - ctx.scale(1, perspectiveScale); - ctx.rotate(angle + Math.PI / 2); - - const flicker = 0.9 + Math.sin(ts * 0.006 + d.baseRadius * 0.02) * 0.18; - const opacity = d.baseOpacity * flicker; - - const hue = d.paletteIndex === 0 ? 190 : d.paletteIndex === 1 ? 325 : 283; - const light = 55 + (1 - d.depth) * 14; - - ctx.font = `${d.size}px monospace`; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - - ctx.shadowColor = `hsla(${hue}, 100%, 65%, ${opacity})`; - ctx.shadowBlur = 12 * (1 - d.depth); - - ctx.fillStyle = `hsla(${hue}, 100%, ${light}%, ${opacity})`; - ctx.fillText(d.char, 0, 0); - ctx.restore(); - - if (Math.random() < 0.0025) d.char = randomBinary(); - }); - - rafId = requestAnimationFrame(draw); - } - - ctx.fillStyle = "#020617"; - ctx.fillRect(0, 0, s.width, s.height); - rafId = requestAnimationFrame(draw); - - return () => { - cancelAnimationFrame(rafId); - window.removeEventListener("resize", resize); - }; - }, []); - - const handleStartRegistration = async (e) => { - e.preventDefault(); - - if (sendingOtp) return; - - const trimmedEmail = authEmail.trim(); - if (!trimmedEmail) { - alert("Please enter your email to continue."); - return; - } - - setSendingOtp(true); - - try { - const { error } = await supabase.auth.signInWithOtp({ - email: trimmedEmail, - options: { - shouldCreateUser: true, - }, - }); - - if (error) throw error; - - sessionStorage.setItem("loginEmail", trimmedEmail); - sessionStorage.setItem("authFlow", "register"); - sessionStorage.setItem( - "registerIntent", - JSON.stringify({ email: trimmedEmail }) - ); - - navigate("/otp"); - } catch (error) { - console.error("OTP dispatch error:", error); - alert(`❌ Failed to send OTP: ${error?.message || "Please try again."}`); - } finally { - setSendingOtp(false); - } - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - if (isSubmitting) return; - - timeoutsRef.current.forEach((id) => clearTimeout(id)); - timeoutsRef.current = []; - - const formData = new FormData(e.target); - const leaderName = formData.get("leaderName"); - const leaderReg = formData.get("leaderReg"); - const leaderEmail = formData.get("leaderEmail"); - const receiptLink = formData.get("receiptLink"); - - if (isVitChennai === "no" && !String(eventHubId || "").trim()) { - alert("Please enter your VIT EventHub Unique ID."); - return; - } - - if (!receiptLink || !receiptLink.trim()) { - alert("⚠️ Please provide a payment receipt link."); - return; - } - - setIsSubmitting(true); - setSubmitMessage("⚡ Registering team leader..."); - - try { - setSubmitMessage("🔥 Creating your account..."); - const { data, error } = await supabase.functions.invoke("register-team", { - body: { - isVitChennai, - eventHubId: isVitChennai === "no" ? eventHubId : null, - leaderName, - leaderReg: isVitChennai === "yes" ? leaderReg : null, - leaderEmail, - receiptLink, - }, - }); - - if (error) { - const errorMsg = error.message || error.error || "Registration failed"; - throw new Error(errorMsg); - } - - // Check for errors in the response data - if (data?.error) { - throw new Error(data.error); - } - - setSubmitMessage("✅ REGISTRATION SUCCESSFUL! Redirecting to login..."); - - if (submitSfxRef.current) { - submitSfxRef.current.currentTime = 0; - submitSfxRef.current.play().catch(() => {}); - } - - stateRef.current.targetSpeedFactor = 28; - setSucked(true); - - const showDelay = 2200; - const visibleDuration = 6000; - - const t1 = setTimeout(() => setVortexVisible(true), showDelay); - const t2 = setTimeout(() => { - setVortexVisible(false); - setSucked(false); - }, showDelay + visibleDuration); - const t3 = setTimeout(() => navigate("/login"), showDelay + visibleDuration + 250); - - timeoutsRef.current.push(t1, t2, t3); - } catch (error) { - console.error("Registration error:", error); - setIsSubmitting(false); - setSubmitMessage(""); - alert("❌ Registration failed. Please try again."); - } - }; - - useEffect(() => { - submitSfxRef.current = new Audio(submitSfxFile); - submitSfxRef.current.preload = "auto"; - submitSfxRef.current.volume = 0.95; - - return () => { - try { - submitSfxRef.current.pause(); - } catch (e) { - // Ignore errors on cleanup - } - }; - }, []); - - useEffect(() => { - return () => { - timeoutsRef.current.forEach((id) => clearTimeout(id)); - timeoutsRef.current = []; - }; - }, []); - - return ( - <> - -
- -
-
- - -
-
-
🛡️ Team Leader Registration
- -

- Register as a team leader first. After logging in, you'll be able to build your team by adding 1-4 members (2-5 total including yourself). -

- -
- - -
-
- - - - -
-
- - {isVitChennai === "no" && ( -
- - setEventHubId(e.target.value)} - /> -
- )} - -
- 👤 Your Details -
- -
-
- - -
- {isVitChennai === "yes" && ( -
- - -
- )} -
- -
- - -
- -
- - -

- Upload your payment receipt to Google Drive and share the link with view access. -

-
- - {isSubmitting && ( -
- {submitMessage} -
- )} - - - -
-
-
- -
-
-

🔥 TEAM LEADER REGISTERED 🔥

-

- You've successfully registered as a TEAM LEADER. Login to your dashboard and use the "Build Your Team" feature to add your team members and complete your squad formation. -

-
-
- - ); -} diff --git a/src/styles/admin.css b/src/styles/admin.css deleted file mode 100644 index dd1c17e..0000000 --- a/src/styles/admin.css +++ /dev/null @@ -1,331 +0,0 @@ -/* FULL PAGE */ -.adminWrapper { - width: 100vw; - min-height: 100vh; - background: #020202; - color: #e8fff2; - padding-top: 80px; -} - -/* NAVBAR */ -.adminNav { - width: 100%; - height: 70px; - background: rgba(0, 30, 15, 0.55); - border-bottom: 1px solid rgba(0, 255, 157, 0.25); - backdrop-filter: blur(10px); - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 35px; - position: fixed; - top: 0; - z-index: 10; -} - -.adminLeft { - display: flex; - align-items: center; - gap: 10px; -} - -.adminLogo { - width: 45px; - height: 45px; -} - -.adminTitle { - font-size: 22px; - color: #00ffae; - text-shadow: 0 0 10px #00ffae; -} - -/* LOGOUT BUTTON */ -.logoutBtn { - padding: 8px 18px; - background: #00ff9d; - border: none; - border-radius: 6px; - font-weight: bold; - cursor: pointer; - transition: 0.25s; -} - -.logoutBtn:hover { - box-shadow: 0 0 15px #00ff9d; -} - -/* GRID CARDS */ -.adminGrid { - margin-top: 40px; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 25px; - justify-content: center; - padding: 0 20px; -} - -.adminCard { - padding: 20px; - background: rgba(0, 35, 20, 0.45); - border: 1px solid rgba(0, 255, 157, 0.3); - border-radius: 12px; - backdrop-filter: blur(10px); - box-shadow: 0 0 18px rgba(0, 255, 157, 0.15); - text-align: center; - transition: 0.25s ease; -} - -.adminCard:hover { - transform: scale(1.05); - box-shadow: 0 0 25px #00ff9d; -} - -.metric { - font-size: 26px; - margin-top: 10px; -} - -.metric.green { color: #00ff95; } -.metric.red { color: #ff5e5e; } -.metric.yellow { color: #ffe76a; } - -/* ACTIVITY PANEL */ -.adminPanel { - width: 90%; - max-width: 900px; - margin: 30px auto; - padding: 20px; - background: rgba(0, 35, 20, 0.45); - border: 1px solid rgba(0, 255, 170, 0.25); - border-radius: 12px; - backdrop-filter: blur(10px); - box-shadow: 0 0 20px rgba(0, 255, 170, 0.15); - display: flex; -} - -/* MOBILE OPTIMIZATION */ -@media (max-width: 768px) { - .adminNav { - padding: 0 15px; - } - - .adminTitle { - font-size: 18px; - } - - .adminGrid { - grid-template-columns: 1fr; - } - - /* Stack the teams list and details panel vertically on small screens - so selecting a team shows details below the list instead of off-screen. */ - .adminPanel { - flex-direction: column; - align-items: stretch; - width: 96%; - max-width: 960px; - } - - .adminPanel > div { - width: 100%; - flex: none; - } -} - -.panelTitle { - color: #00ffae; - margin-bottom: 20px; - font-size: 22px; -} - -.logBox { - font-size: 15px; - line-height: 1.5; - color: #cfffec; - background: rgba(0, 20, 10, 0.4); - padding: 20px; - border-radius: 8px; - border: 1px solid rgba(0,255,150,0.2); -} -/* FULL PAGE */ -/* FULL PAGE */ -.adminWrapper { - width: 100vw; - min-height: 100vh; - background: #020202; - color: #e8fff2; - padding-top: 80px; -} - -/* NAVBAR */ -.adminNav { - width: 100%; - height: 70px; - background: rgba(0, 30, 15, 0.55); - border-bottom: 1px solid rgba(0, 255, 157, 0.25); - backdrop-filter: blur(10px); - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 35px; - position: fixed; - top: 0; - z-index: 10; -} - -.adminLeft { - display: flex; - align-items: center; - gap: 10px; -} -/* FULL PAGE */ -.adminWrapper { - width: 100vw; - min-height: 100vh; - background: #020202; - color: #e8fff2; - padding-top: 80px; -} - -/* NAVBAR */ -.adminNav { - width: 100%; - height: 70px; - background: rgba(0, 30, 15, 0.55); - border-bottom: 1px solid rgba(0, 255, 157, 0.25); - backdrop-filter: blur(10px); - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 35px; - position: fixed; - top: 0; - z-index: 10; -} - -.adminLeft { - display: flex; - align-items: center; - gap: 10px; -} - -.adminLogo { - width: 45px; - height: 45px; -} - -.adminTitle { - font-size: 22px; - color: #00ffae; - text-shadow: 0 0 10px #00ffae; -} - -/* LOGOUT BUTTON */ -.logoutBtn { - padding: 8px 18px; - background: #00ff9d; - border: none; - border-radius: 6px; - font-weight: bold; - cursor: pointer; - transition: 0.25s; -} - -.logoutBtn:hover { - box-shadow: 0 0 15px #00ff9d; -} - -/* GRID CARDS */ -.adminGrid { - margin-top: 40px; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 25px; - justify-content: center; - padding: 0 20px; -} - -.adminCard { - padding: 20px; - background: rgba(0, 35, 20, 0.45); - border: 1px solid rgba(0, 255, 157, 0.3); - border-radius: 12px; - backdrop-filter: blur(10px); - box-shadow: 0 0 18px rgba(0, 255, 157, 0.15); - text-align: center; - transition: 0.25s ease; -} - -.adminCard:hover { - transform: scale(1.05); - box-shadow: 0 0 25px #00ff9d; -} - -.metric { - font-size: 26px; - margin-top: 10px; -} - -.metric.green { color: #00ff95; } -.metric.red { color: #ff5e5e; } -.metric.yellow { color: #ffe76a; } - -/* ACTIVITY PANEL */ -.adminPanel { - width: 90%; - max-width: 900px; - margin: 30px auto; - padding: 20px; - background: rgba(0, 35, 20, 0.45); - border: 1px solid rgba(0, 255, 170, 0.25); - border-radius: 12px; - backdrop-filter: blur(10px); - box-shadow: 0 0 20px rgba(0, 255, 170, 0.15); - display: flex; -} - -/* MOBILE OPTIMIZATION */ -@media (max-width: 768px) { - .adminNav { - padding: 0 15px; - } - - .adminTitle { - font-size: 18px; - } - - .adminGrid { - grid-template-columns: 1fr; - } - - /* Stack the teams list and details panel vertically on small screens - so selecting a team shows details below the list instead of off-screen. */ - .adminPanel { - flex-direction: column; - align-items: stretch; - width: 96%; - max-width: 960px; - } - - .adminPanel > div { - width: 100%; - flex: none; - } -} - -.panelTitle { - color: #00ffae; - margin-bottom: 20px; - font-size: 22px; -} - -.logBox { - font-size: 15px; - line-height: 1.5; - color: #cfffec; - background: rgba(0, 20, 10, 0.4); - padding: 20px; - border-radius: 8px; - border: 1px solid rgba(0,255,150,0.2); -} diff --git a/src/styles/admin_mobile_fix.css b/src/styles/admin_mobile_fix.css deleted file mode 100644 index 0399046..0000000 --- a/src/styles/admin_mobile_fix.css +++ /dev/null @@ -1,14 +0,0 @@ -@media (max-width: 768px) { - /* Force vertical stacking on small screens */ - .adminPanel { - flex-direction: column !important; - align-items: stretch !important; - width: 96% !important; - max-width: 960px !important; - } - - .adminPanel > div { - width: 100% !important; - flex: none !important; - } -} diff --git a/src/styles/animations.css b/src/styles/animations.css new file mode 100644 index 0000000..a2184f7 --- /dev/null +++ b/src/styles/animations.css @@ -0,0 +1,505 @@ +/* ═══════════════════════════════════════════════════════════ + V-VORTEX ANIMATION LIBRARY + Stunning micro-interactions and effects + ═══════════════════════════════════════════════════════════ */ + +/* ─────────────── ANIMATED SECTION BASE ─────────────── */ +/* GPU-accelerated base class for scroll-triggered animations */ +.animated-section { + opacity: 0; + will-change: transform, opacity; + animation-fill-mode: forwards; + animation-delay: var(--anim-delay, 0ms); + backface-visibility: hidden; + -webkit-backface-visibility: hidden; +} + +/* Initial states with GPU-optimized transforms */ +.animated-section.anim-reveal-up { + transform: translate3d(0, 40px, 0); +} + +.animated-section.anim-fade-in { + transform: translate3d(0, 0, 0); +} + +.animated-section.anim-scale-in { + transform: scale3d(0.9, 0.9, 1); +} + +.animated-section.anim-slide-left { + transform: translate3d(-60px, 0, 0); +} + +.animated-section.anim-slide-right { + transform: translate3d(60px, 0, 0); +} + +/* Visible states - trigger animations */ +.animated-section.is-visible.anim-reveal-up { + animation: reveal-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards; + animation-delay: var(--anim-delay, 0ms); +} + +.animated-section.is-visible.anim-fade-in { + animation: fade-in 0.6s cubic-bezier(0.4, 0, 0.2, 1) forwards; + animation-delay: var(--anim-delay, 0ms); +} + +.animated-section.is-visible.anim-scale-in { + animation: scale-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; + animation-delay: var(--anim-delay, 0ms); +} + +.animated-section.is-visible.anim-slide-left { + animation: slide-in-left 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards; + animation-delay: var(--anim-delay, 0ms); +} + +.animated-section.is-visible.anim-slide-right { + animation: slide-in-right 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards; + animation-delay: var(--anim-delay, 0ms); +} + +/* ─────────────── VORTEX SPIN ─────────────── */ +@keyframes vortex-spin { + 0% { + transform: rotate(0deg) scale(1); + } + + 50% { + transform: rotate(180deg) scale(1.05); + } + + 100% { + transform: rotate(360deg) scale(1); + } +} + +.animate-vortex { + animation: vortex-spin 8s linear infinite; +} + +/* ─────────────── FLOAT ─────────────── */ +@keyframes float { + + 0%, + 100% { + transform: translateY(0) rotate(0deg); + } + + 25% { + transform: translateY(-10px) rotate(1deg); + } + + 75% { + transform: translateY(10px) rotate(-1deg); + } +} + +.animate-float { + animation: float 6s ease-in-out infinite; +} + +.animate-float-slow { + animation: float 10s ease-in-out infinite; +} + +.animate-float-fast { + animation: float 3s ease-in-out infinite; +} + +/* ─────────────── GLOW PULSE ─────────────── */ +@keyframes glow-pulse { + + 0%, + 100% { + box-shadow: 0 0 20px var(--vortex-violet-glow), + 0 0 40px var(--vortex-violet-glow); + opacity: 1; + } + + 50% { + box-shadow: 0 0 40px var(--vortex-cyan-glow), + 0 0 80px var(--vortex-cyan-glow); + opacity: 0.8; + } +} + +.animate-glow-pulse { + animation: glow-pulse 3s ease-in-out infinite; +} + +/* ─────────────── TEXT GLOW ─────────────── */ +@keyframes text-glow { + + 0%, + 100% { + text-shadow: 0 0 10px var(--vortex-cyan-glow), + 0 0 20px var(--vortex-cyan-glow), + 0 0 30px var(--vortex-cyan-glow); + } + + 50% { + text-shadow: 0 0 20px var(--vortex-violet-glow), + 0 0 40px var(--vortex-violet-glow), + 0 0 60px var(--vortex-violet-glow); + } +} + +.animate-text-glow { + animation: text-glow 2s ease-in-out infinite; +} + +/* ─────────────── REVEAL UP ─────────────── */ +@keyframes reveal-up { + 0% { + opacity: 0; + transform: translateY(40px); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +} + +.animate-reveal-up { + animation: reveal-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +.reveal-delay-1 { + animation-delay: 0.1s; +} + +.reveal-delay-2 { + animation-delay: 0.2s; +} + +.reveal-delay-3 { + animation-delay: 0.3s; +} + +.reveal-delay-4 { + animation-delay: 0.4s; +} + +.reveal-delay-5 { + animation-delay: 0.5s; +} + +/* ─────────────── FADE IN ─────────────── */ +@keyframes fade-in { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +.animate-fade-in { + animation: fade-in 0.6s ease forwards; +} + +/* ─────────────── SCALE IN ─────────────── */ +@keyframes scale-in { + 0% { + opacity: 0; + transform: scale(0.8); + } + + 100% { + opacity: 1; + transform: scale(1); + } +} + +.animate-scale-in { + animation: scale-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +/* ─────────────── SLIDE IN ─────────────── */ +@keyframes slide-in-left { + 0% { + opacity: 0; + transform: translateX(-60px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slide-in-right { + 0% { + opacity: 0; + transform: translateX(60px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.animate-slide-left { + animation: slide-in-left 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +.animate-slide-right { + animation: slide-in-right 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +/* ─────────────── PARTICLE FLOAT ─────────────── */ +@keyframes particle-float { + + 0%, + 100% { + transform: translate(0, 0) rotate(0deg); + opacity: 0.6; + } + + 25% { + transform: translate(20px, -30px) rotate(90deg); + opacity: 1; + } + + 50% { + transform: translate(-10px, -60px) rotate(180deg); + opacity: 0.8; + } + + 75% { + transform: translate(-30px, -30px) rotate(270deg); + opacity: 0.6; + } +} + +.animate-particle { + animation: particle-float 15s ease-in-out infinite; +} + +/* ─────────────── MORPH SHAPES ─────────────── */ +@keyframes morph { + + 0%, + 100% { + border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; + } + + 25% { + border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%; + } + + 50% { + border-radius: 50% 60% 30% 60% / 30% 60% 70% 40%; + } + + 75% { + border-radius: 60% 40% 60% 30% / 70% 30% 50% 60%; + } +} + +.animate-morph { + animation: morph 8s ease-in-out infinite; +} + +/* ─────────────── GRADIENT MOVE ─────────────── */ +@keyframes gradient-move { + 0% { + background-position: 0% 50%; + } + + 50% { + background-position: 100% 50%; + } + + 100% { + background-position: 0% 50%; + } +} + +.animate-gradient { + background-size: 200% 200%; + animation: gradient-move 5s ease infinite; +} + +/* ─────────────── MAGNETIC HOVER ─────────────── */ +@keyframes magnetic-pulse { + + 0%, + 100% { + transform: scale(1); + } + + 50% { + transform: scale(1.02); + } +} + +.magnetic-hover { + transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1); +} + +.magnetic-hover:hover { + animation: magnetic-pulse 0.6s ease-in-out; +} + +/* ─────────────── TYPEWRITER ─────────────── */ +@keyframes typewriter { + from { + width: 0; + } + + to { + width: 100%; + } +} + +@keyframes blink-caret { + + 0%, + 50% { + border-color: var(--vortex-cyan); + } + + 51%, + 100% { + border-color: transparent; + } +} + +.animate-typewriter { + overflow: hidden; + white-space: nowrap; + border-right: 3px solid var(--vortex-cyan); + animation: typewriter 3s steps(40) forwards, + blink-caret 0.75s step-end infinite; +} + +/* ─────────────── SCANLINES ─────────────── */ +@keyframes scanline { + 0% { + transform: translateY(-100%); + } + + 100% { + transform: translateY(100vh); + } +} + +.scanline-effect::after { + content: ''; + position: fixed; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, + transparent, + var(--vortex-cyan-glow), + transparent); + animation: scanline 8s linear infinite; + pointer-events: none; + z-index: 9998; +} + +/* ─────────────── RIPPLE EFFECT ─────────────── */ +@keyframes ripple { + 0% { + transform: scale(0); + opacity: 1; + } + + 100% { + transform: scale(4); + opacity: 0; + } +} + +.ripple-container { + position: relative; + overflow: hidden; +} + +.ripple-container::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 100px; + height: 100px; + background: var(--vortex-violet-glow); + border-radius: 50%; + transform: translate(-50%, -50%) scale(0); + opacity: 0; +} + +.ripple-container:active::after { + animation: ripple 0.6s ease-out; +} + +/* ─────────────── 3D TILT ─────────────── */ +.tilt-3d { + transform-style: preserve-3d; + transition: transform 0.3s ease; +} + +.tilt-3d:hover { + transform: perspective(1000px) rotateX(5deg) rotateY(-5deg) scale(1.02); +} + +/* ─────────────── NEON FLICKER ─────────────── */ +@keyframes neon-flicker { + + 0%, + 19%, + 21%, + 23%, + 25%, + 54%, + 56%, + 100% { + text-shadow: + 0 0 5px var(--vortex-cyan), + 0 0 10px var(--vortex-cyan), + 0 0 20px var(--vortex-cyan), + 0 0 40px var(--vortex-violet), + 0 0 80px var(--vortex-violet); + } + + 20%, + 24%, + 55% { + text-shadow: none; + } +} + +.animate-neon-flicker { + animation: neon-flicker 2s infinite alternate; +} + +/* ─────────────── SCROLL PROGRESS ─────────────── */ +@keyframes scroll-progress { + 0% { + transform: scaleX(0); + } + + 100% { + transform: scaleX(1); + } +} + +.scroll-progress-bar { + position: fixed; + top: 0; + left: 0; + right: 0; + height: 3px; + background: var(--gradient-hero); + transform-origin: left; + z-index: 9999; +} \ No newline at end of file diff --git a/src/styles/auth.css b/src/styles/auth.css deleted file mode 100644 index d014768..0000000 --- a/src/styles/auth.css +++ /dev/null @@ -1,85 +0,0 @@ -.authWrapper { - width: 100vw; - height: 100vh; - background: #020202; - position: relative; - padding-top: 100px; - color: #e8fff2; -} - -/* Center card */ -.authCard { - width: min(420px, 90vw); - margin: auto; - padding: 35px; - background: rgba(0, 35, 20, 0.45); - border: 1px solid rgba(0, 255, 150, 0.25); - border-radius: 12px; - box-shadow: 0 0 25px rgba(0,255,150,0.15); - backdrop-filter: blur(10px); - animation: fadeIn 1.2s ease-out forwards; -} - -.authTitle { - color: #00ffae; - text-shadow: 0 0 12px #00ffae; - font-size: 28px; - text-align: center; - margin-bottom: 20px; -} - -/* Inputs */ -.authInput { - width: 100%; - padding: 12px; - background: rgba(0, 255, 170, 0.05); - border: 1px solid rgba(0, 255, 170, 0.35); - border-radius: 6px; - color: #ccffe6; - margin-bottom: 18px; - font-size: 15px; - outline: none; - transition: 0.25s ease; -} - -.authInput:focus { - border-color: #00ff9d; - box-shadow: 0 0 12px #00ff9d; -} - -/* Buttons */ -.authBtn { - width: 100%; - padding: 12px; - font-size: 17px; - font-weight: bold; - background: #00ff9d; - color: #002f1c; - border: none; - border-radius: 6px; - cursor: pointer; - transition: 0.25s; -} - -.authBtn:hover { - box-shadow: 0 0 15px #00ff9d; -} - -/* Secondary btn */ -.altLink { - margin-top: 12px; - text-align: center; - color: #00ffb0; - cursor: pointer; - transition: 0.25s; -} - -.altLink:hover { - text-shadow: 0 0 10px #00ffb0; -} - -/* Smooth fade */ -@keyframes fadeIn { - from { opacity: 0; transform: translateY(12px); } - to { opacity: 1; transform: translateY(0); } -} diff --git a/src/styles/build-team.css b/src/styles/build-team.css deleted file mode 100644 index b389447..0000000 --- a/src/styles/build-team.css +++ /dev/null @@ -1,206 +0,0 @@ -.buildTeamContainer { - max-width: 900px; - margin: 0 auto; - padding: 2rem 1rem; -} - -.buildTeamCard { - background: rgba(15, 23, 42, 0.8); - border: 1px solid rgba(139, 92, 246, 0.3); - border-radius: 12px; - padding: 2rem; - box-shadow: 0 8px 32px rgba(139, 92, 246, 0.15); -} - -.buildTeamTitle { - font-size: 2rem; - font-weight: 700; - color: #e879f9; - margin-bottom: 0.5rem; - text-shadow: 0 0 10px rgba(232, 121, 249, 0.5); -} - -.buildTeamDesc { - color: #94a3b8; - margin-bottom: 2rem; - line-height: 1.6; -} - -.buildTeamError { - background: rgba(239, 68, 68, 0.1); - border: 1px solid rgba(239, 68, 68, 0.3); - color: #fca5a5; - padding: 1rem; - border-radius: 8px; - margin-bottom: 1.5rem; -} - -.buildField { - margin-bottom: 1.5rem; -} - -.buildField label { - display: block; - color: #cbd5e1; - font-weight: 500; - margin-bottom: 0.5rem; - font-size: 0.95rem; -} - -.buildInput { - width: 100%; - padding: 0.75rem 1rem; - background: rgba(30, 41, 59, 0.6); - border: 1px solid rgba(139, 92, 246, 0.2); - border-radius: 8px; - color: #f1f5f9; - font-size: 1rem; - transition: all 0.3s ease; -} - -.buildInput:focus { - outline: none; - border-color: #8b5cf6; - box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1); -} - -.buildInput::placeholder { - color: #64748b; -} - -.teamSizeButtons { - display: flex; - gap: 1rem; -} - -.teamSizeBtn { - flex: 1; - padding: 0.75rem 1.5rem; - background: rgba(30, 41, 59, 0.6); - border: 2px solid rgba(139, 92, 246, 0.3); - border-radius: 8px; - color: #cbd5e1; - font-size: 1rem; - font-weight: 600; - cursor: pointer; - transition: all 0.3s ease; -} - -.teamSizeBtn:hover { - border-color: #8b5cf6; - background: rgba(139, 92, 246, 0.1); -} - -.teamSizeBtn.active { - background: rgba(139, 92, 246, 0.2); - border-color: #a78bfa; - color: #e9d5ff; - box-shadow: 0 0 15px rgba(139, 92, 246, 0.3); -} - -.membersSection { - margin-top: 2rem; - margin-bottom: 2rem; -} - -.membersSectionTitle { - font-size: 1.25rem; - color: #a78bfa; - margin-bottom: 1.5rem; - font-weight: 600; -} - -.memberCard { - background: rgba(30, 41, 59, 0.4); - border: 1px solid rgba(139, 92, 246, 0.2); - border-radius: 10px; - padding: 1.5rem; - margin-bottom: 1.5rem; -} - -.memberHeader { - font-size: 1.1rem; - font-weight: 600; - color: #e879f9; - margin-bottom: 1rem; - padding-bottom: 0.5rem; - border-bottom: 1px solid rgba(139, 92, 246, 0.2); -} - -.toggleGroup { - display: flex; - gap: 0.5rem; -} - -.toggleBtn { - flex: 1; - padding: 0.6rem 1rem; - background: rgba(30, 41, 59, 0.6); - border: 1px solid rgba(139, 92, 246, 0.3); - border-radius: 6px; - color: #94a3b8; - font-size: 0.95rem; - cursor: pointer; - transition: all 0.3s ease; -} - -.toggleBtn:hover { - border-color: #8b5cf6; - background: rgba(139, 92, 246, 0.1); -} - -.toggleBtn.active { - background: rgba(139, 92, 246, 0.25); - border-color: #a78bfa; - color: #e9d5ff; - font-weight: 600; -} - -.buildSubmitBtn { - width: 100%; - padding: 1rem 2rem; - background: linear-gradient(135deg, #8b5cf6 0%, #ec4899 100%); - border: none; - border-radius: 10px; - color: white; - font-size: 1.1rem; - font-weight: 700; - cursor: pointer; - transition: all 0.3s ease; - text-transform: uppercase; - letter-spacing: 0.5px; - box-shadow: 0 4px 15px rgba(139, 92, 246, 0.4); -} - -.buildSubmitBtn:hover:not(:disabled) { - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(139, 92, 246, 0.6); -} - -.buildSubmitBtn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Responsive */ -@media (max-width: 768px) { - .buildTeamContainer { - padding: 1rem 0.5rem; - } - - .buildTeamCard { - padding: 1.5rem; - } - - .buildTeamTitle { - font-size: 1.5rem; - } - - .teamSizeButtons { - flex-direction: column; - } - - .toggleGroup { - flex-direction: column; - } -} diff --git a/src/styles/custom-cursor.css b/src/styles/custom-cursor.css new file mode 100644 index 0000000..167b21f --- /dev/null +++ b/src/styles/custom-cursor.css @@ -0,0 +1,224 @@ +/* ═══════════════════════════════════════════════════════════ + V-VORTEX CUSTOM CURSOR STYLES + Interactive cursor with glow and trail effects + ═══════════════════════════════════════════════════════════ */ + +/* Hide default cursor globally */ +* { + cursor: none !important; +} + +/* Main cursor dot */ +.vortex-cursor { + position: fixed; + top: 0; + left: 0; + width: 12px; + height: 12px; + background: var(--vortex-cyan); + border-radius: 50%; + pointer-events: none; + z-index: var(--z-cursor); + mix-blend-mode: difference; + transition: transform 0.15s ease-out, + width 0.2s ease, + height 0.2s ease, + background 0.3s ease; + transform: translate(-50%, -50%); +} + +/* Cursor outer ring */ +.vortex-cursor-ring { + position: fixed; + top: 0; + left: 0; + width: 40px; + height: 40px; + border: 2px solid var(--vortex-violet); + border-radius: 50%; + pointer-events: none; + z-index: calc(var(--z-cursor) - 1); + transition: transform 0.25s ease-out, + width 0.3s ease, + height 0.3s ease, + border-color 0.3s ease, + opacity 0.3s ease; + transform: translate(-50%, -50%); + opacity: 0.6; +} + +/* Cursor glow effect */ +.vortex-cursor::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 30px; + height: 30px; + background: radial-gradient(circle, var(--vortex-cyan-glow) 0%, transparent 70%); + border-radius: 50%; + transform: translate(-50%, -50%); + opacity: 0.8; + pointer-events: none; +} + +/* ─────────────── HOVER STATES ─────────────── */ + +/* Link/Button hover - expand cursor */ +.vortex-cursor.cursor-hover { + width: 60px; + height: 60px; + background: transparent; + border: 2px solid var(--vortex-pink); + mix-blend-mode: normal; +} + +.vortex-cursor.cursor-hover::before { + width: 80px; + height: 80px; + background: radial-gradient(circle, var(--vortex-pink-glow) 0%, transparent 70%); +} + +.vortex-cursor-ring.cursor-hover { + width: 80px; + height: 80px; + border-color: var(--vortex-pink); + opacity: 0.4; +} + +/* Text input hover */ +.vortex-cursor.cursor-text { + width: 3px; + height: 24px; + border-radius: 2px; + background: var(--vortex-cyan); + animation: blink 1s infinite; +} + +@keyframes blink { + + 0%, + 50% { + opacity: 1; + } + + 51%, + 100% { + opacity: 0; + } +} + +.vortex-cursor-ring.cursor-text { + opacity: 0; +} + +/* Clicking state */ +.vortex-cursor.cursor-click { + transform: translate(-50%, -50%) scale(0.8); + background: var(--vortex-pink); +} + +.vortex-cursor-ring.cursor-click { + transform: translate(-50%, -50%) scale(0.6); + border-color: var(--vortex-pink); +} + +/* ─────────────── CURSOR TRAIL ─────────────── */ +.cursor-trail { + position: fixed; + width: 8px; + height: 8px; + background: var(--vortex-violet); + border-radius: 50%; + pointer-events: none; + z-index: calc(var(--z-cursor) - 2); + opacity: 0.5; + transform: translate(-50%, -50%); + transition: opacity 0.3s ease; +} + +.cursor-trail:nth-child(2) { + width: 6px; + height: 6px; + opacity: 0.4; + transition-delay: 0.02s; +} + +.cursor-trail:nth-child(3) { + width: 5px; + height: 5px; + opacity: 0.3; + transition-delay: 0.04s; +} + +.cursor-trail:nth-child(4) { + width: 4px; + height: 4px; + opacity: 0.2; + transition-delay: 0.06s; +} + +.cursor-trail:nth-child(5) { + width: 3px; + height: 3px; + opacity: 0.1; + transition-delay: 0.08s; +} + +/* ─────────────── MAGNETIC EFFECT AREA ─────────────── */ +[data-cursor="magnetic"] { + position: relative; +} + +/* ─────────────── HIDDEN STATE ─────────────── */ +.vortex-cursor.cursor-hidden, +.vortex-cursor-ring.cursor-hidden { + opacity: 0; + transform: translate(-50%, -50%) scale(0); +} + +/* ─────────────── LOADING STATE ─────────────── */ +.vortex-cursor.cursor-loading { + animation: cursor-spin 1s linear infinite; +} + +@keyframes cursor-spin { + 0% { + transform: translate(-50%, -50%) rotate(0deg); + } + + 100% { + transform: translate(-50%, -50%) rotate(360deg); + } +} + +/* ─────────────── MOBILE FALLBACK ─────────────── */ +@media (hover: none) and (pointer: coarse) { + + .vortex-cursor, + .vortex-cursor-ring, + .cursor-trail { + display: none !important; + } + + * { + cursor: auto !important; + } +} + +/* ─────────────── REDUCED MOTION ─────────────── */ +@media (prefers-reduced-motion: reduce) { + + .vortex-cursor, + .vortex-cursor-ring { + transition: none; + } + + .cursor-trail { + display: none; + } + + .vortex-cursor::before { + animation: none; + } +} \ No newline at end of file diff --git a/src/styles/dashboard.css b/src/styles/dashboard.css deleted file mode 100644 index 2a5c8c5..0000000 --- a/src/styles/dashboard.css +++ /dev/null @@ -1,814 +0,0 @@ -/* =============================== - INDEXX LAYOUT STRUCTURE -================================ */ - -/* SIDEBAR */ -.sidebar { - position: fixed; - left: 0; - top: 0; - width: 280px; - height: 100vh; - display: flex; - flex-direction: column; - background: rgba(2, 6, 23, 0.95); - backdrop-filter: blur(16px); - border-right: 1px solid rgba(217, 70, 239, 0.25); - padding: 30px 24px; - z-index: 50; - overflow: hidden; /* clip the decorative logo */ -} - -/* decorative background logo (low opacity) */ -.sidebar::before { - content: ""; - position: absolute; - right: 20px; - bottom: 20px; - width: 120px; - height: 120px; - background-image: url("/logo.jpg"); - background-size: contain; - background-repeat: no-repeat; - background-position: center; - opacity: 0.06; - filter: grayscale(100%); - transform: rotate(-8deg); - pointer-events: none; - z-index: -1; /* keep it behind content */ -} - -/* ensure sidebar content sits above decorative logo */ -.sidebar > * { - position: relative; - z-index: 1; -} - -.sidebarLogo { - display: flex; - align-items: center; - gap: 10px; - font-size: 20px; - font-weight: 900; - color: #d946ef; - margin-bottom: 6px; -} - -.sidebarLogoImg { - width: 36px; - height: 36px; - border-radius: 8px; - object-fit: cover; - box-shadow: 0 0 8px rgba(217,70,239,0.25); -} - -.sidebarSub { - font-size: 11px; - letter-spacing: 0.25em; - color: #f472b6; - margin-bottom: 30px; -} - -.sidebarNav button { - width: 100%; - padding: 12px 14px; - margin-bottom: 10px; - border-radius: 10px; - border: none; - background: transparent; - color: #9ca3af; - font-family: inherit; - cursor: pointer; - transition: 0.25s; -} - -.sidebarNav button:hover { - background: rgba(255, 255, 255, 0.06); - color: white; -} - -.sidebarNav .active { - background: linear-gradient(90deg, #d946ef, #701a75); - color: white; - box-shadow: 0 0 15px rgba(217, 70, 239, 0.6); -} - -/* SIDEBAR FOOTER */ -.sidebarFooter { - margin-top: auto; - padding-top: 12px; -} - -.userCard { - background: rgba(255, 255, 255, 0.05); - border-radius: 12px; - padding: 14px; - margin-bottom: 14px; -} - -.disconnectBtn { - width: 100%; - background: transparent; - border: none; - color: #f87171; - cursor: pointer; - font-size: 12px; - letter-spacing: 0.2em; -} - -/* MAIN AREA */ -.mainArea { - margin-left: 280px; - min-height: 100vh; -} - -/* HEADER */ -.mainHeader { - background: rgba(2, 6, 23, 0.7); - backdrop-filter: blur(14px); - border-bottom: 1px solid rgba(217, 70, 239, 0.25); - padding: 24px 40px; - display: flex; - justify-content: space-between; - align-items: center; -} - -.headerLeft { - display: flex; - align-items: center; - gap: 12px; -} -.headerTitle { - font-size: 28px; - font-weight: 900; - color: white; -} - -.headerSub { - font-size: 12px; - letter-spacing: 0.25em; - color: #e879f9; -} - -/* PAGE CONTENT */ -.pageContent { - padding: 40px; -} - -/* GLASS CARD */ -.glassCard { - background: rgba(255, 255, 255, 0.04); - backdrop-filter: blur(18px); - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 22px; - padding: 32px; -} - -/* MOBILE */ -.menuBtn { - display: none; /* visible on small screens only */ -} - -@media (max-width: 900px) { - .sidebar { - transform: translateX(-100%); - position: fixed; - left: 0; - top: 0; - width: 260px; - height: 100vh; - transition: transform 0.25s ease; - box-shadow: 0 20px 40px rgba(0,0,0,0.6); - z-index: 60; - } - - .sidebar.open { - transform: translateX(0); - } - - .sidebarOverlay { - position: fixed; - inset: 0; - background: rgba(0,0,0,0.4); - z-index: 55; - backdrop-filter: blur(2px); - } - - /* put sidebar content above the decorative background */ - .sidebar > * { - position: relative; - z-index: 2; - } - - .mainArea { - margin-left: 0; - } - - .menuBtn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - background: linear-gradient(90deg,#d946ef,#701a75); - color: #fff; - border-radius: 8px; - border: none; - margin-right: 12px; - cursor: pointer; - font-size: 18px; - } - - .mainHeader { - padding: 16px 18px; - } -} -/* =============================== - VORTEX HUB PAGE -================================ */ - -.vortexHub { - display: flex; - flex-direction: column; - gap: 36px; -} - -/* CARD GRID */ -.vortexGrid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 28px; -} - -@media (max-width: 900px) { - .vortexGrid { - grid-template-columns: repeat(2, 1fr); - gap: 18px; - } -} - -@media (max-width: 600px) { - .vortexGrid { - grid-template-columns: 1fr; - gap: 12px; - } - .pageContent { padding: 20px; } - .teamSummary { flex-direction: column; align-items: stretch; gap: 16px; } - .teamLeft { display: flex; gap: 12px; align-items: center; } - .teamRight { text-align: left; } -} - -.vortexCard { - background: linear-gradient( - 145deg, - rgba(80, 70, 140, 0.25), - rgba(30, 20, 60, 0.55) - ); - border-radius: 26px; - padding: 34px; - min-height: 220px; - - border: 1px solid rgba(255, 255, 255, 0.08); - backdrop-filter: blur(20px); - - box-shadow: - inset 0 0 0 1px rgba(255,255,255,0.04), - 0 30px 80px rgba(0,0,0,0.45); - - transition: 0.35s ease; -} - -.vortexCard:hover { - transform: translateY(-10px); - box-shadow: - 0 40px 90px rgba(217,70,239,0.35); -} - -/* ICON */ -.vortexIcon { - width: 54px; - height: 54px; - border-radius: 16px; - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 24px; - - background: linear-gradient(135deg, #a855f7, #d946ef); - box-shadow: 0 0 25px rgba(217,70,239,0.6); - - font-size: 26px; - font-weight: 900; - color: white; -} - -/* CARD TEXT */ -.vortexCard h3 { - font-size: 26px; - font-weight: 900; - color: white; - margin-bottom: 12px; -} - -.vortexCard p { - font-size: 15px; - color: #c7d2fe; - line-height: 1.6; -} - -/* =============================== - TEAM SUMMARY BAR -================================ */ - -.teamSummary { - background: linear-gradient( - 90deg, - rgba(88, 28, 135, 0.85), - rgba(30, 20, 60, 0.85) - ); - - border-radius: 28px; - padding: 34px 42px; - - display: flex; - align-items: center; - justify-content: space-between; - - border: 1px solid rgba(217, 70, 239, 0.35); - - box-shadow: - 0 0 50px rgba(217,70,239,0.45); -} - -/* LEFT */ -.teamLeft { - display: flex; - align-items: center; - gap: 22px; -} - -.teamBadge { - width: 72px; - height: 72px; - border-radius: 18px; - - background: linear-gradient(135deg, #a855f7, #6366f1); - color: white; - - display: flex; - align-items: center; - justify-content: center; - - font-size: 26px; - font-weight: 900; - - box-shadow: 0 0 35px rgba(168,85,247,0.6); -} - -.teamLeft h2 { - font-size: 30px; - font-weight: 900; - color: white; -} - -.teamLeft p { - font-size: 12px; - letter-spacing: 0.25em; - color: #f0abfc; - margin-top: 6px; -} - -/* RIGHT */ -.teamRight { - text-align: right; -} - -.teamRight span { - font-size: 12px; - letter-spacing: 0.3em; - color: #c084fc; - display: block; - margin-bottom: 10px; -} - -.teamRight strong { - font-size: 56px; - font-weight: 900; - color: #e9d5ff; -} - -/* RESPONSIVE */ -@media (max-width: 1100px) { - .vortexGrid { - grid-template-columns: 1fr; - } - - .teamSummary { - flex-direction: column; - gap: 24px; - text-align: center; - } - - .teamRight { - text-align: center; - } -} -/* =============================== - LEADERBOARD PAGE -================================ */ - -.statsGrid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 26px; - margin-bottom: 36px; -} - -.statCard { - background: linear-gradient( - 145deg, - rgba(80,70,140,0.35), - rgba(30,20,60,0.55) - ); - border-radius: 22px; - padding: 28px 32px; - border: 1px solid rgba(255,255,255,0.1); -} - -.statLabel { - font-size: 11px; - letter-spacing: 0.35em; - color: #e879f9; - margin-bottom: 10px; -} - -.statValue { - font-size: 52px; - font-weight: 900; - color: white; -} - -.statSub { - font-size: 16px; - color: #c084fc; - margin-left: 6px; -} - -/* =============================== - LEADERBOARD TABLE -================================ */ - -.leaderboardTable { - background: linear-gradient( - 90deg, - rgba(40,30,70,0.85), - rgba(30,20,60,0.85) - ); - border-radius: 26px; - overflow: hidden; - border: 1px solid rgba(255,255,255,0.1); -} - -.lbHeader, -.lbRow { - display: grid; - grid-template-columns: 220px 1fr 180px; - align-items: center; - padding: 26px 32px; -} - -.lbHeader { - font-size: 11px; - letter-spacing: 0.35em; - color: #e879f9; - border-bottom: 1px solid rgba(255,255,255,0.1); -} - -.lbRow { - border-bottom: 1px solid rgba(255,255,255,0.06); - transition: 0.25s; -} - -.lbRow:hover { - background: rgba(255,255,255,0.04); -} - -.lbRow.you { - background: rgba(217,70,239,0.12); - border-left: 4px solid #d946ef; -} - -/* =============================== - RANK BADGES -================================ */ - -.rankBadge { - display: flex; - align-items: center; - gap: 10px; - font-weight: 900; - font-size: 18px; -} - -.rank-ultra { color: #facc15; } -.rank-elite { color: #e5e7eb; } -.rank-apex { color: #fb923c; } - -.rankDefault { - font-family: monospace; - color: #9ca3af; -} - -/* =============================== - TEAM CELL -================================ */ - -.teamCell { - display: flex; - align-items: center; - gap: 18px; -} - -.teamIcon { - width: 46px; - height: 46px; - border-radius: 12px; - background: linear-gradient(135deg, #a855f7, #6366f1); - display: flex; - align-items: center; - justify-content: center; - font-weight: 900; -} - -.teamMeta strong { - font-size: 18px; -} - -.teamMeta span { - font-size: 11px; - letter-spacing: 0.25em; - color: #e879f9; -} - -/* =============================== - PAYLOAD -================================ */ - -.payload { - text-align: right; -} - -.payload strong { - font-size: 26px; -} - -.deltaUp { - color: #22c55e; - font-size: 12px; -} - -.deltaDown { - color: #ef4444; - font-size: 12px; -} - -/* RESPONSIVE */ -@media (max-width: 1100px) { - .statsGrid { - grid-template-columns: 1fr; - } - .lbHeader, - .lbRow { - grid-template-columns: 1fr; - gap: 12px; - } -} -/* =============================== - NEXUS ENTRY PAGE -================================ */ - -.nexusWrapper { - display: flex; - justify-content: center; - align-items: center; - min-height: 70vh; -} - -.nexusCard { - width: 420px; - max-width: 90%; - background: linear-gradient( - 180deg, - rgba(60, 40, 90, 0.75), - rgba(25, 15, 45, 0.9) - ); - border-radius: 28px; - padding: 42px 36px; - text-align: center; - - border: 1px solid rgba(217, 70, 239, 0.35); - box-shadow: - 0 0 80px rgba(217, 70, 239, 0.35), - inset 0 0 0 1px rgba(255,255,255,0.05); -} - -/* QR BOX */ -.qrBox { - width: 200px; - height: 200px; - margin: 0 auto 28px; - background: white; - border-radius: 22px; - - display: flex; - align-items: center; - justify-content: center; - - box-shadow: - 0 0 50px rgba(217,70,239,0.8); -} - -.qrBox img { - width: 140px; - height: 140px; -} - -/* TEXT */ -.nexusTitle { - font-size: 26px; - font-weight: 900; - color: white; - margin-bottom: 10px; -} - -.nexusDesc { - font-size: 14px; - color: #c7d2fe; - line-height: 1.6; - margin-bottom: 28px; -} - -/* CODE BAR */ -.nexusCodeBar { - background: linear-gradient( - 135deg, - rgba(88, 28, 135, 0.85), - rgba(30, 20, 60, 0.85) - ); - border-radius: 16px; - padding: 16px 18px; - - font-family: monospace; - font-size: 15px; - letter-spacing: 0.25em; - color: #f5d0fe; - - border: 1px solid rgba(217, 70, 239, 0.45); -} - -/* RESPONSIVE */ -@media (max-width: 600px) { - .nexusCard { - padding: 32px 26px; - } - - .qrBox { - width: 170px; - height: 170px; - } - - .qrBox img { - width: 120px; - height: 120px; - } -} -/* LOCK ICON IN NEXUS */ -.lockIcon { - font-size: 96px; - line-height: 1; - filter: drop-shadow(0 0 25px rgba(217,70,239,0.9)); -} -/* =============================== - MISSION PAGE -================================ */ - -.missionWrapper { - background: linear-gradient( - 145deg, - rgba(40, 30, 70, 0.85), - rgba(60, 20, 40, 0.85) - ); - border-radius: 36px; - padding: 48px 52px; - border: 1px solid rgba(217, 70, 239, 0.35); - box-shadow: 0 0 80px rgba(217,70,239,0.25); -} - -/* TAG */ -.missionTag { - display: inline-block; - padding: 6px 14px; - font-size: 11px; - letter-spacing: 0.25em; - border-radius: 999px; - background: rgba(217,70,239,0.18); - color: #f472b6; - margin-bottom: 18px; -} - -/* TITLE */ -.missionTitle { - font-size: 42px; - font-weight: 900; - line-height: 1.15; - color: white; -} - -.missionTitle span { - color: #f472b6; -} - -/* GRID */ -.missionGrid { - display: grid; - grid-template-columns: 1.1fr 0.9fr; - gap: 36px; - margin-top: 36px; -} - -/* CARDS */ -.missionCard { - background: rgba(255,255,255,0.06); - border-radius: 24px; - padding: 30px; - border: 1px solid rgba(255,255,255,0.12); -} - -/* REQUIREMENTS */ -.reqTitle { - font-size: 18px; - font-weight: 800; - margin-bottom: 20px; -} - -.reqList { - list-style: none; - padding: 0; - margin: 0; -} - -.reqList li { - display: flex; - align-items: flex-start; - gap: 12px; - margin-bottom: 16px; - color: #dbeafe; - font-size: 15px; -} - -.reqDot { - color: #f472b6; - font-weight: 900; -} - -/* EVALUATION */ -.evalRow { - margin-bottom: 22px; -} - -.evalHeader { - display: flex; - justify-content: space-between; - font-size: 12px; - letter-spacing: 0.2em; - color: #e879f9; - margin-bottom: 10px; -} - -.evalBar { - height: 8px; - border-radius: 999px; - background: rgba(255,255,255,0.15); - overflow: hidden; -} - -.evalFill { - height: 100%; - background: linear-gradient(90deg, #d946ef, #f472b6); -} - -/* RESPONSIVE */ -@media (max-width: 1100px) { - .missionGrid { - grid-template-columns: 1fr; - } - - .missionWrapper { - padding: 36px 28px; - } - - .missionTitle { - font-size: 32px; - } -} - diff --git a/src/styles/design-tokens.css b/src/styles/design-tokens.css new file mode 100644 index 0000000..e356a6f --- /dev/null +++ b/src/styles/design-tokens.css @@ -0,0 +1,225 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700;800;900&family=Share+Tech+Mono&display=swap'); + +/* ═══════════════════════════════════════════════════════════ + V-VORTEX DESIGN TOKENS + Custom Brand Identity - Electric Violet + Cyber Cyan Theme + ═══════════════════════════════════════════════════════════ */ + +:root { + /* ─────────────── PRIMARY COLORS ─────────────── */ + --vortex-violet: #8B5CF6; + --vortex-violet-light: #A78BFA; + --vortex-violet-dark: #7C3AED; + --vortex-violet-glow: rgba(139, 92, 246, 0.6); + + --vortex-cyan: #06B6D4; + --vortex-cyan-light: #22D3EE; + --vortex-cyan-dark: #0891B2; + --vortex-cyan-glow: rgba(6, 182, 212, 0.6); + + --vortex-pink: #EC4899; + --vortex-pink-light: #F472B6; + --vortex-pink-dark: #DB2777; + --vortex-pink-glow: rgba(236, 72, 153, 0.6); + + /* ─────────────── ACCENT COLORS ─────────────── */ + --vortex-gold: #F59E0B; + --vortex-emerald: #10B981; + --vortex-red: #EF4444; + + /* ─────────────── BACKGROUND COLORS ─────────────── */ + --bg-deep-space: #0A0A0F; + --bg-void: #050508; + --bg-nebula: #0F0A1F; + --bg-card: rgba(15, 10, 31, 0.8); + --bg-glass: rgba(139, 92, 246, 0.08); + --bg-glass-hover: rgba(139, 92, 246, 0.15); + + /* ─────────────── TEXT COLORS ─────────────── */ + --text-primary: #F8FAFC; + --text-secondary: #CBD5E1; + --text-muted: #64748B; + --text-accent: var(--vortex-cyan-light); + + /* ─────────────── GRADIENTS ─────────────── */ + --gradient-hero: linear-gradient(135deg, var(--vortex-violet) 0%, var(--vortex-cyan) 50%, var(--vortex-pink) 100%); + --gradient-card: linear-gradient(180deg, rgba(139, 92, 246, 0.1) 0%, rgba(6, 182, 212, 0.05) 100%); + --gradient-button: linear-gradient(135deg, var(--vortex-violet) 0%, var(--vortex-pink) 100%); + --gradient-text: linear-gradient(90deg, var(--vortex-cyan-light), var(--vortex-violet-light), var(--vortex-pink-light)); + --gradient-border: linear-gradient(135deg, var(--vortex-violet), var(--vortex-cyan), var(--vortex-pink)); + --gradient-glow: radial-gradient(circle, var(--vortex-violet-glow) 0%, transparent 70%); + + /* ─────────────── GLASSMORPHISM ─────────────── */ + --glass-bg: rgba(15, 10, 31, 0.6); + --glass-border: rgba(139, 92, 246, 0.2); + --glass-blur: blur(20px); + + /* ─────────────── SHADOWS ─────────────── */ + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 8px 40px rgba(0, 0, 0, 0.5); + --shadow-glow-violet: 0 0 30px var(--vortex-violet-glow); + --shadow-glow-cyan: 0 0 30px var(--vortex-cyan-glow); + --shadow-glow-pink: 0 0 30px var(--vortex-pink-glow); + + /* ─────────────── BORDERS ─────────────── */ + --border-subtle: 1px solid rgba(139, 92, 246, 0.15); + --border-accent: 1px solid rgba(139, 92, 246, 0.4); + --border-glow: 2px solid var(--vortex-violet); + --border-radius-sm: 8px; + --border-radius-md: 12px; + --border-radius-lg: 20px; + --border-radius-xl: 32px; + + /* ─────────────── SPACING ─────────────── */ + --space-xs: 4px; + --space-sm: 8px; + --space-md: 16px; + --space-lg: 24px; + --space-xl: 40px; + --space-2xl: 64px; + --space-3xl: 100px; + + /* ─────────────── TYPOGRAPHY ─────────────── */ + --font-display: 'Orbitron', 'Share Tech Mono', monospace; + --font-body: 'Inter', 'Segoe UI', system-ui, sans-serif; + --font-mono: 'Share Tech Mono', 'Fira Code', monospace; + + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-lg: 1.125rem; + --text-xl: 1.25rem; + --text-2xl: 1.5rem; + --text-3xl: 2rem; + --text-4xl: 2.5rem; + --text-5xl: 3.5rem; + --text-6xl: 4.5rem; + + /* ─────────────── TRANSITIONS ─────────────── */ + --transition-fast: 150ms ease; + --transition-base: 250ms ease; + --transition-slow: 400ms ease; + --transition-bounce: 400ms cubic-bezier(0.68, -0.55, 0.265, 1.55); + + /* ─────────────── Z-INDEX ─────────────── */ + --z-background: -1; + --z-base: 1; + --z-dropdown: 100; + --z-sticky: 200; + --z-modal: 500; + --z-cursor: 9999; +} + +/* ═══════════════════════════════════════════════════════════ + GLOBAL RESET & BASE STYLES + ═══════════════════════════════════════════════════════════ */ + +*, +*::before, +*::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + font-size: 16px; + scroll-behavior: smooth; +} + +body { + font-family: var(--font-body); + background: var(--bg-void); + color: var(--text-primary); + line-height: 1.6; + overflow-x: hidden; + cursor: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Hide default cursor on interactive elements */ +a, +button, +[role="button"], +input, +select, +textarea { + cursor: none; +} + +::selection { + background: var(--vortex-violet); + color: var(--text-primary); +} + +/* ═══════════════════════════════════════════════════════════ + UTILITY CLASSES + ═══════════════════════════════════════════════════════════ */ + +.gradient-text { + background: var(--gradient-text); + background-size: 200% auto; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: shimmer 3s linear infinite; +} + +.glass-card { + background: var(--glass-bg); + backdrop-filter: var(--glass-blur); + -webkit-backdrop-filter: var(--glass-blur); + border: var(--border-subtle); + border-radius: var(--border-radius-lg); +} + +.glow-violet { + box-shadow: var(--shadow-glow-violet); +} + +.glow-cyan { + box-shadow: var(--shadow-glow-cyan); +} + +.glow-pink { + box-shadow: var(--shadow-glow-pink); +} + +/* Animated gradient border */ +.gradient-border { + position: relative; + background: var(--bg-card); + border-radius: var(--border-radius-lg); +} + +.gradient-border::before { + content: ''; + position: absolute; + inset: -2px; + background: var(--gradient-border); + border-radius: inherit; + z-index: -1; + animation: rotate-gradient 4s linear infinite; +} + +@keyframes rotate-gradient { + 0% { + filter: hue-rotate(0deg); + } + + 100% { + filter: hue-rotate(360deg); + } +} + +@keyframes shimmer { + 0% { + background-position: 0% center; + } + + 100% { + background-position: 200% center; + } +} \ No newline at end of file diff --git a/src/styles/home.css b/src/styles/home.css index 832a92c..649e65d 100644 --- a/src/styles/home.css +++ b/src/styles/home.css @@ -1,724 +1,1100 @@ -/* RESET */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} +/* ═══════════════════════════════════════════════════════════ + V-VORTEX HOME PAGE STYLES + Stunning visuals with glassmorphism, gradients & animations + ═══════════════════════════════════════════════════════════ */ -html, -body, -#root { - height: 100%; - background: #000; - font-family: "Share Tech Mono", monospace; - color: #00f7ff; +.vv-home { + min-height: 100vh; + background: var(--bg-void); + position: relative; overflow-x: hidden; } -/* =========================== - SCANLINE -=========================== */ -.scanline { +/* ─────────────── GRADIENT ORBS ─────────────── */ +.gradient-orbs { position: fixed; top: 0; left: 0; width: 100%; - height: 2px; - background: linear-gradient(transparent, #ff00ff 50%, transparent); - opacity: 0.09; - animation: scanline 8s linear infinite; - z-index: 9999; + height: 100%; pointer-events: none; + z-index: 0; + overflow: hidden; } -@keyframes scanline { - 0% { - transform: translateY(-100%); - } - 100% { - transform: translateY(100vh); - } +.orb { + position: absolute; + border-radius: 50%; + filter: blur(80px); + opacity: 0.4; + animation: float 20s ease-in-out infinite; +} + +.orb-1 { + width: 600px; + height: 600px; + background: var(--vortex-violet); + top: -200px; + right: -200px; + animation-delay: 0s; } -/* =========================== - NAVIGATION BAR -=========================== */ +.orb-2 { + width: 500px; + height: 500px; + background: var(--vortex-cyan); + bottom: -150px; + left: -150px; + animation-delay: -7s; +} + +.orb-3 { + width: 400px; + height: 400px; + background: var(--vortex-pink); + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + animation-delay: -14s; +} + +/* ─────────────── NAVIGATION ─────────────── */ .vv-nav { position: fixed; top: 0; left: 0; - width: 100%; - padding: 18px 50px; + right: 0; + z-index: 100; + padding: 16px 0; + transition: all 0.3s ease; +} + +.glass-nav { + background: rgba(10, 10, 15, 0.8); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid rgba(139, 92, 246, 0.1); +} + +.nav-content { + max-width: 1400px; + margin: 0 auto; + padding: 0 40px; display: flex; - justify-content: space-between; align-items: center; - background: rgba(10, 0, 20, 0.95); - backdrop-filter: blur(10px); - border-bottom: 2px solid #ff00ff; - z-index: 1000; - box-shadow: 0 0 30px rgba(255, 0, 255, 0.3); + justify-content: space-between; + gap: 40px; } -.vv-nav .logo { +.logo { display: flex; align-items: center; - gap: 10px; + gap: 12px; } -.vv-nav .logo img { - width: 38px; - height: 38px; - filter: drop-shadow(0 0 10px #ff00ff); +.logo-icon { + display: flex; + animation: vortex-spin 10s linear infinite; } -.vv-nav .logo h1 { - font-size: 24px; - background: linear-gradient(90deg, #00f7ff, #ff00ff, #ff6b35); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - letter-spacing: 3px; +.logo h1 { + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 700; + letter-spacing: 2px; } -/* NAV BUTTONS */ -.nav-buttons { +.nav-links { display: flex; - gap: 20px; + gap: 32px; } -.nav-btn { - padding: 12px 26px; - border: 2px solid #ff00ff; - background: transparent; - color: #00f7ff; - font-weight: bold; - letter-spacing: 2px; - cursor: pointer; - border-radius: 8px; +.nav-link { + color: var(--text-secondary); + text-decoration: none; + font-size: 0.9rem; + font-weight: 500; + letter-spacing: 0.5px; + transition: all 0.3s ease; position: relative; - overflow: hidden; - transition: 0.3s; } -.nav-btn::before { - content: ""; +.nav-link::after { + content: ''; position: absolute; - top: 50%; - left: 50%; + bottom: -4px; + left: 0; width: 0; - height: 0; - background: radial-gradient(circle, rgba(255, 0, 255, 0.2), rgba(0, 247, 255, 0.2)); - border-radius: 50%; - transform: translate(-50%, -50%); - transition: width 0.6s, height 0.6s; + height: 2px; + background: var(--gradient-hero); + transition: width 0.3s ease; +} + +.nav-link:hover { + color: var(--text-primary); } -.nav-btn:hover::before { - width: 300px; - height: 300px; +.nav-link:hover::after { + width: 100%; } -.nav-btn:hover { - color: #ff00ff; - border-color: #00f7ff; - transform: translateY(-3px); - box-shadow: 0 0 18px rgba(0, 247, 255, 0.4); +.nav-buttons { + display: flex; + gap: 12px; } -/* =========================== - HERO SECTION -=========================== */ +.nav-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 24px; + border-radius: var(--border-radius-md); + font-family: var(--font-body); + font-size: 0.9rem; + font-weight: 600; + text-decoration: none; + transition: all 0.3s ease; +} + +.nav-btn.primary { + background: var(--gradient-button); + color: var(--text-primary); + border: none; + box-shadow: 0 4px 20px rgba(139, 92, 246, 0.4); +} + +.nav-btn.primary:hover { + transform: translateY(-2px); + box-shadow: 0 6px 30px rgba(139, 92, 246, 0.5); +} + +/* ─────────────── HERO SECTION ─────────────── */ .hero { min-height: 100vh; display: flex; + flex-direction: column; align-items: center; justify-content: center; - padding: 130px 20px 80px; - text-align: center; position: relative; + padding: 120px 40px 80px; + overflow: hidden; } -.hero::before { - content: ""; +.hero-background { position: absolute; - width: 500px; - height: 500px; - background: radial-gradient(circle, rgba(255, 0, 255, 0.15), transparent 70%); - top: 50%; - left: 50%; - transform: translate(-50%, -55%); - border-radius: 50%; - opacity: 0.7; + inset: 0; + overflow: hidden; +} + +.hero-grid { + position: absolute; + inset: 0; + background-image: + linear-gradient(rgba(139, 92, 246, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(139, 92, 246, 0.03) 1px, transparent 1px); + background-size: 60px 60px; + mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%); + -webkit-mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%); } .hero-content { + position: relative; + z-index: 1; + text-align: center; max-width: 900px; - z-index: 10; - animation: fadeInUp 1s ease-out; } -@keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(30px); - } - to { - opacity: 1; - transform: translateY(0); - } +.hero-badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + background: var(--bg-glass); + border: 1px solid rgba(139, 92, 246, 0.2); + border-radius: 50px; + color: var(--vortex-violet-light); + font-size: 0.85rem; + font-weight: 500; + letter-spacing: 1px; + margin-bottom: 24px; } -.hero h2 { - font-size: 70px; - font-weight: bold; - letter-spacing: 6px; - background: linear-gradient(90deg, #ff00ff, #ff4cff); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - text-shadow: 0 0 25px #ff00ff; +.hero-title { + font-family: var(--font-display); + font-size: clamp(3rem, 10vw, 7rem); + font-weight: 900; + line-height: 1.1; + letter-spacing: 4px; + margin-bottom: 16px; } -/* Slightly smaller headline for better balance */ -@media (max-width: 900px) { - .hero h2 { font-size: 44px; letter-spacing: 3px; } - .hero-subtitle { font-size: 18px; } - .hero { padding: 110px 16px 60px; } +.title-line { + display: block; +} + +.title-line:first-child { + color: var(--text-primary); + text-shadow: 0 0 80px rgba(139, 92, 246, 0.5); } .hero-subtitle { - font-size: 22px; - color: #ff00ff; - letter-spacing: 3px; - margin-bottom: 20px; + font-family: var(--font-display); + font-size: clamp(1rem, 3vw, 1.5rem); + font-weight: 600; + letter-spacing: 6px; + color: var(--vortex-cyan); + margin-bottom: 24px; } .hero-desc { - font-size: 17px; - max-width: 850px; - margin: 0 auto; - line-height: 1.9; - color: #1ee3ea; - opacity: 0.9; + font-size: 1.1rem; + color: var(--text-secondary); + line-height: 1.8; + max-width: 600px; + margin: 0 auto 40px; } -/* Improve readability */ -.hero-desc { color: #c7ffff; opacity: 1; } +.hero-cta { + display: flex; + gap: 16px; + justify-content: center; + flex-wrap: wrap; + margin-bottom: 60px; +} -/* CTA button in hero */ -.cta { - background: linear-gradient(90deg, #00f7ff, #ff4cff); - color: #050006; - border: none; - padding: 12px 26px; - border-radius: 10px; - font-weight: 700; - cursor: pointer; - box-shadow: 0 10px 30px rgba(0,231,255,0.12), 0 6px 18px rgba(255,76,204,0.08); - transition: transform .18s ease, box-shadow .18s ease; +.cta-primary { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 16px 36px; + background: var(--gradient-button); + color: var(--text-primary); + text-decoration: none; + font-weight: 600; + font-size: 1rem; + border-radius: var(--border-radius-lg); + transition: all 0.3s ease; + box-shadow: 0 8px 30px rgba(139, 92, 246, 0.4); } -.cta:hover { transform: translateY(-4px); box-shadow: 0 18px 50px rgba(0,231,255,0.18); } +.cta-primary:hover { + transform: translateY(-3px) scale(1.02); + box-shadow: 0 12px 40px rgba(139, 92, 246, 0.5); +} + +.cta-secondary { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 16px 36px; + background: transparent; + color: var(--text-primary); + text-decoration: none; + font-weight: 600; + font-size: 1rem; + border: 2px solid rgba(139, 92, 246, 0.4); + border-radius: var(--border-radius-lg); + transition: all 0.3s ease; +} + +.cta-secondary:hover { + background: var(--bg-glass); + border-color: var(--vortex-violet); + transform: translateY(-2px); +} -/* EVENT INFO CARDS */ -.event-info { +.hero-stats { display: flex; - gap: 30px; + gap: 24px; justify-content: center; flex-wrap: wrap; - margin-top: 40px; } -.info-card { - background: rgba(10, 0, 20, 0.85); - border: 2px solid #ff00ff; - padding: 28px 40px; - border-radius: 12px; - text-align: center; +.stat-card { + display: flex; + align-items: center; + gap: 16px; + padding: 20px 28px; + background: var(--glass-bg); + backdrop-filter: blur(20px); + border: 1px solid rgba(139, 92, 246, 0.15); + border-radius: var(--border-radius-lg); + transition: all 0.3s ease; +} - /* NEW — EXACT GLOW */ - box-shadow: - 0 0 12px #ff00ff, /* sharp pink edge */ - 0 0 24px #ff00ff, /* outer pink bloom */ - 0 0 60px rgba(255, 0, 255, .45), /* thick pink aura */ - 0 0 90px rgba(0, 255, 255, .25), /* cyan outer glow */ - 0 0 130px rgba(255, 0, 255, .4); /* final neon haze */ - - transition: 0.3s ease; +.stat-card:hover { + transform: translateY(-4px); + border-color: rgba(139, 92, 246, 0.3); + box-shadow: 0 10px 40px rgba(139, 92, 246, 0.15); } -/* On hover — stronger neon bloom */ -.info-card:hover { - transform: translateY(-6px); - border-color: #00f7ff; +.stat-card svg { + color: var(--vortex-cyan); +} - box-shadow: - 0 0 16px #00f7ff, - 0 0 32px #00f7ff, - 0 0 70px rgba(0, 247, 255, 0.6), - 0 0 110px rgba(255, 0, 255, 0.4), - 0 0 150px rgba(0, 255, 255, 0.5); +.stat-info { + display: flex; + flex-direction: column; + text-align: left; } -.domains { - padding: 80px 40px; +.stat-value { + font-family: var(--font-display); + font-size: 1.1rem; + font-weight: 700; + color: var(--text-primary); } -.section-title { +.stat-label { + font-size: 0.85rem; + color: var(--text-muted); +} + +.scroll-indicator { + position: absolute; + bottom: 40px; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + color: var(--text-muted); + font-size: 0.8rem; + letter-spacing: 1px; + animation: float 3s ease-in-out infinite; +} + +.scroll-line { + width: 1px; + height: 40px; + background: linear-gradient(to bottom, var(--vortex-violet), transparent); +} + +/* ─────────────── SECTIONS ─────────────── */ +.section { + padding: 120px 40px; + position: relative; +} + +.section-header { text-align: center; - font-size: 48px; - letter-spacing: 4px; - margin-bottom: 40px; - background: linear-gradient(90deg, #00f7ff, #ff00ff); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; + margin-bottom: 60px; +} + +.section-tag { + display: inline-block; + padding: 6px 16px; + background: var(--bg-glass); + border: 1px solid rgba(139, 92, 246, 0.2); + border-radius: 50px; + color: var(--vortex-violet-light); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 2px; + margin-bottom: 16px; +} + +.section-title { + font-family: var(--font-display); + font-size: clamp(2rem, 5vw, 3rem); + font-weight: 800; + color: var(--text-primary); + margin-bottom: 16px; + letter-spacing: 1px; +} + +.section-desc { + font-size: 1.1rem; + color: var(--text-secondary); + max-width: 500px; + margin: 0 auto; +} + +/* ─────────────── DOMAINS SECTION ─────────────── */ +.domains-section { + background: linear-gradient(180deg, transparent 0%, rgba(139, 92, 246, 0.03) 50%, transparent 100%); } .domains-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 30px; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 24px; max-width: 1200px; margin: 0 auto; } .domain-card { - background: rgba(14, 0, 23, 0.8); - border: 2px solid #ff00ff; - padding: 40px; - border-radius: 10px; + position: relative; + padding: 40px 28px; + background: var(--glass-bg); + backdrop-filter: blur(20px); + border: 1px solid rgba(139, 92, 246, 0.1); + border-radius: var(--border-radius-xl); text-align: center; - transition: 0.3s; + cursor: pointer; + transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); + overflow: hidden; } -.domain-card:hover { - border-color: #00f7ff; - box-shadow: 0 0 40px rgba(0, 247, 255, 0.5); - transform: translateY(-10px); +.domain-glow { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 150px; + height: 150px; + border-radius: 50%; + filter: blur(80px); + opacity: 0; + transition: opacity 0.4s ease; } -/* make selected card slightly more visible */ -.domain-card.selected { border-color: #00f7ff; box-shadow: 0 8px 26px rgba(0,247,255,0.12); transform: translateY(-6px); } +.domain-card:hover .domain-glow { + opacity: 0.3; +} + +.domain-card:hover { + transform: translateY(-8px) scale(1.02); + border-color: rgba(139, 92, 246, 0.3); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); +} .domain-icon { - font-size: 48px; margin-bottom: 20px; + transition: transform 0.4s ease; } -.domain-card h3 { - font-size: 26px; - color: #00f7ff; +.domain-card:hover .domain-icon { + transform: scale(1.15) rotate(5deg); } -/* =========================== - EVALUATION / ROUNDS -=========================== */ -.evaluation { - padding: 80px 40px; +.domain-name { + font-family: var(--font-display); + font-size: 1.2rem; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8px; } -.rounds { +.domain-desc { + font-size: 0.85rem; + color: var(--text-muted); + margin-bottom: 20px; +} + +.domain-arrow { + color: var(--vortex-violet); + opacity: 0; + transform: translateX(-10px); + transition: all 0.3s ease; +} + +.domain-card:hover .domain-arrow { + opacity: 1; + transform: translateX(0); +} + +/* ─────────────── ROUNDS/TIMELINE ─────────────── */ +.rounds-section { + overflow: hidden; +} + +.timeline { + max-width: 900px; + margin: 0 auto; + position: relative; +} + +.timeline-item { display: flex; gap: 40px; + margin-bottom: 60px; + position: relative; +} + +.timeline-item:nth-child(even) { + flex-direction: row-reverse; +} + +.timeline-connector { + display: flex; + flex-direction: column; + align-items: center; + flex-shrink: 0; +} + +.timeline-dot { + width: 50px; + height: 50px; + border-radius: 50%; + background: var(--gradient-button); + display: flex; + align-items: center; justify-content: center; - flex-wrap: wrap; + font-family: var(--font-display); + font-weight: 700; + color: var(--text-primary); + box-shadow: 0 0 30px rgba(139, 92, 246, 0.4); + position: relative; + z-index: 1; +} + +.timeline-line { + width: 2px; + flex: 1; + background: linear-gradient(to bottom, var(--vortex-violet), transparent); + margin-top: 10px; +} + +.timeline-item:last-child .timeline-line { + display: none; } .round-card { - background: rgba(10, 0, 20, 0.85); - border: 2px solid #ff00ff; - border-radius: 12px; - padding: 40px; - width: 320px; - position: relative; - overflow: hidden; + flex: 1; + padding: 32px; + background: var(--glass-bg); + backdrop-filter: blur(20px); + border: 1px solid rgba(139, 92, 246, 0.15); + border-radius: var(--border-radius-xl); + transition: all 0.3s ease; +} + +.round-card:hover { + transform: translateY(-4px); + border-color: rgba(139, 92, 246, 0.3); + box-shadow: 0 15px 50px rgba(139, 92, 246, 0.15); +} + +.round-header { + margin-bottom: 20px; +} + +.round-badges { display: flex; - flex-direction: column; - justify-content: space-between; - min-height: 320px; + gap: 10px; + margin-bottom: 12px; } -.round-number { - position: absolute; - top: 10px; - right: 20px; - font-size: 64px; - color: #ff00ff; - opacity: 0.2; +.round-date, +.round-mode { + padding: 4px 12px; + border-radius: 50px; + font-size: 0.75rem; + font-weight: 600; } -.round-card h3 { - font-size: 28px; - color: #00f7ff; - letter-spacing: 2px; - margin-bottom: 15px; +.round-date { + background: rgba(6, 182, 212, 0.15); + color: var(--vortex-cyan); } -.round-card p { - font-size: 16px; - line-height: 1.8; - opacity: 0.85; +.round-mode { + background: rgba(236, 72, 153, 0.15); + color: var(--vortex-pink-light); } -.access { - display: block; - margin-top: auto; - color: #00f7ff; +.round-title { + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 800; + color: var(--text-primary); + margin-bottom: 4px; +} + +.round-subtitle { font-size: 0.85rem; - letter-spacing: 3px; - text-transform: uppercase; - background: none; - border: none; - cursor: pointer; - padding: 0; - text-decoration: none; - width: max-content; - transition: color .15s ease, transform .12s ease; + color: var(--vortex-violet-light); + letter-spacing: 2px; + font-weight: 600; } -.access:hover { color: #ff00ff; transform: translateX(6px); } -.access:focus { outline: 2px solid rgba(0,247,255,0.18); border-radius: 3px; } -/* MODAL */ -.modal-overlay { - position: fixed; - inset: 0; - background: rgba(0,0,0,0.92); - backdrop-filter: blur(10px); - z-index: 9999; +.round-desc { + color: var(--text-secondary); + line-height: 1.7; + margin-bottom: 20px; +} + +.round-highlights { + list-style: none; + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +.round-highlights li { display: flex; align-items: center; + gap: 6px; + padding: 6px 14px; + background: var(--bg-glass); + border-radius: 50px; + font-size: 0.85rem; + color: var(--text-secondary); +} + +.round-highlights li svg { + color: var(--vortex-violet); +} + +/* ─────────────── PRIZES SECTION ─────────────── */ +.prizes-section { + background: linear-gradient(180deg, transparent 0%, rgba(6, 182, 212, 0.03) 50%, transparent 100%); +} + +.prizes-grid { + display: flex; justify-content: center; - opacity: 0; - pointer-events: none; - transition: opacity .25s ease; + gap: 24px; + flex-wrap: wrap; + max-width: 1000px; + margin: 0 auto 50px; } -.modal-overlay.active { - opacity: 1; - pointer-events: all; + +.prize-card { + padding: 40px 50px; + background: var(--glass-bg); + backdrop-filter: blur(20px); + border: 1px solid rgba(139, 92, 246, 0.15); + border-radius: var(--border-radius-xl); + text-align: center; + transition: all 0.3s ease; } -.modal { - background: rgba(5,5,5,0.98); - border: 2px solid #00f7ff; - max-width: 900px; - width: 90%; - max-height: 85vh; - overflow-y: auto; - padding: 36px; - position: relative; - transform: scale(.95) translateY(12px); - opacity: 0; - transition: .3s ease; - box-shadow: 0 0 40px rgba(0,247,255,0.2), 0 0 90px rgba(255,0,255,0.12); + +.prize-card:hover { + transform: translateY(-6px); + border-color: rgba(139, 92, 246, 0.3); } -.modal-overlay.active .modal { - transform: scale(1) translateY(0); - opacity: 1; + +.prize-winner { + background: linear-gradient(135deg, rgba(139, 92, 246, 0.15) 0%, rgba(6, 182, 212, 0.1) 100%); + border-color: rgba(139, 92, 246, 0.3); + transform: scale(1.05); } -.close { - position: absolute; - top: 14px; - right: 18px; - font-size: 26px; - background: none; - border: none; - color: #00f7ff; - cursor: pointer; + +.prize-winner:hover { + transform: scale(1.08) translateY(-6px); +} + +.prize-place { + font-family: var(--font-display); + font-size: 2.5rem; + font-weight: 900; + color: var(--vortex-violet); + margin-bottom: 8px; +} + +.prize-winner .prize-place { + font-size: 3rem; } -.close:hover { color: #ff00ff; } -.modal-sub { color: #00f7ff; letter-spacing: 3px; font-size: .9rem; margin-bottom: 14px; } -.divider { height: 2px; width: 70px; background: #ff00ff; margin: 18px 0; } -.modal-desc { color: #aeeff0; font-style: italic; margin-bottom: 18px; } -.modal-grid { display: grid; grid-template-columns: repeat(auto-fit,minmax(260px,1fr)); gap: 20px; } -.block h4 { color: #00f7ff; margin-bottom: 10px; text-transform: uppercase; } -.block ul { list-style: none; } -.block li { color: #cfeef0; font-size: .95rem; margin-bottom: 8px; } -.block li::before { content: '▶ '; color: #ff00ff; } -/* =========================== - TEAM / COORDINATORS -=========================== */ -.team { - padding: 80px 40px; +.prize-amount { + font-family: var(--font-display); + font-size: 2rem; + font-weight: 800; + margin-bottom: 8px; +} + +.prize-label { + color: var(--text-muted); + font-size: 0.9rem; +} + +.bonus-prizes { + display: flex; + justify-content: center; + gap: 40px; + flex-wrap: wrap; +} + +.bonus-item { + display: flex; + align-items: center; + gap: 12px; + color: var(--text-secondary); + font-size: 0.95rem; } -.coord-title { +.bonus-item svg { + color: var(--vortex-cyan); +} + +/* ─────────────── TEAM SECTION ─────────────── */ +.team-section { + background: linear-gradient(180deg, transparent 0%, rgba(236, 72, 153, 0.03) 50%, transparent 100%); +} + +.team-group { + max-width: 1000px; + margin: 0 auto 60px; +} + +.group-title { + font-family: var(--font-display); + font-size: 1.2rem; + font-weight: 700; + color: var(--vortex-violet-light); text-align: center; - color: #ff6b35; - font-size: 24px; - letter-spacing: 2px; margin-bottom: 30px; + letter-spacing: 2px; } -.coordinator-cards { +.team-grid { display: flex; - gap: 30px; justify-content: center; + gap: 24px; flex-wrap: wrap; } -.coordinator-card { - background: rgba(10, 0, 20, 0.9); - border: 3px solid #ff6b35; - padding: 40px; +.team-grid.leads { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 16px; +} + +.team-card { + padding: 30px; + background: var(--glass-bg); + backdrop-filter: blur(20px); + border: 1px solid rgba(139, 92, 246, 0.1); + border-radius: var(--border-radius-xl); text-align: center; - min-width: 300px; - border-radius: 10px; - box-shadow: 0 0 40px rgba(255, 107, 53, 0.4); + transition: all 0.3s ease; + min-width: 200px; } -.member-photo { - width: 120px; - height: 120px; +.team-card:hover { + transform: translateY(-4px); + border-color: rgba(139, 92, 246, 0.25); +} + +.member-avatar { + width: 70px; + height: 70px; border-radius: 50%; - border: 3px solid #ff00ff; - margin: 0 auto 20px; + background: rgba(139, 92, 246, 0.2); display: flex; align-items: center; justify-content: center; - background: linear-gradient(135deg, #ff00ff, #00f7ff); - font-size: 48px; - color: #000; - font-weight: bold; + font-family: var(--font-display); + font-size: 1.3rem; + font-weight: 700; + color: var(--vortex-violet-light); + margin: 0 auto 16px; } -.team-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 40px; - max-width: 1200px; - margin: 40px auto 0; +.gradient-avatar { + background: var(--gradient-button); + color: var(--text-primary); } -.team-member { - background: rgba(10, 0, 20, 0.85); - border: 2px solid #ff00ff; - padding: 30px; - text-align: center; - transition: 0.3s; - border-radius: 10px; +.team-card h4 { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 6px; } -.team-member:hover { - border-color: #00f7ff; - box-shadow: 0 0 30px rgba(0, 247, 255, 0.5); - transform: scale(1.05); +.team-card span { + font-size: 0.85rem; + color: var(--text-muted); +} + +.contact-link { + display: block; + margin-top: 10px; + color: var(--vortex-cyan); + text-decoration: none; + font-size: 0.85rem; + transition: color 0.3s ease; +} + +.contact-link:hover { + color: var(--vortex-cyan-light); +} + +.team-card-mini { + display: flex; + align-items: center; + gap: 16px; + padding: 16px 24px; + background: var(--glass-bg); + backdrop-filter: blur(20px); + border: 1px solid rgba(139, 92, 246, 0.1); + border-radius: var(--border-radius-lg); + transition: all 0.3s ease; +} + +.team-card-mini:hover { + border-color: rgba(139, 92, 246, 0.25); +} + +.member-avatar-mini { + width: 45px; + height: 45px; + border-radius: 50%; + background: rgba(139, 92, 246, 0.2); + display: flex; + align-items: center; + justify-content: center; + font-family: var(--font-display); + font-size: 0.9rem; + font-weight: 700; + color: var(--vortex-violet-light); + flex-shrink: 0; +} + +.member-info h4 { + font-size: 0.95rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 2px; } -.member-name { - font-size: 22px; - color: #00f7ff; - margin-bottom: 10px; +.member-info span { + font-size: 0.8rem; + color: var(--text-muted); } -.member-role { - font-size: 14px; - color: #ff00ff; - line-height: 1.6; +.dev-team { + margin-bottom: 0; } -/* =========================== - FOOTER -=========================== */ -footer { +.dev-card { + border-color: rgba(139, 92, 246, 0.25); + background: linear-gradient(135deg, rgba(139, 92, 246, 0.08) 0%, rgba(6, 182, 212, 0.05) 100%); +} + +/* ─────────────── FOOTER ─────────────── */ +.vv-footer { + position: relative; + padding-top: 80px; +} + +.footer-wave { + position: absolute; + top: 0; + left: 0; + right: 0; + overflow: hidden; +} + +.footer-wave svg { + width: 100%; + height: auto; +} + +.footer-content { + position: relative; + z-index: 1; padding: 40px; - background: rgba(10, 0, 20, 0.9); text-align: center; - border-top: 2px solid #ff00ff; } -footer p { - font-size: 14px; - letter-spacing: 2px; - opacity: 0.8; +.footer-sponsors { + margin-bottom: 50px; } -footer .muted { - margin-top: 10px; - font-size: 12px; +.footer-sponsors h3 { + font-size: 0.9rem; + color: var(--text-muted); + letter-spacing: 2px; + margin-bottom: 30px; + font-weight: 600; } -/* Footer Sponsors */ -.footer-sponsors { margin-bottom: 18px; } -.sponsor-heading { color: #00f7ff; font-size: 18px; margin-bottom: 12px; letter-spacing: 2px; } .sponsor-logos { display: flex; - gap: 22px; justify-content: center; align-items: center; + gap: 40px; flex-wrap: wrap; - max-width: 1100px; - margin: 0 auto 8px; } -.sponsor-logos a { display: inline-block; display: flex; align-items: center; justify-content: center; } + +.sponsor-logos a { + opacity: 0.7; + transition: all 0.3s ease; + filter: grayscale(30%); +} + +.sponsor-logos a:hover { + opacity: 1; + filter: grayscale(0%); + transform: translateY(-4px); +} + .sponsor-logos img { - max-width: 160px; - height: auto; - opacity: 0.95; - transition: transform 160ms ease, opacity 160ms ease; - filter: drop-shadow(0 6px 18px rgba(0,247,255,0.06)); + height: 45px; + width: auto; + object-fit: contain; } -.sponsor-logos a:hover img { transform: translateY(-6px); opacity: 1; } -@media (max-width: 480px) { - .sponsor-logos img { max-width: 110px; } - .sponsor-heading { font-size: 16px; } +.footer-bottom { + padding-top: 30px; + border-top: 1px solid rgba(139, 92, 246, 0.1); } -/* =========================== - RESPONSIVE / MOBILE STYLES - Breakpoints: 1024px, 768px, 480px -=========================== */ +.footer-brand { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + margin-bottom: 12px; +} + +.footer-brand span { + font-family: var(--font-display); + font-size: 1.3rem; + font-weight: 700; +} -/* Medium screens / landscape tablets */ +.footer-tagline { + color: var(--vortex-cyan); + font-size: 1rem; + margin-bottom: 8px; +} + +.footer-copy { + color: var(--text-muted); + font-size: 0.85rem; +} + +/* ─────────────── RESPONSIVE ─────────────── */ @media (max-width: 1024px) { - .vv-nav { - padding: 14px 36px; + .nav-links { + display: none; } - .vv-nav .logo h1 { font-size: 20px; letter-spacing: 2px; } - .nav-buttons { gap: 14px; } - .nav-btn { padding: 10px 18px; font-size: 0.95rem; border-radius: 7px; } - - .hero { padding: 110px 20px 70px; } - .hero h2 { font-size: 54px; } - .hero-desc { font-size: 16px; max-width: 720px; } - .info-card { padding: 22px 30px; } - .domain-card { padding: 30px; } - .round-card { padding: 32px; width: 300px; min-height: 300px; } - .round-number { font-size: 52px; right: 16px; } + .nav-content { + padding: 0 24px; + } - .coord-title { font-size: 22px; } + .timeline-item, + .timeline-item:nth-child(even) { + flex-direction: column; + } - .member-photo { width: 100px; height: 100px; font-size: 40px; } + .timeline-connector { + flex-direction: row; + gap: 20px; + margin-bottom: 20px; + } - .team-grid { gap: 30px; } + .timeline-line { + width: auto; + height: 2px; + flex: 1; + margin-top: 0; + background: linear-gradient(to right, var(--vortex-violet), transparent); + } } -/* Small screens / portrait tablets and large phones */ @media (max-width: 768px) { - .vv-nav { - padding: 12px 22px; - align-items: center; - } - .vv-nav .logo img { width: 34px; height: 34px; } - .vv-nav .logo h1 { font-size: 18px; } - .nav-buttons { - gap: 10px; - flex-wrap: wrap; - justify-content: flex-end; + .section { + padding: 80px 24px; } - .nav-btn { padding: 8px 14px; font-size: 0.85rem; border-radius: 6px; } .hero { - padding: 100px 16px 60px; + padding: 100px 24px 60px; } - .hero h2 { font-size: 44px; letter-spacing: 3px; } - .hero-subtitle { font-size: 20px; } - .hero-desc { font-size: 15px; line-height: 1.7; } - .event-info { gap: 18px; margin-top: 30px; } - .info-card { padding: 20px 22px; border-radius: 10px; } + .hero-stats { + flex-direction: column; + align-items: center; + } - .domains { padding: 60px 20px; } - .section-title { font-size: 40px; margin-bottom: 30px; } - .domains-grid { gap: 20px; } + .stat-card { + width: 100%; + max-width: 320px; + } - .evaluation { padding: 60px 20px; } - .rounds { gap: 20px; } - .round-card { width: 100%; max-width: 520px; } + .domains-grid { + grid-template-columns: repeat(2, 1fr); + gap: 16px; + } - /* Modal adjustments for smaller devices */ - .modal { width: 94%; padding: 28px; max-height: 90vh; } - .modal-grid { gap: 16px; } + .domain-card { + padding: 28px 20px; + } - .team { padding: 60px 20px; } - .coordinator-cards { gap: 20px; } - .coordinator-card { padding: 30px; min-width: unset; width: 100%; max-width: 520px; } - .member-photo { width: 90px; height: 90px; font-size: 42px; } - .team-grid { gap: 28px; } -} + .prizes-grid { + flex-direction: column; + align-items: center; + } -/* Extra small phones */ -@media (max-width: 480px) { - /* hide scanline on small phones to reduce CPU/battery */ - .scanline { display: none; } + .prize-winner { + transform: none; + } - .vv-nav { - padding: 10px 12px; - background: rgba(10,0,20,0.98); + .prize-winner:hover { + transform: translateY(-6px); } - .vv-nav .logo img { width: 30px; height: 30px; } - .vv-nav .logo h1 { font-size: 16px; letter-spacing: 1.5px; } - .nav-buttons { - gap: 8px; - /* allow nav buttons to wrap under logo if present */ - flex-wrap: wrap; - justify-content: flex-end; + + .bonus-prizes { + flex-direction: column; + gap: 20px; } - .nav-btn { - padding: 6px 10px; - font-size: 0.8rem; - border-radius: 6px; - letter-spacing: 1px; + + .sponsor-logos { + gap: 24px; } - .hero { - padding: 90px 12px 50px; - min-height: 70vh; + .sponsor-logos img { + height: 35px; } - .hero h2 { - font-size: 32px; +} + +@media (max-width: 480px) { + .hero-title { letter-spacing: 2px; - line-height: 1.05; } - .hero-subtitle { font-size: 18px; } - .hero-desc { font-size: 14px; max-width: 100%; line-height: 1.6; padding: 0 6px; } - .event-info { flex-direction: column; gap: 14px; margin-top: 24px; } - .info-card { width: 100%; padding: 18px; border-radius: 10px; box-shadow: none; } - - .domains { padding: 40px 12px; } - .section-title { font-size: 32px; } - .domains-grid { grid-template-columns: 1fr; gap: 18px; max-width: 420px; margin: 0 auto; } + .hero-subtitle { + letter-spacing: 3px; + } - .domain-card { padding: 24px; } + .hero-cta { + flex-direction: column; + align-items: center; + } - .evaluation { padding: 40px 12px; } - .rounds { flex-direction: column; gap: 16px; align-items: stretch; } - .round-card { width: 100%; padding: 22px; min-height: unset; } + .cta-primary, + .cta-secondary { + width: 100%; + justify-content: center; + } - .round-number { font-size: 40px; top: 6px; right: 12px; } + .domains-grid { + grid-template-columns: 1fr; + } - .modal { - width: 98%; - max-width: 98%; - max-height: 100vh; - padding: 18px; - border-radius: 6px; - box-shadow: none; + .team-grid.leads { + grid-template-columns: 1fr; } - .modal-sub { font-size: 0.85rem; letter-spacing: 2px; } - .team { padding: 40px 12px; } - .coordinator-cards, .team-grid { gap: 16px; display: grid; grid-template-columns: 1fr; max-width: 420px; margin: 0 auto; } - .coordinator-card { padding: 20px; border-radius: 8px; box-shadow: none; border-width: 2px; } - .member-photo { width: 80px; height: 80px; font-size: 36px; border-width: 2px; } - .member-name { font-size: 18px; } - .member-role { font-size: 13px; } + .orb-1, + .orb-2, + .orb-3 { + width: 300px; + height: 300px; + } +} - footer { padding: 28px 12px; } - footer p { font-size: 13px; } - footer .muted { font-size: 11px; } +/* ─────────────── SCROLLBAR ─────────────── */ +::-webkit-scrollbar { + width: 8px; } -/* Small accessibility/touch targets improvements */ -@media (pointer: coarse) { - .nav-btn { min-height: 40px; } - .access { padding: 6px 4px; } +::-webkit-scrollbar-track { + background: var(--bg-void); } -/* END RESPONSIVE STYLES */ +::-webkit-scrollbar-thumb { + background: var(--vortex-violet); + border-radius: 4px; +} -/* NOTE: - - For best mobile behavior ensure your HTML contains: - - - If you'd like a hamburger menu for very small screens we can add the markup + JS and styles next. -*/ +::-webkit-scrollbar-thumb:hover { + background: var(--vortex-violet-light); +} \ No newline at end of file diff --git a/src/styles/login.css b/src/styles/login.css deleted file mode 100644 index cda1279..0000000 --- a/src/styles/login.css +++ /dev/null @@ -1,665 +0,0 @@ -/* src/styles/login.css - FINAL CORRECTED VERSION */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -/* ANIMATIONS */ -@keyframes glitch { - 0%, 100% { transform: translate(0); } - 20% { transform: translate(-2px, 2px); } - 40% { transform: translate(-2px, -2px); } - 60% { transform: translate(2px, 2px); } - 80% { transform: translate(2px, -2px); } -} - -@keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.3; } -} - -@keyframes scanline { - 0% { transform: translateY(-100%); } - 100% { transform: translateY(100%); } -} - -@keyframes flicker { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.8; } -} - -@keyframes scroll { - 0% { transform: translateX(0); } - 100% { transform: translateX(-50%); } -} - -body, #root { - font-family: 'Courier New', monospace; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -/* WRAPPER */ -.loginWrapper { - font-family: 'Courier New', monospace; - background: #000; - color: #00f7ff; - overflow-x: hidden; - overflow-y: auto; - position: relative; - min-height: 100vh; - padding: 20px; - padding-top: 70px; - padding-bottom: 60px; -} - -/* SCANLINE EFFECT */ -.loginWrapper::before { - content: ''; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 2px; - background: linear-gradient(transparent, #00f7ff 50%, transparent); - opacity: 0.1; - animation: scanline 8s linear infinite; - z-index: 2; - pointer-events: none; -} - -/* NOISE EFFECT */ -.loginWrapper::after { - content: ''; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-image: url('data:image/svg+xml,%3Csvg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"%3E%3Cfilter id="noise"%3E%3CfeTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="4" /%3E%3C/filter%3E%3Crect width="100%25" height="100%25" filter="url(%23noise)" opacity="0.03"/%3E%3C/svg%3E'); - pointer-events: none; - z-index: 2; - animation: flicker 0.15s infinite; -} - -/* TOP MARQUEE - FIXED */ -.marquee-bar { - position: fixed; - top: 0; - left: 0; - width: 100%; - background: rgba(255, 0, 255, 0.05); - border-bottom: 2px solid #ff00ff; - padding: 10px 0; - overflow: hidden; - z-index: 100; - box-shadow: 0 0 20px rgba(255, 0, 255, 0.3); - height: auto; -} - -.marquee-track { - display: flex; - white-space: nowrap; - animation: scroll 30s linear infinite; - font-size: 12px; - font-weight: bold; - letter-spacing: 2px; -} - -.marquee-track span { - padding: 0 50px; - text-shadow: 0 0 10px #ff00ff; - background: linear-gradient(90deg, #00f7ff, #ff00ff, #ff6b35); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - animation: pulse 3s ease-in-out infinite; -} - -/* TERMINAL BOX - CENTERED WITH PROPER SPACING */ -.terminalBox { - background: rgba(10, 0, 20, 0.95); - border: 3px solid #ff00ff; - box-shadow: - 0 0 30px rgba(255, 0, 255, 0.5), - 0 0 60px rgba(0, 247, 255, 0.3), - inset 0 0 50px rgba(255, 0, 255, 0.05); - padding: 40px; - max-width: 600px; - width: 100%; - position: relative; - margin: 0 auto; - z-index: 3; -} - -/* Removed the flashing colorful border effect */ - -/* HEADER */ -.terminalHeader { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 30px; - padding-bottom: 15px; - border-bottom: 2px solid; - border-image: linear-gradient(90deg, #00f7ff, #ff00ff) 1; -} - -.headerLeft { - display: flex; - align-items: center; - gap: 15px; -} - -.title { - font-size: 24px; - font-weight: bold; - letter-spacing: 4px; - animation: glitch 0.5s infinite; - background: linear-gradient(90deg, #00f7ff, #ff00ff, #ff6b35); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - text-shadow: - 0 0 10px rgba(0, 247, 255, 0.5), - 0 0 20px rgba(255, 0, 255, 0.5); -} - -.headerLogo { - width: 40px; - height: 40px; - object-fit: contain; - filter: drop-shadow(0 0 10px rgba(255, 0, 255, 0.5)); -} - -.headerDots { - display: flex; - gap: 8px; -} - -.dot { - width: 12px; - height: 12px; - border-radius: 50%; - border: 2px solid #ff00ff; - animation: pulse 1.5s ease-in-out infinite; -} - -.dot:nth-child(1), -.d1 { - border-color: #00f7ff; -} - -.dot:nth-child(2), -.d2 { - animation-delay: 0.5s; - border-color: #ff00ff; -} - -.dot:nth-child(3), -.d3 { - animation-delay: 1s; - border-color: #ff6b35; -} - -/* SUBTITLE */ -.sectionSubtitle { - font-size: 14px; - margin-bottom: 30px; - opacity: 0.8; - letter-spacing: 2px; - text-align: center; -} - -/* FORM */ -form { - display: flex; - flex-direction: column; -} - -form > div, -form > label, -form > input, -form > select, -form > p, -form > button { - margin-bottom: 0; -} - -.fieldLabel { - display: block; - margin-bottom: 8px; - margin-top: 20px; - font-size: 12px; - letter-spacing: 1px; - text-transform: uppercase; - opacity: 0.9; -} - -.fieldLabel:first-of-type { - margin-top: 0; -} - -/* INPUT FIELDS */ -.inputField, -input[type="email"], -input[type="text"], -select { - width: 100%; - padding: 15px; - background: rgba(0, 0, 0, 0.8); - border: 2px solid #ff00ff; - color: #00f7ff; - font-family: 'Courier New', monospace; - font-size: 14px; - outline: none; - transition: all 0.3s; - box-shadow: inset 0 0 10px rgba(255, 0, 255, 0.1); -} - -.inputField:focus, -input:focus, -select:focus { - border-color: #00f7ff; - box-shadow: - 0 0 20px rgba(0, 247, 255, 0.5), - 0 0 30px rgba(255, 0, 255, 0.3), - inset 0 0 20px rgba(0, 247, 255, 0.1); - color: #00f7ff; -} - -.inputField::placeholder, -input::placeholder { - color: rgba(255, 0, 255, 0.3); -} - -/* SELECT DROPDOWN */ -select.inputField, -select { - cursor: pointer; - appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill='%2300f7ff' d='M8 12L2 5h12z'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 15px center; - background-color: rgba(0, 0, 0, 0.8); - padding-right: 40px; -} - -select option { - background: rgba(0, 0, 0, 0.98); - color: #00f7ff; -} - -.helper { - font-size: 11px; - opacity: 0.6; - margin-top: 5px; - margin-bottom: 0; - letter-spacing: 1px; -} - -/* BUTTON */ -.submitBtn { - width: 100%; - padding: 15px; - background: transparent; - border: 2px solid #ff00ff; - color: #00f7ff; - font-family: 'Courier New', monospace; - font-size: 14px; - font-weight: bold; - letter-spacing: 2px; - text-transform: uppercase; - cursor: pointer; - position: relative; - overflow: hidden; - transition: all 0.3s; - margin-top: 20px; -} - -.submitBtn::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 0; - height: 0; - background: radial-gradient(circle, rgba(255, 0, 255, 0.3), rgba(0, 247, 255, 0.2)); - border-radius: 50%; - transform: translate(-50%, -50%); - transition: width 0.6s, height 0.6s; -} - -.submitBtn:hover::before { - width: 300px; - height: 300px; -} - -.submitBtn:hover { - border-color: #00f7ff; - color: #ff00ff; - box-shadow: - 0 0 20px rgba(0, 247, 255, 0.5), - 0 0 30px rgba(255, 0, 255, 0.3); - transform: translateY(-2px); -} - -.submitBtn:active { - transform: translateY(0); -} - -.submitBtn span { - position: relative; - z-index: 1; -} - -/* STATUS BAR - FIXED BOTTOM */ -.statusBar { - position: fixed; - bottom: 0; - left: 0; - width: 100%; - background: rgba(255, 0, 255, 0.05); - border-top: 2px solid #ff00ff; - padding: 10px 20px; - font-size: 11px; - display: flex; - justify-content: space-between; - align-items: center; - z-index: 100; -} - -.statusItem { - display: flex; - align-items: center; - gap: 10px; -} - -.statusDot { - width: 8px; - height: 8px; - background: #00f7ff; - border-radius: 50%; - animation: pulse 1s ease-in-out infinite; - box-shadow: 0 0 10px #00f7ff; -} - -/* MODAL */ -.mobile-modal-overlay { - position: fixed; - inset: 0; - z-index: 9999; - display: flex; - align-items: center; - justify-content: center; - background: rgba(0, 0, 0, 0.95); - backdrop-filter: blur(10px); - padding: 20px; -} - -.mobile-modal-content { - background: rgba(10, 0, 20, 0.98); - border: 3px solid #ff00ff; - box-shadow: - 0 0 40px rgba(255, 0, 255, 0.6), - 0 0 80px rgba(0, 247, 255, 0.4), - inset 0 0 40px rgba(255, 0, 255, 0.05); - color: #00f7ff; - width: min(95vw, 500px); - max-width: 100%; - padding: 40px; - max-height: 90vh; - overflow-y: auto; - position: relative; -} - -/* Removed the flashing colorful border effect from modal */ - -.modal-title { - font-family: 'Courier New', monospace; - font-size: 24px; - margin-bottom: 20px; - color: #00f7ff; - font-weight: bold; - letter-spacing: 3px; - text-transform: uppercase; -} - -.modal-text { - font-size: 14px; - line-height: 1.6; - color: #00f7ff; - margin-bottom: 30px; - font-family: 'Courier New', monospace; - opacity: 0.9; -} - -.modal-buttons { - display: flex; - gap: 15px; - justify-content: center; - flex-wrap: wrap; -} - -.authBtn { - flex: 1; - padding: 15px 30px; - border: 2px solid #ff00ff; - font-size: 14px; - font-weight: bold; - cursor: pointer; - transition: all 0.3s; - min-width: 120px; - text-transform: uppercase; - font-family: 'Courier New', monospace; - position: relative; - overflow: hidden; - letter-spacing: 1px; - background: transparent; - color: #00f7ff; -} - -.authBtn::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 0; - height: 0; - background: radial-gradient(circle, rgba(255, 0, 255, 0.3), rgba(0, 247, 255, 0.2)); - border-radius: 50%; - transform: translate(-50%, -50%); - transition: width 0.6s, height 0.6s; -} - -.authBtn:hover::before { - width: 300px; - height: 300px; -} - -.authBtn span { - position: relative; - z-index: 1; -} - -.authBtn.primary:hover { - border-color: #00f7ff; - color: #ff00ff; - box-shadow: - 0 0 20px rgba(0, 247, 255, 0.5), - 0 0 30px rgba(255, 0, 255, 0.3); - transform: translateY(-2px); -} - -.authBtn.primary:active { - transform: translateY(0); -} - -.authBtn.secondary { - border-color: rgba(255, 0, 255, 0.5); - opacity: 0.8; -} - -.authBtn.secondary:hover { - border-color: #ff00ff; - opacity: 1; -} - -/* RESPONSIVE */ -@media (max-width: 768px) { - .loginWrapper { - padding: 10px; - padding-top: 80px; - padding-bottom: 70px; - } - - .terminalBox { - padding: 20px; - } - - .title { - font-size: 16px; - letter-spacing: 2px; - } - - .headerLogo { - width: 30px; - height: 30px; - } - - .sectionSubtitle { - font-size: 11px; - margin-bottom: 20px; - } - - .marquee-track { - font-size: 9px; - animation-duration: 40s; - } - - .marquee-bar { - padding: 8px 0; - } - - .marquee-track span { - padding: 0 24px; - } - - .fieldLabel { - font-size: 11px; - } - - .inputField, - input, - select { - padding: 12px; - font-size: 13px; - } - - .submitBtn { - padding: 12px; - font-size: 12px; - } - - .headerDots { - gap: 6px; - } - - .dot { - width: 10px; - height: 10px; - } - - .statusBar { - font-size: 9px; - padding: 8px 15px; - flex-wrap: wrap; - justify-content: center; - gap: 10px; - } - - .statusItem { - gap: 8px; - font-size: 9px; - } - - .modal-buttons { - flex-direction: column; - } - - .authBtn { - width: 100%; - } -} - -@media (max-width: 480px) { - .terminalBox { - padding: 15px; - } - - .title { - font-size: 14px; - gap: 10px; - } - - .headerLogo { - width: 25px; - height: 25px; - } - - .sectionSubtitle { - font-size: 10px; - padding: 0 4px; - } - - .marquee-track { - font-size: 8px; - } - - .inputField, - input, - select { - font-size: 12px; - } - - .submitBtn { - font-size: 11px; - } - - .fieldLabel { - margin-top: 15px; - } -} - -/* Custom Scrollbar */ -.mobile-modal-content::-webkit-scrollbar { - width: 6px; -} -.mobile-modal-content::-webkit-scrollbar-track { - background: rgba(255, 0, 255, 0.1); -} -.mobile-modal-content::-webkit-scrollbar-thumb { - background: linear-gradient(180deg, #ff00ff, #00f7ff); - border-radius: 3px; - box-shadow: 0 0 6px rgba(0, 247, 255, 0.4); -} - -/* Accessibility */ -.inputField:focus-visible, -.submitBtn:focus-visible, -.authBtn:focus-visible { - outline: 2px solid rgba(0, 247, 255, 0.6); - outline-offset: 2px; -} - -/* Performance */ -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } -} diff --git a/src/styles/member.css b/src/styles/member.css deleted file mode 100644 index ed4a4c4..0000000 --- a/src/styles/member.css +++ /dev/null @@ -1,322 +0,0 @@ -/* =============================== - ROOT + RESET -================================ */ -:root { - --bg-dark: #020617; - --card-bg: rgba(15, 23, 42, 0.65); - --purple: #8b5cf6; - --pink: #ec4899; - --text-main: #f8fafc; - --text-muted: #94a3b8; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html, -body { - background: var(--bg-dark); - color: var(--text-main); - font-family: "Plus Jakarta Sans", system-ui, sans-serif; - height: 100%; - overflow-x: hidden; -} - -/* =============================== - BACKGROUND MESH -================================ */ -.mesh-bg { - position: fixed; - inset: 0; - z-index: -1; - background: - radial-gradient(circle at 0% 0%, rgba(139, 92, 246, 0.15), transparent 35%), - radial-gradient(circle at 100% 100%, rgba(236, 72, 153, 0.12), transparent 35%); -} - -/* =============================== - LAYOUT -================================ */ -.dashboard { - display: flex; - min-height: 100vh; -} - -.main { - flex: 1; - margin-left: 0; -} - -@media (min-width: 768px) { - .main { - margin-left: 288px; - } -} - -/* =============================== - GLASS EFFECT -================================ */ -.glass { - background: var(--card-bg); - backdrop-filter: blur(14px); - border: 1px solid rgba(255, 255, 255, 0.08); -} - -/* =============================== - SIDEBAR -================================ */ -.sidebar { - display: none; - position: fixed; - left: 0; - top: 0; - width: 288px; - height: 100vh; - z-index: 50; - flex-direction: column; -} - -@media (min-width: 768px) { - .sidebar { - display: flex; - } -} - -.sidebar-header { - padding: 32px; -} - -.sidebar-title { - display: flex; - align-items: center; - gap: 10px; - font-size: 20px; - font-weight: 800; -} - -.sidebarLogoImg { - width: 36px; - height: 36px; - border-radius: 8px; - object-fit: cover; - box-shadow: 0 0 8px rgba(217,70,239,0.25); -} - -.sidebar-subtitle { - font-size: 10px; - letter-spacing: 0.2em; - color: var(--purple); - text-transform: uppercase; -} - -.sidebar-nav { - flex: 1; - padding: 0 16px; -} - -.sidebar-btn { - width: 100%; - padding: 14px 20px; - margin-bottom: 8px; - border-radius: 14px; - background: transparent; - border: none; - color: var(--text-muted); - text-align: left; - font-weight: 600; - cursor: pointer; - transition: all 0.25s ease; -} - -.sidebar-btn:hover { - color: white; -} - -.sidebar-btn.active { - background: linear-gradient( - 90deg, - rgba(139, 92, 246, 0.18), - transparent - ); - color: white; - position: relative; -} - -.sidebar-btn.active::before { - content: ""; - position: absolute; - left: 0; - top: 15%; - height: 70%; - width: 4px; - background: var(--purple); - border-radius: 0 4px 4px 0; - box-shadow: 0 0 15px var(--purple); -} - -/* =============================== - USER CARD -================================ */ -.sidebar-user { - padding: 24px; - border-top: 1px solid rgba(255,255,255,0.08); -} - -.user-box { - display: flex; - gap: 12px; - padding: 12px; - border-radius: 18px; -} - -.user-avatar { - width: 42px; - height: 42px; - border-radius: 14px; - background: linear-gradient(135deg, var(--purple), var(--pink)); - display: flex; - align-items: center; - justify-content: center; - font-weight: 800; -} - -.user-meta p:first-child { - font-weight: 700; - font-size: 14px; -} - -.user-meta p:last-child { - font-size: 10px; - color: var(--text-muted); - font-family: monospace; -} - -/* =============================== - HEADER -================================ */ -.header { - position: sticky; - top: 0; - z-index: 40; - padding: 24px 32px; - display: flex; - justify-content: space-between; - align-items: center; -} - -.header h1 { - font-size: 28px; - font-weight: 800; -} - -.header p { - font-size: 13px; - color: var(--text-muted); -} - -.clock { - padding: 12px 20px; - border-radius: 18px; - text-align: right; -} - -.clock span { - font-size: 10px; - text-transform: uppercase; - color: var(--purple); - font-weight: 700; -} - -.clock strong { - display: block; - font-family: "JetBrains Mono", monospace; - font-size: 20px; -} - -/* =============================== - PAGE ANIMATION -================================ */ -.page { - animation: slideUp 0.4s ease; -} - -@keyframes slideUp { - from { - opacity: 0; - transform: translateY(18px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* =============================== - CONTENT -================================ */ -.content { - padding: 32px; - max-width: 1200px; - margin: auto; -} - -/* =============================== - CARDS -================================ */ -.card { - padding: 32px; - border-radius: 32px; -} - -.center { - text-align: center; -} - -.card h2 { - font-size: 40px; - font-weight: 900; -} - -.card p { - margin-top: 8px; - color: var(--text-muted); - font-size: 18px; -} - -/* =============================== - QR PLACEHOLDER -================================ */ -.qr-box { - width: 260px; - height: 260px; - background: black; - color: white; - display: flex; - align-items: center; - justify-content: center; - margin: 32px auto; -} - -/* =============================== - BUTTON -================================ */ -.primary-btn { - margin-top: 24px; - padding: 16px; - width: 100%; - border-radius: 18px; - border: none; - cursor: pointer; - font-weight: 800; - background: linear-gradient(135deg, var(--purple), var(--pink)); - color: white; - transition: transform 0.25s ease, box-shadow 0.25s ease; -} - -.primary-btn:hover { - transform: scale(1.03); - box-shadow: 0 0 30px rgba(139, 92, 246, 0.35); -} diff --git a/src/styles/otp.css b/src/styles/otp.css deleted file mode 100644 index 74e5e7f..0000000 --- a/src/styles/otp.css +++ /dev/null @@ -1,236 +0,0 @@ -/* RESET */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -.otpWrapper { - width: 100vw; - height: 100vh; - min-height: 100vh; - position: relative; /* ensure z-index works */ - background: transparent; - overflow: hidden; - font-family: "Share Tech Mono", monospace; - color: #d6eaff; -} - -/* MARQUEE */ -.marquee-bar { - width: 100%; - padding: 6px 0; - border-bottom: 2px solid #ff00ff; - background: rgba(255, 0, 255, 0.05); - position: fixed; - top: 0; - z-index: 60; /* above canvas */ -} - -.marquee-track { - display: flex; - white-space: nowrap; - animation: marqueeRoll 20s linear infinite; - font-size: 11px; - letter-spacing: 2px; - color: #ff4cff; - text-shadow: 0 0 6px #ff00ff; -} - -.marquee-track span { - padding: 0 40px; -} - -@keyframes marqueeRoll { - from { transform: translateX(0); } - to { transform: translateX(-50%); } -} - -/* OTP BOX */ -.otpBox { - width: min(650px, 90vw); - margin: 120px auto 0 auto; - background: rgba(10, 0, 20, 0.9); - border: 2px solid #ff00ff; - border-radius: 8px; - padding: 40px; - position: relative; - z-index: 55; /* above canvas */ - position: relative; - box-shadow: - 0 0 25px #ff00ff, - 0 0 60px rgba(0, 255, 240, 0.2), - 0 0 90px rgba(255, 0, 255, 0.25); -} - -@media (max-width: 600px) { - .otpBox { - padding: 20px; - margin-top: 80px; - } - - .otpHeader { - flex-direction: column; - align-items: flex-start; - gap: 10px; - } - - .headerDots { - display: none; - } -} - -/* HEADER */ -.otpHeader { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 18px; - padding-bottom: 16px; - border-bottom: 1px solid #ff00ff; -} - -.headerLeft { - display: flex; - gap: 12px; - align-items: center; -} - -.headerLogo { - width: 38px; - height: 38px; - border-radius: 5px; -} - -.title { - font-size: 24px; - font-weight: bold; - color: #ffc5ff; - text-shadow: 0 0 10px #ff00ff; -} - -/* DOTS */ -.headerDots { - display: flex; - gap: 12px; -} - -.dot { - width: 12px; - height: 12px; - border-radius: 50%; - border: 2px solid #ff00ff; -} - -.d1 { border-color: cyan; } -.d2 { border-color: magenta; } -.d3 { border-color: orange; } - -/* SUBTITLE */ -.otpSubtitle { - text-align: center; - margin: 12px 0 28px; - color: #c7d9ff; - letter-spacing: 2px; -} - -/* INPUT */ -.fieldLabel { - display: block; - margin: 15px 0 6px; - color: #5effff; - font-size: 12px; - letter-spacing: 1px; -} - -.otpInput { - width: 100%; - padding: 14px; - background: rgba(0, 0, 0, 0.75); - border: 2px solid #ff00ff; - color: #9beaff; - font-size: 25px; - text-align: center; - letter-spacing: 8px; - font-weight: bold; - outline: none; - transition: 0.25s; -} - -.otpInput:focus { - border-color: cyan; - box-shadow: 0 0 25px cyan; -} - -/* HELPER */ -.helper { - margin-top: 6px; - color: #7f9ca6; - font-size: 12px; -} - -/* VERIFY BUTTON */ -.verifyBtn { - width: 100%; - margin-top: 26px; - padding: 14px; - background: rgba(255, 0, 255, 0.08); - border: 2px solid #ff00ff; - color: #ffbdff; - font-size: 15px; - cursor: pointer; - transition: 0.3s; - font-weight: bold; - letter-spacing: 2px; -} - -.verifyBtn:hover { - background: rgba(0, 255, 255, 0.1); - border-color: cyan; - box-shadow: 0 0 25px cyan; -} - -/* BACK BUTTON */ -.backBtn { - text-align: center; - margin-top: 18px; - cursor: pointer; - color: #7fe8ff; - transition: 0.3s; -} - -.backBtn:hover { - text-shadow: 0 0 10px cyan; - color: cyan; -} - -/* STATUS BAR */ -.otpStatusBar { - width: 100%; - position: fixed; - bottom: 0; - left: 0; - background: rgba(255, 0, 255, 0.05); - border-top: 2px solid #ff00ff; - padding: 8px 20px; - font-size: 12px; - display: flex; - justify-content: space-between; - color: #7ffcff; - letter-spacing: 1px; - z-index: 60; /* above canvas */ -} - -.statusItem { - display: flex; - align-items: center; - gap: 8px; -} - -.statusDot { - width: 8px; - height: 8px; - background: cyan; - border-radius: 50%; - box-shadow: 0 0 8px cyan; -} diff --git a/src/styles/preloader.css b/src/styles/preloader.css index b510719..662a190 100644 --- a/src/styles/preloader.css +++ b/src/styles/preloader.css @@ -1,10 +1,15 @@ -/* Remove browser margins */ -html, body { +/* ═══════════════════════════════════════════════════════════ + V-VORTEX PRELOADER STYLES + Updated to match new design system + ═══════════════════════════════════════════════════════════ */ + +/* Remove browser margins - overflow handled by design-tokens.css */ +html, +body { margin: 0; padding: 0; width: 100%; height: 100%; - overflow: hidden; } /* =============================== @@ -17,41 +22,114 @@ html, body { width: 100vw; height: 100vh; - background: black; + background: linear-gradient(135deg, #0A0A0F 0%, #0F0A1F 50%, #050508 100%); display: flex; flex-direction: column; justify-content: center; align-items: center; - cursor: pointer; + cursor: none; z-index: 500; - transition: transform 0.4s ease; + transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1); + will-change: transform, opacity; + backface-visibility: hidden; +} + +/* Animated gradient background */ +#enterScreen::before { + content: ''; + position: absolute; + inset: 0; + background: radial-gradient(circle at 30% 30%, rgba(139, 92, 246, 0.2) 0%, transparent 50%), + radial-gradient(circle at 70% 70%, rgba(6, 182, 212, 0.15) 0%, transparent 50%), + radial-gradient(circle at 50% 50%, rgba(236, 72, 153, 0.1) 0%, transparent 60%); + animation: bgPulse 8s ease-in-out infinite; +} + +@keyframes bgPulse { + + 0%, + 100% { + opacity: 0.8; + } + + 50% { + opacity: 1; + } } #enterText { - font-size: 38px; - margin-bottom: 12px; - color: #00ff9d; - animation: pulse 1.6s infinite alternate ease; - text-shadow: 0 0 12px #00ff9d; + position: relative; + z-index: 1; + font-family: 'Orbitron', 'Share Tech Mono', monospace; + font-size: clamp(18px, 4vw, 38px); + font-weight: 600; + margin-bottom: 20px; + text-align: center; + padding: 0 20px; + + /* Gradient text */ + background: linear-gradient(90deg, #8B5CF6, #06B6D4, #EC4899); + background-size: 200% auto; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + + animation: pulse 1.6s infinite alternate ease, textShimmer 3s linear infinite; + filter: drop-shadow(0 0 20px rgba(139, 92, 246, 0.5)); +} + +@keyframes textShimmer { + 0% { + background-position: 0% center; + } + + 100% { + background-position: 200% center; + } } #arrow { + position: relative; + z-index: 1; font-size: 26px; - color: #00ff9d; + color: #8B5CF6; opacity: 0.85; + animation: bounce 1.5s ease-in-out infinite; +} + +@keyframes bounce { + + 0%, + 100% { + transform: translateY(0); + } + + 50% { + transform: translateY(8px); + } } /* Pulse animation */ @keyframes pulse { - from { opacity: 0.4; transform: scale(0.97); } - to { opacity: 1; transform: scale(1.06); } + from { + opacity: 0.6; + transform: scale(0.98); + } + + to { + opacity: 1; + transform: scale(1.02); + } } @media (max-width: 600px) { #enterText { - font-size: 24px; + font-size: 20px; + line-height: 1.5; } + #arrow { font-size: 20px; } @@ -70,18 +148,22 @@ html, body { position: absolute; width: 100%; height: 100%; - background: repeating-linear-gradient( - transparent, - rgba(0,255,157,0.03) 2px - ); + background: repeating-linear-gradient(transparent, + rgba(139, 92, 246, 0.02) 2px, + transparent 4px); mix-blend-mode: difference; animation: glitchMove 0.6s infinite linear alternate; pointer-events: none; } @keyframes glitchMove { - from { transform: translateY(0); } - to { transform: translateY(10px); } + from { + transform: translateY(0); + } + + to { + transform: translateY(10px); + } } /* =============================== @@ -97,15 +179,19 @@ html, body { object-fit: cover; opacity: 0; - transition: opacity 1.2s ease-in-out; + transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1); z-index: 900; - + /* Disable video controls completely */ pointer-events: none; user-select: none; -webkit-user-select: none; } +#introVideo.video-loaded { + opacity: 1; +} + /* Remove default video controls styling */ #introVideo::-webkit-media-controls { display: none !important; @@ -132,7 +218,7 @@ html, body { } /* =============================== - WHITE FLASH TRANSITION + GRADIENT FLASH TRANSITION ================================ */ #flash { position: fixed; @@ -140,11 +226,11 @@ html, body { left: 0; width: 100vw; height: 100vh; - background: white; + background: linear-gradient(135deg, #8B5CF6 0%, #06B6D4 50%, #EC4899 100%); opacity: 0; pointer-events: none; z-index: 2000; - transition: opacity 0.2s ease-out; + transition: opacity 0.3s ease-out; } .flashVisible { @@ -162,13 +248,13 @@ html, body { width: 100vw; height: 100vh; - background: repeating-linear-gradient( - transparent 0, - rgba(0, 255, 100, 0.055) 2px - ); + background: repeating-linear-gradient(0deg, + transparent 0, + rgba(139, 92, 246, 0.03) 2px, + transparent 4px); mix-blend-mode: overlay; - opacity: 0.08; + opacity: 0.1; z-index: 1500; pointer-events: none; -} +} \ No newline at end of file diff --git a/src/styles/register.css b/src/styles/register.css deleted file mode 100644 index 18718c6..0000000 --- a/src/styles/register.css +++ /dev/null @@ -1,727 +0,0 @@ -/* ========== GLOBAL RESET & BASE ========== */ - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - background: - radial-gradient(circle at top, #020617 0%, #020617 45%, #020617 100%); - min-height: 100vh; - overflow-x: hidden; - color: #e2e8f0; -} - -/* ========== VORTEX CANVAS & OVERLAY ========== */ - -#vortexCanvas { - position: fixed; - inset: 0; - width: 100%; - height: 100%; - z-index: 0; -} - -.bg-overlay { - position: fixed; - inset: 0; - background: - radial-gradient(circle at 10% 0%, rgba(56, 189, 248, 0.08), transparent 55%), - radial-gradient(circle at 90% 100%, rgba(244, 114, 182, 0.14), transparent 55%), - radial-gradient(ellipse at center, transparent 0%, rgba(2, 6, 23, 0.55) 50%, #020617 100%); - z-index: 1; - pointer-events: none; -} - -/* ========== WRAPPER & SHELL ========== */ - -.form-wrapper { - position: relative; - z-index: 10; - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; - padding: 2rem; - transition: - transform 1.8s cubic-bezier(0.68, -0.55, 0.27, 1.55), - opacity 1.2s ease-in, - filter 1.5s ease-in; -} - -.form-wrapper.sucked { - transform: scale(0) rotate(720deg); - opacity: 0; - filter: blur(20px); -} - -.shell { - display: flex; - max-width: 1120px; - width: 100%; - border-radius: 1.9rem; - overflow: hidden; - background: - radial-gradient(circle at top left, rgba(148, 163, 184, 0.26), transparent 55%) padding-box, - rgba(15, 23, 42, 0.96); - box-shadow: - 0 0 0 1px rgba(148, 163, 184, 0.25), - 0 32px 100px rgba(15, 23, 42, 0.95), - 0 0 90px rgba(129, 140, 248, 0.35), - 0 0 160px rgba(236, 72, 153, 0.28); - backdrop-filter: blur(30px) saturate(170%); -} - -/* Panels share consistent padding so spacing feels intentional */ -.panel { - flex: 1; - padding: 2.2rem 2.6rem; -} - -/* ========== LEFT PANEL / HERO ========== */ - -.panel:first-child { - position: relative; - display: flex; - flex-direction: column; - justify-content: flex-start; - background: - radial-gradient(circle at 0% 0%, rgba(56, 189, 248, 0.24), transparent 55%), - radial-gradient(circle at 100% 100%, rgba(236, 72, 153, 0.24), transparent 55%), - linear-gradient(150deg, #020617, #020617 40%, #020617 100%); - border-right: 1px solid rgba(148, 163, 184, 0.45); -} - -/* subtle grid / scanline texture */ -.panel:first-child::before { - content: ''; - position: absolute; - inset: 0; - background-image: - linear-gradient( - 120deg, - rgba(148, 163, 184, 0.06) 0, - transparent 40%, - rgba(15, 23, 42, 0.7) 65% - ); - mix-blend-mode: screen; - opacity: 0.6; - pointer-events: none; -} - -/* corner glow orbs */ -.panel:first-child::after { - content: ''; - position: absolute; - width: 260px; - height: 260px; - bottom: -22%; - left: -18%; - background: radial-gradient(circle, rgba(236, 72, 153, 0.8), transparent 65%); - filter: blur(40px); - opacity: 0.7; - pointer-events: none; -} - -.panel-header { - position: relative; - z-index: 1; - margin-bottom: 2.1rem; -} - -/* ========== LOGO – BIG, HERO, BUT NOT FLOATING ========== */ - -.logo-placeholder { - width: 142px; - height: 142px; - border-radius: 1.8rem; - background: - radial-gradient(circle at 20% 0, #f9a8d4 0, #8b5cf6 40%, #0f172a 100%); - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 1.8rem; - box-shadow: - 0 0 0 1px rgba(248, 250, 252, 0.12), - 0 26px 70px rgba(129, 140, 248, 0.9); - position: relative; - overflow: hidden; -} - -/* inner ring + scanline */ -.logo-placeholder::after { - content: ''; - position: absolute; - inset: 10px; - border-radius: inherit; - border: 1px solid rgba(248, 250, 252, 0.22); - box-shadow: - 0 0 18px rgba(248, 250, 252, 0.22) inset, - 0 0 40px rgba(56, 189, 248, 0.45); - pointer-events: none; -} - -.logo-img { - width: 100%; - height: 100%; - object-fit: cover; -} - -/* ========== LEFT TEXT HIERARCHY ========== */ - -.tagline { - font-size: 0.78rem; - text-transform: uppercase; - letter-spacing: 0.3em; - color: #a5b4fc; - margin-bottom: 0.7rem; - opacity: 0.95; -} - -.title { - font-size: 2.8rem; - font-weight: 800; - line-height: 1.05; - background: conic-gradient(from 200deg, #e0e7ff, #f9a8d4, #a855f7, #22d3ee, #e0e7ff); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - margin-bottom: 0.9rem; - text-shadow: 0 0 22px rgba(59, 130, 246, 0.55); -} - -.subtitle { - color: #e2e8f0; - font-size: 0.96rem; - line-height: 1.7; - max-width: 28rem; - opacity: 0.9; -} - -/* ========== BULLET POINTS ========== */ - -.bullet-list { - position: relative; - z-index: 1; - list-style: none; - display: flex; - flex-direction: column; - gap: 0.9rem; - margin-top: 1.9rem; -} - -.bullet-list li { - display: flex; - align-items: flex-start; - gap: 0.8rem; - font-size: 0.88rem; - color: #e5e7eb; -} - -.bullet-list li::before { - content: ''; - width: 8px; - height: 8px; - border-radius: 999px; - background: radial-gradient(circle, #a855f7, #ec4899); - margin-top: 0.25rem; - box-shadow: - 0 0 10px rgba(236, 72, 153, 0.9), - 0 0 16px rgba(129, 140, 248, 0.8); -} - -/* ========== SECTION LABELS ========== */ - -.section-label { - font-size: 0.7rem; - text-transform: uppercase; - letter-spacing: 0.16em; - color: #a5b4fc; - margin-bottom: 0.9rem; - font-weight: 600; -} - -/* ========== RIGHT PANEL / FORM ========== */ - -.panel:last-child { - background: - radial-gradient(circle at 100% 0%, rgba(56, 189, 248, 0.15), transparent 60%), - radial-gradient(circle at 0% 100%, rgba(236, 72, 153, 0.13), transparent 60%), - linear-gradient(140deg, #020617, #020617 45%, #020617 100%); -} - -/* ========== FIELDS & INPUTS ========== */ - -.field { - margin-bottom: 1rem; -} - -.field label { - display: flex; - align-items: center; - justify-content: space-between; - font-size: 0.8rem; - color: #94a3b8; - margin-bottom: 0.4rem; - font-weight: 500; -} - -.field label span.helper { - font-size: 0.7rem; - color: #64748b; -} - -/* universal input base */ - -.input-base { - width: 100%; - padding: 0.78rem 1rem; - background: - radial-gradient(circle at top left, rgba(30, 64, 175, 0.22), transparent 55%) padding-box, - rgba(15, 23, 42, 0.98); - border: 1px solid rgba(148, 163, 184, 0.55); - border-radius: 0.7rem; - color: #e2e8f0; - font-size: 0.9rem; - outline: none; - transition: - border-color 0.18s ease, - box-shadow 0.18s ease, - background 0.18s ease, - transform 0.12s ease; -} - -.input-base::placeholder { - color: #64748b; -} - -.input-base:focus { - border-color: #a855f7; - box-shadow: - 0 0 0 1px rgba(168, 85, 247, 0.8), - 0 0 26px rgba(168, 85, 247, 0.35); - transform: translateY(-1px); -} - -/* two-column rows */ - -.field-row { - display: flex; - gap: 1rem; -} - -.field-row .field { - flex: 1; -} - -/* ========== TEAM SIZE DROPDOWN ========== */ - -.team-size-row { - display: flex; - align-items: center; - gap: 1rem; - margin-bottom: 0.5rem; -} - -.team-size-row span { - font-size: 0.875rem; - color: #94a3b8; -} - -.dropdown { - position: relative; - min-width: 170px; -} - -.dropdown-toggle { - display: flex; - align-items: center; - justify-content: space-between; - cursor: pointer; - user-select: none; -} - -.chevron { - font-size: 0.8rem; - color: #64748b; - transition: transform 0.18s ease; -} - -.dropdown-toggle[aria-expanded='true'] .chevron { - transform: rotate(180deg); -} - -.dropdown-menu { - position: absolute; - top: calc(100% + 0.35rem); - left: 0; - right: 0; - background: rgba(15, 23, 42, 0.99); - border: 1px solid rgba(148, 163, 184, 0.75); - border-radius: 0.75rem; - box-shadow: - 0 22px 50px rgba(15, 23, 42, 0.98), - 0 0 0 1px rgba(15, 23, 42, 1); - list-style: none; - overflow: hidden; - opacity: 0; - visibility: hidden; - transform: translateY(-8px); - transition: - opacity 0.18s ease, - transform 0.18s ease, - visibility 0.18s ease; - z-index: 100; -} - -.dropdown-menu.open { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -.dropdown-item { - padding: 0.75rem 1rem; - font-size: 0.875rem; - color: #e2e8f0; - cursor: pointer; - transition: background 0.14s ease, color 0.14s ease; -} - -.dropdown-item:hover { - background: linear-gradient( - 90deg, - rgba(79, 70, 229, 0.55), - rgba(236, 72, 153, 0.5) - ); - color: #f9fafb; -} - -/* ========== HINT TEXT ========== */ - -.hint { - font-size: 0.75rem; - color: #64748b; - margin-bottom: 1.1rem; - line-height: 1.5; -} - -/* ========== TOGGLE (VIT / OTHER) ========== */ - -.toggle-group { - display: flex; - margin-top: 0.5rem; - background: - radial-gradient(circle at top left, rgba(30, 64, 175, 0.24), transparent 55%) padding-box, - rgba(15, 23, 42, 0.98); - border-radius: 1rem; - padding: 0.25rem; - border: 1px solid rgba(148, 163, 184, 0.7); - position: relative; - width: fit-content; - box-shadow: - inset 0 0 0 1px rgba(15, 23, 42, 1), - 0 12px 32px rgba(15, 23, 42, 0.96); -} - -.toggle-option { - position: relative; - z-index: 2; - cursor: pointer; - user-select: none; -} - -.toggle-option input[type='radio'] { - display: none; -} - -.toggle-label { - display: flex; - align-items: center; - gap: 0.55rem; - padding: 0.65rem 1.4rem; - font-size: 0.84rem; - font-weight: 500; - color: #6b7280; - border-radius: 0.75rem; - transition: color 0.22s ease, text-shadow 0.22s ease; -} - -.toggle-label .toggle-icon { - font-size: 1rem; - transition: transform 0.24s ease; -} - -.toggle-option input[type='radio']:checked + .toggle-label { - color: #f9fafb; -} - -.toggle-option:first-child input[type='radio']:checked + .toggle-label { - text-shadow: 0 0 18px rgba(129, 140, 248, 0.9); -} - -.toggle-option:last-child input[type='radio']:checked + .toggle-label { - text-shadow: 0 0 18px rgba(244, 114, 182, 0.9); -} - -.toggle-option:hover .toggle-label { - color: #9ca3af; -} - -.toggle-option input[type='radio']:checked + .toggle-label:hover { - color: #f9fafb; -} - -.toggle-option:hover .toggle-label .toggle-icon { - transform: translateY(-1px); -} - -.toggle-slider { - position: absolute; - top: 0.25rem; - left: 0.25rem; - height: calc(100% - 0.5rem); - width: calc(50% - 0.25rem); - border-radius: 0.8rem; - background: linear-gradient(135deg, #6366f1 0%, #a855f7 45%, #22d3ee 100%); - box-shadow: - 0 10px 30px rgba(99, 102, 241, 0.7), - 0 0 26px rgba(129, 140, 248, 0.85); - transition: all 0.35s cubic-bezier(0.68, -0.15, 0.27, 1.15); -} - -.toggle-group.no-selected .toggle-slider { - left: calc(50% + 0rem); - background: linear-gradient(135deg, #ec4899 0%, #f472b6 45%, #fb923c 100%); - box-shadow: - 0 10px 30px rgba(236, 72, 153, 0.7), - 0 0 26px rgba(236, 72, 153, 0.85); -} - -.toggle-group:hover { - border-color: rgba(129, 140, 248, 0.85); - box-shadow: - 0 0 0 1px rgba(15, 23, 42, 1), - 0 18px 50px rgba(15, 23, 42, 0.98), - 0 0 40px rgba(129, 140, 248, 0.55); -} - -.toggle-group.no-selected:hover { - border-color: rgba(244, 114, 182, 0.9); -} - -/* ========== CONDITIONAL COLLEGE FIELD ========== */ - -.college-field { - animation: slideDown 0.28s ease; -} - -@keyframes slideDown { - from { - opacity: 0; - transform: translateY(-8px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* ========== PARTICIPANTS GRID ========== */ - -.participant-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); - gap: 1rem; - margin-bottom: 1.6rem; -} - -.participant-card { - background: - radial-gradient(circle at top left, rgba(30, 64, 175, 0.22), transparent 55%) padding-box, - rgba(15, 23, 42, 0.99); - border: 1px solid rgba(148, 163, 184, 0.7); - border-radius: 1rem; - padding: 1.05rem 1rem; - box-shadow: - 0 12px 36px rgba(15, 23, 42, 0.98), - 0 0 0 1px rgba(15, 23, 42, 1); -} - -.participant-title { - font-size: 0.74rem; - text-transform: uppercase; - letter-spacing: 0.12em; - color: #f472b6; - margin-bottom: 0.8rem; - font-weight: 600; -} - -.participant-card .field { - margin-bottom: 0.75rem; -} - -.participant-card .field:last-child { - margin-bottom: 0; -} - -/* ========== SUBMIT BUTTON ========== */ - -.submit-btn { - width: 100%; - padding: 0.98rem 1.7rem; - background: linear-gradient(135deg, #6366f1 0%, #ec4899 40%, #fb923c 80%); - border: none; - border-radius: 0.9rem; - color: #f9fafb; - font-size: 0.98rem; - font-weight: 700; - cursor: pointer; - text-transform: uppercase; - letter-spacing: 0.16em; - box-shadow: - 0 16px 46px rgba(79, 70, 229, 0.8), - 0 0 34px rgba(236, 72, 153, 0.8); - transition: - transform 0.14s ease, - box-shadow 0.18s ease, - filter 0.14s ease; -} - -.submit-btn:hover:not(:disabled) { - transform: translateY(-2px) scale(1.015); - box-shadow: - 0 24px 60px rgba(79, 70, 229, 0.96), - 0 0 46px rgba(236, 72, 153, 1); - filter: brightness(1.06); -} - -.submit-btn:active:not(:disabled) { - transform: translateY(0) scale(0.99); - box-shadow: - 0 12px 36px rgba(79, 70, 229, 0.85), - 0 0 30px rgba(236, 72, 153, 0.9); -} - -/* ========== VORTEX SUCCESS MODAL ========== */ - -.vortex-message { - position: fixed; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - z-index: 100; - opacity: 0; - visibility: hidden; - transition: all 0.6s ease; -} - -.vortex-message.visible { - opacity: 1; - visibility: visible; -} - -.vortex-message-inner { - text-align: center; - padding: 3rem; - max-width: 520px; - background: rgba(15, 23, 42, 0.98); - border-radius: 1.8rem; - border: 1px solid rgba(148, 163, 184, 0.8); - box-shadow: - 0 0 110px rgba(129, 140, 248, 0.6), - 0 0 190px rgba(236, 72, 153, 0.4); - backdrop-filter: blur(30px) saturate(180%); - animation: pulse-glow 2s ease-in-out infinite; -} - -@keyframes pulse-glow { - 0%, 100% { - box-shadow: - 0 0 110px rgba(129, 140, 248, 0.6), - 0 0 190px rgba(236, 72, 153, 0.4); - } - 50% { - box-shadow: - 0 0 130px rgba(129, 140, 248, 0.9), - 0 0 230px rgba(236, 72, 153, 0.6); - } -} - -.vortex-message h2 { - font-size: 2.6rem; - font-weight: 800; - letter-spacing: 0.18em; - background: linear-gradient(135deg, #e0e7ff 0%, #f0abfc 45%, #a855f7 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - margin-bottom: 1rem; -} - -.vortex-message p { - color: #cbd5f5; - font-size: 1.02rem; - line-height: 1.7; -} - -.vortex-message strong { - color: #a5b4fc; -} - -/* ========== RESPONSIVE ========== */ - -@media (max-width: 900px) { - .shell { - border-radius: 1.6rem; - } - - .panel { - padding: 2rem 2rem; - } -} - -@media (max-width: 768px) { - .shell { - flex-direction: column; - } - - .panel:first-child { - border-right: none; - border-bottom: 1px solid rgba(148, 163, 184, 0.45); - justify-content: center; - padding: 1.9rem 1.8rem 1.8rem; - } - - .logo-placeholder { - width: 120px; - height: 120px; - border-radius: 1.6rem; - margin-bottom: 1.7rem; - } - - .title { - font-size: 2.4rem; - } - - .form-wrapper { - padding: 1.4rem; - } - - .panel { - padding: 1.9rem 1.7rem; - } - - .field-row { - flex-direction: column; - gap: 0; - } - - .participant-grid { - grid-template-columns: 1fr; - } -} diff --git a/src/supabaseClient.js b/src/supabaseClient.js deleted file mode 100644 index 733b874..0000000 --- a/src/supabaseClient.js +++ /dev/null @@ -1,6 +0,0 @@ -import { createClient } from '@supabase/supabase-js'; - -const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; -const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; - -export const supabase = createClient(supabaseUrl, supabaseAnonKey); diff --git a/src/utils/authRouting.js b/src/utils/authRouting.js deleted file mode 100644 index 12c4170..0000000 --- a/src/utils/authRouting.js +++ /dev/null @@ -1,23 +0,0 @@ -export const ROUTES = { - login: "/login", - otp: "/otp", - register: "/register", - leaderDashboard: "/dashboard", - memberDashboard: "/member", -}; - -export function routeForContext(context) { - if (!context) { - return ROUTES.login; - } - - if (context.role === "team_leader") { - return context.teamId ? ROUTES.leaderDashboard : ROUTES.register; - } - - if (context.role === "team_member") { - return context.teamId ? ROUTES.memberDashboard : ROUTES.register; - } - - return ROUTES.register; -} diff --git a/src/utils/buildTeam.js b/src/utils/buildTeam.js deleted file mode 100644 index 12d8f6e..0000000 --- a/src/utils/buildTeam.js +++ /dev/null @@ -1,30 +0,0 @@ -import { supabase } from "../supabaseClient"; - -export async function buildTeam(payload) { - const { data: { session } } = await supabase.auth.getSession(); - - if (!session) { - throw new Error("Not authenticated"); - } - - const res = await fetch( - "https://zmcrdozxxclgzpltwpme.supabase.co/functions/v1/build-team", - { - method: "POST", - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": `Bearer ${session.access_token}`, - }, - body: JSON.stringify(payload), - } - ); - - const data = await res.json(); - - if (!res.ok) { - throw new Error(data.error || "Failed to build team"); - } - - return data; -} diff --git a/src/utils/selectProblem.js b/src/utils/selectProblem.js deleted file mode 100644 index fc89bb1..0000000 --- a/src/utils/selectProblem.js +++ /dev/null @@ -1,42 +0,0 @@ -import { supabase } from "../supabaseClient"; - -export async function selectProblem(payload) { - const { data: { session } } = await supabase.auth.getSession(); - - if (!session) { - throw new Error("Not authenticated"); - } - - const base = import.meta.env.VITE_SUPABASE_URL || ""; - const fnUrl = base.replace(/\/$/, "") + "/functions/v1/select-problem"; - console.debug("selectProblem: POST", fnUrl, payload); - - const res = await fetch(fnUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": `Bearer ${session.access_token}`, - }, - body: JSON.stringify(payload), - }).catch((err) => { - console.error("selectProblem: network error", err); - throw new Error("Network error while contacting select-problem function"); - }); - - let data = {}; - try { - data = await res.json(); - } catch (e) { - console.warn("selectProblem: response not JSON", e); - } - - console.debug("selectProblem: response", res.status, data); - - if (!res.ok) { - const message = data?.error || data?.message || `Request failed (${res.status})`; - throw new Error(message); - } - - return data; -} diff --git a/supabase/.gitignore b/supabase/.gitignore deleted file mode 100644 index ad9264f..0000000 --- a/supabase/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Supabase -.branches -.temp - -# dotenvx -.env.keys -.env.local -.env.*.local diff --git a/supabase/.temp/cli-latest b/supabase/.temp/cli-latest deleted file mode 100644 index 8c68db7..0000000 --- a/supabase/.temp/cli-latest +++ /dev/null @@ -1 +0,0 @@ -v2.67.1 \ No newline at end of file diff --git a/supabase/.temp/gotrue-version b/supabase/.temp/gotrue-version deleted file mode 100644 index 52e18b3..0000000 --- a/supabase/.temp/gotrue-version +++ /dev/null @@ -1 +0,0 @@ -v2.182.1 \ No newline at end of file diff --git a/supabase/.temp/pooler-url b/supabase/.temp/pooler-url deleted file mode 100644 index f288149..0000000 --- a/supabase/.temp/pooler-url +++ /dev/null @@ -1 +0,0 @@ -postgresql://postgres.zmcrdozxxclgzpltwpme@aws-1-us-east-1.pooler.supabase.com:5432/postgres \ No newline at end of file diff --git a/supabase/.temp/postgres-version b/supabase/.temp/postgres-version deleted file mode 100644 index 18f1b99..0000000 --- a/supabase/.temp/postgres-version +++ /dev/null @@ -1 +0,0 @@ -17.6.1.054 \ No newline at end of file diff --git a/supabase/.temp/project-ref b/supabase/.temp/project-ref deleted file mode 100644 index 68d4c76..0000000 --- a/supabase/.temp/project-ref +++ /dev/null @@ -1 +0,0 @@ -zmcrdozxxclgzpltwpme \ No newline at end of file diff --git a/supabase/.temp/rest-version b/supabase/.temp/rest-version deleted file mode 100644 index 93c142b..0000000 --- a/supabase/.temp/rest-version +++ /dev/null @@ -1 +0,0 @@ -v13.0.5 \ No newline at end of file diff --git a/supabase/.temp/storage-migration b/supabase/.temp/storage-migration deleted file mode 100644 index dbaf11b..0000000 --- a/supabase/.temp/storage-migration +++ /dev/null @@ -1 +0,0 @@ -iceberg-catalog-ids \ No newline at end of file diff --git a/supabase/.temp/storage-version b/supabase/.temp/storage-version deleted file mode 100644 index 6f98654..0000000 --- a/supabase/.temp/storage-version +++ /dev/null @@ -1 +0,0 @@ -v1.31.1 \ No newline at end of file diff --git a/supabase/config.toml b/supabase/config.toml deleted file mode 100644 index afc4a09..0000000 --- a/supabase/config.toml +++ /dev/null @@ -1,393 +0,0 @@ -# For detailed configuration reference documentation, visit: -# https://supabase.com/docs/guides/local-development/cli/config -# A string used to distinguish different Supabase projects on the same host. Defaults to the -# working directory name when running `supabase init`. -project_id = "V-Vortex" - -[api] -enabled = true -# Port to use for the API URL. -port = 54321 -# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API -# endpoints. `public` and `graphql_public` schemas are included by default. -schemas = ["public", "graphql_public"] -# Extra schemas to add to the search_path of every request. -extra_search_path = ["public", "extensions"] -# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size -# for accidental or malicious requests. -max_rows = 1000 - -[api.tls] -# Enable HTTPS endpoints locally using a self-signed certificate. -enabled = false -# Paths to self-signed certificate pair. -# cert_path = "../certs/my-cert.pem" -# key_path = "../certs/my-key.pem" - -[db] -# Port to use for the local database URL. -port = 54322 -# Port used by db diff command to initialize the shadow database. -shadow_port = 54320 -# The database major version to use. This has to be the same as your remote database's. Run `SHOW -# server_version;` on the remote database to check. -major_version = 17 - -[db.pooler] -enabled = false -# Port to use for the local connection pooler. -port = 54329 -# Specifies when a server connection can be reused by other clients. -# Configure one of the supported pooler modes: `transaction`, `session`. -pool_mode = "transaction" -# How many server connections to allow per user/database pair. -default_pool_size = 20 -# Maximum number of client connections allowed. -max_client_conn = 100 - -# [db.vault] -# secret_key = "env(SECRET_VALUE)" - -[db.migrations] -# If disabled, migrations will be skipped during a db push or reset. -enabled = true -# Specifies an ordered list of schema files that describe your database. -# Supports glob patterns relative to supabase directory: "./schemas/*.sql" -schema_paths = [] - -[db.seed] -# If enabled, seeds the database after migrations during a db reset. -enabled = true -# Specifies an ordered list of seed files to load during db reset. -# Supports glob patterns relative to supabase directory: "./seeds/*.sql" -sql_paths = ["./seed.sql"] - -[db.network_restrictions] -# Enable management of network restrictions. -enabled = false -# List of IPv4 CIDR blocks allowed to connect to the database. -# Defaults to allow all IPv4 connections. Set empty array to block all IPs. -allowed_cidrs = ["0.0.0.0/0"] -# List of IPv6 CIDR blocks allowed to connect to the database. -# Defaults to allow all IPv6 connections. Set empty array to block all IPs. -allowed_cidrs_v6 = ["::/0"] - -[realtime] -enabled = true -# Bind realtime via either IPv4 or IPv6. (default: IPv4) -# ip_version = "IPv6" -# The maximum length in bytes of HTTP request headers. (default: 4096) -# max_header_length = 4096 - -[studio] -enabled = true -# Port to use for Supabase Studio. -port = 54323 -# External URL of the API server that frontend connects to. -api_url = "http://127.0.0.1" -# OpenAI API Key to use for Supabase AI in the Supabase Studio. -openai_api_key = "env(OPENAI_API_KEY)" - -# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they -# are monitored, and you can view the emails that would have been sent from the web interface. -[inbucket] -enabled = true -# Port to use for the email testing server web interface. -port = 54324 -# Uncomment to expose additional ports for testing user applications that send emails. -# smtp_port = 54325 -# pop3_port = 54326 -# admin_email = "admin@email.com" -# sender_name = "Admin" - -[storage] -enabled = true -# The maximum file size allowed (e.g. "5MB", "500KB"). -file_size_limit = "50MiB" - -# Uncomment to configure local storage buckets -# [storage.buckets.images] -# public = false -# file_size_limit = "50MiB" -# allowed_mime_types = ["image/png", "image/jpeg"] -# objects_path = "./images" - -# Uncomment to allow connections via S3 compatible clients -# [storage.s3_protocol] -# enabled = true - -# Image transformation API is available to Supabase Pro plan. -# [storage.image_transformation] -# enabled = true - -# Store analytical data in S3 for running ETL jobs over Iceberg Catalog -# This feature is only available on the hosted platform. -[storage.analytics] -enabled = false -max_namespaces = 5 -max_tables = 10 -max_catalogs = 2 - -# Analytics Buckets is available to Supabase Pro plan. -# [storage.analytics.buckets.my-warehouse] - -# Store vector embeddings in S3 for large and durable datasets -# This feature is only available on the hosted platform. -[storage.vector] -enabled = false -max_buckets = 10 -max_indexes = 5 - -# Vector Buckets is available to Supabase Pro plan. -# [storage.vector.buckets.documents-openai] - -[auth] -enabled = true -# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used -# in emails. -site_url = "http://127.0.0.1:3000" -# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. -additional_redirect_urls = ["https://127.0.0.1:3000"] -# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). -jwt_expiry = 3600 -# JWT issuer URL. If not set, defaults to the local API URL (http://127.0.0.1:/auth/v1). -# jwt_issuer = "" -# Path to JWT signing key. DO NOT commit your signing keys file to git. -# signing_keys_path = "./signing_keys.json" -# If disabled, the refresh token will never expire. -enable_refresh_token_rotation = true -# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. -# Requires enable_refresh_token_rotation = true. -refresh_token_reuse_interval = 10 -# Allow/disallow new user signups to your project. -enable_signup = true -# Allow/disallow anonymous sign-ins to your project. -enable_anonymous_sign_ins = false -# Allow/disallow testing manual linking of accounts -enable_manual_linking = false -# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. -minimum_password_length = 6 -# Passwords that do not meet the following requirements will be rejected as weak. Supported values -# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols` -password_requirements = "" - -[auth.rate_limit] -# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled. -email_sent = 2 -# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled. -sms_sent = 30 -# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true. -anonymous_users = 30 -# Number of sessions that can be refreshed in a 5 minute interval per IP address. -token_refresh = 150 -# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users). -sign_in_sign_ups = 30 -# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address. -token_verifications = 30 -# Number of Web3 logins that can be made in a 5 minute interval per IP address. -web3 = 30 - -# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`. -# [auth.captcha] -# enabled = true -# provider = "hcaptcha" -# secret = "" - -[auth.email] -# Allow/disallow new user signups via email to your project. -enable_signup = true -# If enabled, a user will be required to confirm any email change on both the old, and new email -# addresses. If disabled, only the new email is required to confirm. -double_confirm_changes = true -# If enabled, users need to confirm their email address before signing in. -enable_confirmations = false -# If enabled, users will need to reauthenticate or have logged in recently to change their password. -secure_password_change = false -# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. -max_frequency = "1s" -# Number of characters used in the email OTP. -otp_length = 6 -# Number of seconds before the email OTP expires (defaults to 1 hour). -otp_expiry = 3600 - -# Use a production-ready SMTP server -# [auth.email.smtp] -# enabled = true -# host = "smtp.sendgrid.net" -# port = 587 -# user = "apikey" -# pass = "env(SENDGRID_API_KEY)" -# admin_email = "admin@email.com" -# sender_name = "Admin" - -# Uncomment to customize email template -# [auth.email.template.invite] -# subject = "You have been invited" -# content_path = "./supabase/templates/invite.html" - -# Uncomment to customize notification email template -# [auth.email.notification.password_changed] -# enabled = true -# subject = "Your password has been changed" -# content_path = "./templates/password_changed_notification.html" - -[auth.sms] -# Allow/disallow new user signups via SMS to your project. -enable_signup = false -# If enabled, users need to confirm their phone number before signing in. -enable_confirmations = false -# Template for sending OTP to users -template = "Your code is {{ .Code }}" -# Controls the minimum amount of time that must pass before sending another sms otp. -max_frequency = "5s" - -# Use pre-defined map of phone number to OTP for testing. -# [auth.sms.test_otp] -# 4152127777 = "123456" - -# Configure logged in session timeouts. -# [auth.sessions] -# Force log out after the specified duration. -# timebox = "24h" -# Force log out if the user has been inactive longer than the specified duration. -# inactivity_timeout = "8h" - -# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object. -# [auth.hook.before_user_created] -# enabled = true -# uri = "pg-functions://postgres/auth/before-user-created-hook" - -# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. -# [auth.hook.custom_access_token] -# enabled = true -# uri = "pg-functions:////" - -# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. -[auth.sms.twilio] -enabled = false -account_sid = "" -message_service_sid = "" -# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: -auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" - -# Multi-factor-authentication is available to Supabase Pro plan. -[auth.mfa] -# Control how many MFA factors can be enrolled at once per user. -max_enrolled_factors = 10 - -# Control MFA via App Authenticator (TOTP) -[auth.mfa.totp] -enroll_enabled = false -verify_enabled = false - -# Configure MFA via Phone Messaging -[auth.mfa.phone] -enroll_enabled = false -verify_enabled = false -otp_length = 6 -template = "Your code is {{ .Code }}" -max_frequency = "5s" - -# Configure MFA via WebAuthn -# [auth.mfa.web_authn] -# enroll_enabled = true -# verify_enabled = true - -# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, -# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, -# `twitter`, `slack`, `spotify`, `workos`, `zoom`. -[auth.external.apple] -enabled = false -client_id = "" -# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: -secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" -# Overrides the default auth redirectUrl. -redirect_uri = "" -# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, -# or any other third-party OIDC providers. -url = "" -# If enabled, the nonce check will be skipped. Required for local sign in with Google auth. -skip_nonce_check = false -# If enabled, it will allow the user to successfully authenticate when the provider does not return an email address. -email_optional = false - -# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard. -# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting. -[auth.web3.solana] -enabled = false - -# Use Firebase Auth as a third-party provider alongside Supabase Auth. -[auth.third_party.firebase] -enabled = false -# project_id = "my-firebase-project" - -# Use Auth0 as a third-party provider alongside Supabase Auth. -[auth.third_party.auth0] -enabled = false -# tenant = "my-auth0-tenant" -# tenant_region = "us" - -# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth. -[auth.third_party.aws_cognito] -enabled = false -# user_pool_id = "my-user-pool-id" -# user_pool_region = "us-east-1" - -# Use Clerk as a third-party provider alongside Supabase Auth. -[auth.third_party.clerk] -enabled = false -# Obtain from https://clerk.com/setup/supabase -# domain = "example.clerk.accounts.dev" - -# OAuth server configuration -[auth.oauth_server] -# Enable OAuth server functionality -enabled = false -# Path for OAuth consent flow UI -authorization_url_path = "/oauth/consent" -# Allow dynamic client registration -allow_dynamic_registration = false - -[edge_runtime] -enabled = true -# Supported request policies: `oneshot`, `per_worker`. -# `per_worker` (default) — enables hot reload during local development. -# `oneshot` — fallback mode if hot reload causes issues (e.g. in large repos or with symlinks). -policy = "per_worker" -# Port to attach the Chrome inspector for debugging edge functions. -inspector_port = 8083 -# The Deno major version to use. -deno_version = 2 - -# [edge_runtime.secrets] -# secret_key = "env(SECRET_VALUE)" - -[analytics] -enabled = true -port = 54327 -# Configure one of the supported backends: `postgres`, `bigquery`. -backend = "postgres" - -# Experimental features may be deprecated any time -[experimental] -# Configures Postgres storage engine to use OrioleDB (S3) -orioledb_version = "" -# Configures S3 bucket URL, eg. .s3-.amazonaws.com -s3_host = "env(S3_HOST)" -# Configures S3 bucket region, eg. us-east-1 -s3_region = "env(S3_REGION)" -# Configures AWS_ACCESS_KEY_ID for S3 bucket -s3_access_key = "env(S3_ACCESS_KEY)" -# Configures AWS_SECRET_ACCESS_KEY for S3 bucket -s3_secret_key = "env(S3_SECRET_KEY)" - -[functions.register-team] -enabled = true -verify_jwt = true -import_map = "./functions/register-team/deno.json" -# Uncomment to specify a custom file path to the entrypoint. -# Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx -entrypoint = "./functions/register-team/index.ts" -# Specifies static files to be bundled with the function. Supports glob patterns. -# For example, if you want to serve static HTML pages in your function: -# static_files = [ "./functions/register-team/*.html" ] diff --git a/supabase/functions/build-team/deno.json b/supabase/functions/build-team/deno.json deleted file mode 100644 index 5f2d173..0000000 --- a/supabase/functions/build-team/deno.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "imports": { - "supabase": "jsr:@supabase/supabase-js@2" - } -} diff --git a/supabase/functions/build-team/index.ts b/supabase/functions/build-team/index.ts deleted file mode 100644 index 7a747ad..0000000 --- a/supabase/functions/build-team/index.ts +++ /dev/null @@ -1,254 +0,0 @@ -import "jsr:@supabase/functions-js/edge-runtime.d.ts"; -import { createClient } from "jsr:@supabase/supabase-js@2"; - -/* ========================= - CORS - ========================= */ -const corsHeaders = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": - "authorization, x-client-info, apikey, content-type, accept", - "Access-Control-Allow-Methods": "GET,POST,OPTIONS", - "Access-Control-Allow-Credentials": "true", -}; - -Deno.serve(async (req) => { - // Handle CORS preflight - if (req.method === "OPTIONS") { - return new Response(null, { status: 204, headers: corsHeaders }); - } - - try { - /* ========================= - 1️⃣ AUTHENTICATION - ========================= */ - const authHeader = req.headers.get("authorization"); - - if (!authHeader) { - return new Response( - JSON.stringify({ error: "Unauthorized: Missing token" }), - { status: 401, headers: corsHeaders } - ); - } - - // Auth client (validates JWT) - const authClient = createClient( - Deno.env.get("SUPABASE_URL") ?? "", - Deno.env.get("SUPABASE_ANON_KEY") ?? "", - { - global: { - headers: { Authorization: authHeader }, - }, - } - ); - - const { - data: { user }, - error: authError, - } = await authClient.auth.getUser(); - - if (authError || !user) { - return new Response( - JSON.stringify({ error: "Unauthorized: Invalid session" }), - { status: 401, headers: corsHeaders } - ); - } - - /* ========================= - 2️⃣ ADMIN CLIENT (DB) - ========================= */ - const supabase = createClient( - Deno.env.get("SUPABASE_URL") ?? "", - Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "" - ); - - /* ========================= - 3️⃣ INPUT - ========================= */ - const { teamId, teamName, teamSize, members } = await req.json(); - - console.log("Building team:", { - teamId, - teamName, - teamSize, - memberCount: members?.length, - caller: user.email, - }); - - if (!teamId || !teamName || !teamSize || !members) { - return new Response( - JSON.stringify({ error: "Missing required fields" }), - { status: 400, headers: corsHeaders } - ); - } - - if (teamSize < 2 || teamSize > 5) { - return new Response( - JSON.stringify({ error: "Team size must be between 2 and 5" }), - { status: 400, headers: corsHeaders } - ); - } - - if (members.length !== teamSize - 1) { - return new Response( - JSON.stringify({ - error: `Expected ${teamSize - 1} members for team size ${teamSize}`, - }), - { status: 400, headers: corsHeaders } - ); - } - - /* ========================= - 4️⃣ AUTHORIZE TEAM OWNER - ========================= */ - const { data: team } = await supabase - .from("teams") - .select("lead_email") - .eq("id", teamId) - .single(); - - if (!team) { - return new Response( - JSON.stringify({ error: "Team not found" }), - { status: 404, headers: corsHeaders } - ); - } - - if (team.lead_email !== user.email) { - return new Response( - JSON.stringify({ error: "Not allowed to build this team" }), - { status: 403, headers: corsHeaders } - ); - } - - /* ========================= - 5️⃣ VALIDATIONS - ========================= */ - const { data: existingMembers } = await supabase - .from("team_members") - .select("id") - .eq("team_id", teamId); - - if (existingMembers?.length) { - return new Response( - JSON.stringify({ error: "Team already has members" }), - { status: 409, headers: corsHeaders } - ); - } - - const memberEmails = members.map((m: any) => m.email); - - if (memberEmails.includes(team.lead_email)) { - return new Response( - JSON.stringify({ - error: "Team leader email cannot be used as a member email", - }), - { status: 400, headers: corsHeaders } - ); - } - - if (new Set(memberEmails).size !== memberEmails.length) { - return new Response( - JSON.stringify({ error: "Duplicate member emails detected" }), - { status: 400, headers: corsHeaders } - ); - } - - for (const member of members) { - const { data: exists } = await supabase - .from("team_members") - .select("id") - .eq("member_email", member.email) - .maybeSingle(); - - if (exists) { - return new Response( - JSON.stringify({ - error: `Email ${member.email} is already registered`, - }), - { status: 409, headers: corsHeaders } - ); - } - } - - /* ========================= - 6️⃣ UPDATE TEAM - ========================= */ - // Prevent duplicate team names (case-insensitive). If another team - // already uses this name, return a 409 conflict with a helpful message. - const { data: nameConflict } = await supabase - .from("teams") - .select("id") - .ilike("team_name", teamName) - .neq("id", teamId) - .limit(1); - - if (nameConflict && nameConflict.length > 0) { - return new Response( - JSON.stringify({ error: "Team name already in use" }), - { status: 409, headers: corsHeaders } - ); - } - - const { error: updateError } = await supabase - .from("teams") - .update({ team_name: teamName, team_size: teamSize }) - .eq("id", teamId); - - if (updateError) { - // Map common unique-violation to a friendly 409 response. - const msg = updateError.message || "Database update failed"; - if (msg.toLowerCase().includes("duplicate") || msg.includes("unique")) { - return new Response( - JSON.stringify({ error: "Team name already in use" }), - { status: 409, headers: corsHeaders } - ); - } - - throw new Error(msg); - } - - /* ========================= - 7️⃣ INSERT MEMBERS - ========================= */ - const memberRecords = members.map((m: any) => ({ - team_id: teamId, - member_name: m.name, - member_email: m.email, - member_reg_no: m.isVitChennai ? m.regNo ?? null : null, - institution: m.isVitChennai ? "VIT Chennai" : m.eventHubId ?? null, - })); - - await supabase.from("team_members").insert(memberRecords); - - /* ========================= - 8️⃣ SCORECARD - ========================= */ - const { data: scorecard } = await supabase - .from("scorecards") - .select("id") - .eq("team_id", teamId) - .maybeSingle(); - - if (!scorecard) { - await supabase.from("scorecards").insert({ - team_id: teamId, - innovation_score: 0, - implementation_score: 0, - presentation_score: 0, - impact_score: 0, - }); - } - - return new Response( - JSON.stringify({ success: true, message: "Team built successfully" }), - { status: 200, headers: corsHeaders } - ); - } catch (err) { - console.error("Build team error:", err); - return new Response( - JSON.stringify({ error: "Internal server error" }), - { status: 500, headers: corsHeaders } - ); - } -}); diff --git a/supabase/functions/register-team/.npmrc b/supabase/functions/register-team/.npmrc deleted file mode 100644 index 48c6388..0000000 --- a/supabase/functions/register-team/.npmrc +++ /dev/null @@ -1,3 +0,0 @@ -# Configuration for private npm package dependencies -# For more information on using private registries with Edge Functions, see: -# https://supabase.com/docs/guides/functions/import-maps#importing-from-private-registries diff --git a/supabase/functions/register-team/deno.json b/supabase/functions/register-team/deno.json deleted file mode 100644 index f6ca845..0000000 --- a/supabase/functions/register-team/deno.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "imports": {} -} diff --git a/supabase/functions/register-team/index.ts b/supabase/functions/register-team/index.ts deleted file mode 100644 index 594294f..0000000 --- a/supabase/functions/register-team/index.ts +++ /dev/null @@ -1,147 +0,0 @@ -import "jsr:@supabase/functions-js/edge-runtime.d.ts"; -import { createClient } from "jsr:@supabase/supabase-js@2"; - -const corsHeaders = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type, accept", - "Access-Control-Allow-Methods": "GET,POST,OPTIONS", - "Access-Control-Allow-Credentials": "true", -}; - -Deno.serve(async (req) => { - // Handle CORS preflight - if (req.method === "OPTIONS") { - return new Response(null, { status: 204, headers: corsHeaders }); - } - - try { - // Create Supabase client using the anon key and forward the caller's - // Authorization header so DB operations run as the caller (RLS applies). - // Use the service role key for server-side trusted operations so RLS - // doesn't block legitimate inserts from the function. This key must - // only be available in the function's environment variables. - const supabase = createClient( - Deno.env.get("SUPABASE_URL") ?? "", - Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "" - ); - - // Log incoming headers and raw body to help debug client differences - try { - const rawBody = await req.text(); - console.log("Incoming request headers:", Object.fromEntries(req.headers.entries())); - console.log("Incoming raw body:", rawBody); - - // Parse body from raw text (preserves logging above) - var parsedBody = rawBody ? JSON.parse(rawBody) : {}; - } catch (logErr) { - console.error("Failed to log/parse request body:", logErr); - var parsedBody = {}; - } - - const { isVitChennai, eventHubId, leaderName, leaderReg, leaderEmail, receiptLink } = parsedBody; - console.log("Received team leader registration:", { leaderEmail, isVitChennai, receiptLink }); - - // Quick schema check to fail fast if deployed function is running against - // a DB with a mismatched schema (avoids obscure PostgREST cache errors). - const { error: schemaCheckError } = await supabase.from("teams").select("lead_name").limit(0); - if (schemaCheckError) { - console.error("Schema validation failed:", schemaCheckError); - throw new Error(`Schema validation failed: ${schemaCheckError.message}`); - } - - // Validate input - conditional validation based on VIT Chennai status - if (!leaderName || !leaderEmail || !receiptLink) { - return new Response( - JSON.stringify({ error: "Missing required fields" }), - { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } - ); - } - - // Validate VIT Chennai students have reg numbers - if (isVitChennai === "yes" && !leaderReg) { - return new Response( - JSON.stringify({ error: "VIT Chennai students must provide registration number" }), - { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } - ); - } - - // Validate non-VIT students have eventHubId - if (isVitChennai === "no" && !eventHubId) { - return new Response( - JSON.stringify({ error: "Non-VIT students must provide EventHub Unique ID" }), - { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } - ); - } - - // Check for duplicate leader email in teams table - const { data: existingLeadEmail } = await supabase - .from("teams") - .select("id") - .eq("lead_email", leaderEmail) - .maybeSingle(); - - if (existingLeadEmail) { - return new Response( - JSON.stringify({ error: "This email is already registered as a team leader. Each email can only be used once." }), - { status: 409, headers: { ...corsHeaders, "Content-Type": "application/json" } } - ); - } - - // Check if leader email exists in team_members table - const { data: existingMemberEmail } = await supabase - .from("team_members") - .select("id") - .eq("member_email", leaderEmail) - .maybeSingle(); - - if (existingMemberEmail) { - return new Response( - JSON.stringify({ error: "This email is already registered as a team member. Each email can only be used once." }), - { status: 409, headers: { ...corsHeaders, "Content-Type": "application/json" } } - ); - } - - // 1. Insert team leader into database (team not fully formed yet) - // Generate random temporary team name - const randomId = crypto.randomUUID().split('-')[0].toUpperCase(); - const teamData = { - team_name: `TEMP-${randomId}`, // Temporary random name, will be updated when building team - team_size: 2, // Set to minimum size (2) to satisfy constraint, will be updated when building team - lead_name: leaderName, - // For VIT Chennai leaders we store `lead_reg_no` and leave `institution` NULL to satisfy the - // check constraint added in migrations. For non-VIT leaders we store `institution` and leave - // `lead_reg_no` NULL. - lead_reg_no: isVitChennai === "yes" ? leaderReg : null, - // Store a readable institution value for VIT leaders as requested. - institution: isVitChennai === "no" ? eventHubId : "VIT Chennai", - lead_email: leaderEmail, - receipt_link: receiptLink, - }; - - console.log("Inserting team leader data:", teamData); - - const { data: team, error: teamError } = await supabase - .from("teams") - .insert(teamData) - .select() - .single(); - - if (teamError) { - console.error("Database error:", teamError); - throw new Error(`Database error: ${teamError.message}`); - } - - console.log("Team leader registered successfully:", team.id); - - return new Response( - JSON.stringify({ success: true, teamId: team.id }), - { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } } - ); - } catch (error) { - console.error("Registration error:", error); - return new Response( - JSON.stringify({ error: error.message }), - { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } - ); - } -}); diff --git a/supabase/functions/select-problem/deno.json b/supabase/functions/select-problem/deno.json deleted file mode 100644 index 5f2d173..0000000 --- a/supabase/functions/select-problem/deno.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "imports": { - "supabase": "jsr:@supabase/supabase-js@2" - } -} diff --git a/supabase/functions/select-problem/index.ts b/supabase/functions/select-problem/index.ts deleted file mode 100644 index 388af2c..0000000 --- a/supabase/functions/select-problem/index.ts +++ /dev/null @@ -1,121 +0,0 @@ -import "jsr:@supabase/functions-js/edge-runtime.d.ts"; -import { createClient } from "jsr:@supabase/supabase-js@2"; - -const corsHeaders = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": - "authorization, x-client-info, apikey, content-type, accept", - "Access-Control-Allow-Methods": "GET,POST,OPTIONS", - "Access-Control-Allow-Credentials": "true", -}; - -Deno.serve(async (req) => { - if (req.method === "OPTIONS") { - return new Response(null, { status: 204, headers: corsHeaders }); - } - - try { - const authHeader = req.headers.get("authorization"); - // Strictly require a Bearer token to avoid SDK inconsistencies with malformed headers - if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) { - return new Response(JSON.stringify({ error: "Invalid authorization header" }), { - status: 401, - headers: corsHeaders, - }); - } - - const authClient = createClient( - Deno.env.get("SUPABASE_URL") ?? "", - Deno.env.get("SUPABASE_ANON_KEY") ?? "", - { global: { headers: { Authorization: authHeader } } } - ); - - const { - data: { user }, - error: authError, - } = await authClient.auth.getUser(); - - if (authError || !user) { - return new Response(JSON.stringify({ error: "Unauthorized: Invalid session" }), { - status: 401, - headers: corsHeaders, - }); - } - - const supabase = createClient(Deno.env.get("SUPABASE_URL") ?? "", Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""); - - let body: any; - try { - body = await req.json(); - } catch (e) { - return new Response(JSON.stringify({ error: "Invalid JSON body" }), { status: 400, headers: corsHeaders }); - } - - const { teamId, domain, problemName, problemDescription } = body || {}; - - if (!teamId || !domain || !problemName) { - return new Response(JSON.stringify({ error: "Missing required fields" }), { status: 400, headers: corsHeaders }); - } - - // verify team exists and user is lead - const { data: team } = await supabase.from("teams").select("lead_email").eq("id", teamId).maybeSingle(); - - if (!team) { - return new Response(JSON.stringify({ error: "Team not found" }), { status: 404, headers: corsHeaders }); - } - - // Compare emails case-insensitively to avoid casing mismatches - if ((team.lead_email || "").toLowerCase() !== (user.email || "").toLowerCase()) { - return new Response(JSON.stringify({ error: "Not allowed: only team lead can assign problem" }), { status: 403, headers: corsHeaders }); - } - - // Expect a problem-statement id (psId) to perform an atomic check + seat lock - let psId = body.psId || body.ps_id; - - // If client did not provide a psId, attempt to find a matching problem statement by title+domain - if (!psId) { - const { data: matches } = await supabase.from('problem_statements').select('id').ilike('title', problemName).eq('domain', domain).limit(1); - psId = matches?.[0]?.id; - } - - if (!psId) { - return new Response(JSON.stringify({ error: "Missing psId in request and no matching problem found" }), { status: 400, headers: corsHeaders }); - } - - // Call the DB-side atomic function which locks row FOR UPDATE and updates both tables - const { data: rpcData, error: rpcError } = await supabase.rpc("select_problem_atomic", { - p_team_id: teamId, - p_ps_id: psId, - p_domain: domain, - p_problem_name: problemName, - p_problem_description: problemDescription ?? null, - p_requester_email: user.email, - }); - - if (rpcError) { - console.error("RPC error:", rpcError); - const msg = rpcError.message || rpcError.details || "RPC invocation failed"; - return new Response(JSON.stringify({ error: msg, rpc: rpcError }), { status: 500, headers: corsHeaders }); - } - - // rpcData may come back as a plain object or an array depending on the RPC return shape. - // Normalize to inspect for error keys emitted by the PL/pgSQL function. - let normalized: any = rpcData; - if (Array.isArray(rpcData) && rpcData.length > 0) { - normalized = rpcData[0]; - } - - if (normalized && normalized.error) { - const errMsg = normalized.error; - const status = errMsg === "no_seats" || errMsg === "ps_not_found" ? 409 : 400; - console.warn("RPC returned error object:", normalized); - return new Response(JSON.stringify({ error: errMsg, rpc: normalized }), { status, headers: corsHeaders }); - } - - // Return rpc result for debugging/confirmation - return new Response(JSON.stringify({ success: true, rpc: normalized }), { status: 200, headers: corsHeaders }); - } catch (err) { - console.error("select-problem error:", err); - return new Response(JSON.stringify({ error: "Internal server error" }), { status: 500, headers: corsHeaders }); - } -}); diff --git a/supabase/migrations/20251208183150_initial_schema.sql b/supabase/migrations/20251208183150_initial_schema.sql deleted file mode 100644 index 66faecd..0000000 --- a/supabase/migrations/20251208183150_initial_schema.sql +++ /dev/null @@ -1,52 +0,0 @@ - -create table teams ( - id uuid default gen_random_uuid() primary key, - created_at timestamp with time zone default timezone('utc'::text, now()) not null, - - team_name text not null, - team_size integer not null, - - -- Team Lead Details - lead_name text not null, - lead_reg_no text not null, - lead_email text not null, - - user_id uuid references auth.users default auth.uid(), - - -- Constraint: Team size must be between 2 and 5 - constraint valid_team_size check (team_size >= 2 and team_size <= 5) -); - --- This table stores the details of the additional members (excluding the lead) -create table team_members ( - id uuid default gen_random_uuid() primary key, - team_id uuid references teams(id) on delete cascade not null, - - member_name text not null, - member_reg_no text not null -); - -alter table teams enable row level security; -alter table team_members enable row level security; - --- Policies for TEAMS --- 1. Everyone can view teams (useful for leaderboards) -create policy "Teams are viewable by everyone" - on teams for select using (true); - --- 2. Only authenticated users can create a team -create policy "Authenticated users can create teams" - on teams for insert with check (auth.role() = 'authenticated'); - --- 3. Users can only update their own team -create policy "Users can update own team" - on teams for update using (auth.uid() = user_id); - --- Policies for TEAM MEMBERS --- 1. Everyone can view members -create policy "Members are viewable by everyone" - on team_members for select using (true); - --- 2. Authenticated users can add members (usually done at same time as team creation) -create policy "Authenticated users can add members" - on team_members for insert with check (auth.role() = 'authenticated'); diff --git a/supabase/migrations/20251211181257_add_team_size_column.sql b/supabase/migrations/20251211181257_add_team_size_column.sql deleted file mode 100644 index 0a82f98..0000000 --- a/supabase/migrations/20251211181257_add_team_size_column.sql +++ /dev/null @@ -1,4 +0,0 @@ -ALTER TABLE teams ADD COLUMN IF NOT EXISTS team_size integer NOT NULL DEFAULT 2; - -ALTER TABLE teams DROP CONSTRAINT IF EXISTS valid_team_size; -ALTER TABLE teams ADD CONSTRAINT valid_team_size CHECK (team_size >= 2 AND team_size <= 5); diff --git a/supabase/migrations/20251212040905_create_scorecards_table.sql b/supabase/migrations/20251212040905_create_scorecards_table.sql deleted file mode 100644 index d9b2bbd..0000000 --- a/supabase/migrations/20251212040905_create_scorecards_table.sql +++ /dev/null @@ -1,41 +0,0 @@ --- Create scorecards table -CREATE TABLE scorecards ( - id uuid DEFAULT gen_random_uuid() PRIMARY KEY, - created_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL, - updated_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL, - - team_id uuid REFERENCES teams(id) ON DELETE CASCADE NOT NULL, - - -- Scoring fields (updated naming to match review stages) - ideavortex integer DEFAULT 0 CHECK (ideavortex >= 0 AND ideavortex <= 100), - review_1 integer DEFAULT 0 CHECK (review_1 >= 0 AND review_1 <= 100), - review_2 integer DEFAULT 0 CHECK (review_2 >= 0 AND review_2 <= 100), - -- Review 3 is weighted (scaled) in the admin UI; allow up to 165 to accommodate weighting - review_3 integer DEFAULT 0 CHECK (review_3 >= 0 AND review_3 <= 165), - pitch_vortex integer DEFAULT 0 CHECK (pitch_vortex >= 0 AND pitch_vortex <= 100), - - total_score integer GENERATED ALWAYS AS (ideavortex + review_1 + review_2 + review_3 + pitch_vortex) STORED, - - judge_comments text, - - UNIQUE(team_id) -); - --- Enable RLS -ALTER TABLE scorecards ENABLE ROW LEVEL SECURITY; - --- Policies -CREATE POLICY "Teams can view their own scorecard" - ON scorecards FOR SELECT - USING (team_id IN ( - SELECT id FROM teams WHERE lead_email = auth.jwt() ->> 'email' - )); - -CREATE POLICY "Everyone can view all scorecards (for leaderboard)" - ON scorecards FOR SELECT - USING (true); - --- Admin can insert/update scorecards (you'll need to add admin role later) -CREATE POLICY "Service role can manage scorecards" - ON scorecards FOR ALL - USING (auth.role() = 'service_role'); diff --git a/supabase/migrations/20251212215000_add_institution_field.sql b/supabase/migrations/20251212215000_add_institution_field.sql deleted file mode 100644 index c016ce5..0000000 --- a/supabase/migrations/20251212215000_add_institution_field.sql +++ /dev/null @@ -1,24 +0,0 @@ --- Add institution field to teams table and make registration numbers optional -ALTER TABLE teams -ADD COLUMN institution TEXT, -ALTER COLUMN lead_reg_no DROP NOT NULL; - --- Add institution field to team_members table and make registration numbers optional -ALTER TABLE team_members -ALTER COLUMN member_reg_no DROP NOT NULL, -ADD COLUMN institution TEXT; - --- Add check constraint to ensure either VIT (has reg_no) or external college (has institution) -ALTER TABLE teams -ADD CONSTRAINT check_vit_or_institution -CHECK ( - (lead_reg_no IS NOT NULL AND institution IS NULL) OR - (lead_reg_no IS NULL AND institution IS NOT NULL) -); - -ALTER TABLE team_members -ADD CONSTRAINT check_member_vit_or_institution -CHECK ( - (member_reg_no IS NOT NULL AND institution IS NULL) OR - (member_reg_no IS NULL AND institution IS NOT NULL) -); diff --git a/supabase/migrations/20251214000000_add_receipt_link.sql b/supabase/migrations/20251214000000_add_receipt_link.sql deleted file mode 100644 index 7d0b391..0000000 --- a/supabase/migrations/20251214000000_add_receipt_link.sql +++ /dev/null @@ -1,6 +0,0 @@ --- Add receipt_link column to teams table for payment verification -ALTER TABLE teams -ADD COLUMN receipt_link TEXT; - --- Add comment to explain the column -COMMENT ON COLUMN teams.receipt_link IS 'Google Drive link to payment receipt for manual verification'; diff --git a/supabase/migrations/20251216000000_add_member_login_and_qr_codes.sql b/supabase/migrations/20251216000000_add_member_login_and_qr_codes.sql deleted file mode 100644 index 0469027..0000000 --- a/supabase/migrations/20251216000000_add_member_login_and_qr_codes.sql +++ /dev/null @@ -1,68 +0,0 @@ --- Add member_email column to team_members table -ALTER TABLE team_members -ADD COLUMN member_email TEXT; - --- Add QR code identifier for each member (unique per member) -ALTER TABLE team_members -ADD COLUMN qr_code_data TEXT UNIQUE; - --- Create session table for tracking attendance across 3 scanning rounds -CREATE TABLE member_attendance_sessions ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL, - session_number INTEGER NOT NULL, -- 1, 2, or 3 - session_name TEXT NOT NULL, -- "Round 1", "Round 2", "Round 3" - is_active BOOLEAN DEFAULT true NOT NULL, - CONSTRAINT valid_session_number CHECK (session_number BETWEEN 1 AND 3) -); - --- Create table to track which members were scanned in which session -CREATE TABLE member_attendance ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - member_id UUID NOT NULL REFERENCES team_members(id) ON DELETE CASCADE, - session_id UUID NOT NULL REFERENCES member_attendance_sessions(id) ON DELETE CASCADE, - scanned_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL, - UNIQUE(member_id, session_id) -- Each member can only be scanned once per session -); - --- Create a table to store member login sessions (separate from team lead logins) -CREATE TABLE member_login_sessions ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - member_id UUID NOT NULL REFERENCES team_members(id) ON DELETE CASCADE, - email TEXT NOT NULL, - last_login TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()), - created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL -); - --- Enable RLS for new tables -ALTER TABLE member_attendance_sessions ENABLE ROW LEVEL SECURITY; -ALTER TABLE member_attendance ENABLE ROW LEVEL SECURITY; -ALTER TABLE member_login_sessions ENABLE ROW LEVEL SECURITY; - --- Policies for member_attendance_sessions (admin can manage, everyone can view active sessions) -CREATE POLICY "Attendance sessions viewable by everyone" - ON member_attendance_sessions FOR SELECT USING (true); - -CREATE POLICY "Admin can manage attendance sessions" - ON member_attendance_sessions FOR ALL USING (true); -- TODO: Restrict to admin role in future - --- Policies for member_attendance (track who was scanned) -CREATE POLICY "Attendance records viewable by everyone" - ON member_attendance FOR SELECT USING (true); - -CREATE POLICY "Admin can record attendance" - ON member_attendance FOR INSERT WITH CHECK (true); -- TODO: Restrict to admin role - --- Policies for member_login_sessions -CREATE POLICY "Members can view their own login session" - ON member_login_sessions FOR SELECT USING (true); -- TODO: Restrict to own record - -CREATE POLICY "System can create member login sessions" - ON member_login_sessions FOR INSERT WITH CHECK (true); - --- Add comment -COMMENT ON TABLE member_attendance_sessions IS 'Tracks the 3 different scanning rounds for member attendance'; -COMMENT ON TABLE member_attendance IS 'Records which members were scanned in which session'; -COMMENT ON TABLE member_login_sessions IS 'Tracks member login sessions separately from team lead logins'; -COMMENT ON COLUMN team_members.member_email IS 'Gmail address for member login and OTP'; -COMMENT ON COLUMN team_members.qr_code_data IS 'Unique QR code data for scanning'; diff --git a/supabase/migrations/20251216120000_add_member_email_column.sql b/supabase/migrations/20251216120000_add_member_email_column.sql deleted file mode 100644 index 63e3ee6..0000000 --- a/supabase/migrations/20251216120000_add_member_email_column.sql +++ /dev/null @@ -1,23 +0,0 @@ --- Add member_email column to team_members table if it doesn't exist -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'team_members' AND column_name = 'member_email' - ) THEN - ALTER TABLE team_members ADD COLUMN member_email TEXT; - COMMENT ON COLUMN team_members.member_email IS 'Gmail address for member login and OTP'; - END IF; -END $$; - --- Add qr_code_data column if it doesn't exist -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'team_members' AND column_name = 'qr_code_data' - ) THEN - ALTER TABLE team_members ADD COLUMN qr_code_data TEXT UNIQUE; - COMMENT ON COLUMN team_members.qr_code_data IS 'Unique QR code data for scanning'; - END IF; -END $$; diff --git a/supabase/migrations/20251217000000_create_leaderboard_view.sql b/supabase/migrations/20251217000000_create_leaderboard_view.sql deleted file mode 100644 index 3a35f5e..0000000 --- a/supabase/migrations/20251217000000_create_leaderboard_view.sql +++ /dev/null @@ -1,25 +0,0 @@ --- Drop existing view if it exists -DROP VIEW IF EXISTS leaderboard_view; - --- Create a view for the leaderboard that combines teams and scorecards -CREATE VIEW leaderboard_view AS -SELECT - ROW_NUMBER() OVER (ORDER BY COALESCE(s.total_score, 0) DESC, t.team_name ASC) as position, - t.id as team_id, - t.team_name, - COALESCE(s.total_score, 0) as score, - 0 as delta, - s.ideavortex, - s.review_1, - s.review_2, - s.review_3, - s.pitch_vortex -FROM teams t -LEFT JOIN scorecards s ON t.id = s.team_id -ORDER BY COALESCE(s.total_score, 0) DESC, t.team_name ASC; - --- Grant access to the view -GRANT SELECT ON leaderboard_view TO authenticated; -GRANT SELECT ON leaderboard_view TO anon; - -COMMENT ON VIEW leaderboard_view IS 'Leaderboard ranking teams by total score with position numbers'; diff --git a/supabase/migrations/20251217000001_add_scorecard_history.sql b/supabase/migrations/20251217000001_add_scorecard_history.sql deleted file mode 100644 index 17f2568..0000000 --- a/supabase/migrations/20251217000001_add_scorecard_history.sql +++ /dev/null @@ -1,134 +0,0 @@ --- Create scorecard history table to track score changes over time -CREATE TABLE scorecard_history ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE, - recorded_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL, - - ideavortex INTEGER NOT NULL CHECK (ideavortex >= 0 AND ideavortex <= 100), - review_1 INTEGER NOT NULL CHECK (review_1 >= 0 AND review_1 <= 100), - review_2 INTEGER NOT NULL CHECK (review_2 >= 0 AND review_2 <= 100), - -- Review 3 is weighted in admin UI; allow up to 165 in history snapshots as well - review_3 INTEGER NOT NULL CHECK (review_3 >= 0 AND review_3 <= 165), - pitch_vortex INTEGER NOT NULL CHECK (pitch_vortex >= 0 AND pitch_vortex <= 100), - total_score INTEGER NOT NULL, - - snapshot_type TEXT DEFAULT 'update' CHECK (snapshot_type IN ('initial', 'update', 'final')) -); - --- Index for faster queries -CREATE INDEX idx_scorecard_history_team_time ON scorecard_history(team_id, recorded_at DESC); - --- Enable RLS -ALTER TABLE scorecard_history ENABLE ROW LEVEL SECURITY; - --- Policies -CREATE POLICY "Everyone can view scorecard history" - ON scorecard_history FOR SELECT - USING (true); - -CREATE POLICY "Service role can manage scorecard history" - ON scorecard_history FOR ALL - USING (auth.role() = 'service_role'); - --- Function to calculate delta (difference from previous snapshot) -CREATE OR REPLACE FUNCTION get_score_delta(p_team_id UUID) -RETURNS INTEGER AS $$ -DECLARE - current_score INTEGER; - previous_score INTEGER; -BEGIN - -- Get current score - SELECT total_score INTO current_score - FROM scorecards - WHERE team_id = p_team_id; - - -- Get previous score (second most recent) - SELECT total_score INTO previous_score - FROM scorecard_history - WHERE team_id = p_team_id - ORDER BY recorded_at DESC - OFFSET 1 - LIMIT 1; - - -- Return delta (0 if no previous score) - RETURN COALESCE(current_score - previous_score, 0); -END; -$$ LANGUAGE plpgsql; - --- Drop and recreate leaderboard view with delta support -DROP VIEW IF EXISTS leaderboard_view; - -CREATE VIEW leaderboard_view AS -SELECT - ROW_NUMBER() OVER (ORDER BY COALESCE(s.total_score, 0) DESC, t.team_name ASC) as position, - t.team_name, - COALESCE(s.total_score, 0) as score, - COALESCE(get_score_delta(t.id), 0) as delta, - t.id as team_id, - s.ideavortex, - s.review_1, - s.review_2, - s.review_3, - s.pitch_vortex, - COALESCE(s.total_score, 0) as total_score -FROM teams t -LEFT JOIN scorecards s ON t.id = s.team_id -ORDER BY COALESCE(s.total_score, 0) DESC, t.team_name ASC; - --- Trigger function to record score changes -CREATE OR REPLACE FUNCTION record_scorecard_change() -RETURNS TRIGGER AS $$ -BEGIN - -- Record the new score in history - INSERT INTO scorecard_history ( - team_id, - ideavortex, - review_1, - review_2, - review_3, - pitch_vortex, - total_score, - snapshot_type - ) VALUES ( - NEW.team_id, - NEW.ideavortex, - NEW.review_1, - NEW.review_2, - NEW.review_3, - NEW.pitch_vortex, - NEW.total_score, - CASE - WHEN TG_OP = 'INSERT' THEN 'initial' - ELSE 'update' - END - ); - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Create trigger on scorecards table -DROP TRIGGER IF EXISTS scorecard_history_trigger ON scorecards; -CREATE TRIGGER scorecard_history_trigger - AFTER INSERT OR UPDATE OF ideavortex, review_1, review_2, review_3, pitch_vortex - ON scorecards - FOR EACH ROW - EXECUTE FUNCTION record_scorecard_change(); - --- Update the scorecards updated_at timestamp -CREATE OR REPLACE FUNCTION update_scorecard_timestamp() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = TIMEZONE('utc'::text, NOW()); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS update_scorecard_timestamp_trigger ON scorecards; -CREATE TRIGGER update_scorecard_timestamp_trigger - BEFORE UPDATE ON scorecards - FOR EACH ROW - EXECUTE FUNCTION update_scorecard_timestamp(); - -COMMENT ON TABLE scorecard_history IS 'Historical snapshots of team scores for tracking changes and calculating deltas'; -COMMENT ON FUNCTION get_score_delta IS 'Calculates the point difference between current and previous score snapshot'; diff --git a/supabase/migrations/20251218000000_unique_emails.sql b/supabase/migrations/20251218000000_unique_emails.sql deleted file mode 100644 index b9455b6..0000000 --- a/supabase/migrations/20251218000000_unique_emails.sql +++ /dev/null @@ -1,28 +0,0 @@ --- Ensure all emails (both leaders and members) are unique across the entire system - --- Add unique constraint to member_email in team_members table -CREATE UNIQUE INDEX IF NOT EXISTS unique_member_email ON team_members(member_email) -WHERE member_email IS NOT NULL; - --- Add unique constraint to lead_email in teams table (if not already exists) -CREATE UNIQUE INDEX IF NOT EXISTS unique_lead_email ON teams(lead_email); - --- Create a function to check if email exists anywhere -CREATE OR REPLACE FUNCTION check_email_uniqueness(email_to_check TEXT) -RETURNS BOOLEAN AS $$ -BEGIN - -- Check if email exists in teams table - IF EXISTS (SELECT 1 FROM teams WHERE lead_email = email_to_check) THEN - RETURN FALSE; - END IF; - - -- Check if email exists in team_members table - IF EXISTS (SELECT 1 FROM team_members WHERE member_email = email_to_check) THEN - RETURN FALSE; - END IF; - - RETURN TRUE; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION check_email_uniqueness IS 'Returns TRUE if email is available, FALSE if already taken'; diff --git a/supabase/migrations/20251220000000_add_is_vit_chennai.sql b/supabase/migrations/20251220000000_add_is_vit_chennai.sql deleted file mode 100644 index f05d011..0000000 --- a/supabase/migrations/20251220000000_add_is_vit_chennai.sql +++ /dev/null @@ -1,11 +0,0 @@ --- Add is_vit_chennai boolean column to teams table -ALTER TABLE teams -ADD COLUMN IF NOT EXISTS is_vit_chennai BOOLEAN DEFAULT true; - --- Add comment -COMMENT ON COLUMN teams.is_vit_chennai IS 'Indicates if team leader is from VIT Chennai or external college'; - --- Update existing records based on whether they have reg_no or institution -UPDATE teams -SET is_vit_chennai = (lead_reg_no IS NOT NULL) -WHERE is_vit_chennai IS NULL; diff --git a/supabase/migrations/20251220000001_update_institution_constraint.sql b/supabase/migrations/20251220000001_update_institution_constraint.sql deleted file mode 100644 index 0aae80b..0000000 --- a/supabase/migrations/20251220000001_update_institution_constraint.sql +++ /dev/null @@ -1,21 +0,0 @@ --- Drop old constraints that prevented VIT Chennai students from having institution field -ALTER TABLE teams DROP CONSTRAINT IF EXISTS check_vit_or_institution; -ALTER TABLE team_members DROP CONSTRAINT IF EXISTS check_member_vit_or_institution; - --- Add new constraints: must have EITHER (reg_no AND institution='VIT Chennai') OR (institution!=NULL AND reg_no=NULL) -ALTER TABLE teams -ADD CONSTRAINT check_vit_or_institution -CHECK ( - (lead_reg_no IS NOT NULL AND institution = 'VIT Chennai') OR - (lead_reg_no IS NULL AND institution IS NOT NULL) -); - -ALTER TABLE team_members -ADD CONSTRAINT check_member_vit_or_institution -CHECK ( - (member_reg_no IS NOT NULL AND institution = 'VIT Chennai') OR - (member_reg_no IS NULL AND institution IS NOT NULL) -); - -COMMENT ON CONSTRAINT check_vit_or_institution ON teams IS 'VIT Chennai students must have reg_no and institution=VIT Chennai. Other college students must have institution (EventHub ID) and no reg_no.'; -COMMENT ON CONSTRAINT check_member_vit_or_institution ON team_members IS 'VIT Chennai members must have reg_no and institution=VIT Chennai. Other college members must have institution (EventHub ID) and no reg_no.'; diff --git a/supabase/migrations/20251221000000_enhanced_security_policies.sql b/supabase/migrations/20251221000000_enhanced_security_policies.sql deleted file mode 100644 index a8a4284..0000000 --- a/supabase/migrations/20251221000000_enhanced_security_policies.sql +++ /dev/null @@ -1,143 +0,0 @@ --- Enhanced Security Policies --- This migration improves RLS policies to prevent unauthorized data access - --- ===================================================== --- TEAMS TABLE - Enhanced Policies --- ===================================================== - --- Drop existing overly permissive policies -DROP POLICY IF EXISTS "Teams are viewable by everyone" ON teams; -DROP POLICY IF EXISTS "Users can update own team" ON teams; - --- Allow authenticated users to view only minimal team info for leaderboard -CREATE POLICY "Public can view team names and scores only" - ON teams FOR SELECT - USING (true); - --- Team leaders can view their full team details -CREATE POLICY "Leaders can view their own team details" - ON teams FOR SELECT - USING (auth.uid() = user_id OR lead_email = auth.jwt() ->> 'email'); - --- Only team leaders can update their own team -CREATE POLICY "Leaders can update their own team" - ON teams FOR UPDATE - USING (auth.uid() = user_id OR lead_email = auth.jwt() ->> 'email'); - --- Prevent team deletion from client (only service role) -CREATE POLICY "Only service can delete teams" - ON teams FOR DELETE - USING (auth.role() = 'service_role'); - --- ===================================================== --- TEAM MEMBERS TABLE - Enhanced Policies --- ===================================================== - -DROP POLICY IF EXISTS "Members are viewable by everyone" ON team_members; -DROP POLICY IF EXISTS "Authenticated users can add members" ON team_members; - --- Only team members and their leader can view member details -CREATE POLICY "Members can view their own team" - ON team_members FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM teams - WHERE teams.id = team_members.team_id - AND (teams.lead_email = auth.jwt() ->> 'email' - OR teams.user_id = auth.uid()) - ) - OR member_email = auth.jwt() ->> 'email' - ); - --- Restrict member insertion through Edge Functions only -CREATE POLICY "Only service can add members" - ON team_members FOR INSERT - WITH CHECK (auth.role() = 'service_role'); - --- Prevent member updates from client -CREATE POLICY "Only service can update members" - ON team_members FOR UPDATE - USING (auth.role() = 'service_role'); - --- Prevent member deletion from client -CREATE POLICY "Only service can delete members" - ON team_members FOR DELETE - USING (auth.role() = 'service_role'); - --- ===================================================== --- SCORECARDS TABLE - Enhanced Policies --- ===================================================== - --- Update existing policies to be more restrictive -DROP POLICY IF EXISTS "Teams can view their own scorecard" ON scorecards; -DROP POLICY IF EXISTS "Everyone can view all scorecards (for leaderboard)" ON scorecards; - --- Teams can only view their own scorecard details -CREATE POLICY "Teams can view their own scorecard" - ON scorecards FOR SELECT - USING ( - team_id IN ( - SELECT id FROM teams - WHERE lead_email = auth.jwt() ->> 'email' - OR user_id = auth.uid() - ) - OR team_id IN ( - SELECT team_id FROM team_members - WHERE member_email = auth.jwt() ->> 'email' - ) - ); - --- Public can only view scores for leaderboard (minimal info) -CREATE POLICY "Public can view scores for leaderboard" - ON scorecards FOR SELECT - USING (true); - --- Block all write operations from client (only service role can update scores) -CREATE POLICY "Only service can insert scorecards" - ON scorecards FOR INSERT - WITH CHECK (auth.role() = 'service_role'); - -CREATE POLICY "Only service can update scorecards" - ON scorecards FOR UPDATE - USING (auth.role() = 'service_role'); - -CREATE POLICY "Only service can delete scorecards" - ON scorecards FOR DELETE - USING (auth.role() = 'service_role'); - --- ===================================================== --- Add helper function to check team ownership --- ===================================================== - -CREATE OR REPLACE FUNCTION is_team_owner(team_uuid uuid) -RETURNS boolean -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -BEGIN - RETURN EXISTS ( - SELECT 1 FROM teams - WHERE id = team_uuid - AND ( - lead_email = auth.jwt() ->> 'email' - OR user_id = auth.uid() - ) - ); -END; -$$; - --- ===================================================== --- Add security notes --- ===================================================== - -COMMENT ON POLICY "Public can view team names and scores only" ON teams IS - 'Allows public leaderboard viewing while protecting sensitive team data'; - -COMMENT ON POLICY "Leaders can view their own team details" ON teams IS - 'Team leaders have full access to their team information'; - -COMMENT ON POLICY "Only service can add members" ON team_members IS - 'All member operations must go through validated Edge Functions'; - -COMMENT ON FUNCTION is_team_owner(uuid) IS - 'Helper function to check if current user owns a specific team'; diff --git a/supabase/migrations/20251221000001_fix_function_search_path.sql b/supabase/migrations/20251221000001_fix_function_search_path.sql deleted file mode 100644 index e76c5a6..0000000 --- a/supabase/migrations/20251221000001_fix_function_search_path.sql +++ /dev/null @@ -1,155 +0,0 @@ --- Fix Security Advisor Warnings --- This migration addresses the "Function Search Path Mutable" warnings --- by setting search_path explicitly in all functions - --- ===================================================== --- Fix get_score_delta function --- ===================================================== -CREATE OR REPLACE FUNCTION get_score_delta(p_team_id UUID) -RETURNS INTEGER -LANGUAGE plpgsql -SECURITY DEFINER --- Set a safe search_path to prevent search_path hijacking -SET search_path = public, pg_temp -AS $$ -DECLARE - current_score INTEGER; - previous_score INTEGER; -BEGIN - -- Get current score - SELECT total_score INTO current_score - FROM scorecards - WHERE team_id = p_team_id; - - -- Get previous score (second most recent) - SELECT total_score INTO previous_score - FROM scorecard_history - WHERE team_id = p_team_id - ORDER BY recorded_at DESC - OFFSET 1 - LIMIT 1; - - -- Return delta (0 if no previous score) - RETURN COALESCE(current_score - previous_score, 0); -END; -$$; - --- ===================================================== --- Fix record_scorecard_change function --- ===================================================== -CREATE OR REPLACE FUNCTION record_scorecard_change() -RETURNS TRIGGER -LANGUAGE plpgsql -SECURITY DEFINER --- Set a safe search_path to prevent search_path hijacking -SET search_path = public, pg_temp -AS $$ -BEGIN - -- Record the new score in history - INSERT INTO scorecard_history ( - team_id, - ideavortex, - review_1, - review_2, - review_3, - pitch_vortex, - total_score, - snapshot_type - ) VALUES ( - NEW.team_id, - NEW.ideavortex, - NEW.review_1, - NEW.review_2, - NEW.review_3, - NEW.pitch_vortex, - NEW.total_score, - CASE - WHEN TG_OP = 'INSERT' THEN 'initial' - ELSE 'update' - END - ); - - RETURN NEW; -END; -$$; - --- ===================================================== --- Fix update_scorecard_timestamp function --- ===================================================== -CREATE OR REPLACE FUNCTION update_scorecard_timestamp() -RETURNS TRIGGER -LANGUAGE plpgsql -SECURITY DEFINER --- Set a safe search_path to prevent search_path hijacking -SET search_path = public, pg_temp -AS $$ -BEGIN - NEW.updated_at = TIMEZONE('utc'::text, NOW()); - RETURN NEW; -END; -$$; - --- ===================================================== --- Fix is_team_owner helper function (from previous migration) --- ===================================================== -CREATE OR REPLACE FUNCTION is_team_owner(team_uuid uuid) -RETURNS boolean -LANGUAGE plpgsql -SECURITY DEFINER --- Set a safe search_path to prevent search_path hijacking -SET search_path = public, pg_temp -AS $$ -BEGIN - RETURN EXISTS ( - SELECT 1 FROM teams - WHERE id = team_uuid - AND ( - lead_email = auth.jwt() ->> 'email' - OR user_id = auth.uid() - ) - ); -END; -$$; - --- ===================================================== --- Fix check_email_uniqueness function (from unique_emails migration) --- ===================================================== -CREATE OR REPLACE FUNCTION check_email_uniqueness(email_to_check TEXT) -RETURNS BOOLEAN -LANGUAGE plpgsql -SECURITY DEFINER --- Set a safe search_path to prevent search_path hijacking -SET search_path = public, pg_temp -AS $$ -BEGIN - -- Check if email exists in teams table - IF EXISTS (SELECT 1 FROM teams WHERE lead_email = email_to_check) THEN - RETURN FALSE; - END IF; - - -- Check if email exists in team_members table - IF EXISTS (SELECT 1 FROM team_members WHERE member_email = email_to_check) THEN - RETURN FALSE; - END IF; - - RETURN TRUE; -END; -$$; - --- ===================================================== --- Add comments explaining the security fix --- ===================================================== -COMMENT ON FUNCTION get_score_delta(UUID) IS - 'Calculates score delta with fixed search_path to prevent search_path hijacking attacks'; - -COMMENT ON FUNCTION record_scorecard_change() IS - 'Records scorecard changes with fixed search_path to prevent search_path hijacking attacks'; - -COMMENT ON FUNCTION update_scorecard_timestamp() IS - 'Updates scorecard timestamp with fixed search_path to prevent search_path hijacking attacks'; - -COMMENT ON FUNCTION is_team_owner(UUID) IS - 'Checks team ownership with fixed search_path to prevent search_path hijacking attacks'; - -COMMENT ON FUNCTION check_email_uniqueness(TEXT) IS - 'Checks email uniqueness with fixed search_path to prevent search_path hijacking attacks'; diff --git a/supabase/migrations/20251221000002_enable_password_protection.sql b/supabase/migrations/20251221000002_enable_password_protection.sql deleted file mode 100644 index 5ebe4a8..0000000 --- a/supabase/migrations/20251221000002_enable_password_protection.sql +++ /dev/null @@ -1,24 +0,0 @@ --- Enable Leaked Password Protection --- This addresses the "Leaked Password Protection Disabled" warning --- Supabase will check passwords against known leaked password databases - --- Note: This setting is typically configured in the Supabase Dashboard --- under Authentication > Policies, but can also be set via SQL - --- Enable leaked password protection for auth --- This prevents users from using passwords that have been compromised in data breaches -ALTER DATABASE postgres SET app.settings.auth_password_required_characters TO 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - --- Set minimum password length -ALTER DATABASE postgres SET app.settings.auth_password_min_length TO '8'; - --- Additional security settings -COMMENT ON DATABASE postgres IS 'Leaked password protection enabled. Users cannot use compromised passwords.'; - --- Note: For full leaked password protection, you need to: --- 1. Go to Supabase Dashboard > Authentication > Policies --- 2. Enable "Leaked Password Protection" --- 3. This will integrate with HaveIBeenPwned API to check passwords - --- For OTP-based authentication (which you're using), this is less critical --- since users don't set passwords, but it's good practice to enable it diff --git a/supabase/migrations/20251226080000_add_member_is_vit_chennai.sql b/supabase/migrations/20251226080000_add_member_is_vit_chennai.sql deleted file mode 100644 index cbd43a6..0000000 --- a/supabase/migrations/20251226080000_add_member_is_vit_chennai.sql +++ /dev/null @@ -1,19 +0,0 @@ --- Migration: add is_vit_chennai to team_members and populate from member_reg_no --- Date: 2025-12-26 - -BEGIN; - --- Add column if missing -ALTER TABLE team_members -ADD COLUMN IF NOT EXISTS is_vit_chennai BOOLEAN; - --- Populate existing rows: if member_reg_no is present, mark as VIT Chennai -UPDATE team_members -SET is_vit_chennai = (member_reg_no IS NOT NULL) -WHERE is_vit_chennai IS NULL; - --- Add a comment for clarity -COMMENT ON COLUMN team_members.is_vit_chennai IS - 'Indicates if member is from VIT Chennai (true) or external college (false)'; - -COMMIT; diff --git a/supabase/migrations/20251229090000_add_team_problem_fields.sql b/supabase/migrations/20251229090000_add_team_problem_fields.sql deleted file mode 100644 index 6f128e6..0000000 --- a/supabase/migrations/20251229090000_add_team_problem_fields.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Add columns to store selected domain and problem statement for teams -ALTER TABLE public.teams - ADD COLUMN IF NOT EXISTS domain TEXT, - ADD COLUMN IF NOT EXISTS problem_statement TEXT, - ADD COLUMN IF NOT EXISTS problem_description TEXT; - --- Optional: index for faster queries by domain -CREATE INDEX IF NOT EXISTS idx_teams_domain ON public.teams (domain); diff --git a/supabase/migrations/20251229120000_create_problem_statements_and_select_problem_function.sql b/supabase/migrations/20251229120000_create_problem_statements_and_select_problem_function.sql deleted file mode 100644 index 9d786a6..0000000 --- a/supabase/migrations/20251229120000_create_problem_statements_and_select_problem_function.sql +++ /dev/null @@ -1,90 +0,0 @@ --- Create table for problem statements and an atomic selection function - -BEGIN; - -CREATE TABLE IF NOT EXISTS public.problem_statements ( - id TEXT PRIMARY KEY, - domain TEXT NOT NULL, - code TEXT, - title TEXT, - description TEXT, - total_seats INT NOT NULL DEFAULT 10, - seats_booked INT NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ DEFAULT now() -); - --- Optional initial seed data to match frontend IDs (adjust counts as needed) -INSERT INTO public.problem_statements (id, domain, code, title, description, total_seats, seats_booked) -VALUES - ('ps1', 'ai', 'PS 1', 'AI-Generated Image Authenticity Detection', 'Design a system that determines whether an image is AI-generated or real...', 10, 0), - ('ps2', 'ai', 'PS 2', 'AI-Powered Mind Map Search Engine', 'Design a search system that retrieves information for a user query...', 10, 0), - ('ps3', 'ai', 'PS 3', 'AI-Powered Mental Well-Being Risk Indicator (Non-Clinical)', 'Design a non-clinical system that analyzes anonymized behavioral patterns...', 10, 0), - ('fintech1', 'fintech', 'PS 1', 'Unified Payment Orchestration & Automated Settlements', 'Design and prototype a unified payment orchestration platform...', 10, 0), - ('fintech2', 'fintech', 'PS 2', 'Privacy-Preserving Collaborative Fraud Intelligence Platform', 'Design and prototype a real-time transaction monitoring...', 10, 0), - ('fintech3', 'fintech', 'PS 3', 'Adaptive Pricing in Real-Time Digital Marketplaces', 'Design a real-time adaptive pricing system...', 10, 0), - ('cybersecurity1', 'cybersecurity', 'PS 1', 'Secure Identity & Authentication', 'Design a privacy-preserving identity framework...', 10, 0), - ('cybersecurity2', 'cybersecurity', 'PS 2', 'Automated Vulnerability Detection', 'Create a system that detects software and infrastructure vulnerabilities...', 10, 0), - ('cybersecurity3', 'cybersecurity', 'PS 3', 'Resilient Network Defense Architecture', 'Design defensive architectures and rapid incident response tooling...', 10, 0), - ('healthcare1', 'healthcare', 'PS 1', 'AI-Powered Early Disease Detection System', 'Create an intelligent diagnostic system...', 10, 0), - ('healthcare2', 'healthcare', 'PS 2', 'Telemedicine Platform with Remote Monitoring', 'Design a comprehensive telemedicine platform...', 10, 0), - ('healthcare3', 'healthcare', 'PS 3', 'Medical Supply Chain Transparency System', 'Develop a blockchain-enabled supply chain tracking system...', 10, 0), - ('iot1', 'iot', 'PS 1', 'Sky-Glow Sentinel (Urban Light Pollution Mapping)', 'Design a high-sensitivity Sky Quality Monitoring system...', 10, 0), - ('iot2', 'iot', 'PS 2', 'Decentralized Communication in Infrastructure-Denied Environments', 'Develop a decentralized, peer-to-peer hardware communication network...', 10, 0), - ('iot3', 'iot', 'PS 3', 'Smart Parking Occupancy Detection System', 'Design a low-cost IoT-based system that detects parking spot occupancy...', 10, 0), - ('cybersecurity4', 'cybersecurity', 'PS 4', 'Autonomous Threat Hunting & Response', 'Design an autonomous security system that proactively hunts for threats, analyzes attack patterns, and implements automated response mechanisms to neutralize cyber threats in real-time.', 10, 0) -ON CONFLICT (id) DO NOTHING; - --- Atomic selection function: verifies team lead, checks/locks seats, updates both tables in one transaction -CREATE OR REPLACE FUNCTION public.select_problem_atomic( - p_team_id uuid, - p_ps_id text, - p_domain text, - p_problem_name text, - p_problem_description text, - p_requester_email text -) RETURNS jsonb -LANGUAGE plpgsql -AS $$ -DECLARE - lead_email text; - existing_problem text; - seats_left int; -BEGIN - SELECT t.lead_email, t.problem_statement - INTO lead_email, existing_problem - FROM public.teams t - WHERE t.id = p_team_id; - IF lead_email IS NULL THEN - RETURN jsonb_build_object('error','team_not_found'); - END IF; - IF lower(lead_email) != lower(coalesce(p_requester_email,'')) THEN - RETURN jsonb_build_object('error','not_lead'); - END IF; - IF existing_problem IS NOT NULL THEN - RETURN jsonb_build_object('error','already_assigned'); - END IF; - - -- Lock the problem row to prevent concurrent seat changes - PERFORM 1 FROM public.problem_statements WHERE id = p_ps_id FOR UPDATE; - - SELECT total_seats - seats_booked INTO seats_left FROM public.problem_statements WHERE id = p_ps_id; - IF seats_left IS NULL THEN - RETURN jsonb_build_object('error','ps_not_found'); - END IF; - IF seats_left <= 0 THEN - RETURN jsonb_build_object('error','no_seats'); - END IF; - - UPDATE public.problem_statements SET seats_booked = seats_booked + 1 WHERE id = p_ps_id; - - UPDATE public.teams - SET domain = p_domain, - problem_statement = p_problem_name, - problem_description = p_problem_description - WHERE id = p_team_id; - - RETURN jsonb_build_object('success', true); -END; -$$; - -COMMIT; diff --git a/supabase/migrations/20251229171500_fix_select_problem_atomic.sql b/supabase/migrations/20251229171500_fix_select_problem_atomic.sql deleted file mode 100644 index 2b429b1..0000000 --- a/supabase/migrations/20251229171500_fix_select_problem_atomic.sql +++ /dev/null @@ -1,65 +0,0 @@ --- Replace select_problem_atomic with unambiguous, row-locked implementation - -CREATE OR REPLACE FUNCTION public.select_problem_atomic( - p_team_id uuid, - p_ps_id text, - p_domain text, - p_problem_name text, - p_problem_description text, - p_requester_email text -) RETURNS jsonb -LANGUAGE plpgsql -AS $$ -DECLARE - v_lead_email text; - v_existing_problem text; - v_seats_left int; -BEGIN - -- Read team info with explicit aliasing - SELECT t.lead_email, t.problem_statement - INTO v_lead_email, v_existing_problem - FROM public.teams t - WHERE t.id = p_team_id; - - IF v_lead_email IS NULL THEN - RETURN jsonb_build_object('error','team_not_found'); - END IF; - - IF lower(v_lead_email) != lower(coalesce(p_requester_email,'')) THEN - RETURN jsonb_build_object('error','not_lead'); - END IF; - - IF v_existing_problem IS NOT NULL THEN - RETURN jsonb_build_object('error','already_assigned'); - END IF; - - -- Lock the chosen problem statement row and compute seats left in one statement - SELECT (ps.total_seats - ps.seats_booked) - INTO v_seats_left - FROM public.problem_statements ps - WHERE ps.id = p_ps_id - FOR UPDATE; - - IF v_seats_left IS NULL THEN - RETURN jsonb_build_object('error','ps_not_found'); - END IF; - - IF v_seats_left <= 0 THEN - RETURN jsonb_build_object('error','no_seats'); - END IF; - - -- Increment booked seats - UPDATE public.problem_statements - SET seats_booked = seats_booked + 1 - WHERE id = p_ps_id; - - -- Update team assignment - UPDATE public.teams t - SET domain = p_domain, - problem_statement = p_problem_name, - problem_description = p_problem_description - WHERE t.id = p_team_id; - - RETURN jsonb_build_object('success', true); -END; -$$; diff --git a/supabase/migrations/20251230120000_create_scorecard_on_team_insert.sql b/supabase/migrations/20251230120000_create_scorecard_on_team_insert.sql deleted file mode 100644 index b3e214d..0000000 --- a/supabase/migrations/20251230120000_create_scorecard_on_team_insert.sql +++ /dev/null @@ -1,28 +0,0 @@ --- Auto-create a default scorecard when a new team is inserted --- Run this migration as a DB admin / service role - -CREATE OR REPLACE FUNCTION public.create_scorecard_for_team() -RETURNS trigger -LANGUAGE plpgsql -SECURITY DEFINER -SET search_path = public, pg_temp -AS $$ -BEGIN - INSERT INTO scorecards (team_id) - VALUES (NEW.id) - ON CONFLICT (team_id) DO NOTHING; - - RETURN NEW; -END; -$$; - -DROP TRIGGER IF EXISTS trg_create_scorecard_after_team_insert ON teams; -CREATE TRIGGER trg_create_scorecard_after_team_insert -AFTER INSERT ON teams -FOR EACH ROW -EXECUTE FUNCTION public.create_scorecard_for_team(); - --- Notes: --- 1) The function is SECURITY DEFINER so it can insert even when RLS restricts client inserts. --- 2) The INSERT uses ON CONFLICT to avoid duplicate scorecards for the same team. --- 3) If you prefer to run this as a one-off SQL in Supabase SQL Editor, copy the function+trigger body there. diff --git a/supabase/migrations/20260102000000_add_app_settings.sql b/supabase/migrations/20260102000000_add_app_settings.sql deleted file mode 100644 index 79a9388..0000000 --- a/supabase/migrations/20260102000000_add_app_settings.sql +++ /dev/null @@ -1,30 +0,0 @@ --- Create app_settings table for admin-controlled settings -CREATE TABLE IF NOT EXISTS app_settings ( - id TEXT PRIMARY KEY DEFAULT 'main', - leaderboard_public BOOLEAN NOT NULL DEFAULT false, - updated_at TIMESTAMPTZ DEFAULT now() -); - --- Insert default row -INSERT INTO app_settings (id, leaderboard_public) VALUES ('main', false) ON CONFLICT DO NOTHING; - --- Allow authenticated users to read settings -GRANT SELECT ON app_settings TO authenticated; -GRANT SELECT ON app_settings TO anon; - --- Only admins can update (we'll handle this via RLS or service role in edge function) --- For simplicity, allow authenticated to update (admin check done in frontend) -GRANT UPDATE ON app_settings TO authenticated; - --- RLS policies -ALTER TABLE app_settings ENABLE ROW LEVEL SECURITY; - --- Drop existing policies if they exist -DROP POLICY IF EXISTS "Anyone can read settings" ON app_settings; -DROP POLICY IF EXISTS "Authenticated can update settings" ON app_settings; - --- Everyone can read settings -CREATE POLICY "Anyone can read settings" ON app_settings FOR SELECT USING (true); - --- Only allow updates (admin check is done in application layer) -CREATE POLICY "Authenticated can update settings" ON app_settings FOR UPDATE USING (true); diff --git a/supabase/migrations/20260103000000_change_iot4_to_cyber4.sql b/supabase/migrations/20260103000000_change_iot4_to_cyber4.sql deleted file mode 100644 index 216788a..0000000 --- a/supabase/migrations/20260103000000_change_iot4_to_cyber4.sql +++ /dev/null @@ -1,15 +0,0 @@ --- Move iot4 to cybersecurity4 - -BEGIN; - --- Update the iot4 problem statement to become cybersecurity4 -UPDATE public.problem_statements -SET - id = 'cybersecurity4', - domain = 'cybersecurity', - code = 'PS 4', - title = 'Autonomous Threat Hunting & Response', - description = 'Design an autonomous security system that proactively hunts for threats, analyzes attack patterns, and implements automated response mechanisms to neutralize cyber threats in real-time.' -WHERE id = 'iot4'; - -COMMIT; diff --git a/supabase/migrations/20260103001000_move_iot4_to_cyber4.sql b/supabase/migrations/20260103001000_move_iot4_to_cyber4.sql deleted file mode 100644 index ebe4951..0000000 --- a/supabase/migrations/20260103001000_move_iot4_to_cyber4.sql +++ /dev/null @@ -1,28 +0,0 @@ --- Move the 4th IoT problem to Cybersecurity as cybersecurity4 - -BEGIN; - --- Remove the IoT PS 4 entry if it exists -DELETE FROM public.problem_statements -WHERE id = 'iot4'; - --- Upsert the Cybersecurity PS 4 entry -INSERT INTO public.problem_statements (id, domain, code, title, description, total_seats, seats_booked) -VALUES ( - 'cybersecurity4', - 'cybersecurity', - 'PS 4', - 'Autonomous Threat Hunting & Response', - 'Design an autonomous security system that proactively hunts for threats, analyzes attack patterns, and implements automated response mechanisms to neutralize cyber threats in real-time.', - 10, - 0 -) -ON CONFLICT (id) DO UPDATE SET - domain = EXCLUDED.domain, - code = EXCLUDED.code, - title = EXCLUDED.title, - description = EXCLUDED.description, - total_seats = EXCLUDED.total_seats, - seats_booked = LEAST(public.problem_statements.seats_booked, EXCLUDED.total_seats); - -COMMIT; diff --git a/supabase/migrations/20260103002000_connect_cybersecurity4.sql b/supabase/migrations/20260103002000_connect_cybersecurity4.sql deleted file mode 100644 index 9a9618e..0000000 --- a/supabase/migrations/20260103002000_connect_cybersecurity4.sql +++ /dev/null @@ -1,17 +0,0 @@ --- Connect existing cybersecurity4 row to the cybersecurity domain (no new row created) --- If it was still stored as iot4, rename it to cybersecurity4 and move the domain. - -BEGIN; - --- If the row exists as iot4, rename it while preserving seats_booked -UPDATE public.problem_statements -SET id = 'cybersecurity4' -WHERE id = 'iot4'; - --- Ensure cybersecurity4 is in the right domain with the correct code label -UPDATE public.problem_statements -SET domain = 'cybersecurity', - code = 'PS 4' -WHERE id = 'cybersecurity4'; - -COMMIT; diff --git a/supabase/migrations/20260103003000_insert_cybersecurity4.sql b/supabase/migrations/20260103003000_insert_cybersecurity4.sql deleted file mode 100644 index 20e08a3..0000000 --- a/supabase/migrations/20260103003000_insert_cybersecurity4.sql +++ /dev/null @@ -1,24 +0,0 @@ --- Add the 4th cybersecurity problem statement (id: cybersecurity4) --- Safe to run multiple times; uses upsert semantics. - -BEGIN; - -INSERT INTO public.problem_statements (id, domain, code, title, description, total_seats, seats_booked) -VALUES ( - 'cybersecurity4', - 'cybersecurity', - 'PS 4', - 'Autonomous Threat Hunting & Response', - 'Design an autonomous security system that proactively hunts for threats, analyzes attack patterns, and implements automated response mechanisms to neutralize cyber threats in real-time.', - 10, - 0 -) -ON CONFLICT (id) DO UPDATE SET - domain = EXCLUDED.domain, - code = EXCLUDED.code, - title = EXCLUDED.title, - description = EXCLUDED.description, - total_seats = EXCLUDED.total_seats, - seats_booked = LEAST(public.problem_statements.seats_booked, EXCLUDED.total_seats); - -COMMIT; diff --git a/supabase/migrations/20260107090000_fix_leaderboard_rls.sql b/supabase/migrations/20260107090000_fix_leaderboard_rls.sql deleted file mode 100644 index adbc880..0000000 --- a/supabase/migrations/20260107090000_fix_leaderboard_rls.sql +++ /dev/null @@ -1,39 +0,0 @@ --- Fix leaderboard exposure by tightening RLS and app_settings update policy - --- 1) Restrict scorecards SELECT to when leaderboard is public OR caller is admin/service -DROP POLICY IF EXISTS "Everyone can view all scorecards (for leaderboard)" ON scorecards; - -CREATE POLICY "Leaderboard visible when enabled or admin/service" ON scorecards - FOR SELECT - USING ( - -- allow if app setting is enabled - (SELECT leaderboard_public FROM app_settings WHERE id = 'main') - -- OR allow if caller is admin (app_metadata.role = 'admin') - OR (auth.jwt() -> 'app_metadata' ->> 'role' = 'admin') - -- OR allow if this is a service role (edge function / server) - OR auth.role() = 'service_role' - ); - --- 2) Tighten who can UPDATE app_settings (only admins / service role) -DROP POLICY IF EXISTS "Authenticated can update settings" ON app_settings; - -CREATE POLICY "Admins or service role can update settings" ON app_settings - FOR UPDATE - USING ( - (auth.jwt() -> 'app_metadata' ->> 'role' = 'admin') - OR auth.role() = 'service_role' - ) - WITH CHECK ( - (auth.jwt() -> 'app_metadata' ->> 'role' = 'admin') - OR auth.role() = 'service_role' - ); - --- Optional: Prevent anonymous from seeing leaderboard_view directly (defense in depth) --- If you want anonymous users not to be able to query the view at all, uncomment the next two lines. --- REVOKE SELECT ON leaderboard_view FROM anon; --- REVOKE SELECT ON leaderboard_view FROM authenticated; -- keep this if you prefer app-layer gating - --- Notes: --- - This migration relies on the app_settings table existing with a row id='main'. --- - After applying, only admins/service role can flip leaderboard visibility. The frontend should call an admin-only endpoint or update using a JWT with app_metadata.role='admin'. --- - To test: connect as an ordinary authenticated user and confirm SELECT from leaderboard_view returns zero rows when leaderboard_public=false, and returns rows when true. Admin/service should always see rows. diff --git a/supabase/seed_test_scores.sql b/supabase/seed_test_scores.sql deleted file mode 100644 index a61aa70..0000000 --- a/supabase/seed_test_scores.sql +++ /dev/null @@ -1,46 +0,0 @@ --- Insert test scorecards for existing teams --- Note: Replace these team_ids with actual IDs from your teams table - --- This script will insert test scores for teams that exist in your database --- Run this manually in Supabase SQL Editor after replacing team IDs - --- Example test data structure (customize with real team IDs): -/* -INSERT INTO scorecards (team_id, ideavortex, review_1, review_2, review_3, pitch_vortex, judge_comments) -VALUES - ('TEAM_ID_1', 85, 90, 78, 82, 80, 'Excellent innovation and solid implementation'), - ('TEAM_ID_2', 92, 88, 95, 89, 90, 'Outstanding presentation with great impact'), - ('TEAM_ID_3', 75, 80, 70, 77, 72, 'Good effort, needs improvement in presentation'), - ('TEAM_ID_4', 88, 85, 92, 86, 88, 'Well-rounded project with strong execution'), - ('TEAM_ID_5', 70, 72, 68, 71, 70, 'Decent concept, implementation could be better'); -*/ - --- Or use this dynamic approach to add scores to ALL existing teams: --- WARNING: This will add random test scores to ALL teams in your database - -DO $$ -DECLARE - team_record RECORD; -BEGIN - FOR team_record IN SELECT id FROM teams LOOP - INSERT INTO scorecards ( - team_id, - ideavortex, - review_1, - review_2, - review_3, - pitch_vortex, - judge_comments - ) - VALUES ( - team_record.id, - FLOOR(RANDOM() * 40 + 60)::integer, -- Random score between 60-100 - FLOOR(RANDOM() * 40 + 60)::integer, - FLOOR(RANDOM() * 40 + 60)::integer, - FLOOR(RANDOM() * 40 + 60)::integer, - FLOOR(RANDOM() * 40 + 60)::integer, - 'Test evaluation - scores generated automatically' - ) - ON CONFLICT (team_id) DO NOTHING; -- Skip if scorecard already exists - END LOOP; -END $$; diff --git a/tmp_get_team.cjs b/tmp_get_team.cjs deleted file mode 100644 index 35d3665..0000000 --- a/tmp_get_team.cjs +++ /dev/null @@ -1,31 +0,0 @@ -const https = require('https'); - -const ANON_KEY = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InptY3Jkb3p4eGNsZ3pwbHR3cG1lIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjUwMzUwOTksImV4cCI6MjA4MDYxMTA5OX0.ecoF_ZdT19cpuu41OkR_lFI27yKMA1ZAtl3d2Z2AAnc`; -const hostname = 'zmcrdozxxclgzpltwpme.supabase.co'; -const email = encodeURIComponent('test-invocation@example.com'); - -const options = { - hostname, - path: `/rest/v1/teams?lead_email=eq.${email}`, - method: 'GET', - headers: { - 'apikey': ANON_KEY, - 'Authorization': `Bearer ${ANON_KEY}` - } -}; - -const req = https.request(options, (res) => { - let body = ''; - res.on('data', (chunk) => body += chunk); - res.on('end', () => { - console.log('STATUS', res.statusCode); - try { - console.log('BODY', JSON.stringify(JSON.parse(body), null, 2)); - } catch (e) { - console.log('BODY', body); - } - }); -}); - -req.on('error', (e) => console.error('Request error', e)); -req.end(); diff --git a/tmp_invoke_register.cjs b/tmp_invoke_register.cjs deleted file mode 100644 index e33b821..0000000 --- a/tmp_invoke_register.cjs +++ /dev/null @@ -1,33 +0,0 @@ -const https = require('https'); -const data = JSON.stringify({ - isVitChennai: 'yes', - eventHubId: null, - leaderName: 'Test Leader', - leaderReg: '12345', - leaderEmail: 'test-invocation@example.com', - receiptLink: 'https://example.com/receipt' -}); - -const options = { - hostname: 'zmcrdozxxclgzpltwpme.supabase.co', - path: '/functions/v1/register-team', - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(data), - 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InptY3Jkb3p4eGNsZ3pwbHR3cG1lIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjUwMzUwOTksImV4cCI6MjA4MDYxMTA5OX0.ecoF_ZdT19cpuu41OkR_lFI27yKMA1ZAtl3d2Z2AAnc' - } -}; - -const req = https.request(options, (res) => { - let body = ''; - res.on('data', (chunk) => body += chunk); - res.on('end', () => { - console.log('STATUS', res.statusCode); - console.log('BODY', body); - }); -}); - -req.on('error', (e) => console.error('Request error', e)); -req.write(data); -req.end(); diff --git a/tmp_invoke_register.js b/tmp_invoke_register.js deleted file mode 100644 index e33b821..0000000 --- a/tmp_invoke_register.js +++ /dev/null @@ -1,33 +0,0 @@ -const https = require('https'); -const data = JSON.stringify({ - isVitChennai: 'yes', - eventHubId: null, - leaderName: 'Test Leader', - leaderReg: '12345', - leaderEmail: 'test-invocation@example.com', - receiptLink: 'https://example.com/receipt' -}); - -const options = { - hostname: 'zmcrdozxxclgzpltwpme.supabase.co', - path: '/functions/v1/register-team', - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(data), - 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InptY3Jkb3p4eGNsZ3pwbHR3cG1lIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjUwMzUwOTksImV4cCI6MjA4MDYxMTA5OX0.ecoF_ZdT19cpuu41OkR_lFI27yKMA1ZAtl3d2Z2AAnc' - } -}; - -const req = https.request(options, (res) => { - let body = ''; - res.on('data', (chunk) => body += chunk); - res.on('end', () => { - console.log('STATUS', res.statusCode); - console.log('BODY', body); - }); -}); - -req.on('error', (e) => console.error('Request error', e)); -req.write(data); -req.end(); diff --git a/vercel.json b/vercel.json deleted file mode 100644 index 1323cda..0000000 --- a/vercel.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "rewrites": [ - { - "source": "/(.*)", - "destination": "/index.html" - } - ] -} diff --git a/wait b/wait deleted file mode 100644 index 9a3680a..0000000 --- a/wait +++ /dev/null @@ -1 +0,0 @@ -fit \ No newline at end of file From 41ae7511fcf32c4c9c26c5a2068e22f9b0f1bd23 Mon Sep 17 00:00:00 2001 From: Srijan Guchhait <62981066+qwertystars@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:23:34 +0530 Subject: [PATCH 2/3] done --- src/pages/home.jsx | 4 +- src/styles/home.css | 285 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 234 insertions(+), 55 deletions(-) diff --git a/src/pages/home.jsx b/src/pages/home.jsx index 67fb6f1..500a24f 100644 --- a/src/pages/home.jsx +++ b/src/pages/home.jsx @@ -94,9 +94,7 @@ export default function Home({ setTransition }) {