diff --git a/submissions/team7_airpay/README.md b/submissions/team7_airpay/README.md new file mode 100644 index 0000000..598bfbd --- /dev/null +++ b/submissions/team7_airpay/README.md @@ -0,0 +1,154 @@ +# airpay + +**GSM-assisted offline payment infrastructure for Android.** +Built for the dead zones — no internet, no problem. + +--- + +## Overview + +airpay is a native Android application that rethinks the payment flow for environments where internet connectivity is unreliable or entirely absent. Instead of depending on data networks, airpay routes payment interactions through the GSM layer — the same infrastructure your carrier uses for voice and SMS — enabling users to complete transactions from virtually anywhere a cellular signal exists. + +The project targets a real and underserved gap: hundreds of millions of users in India and emerging markets face dropped connections, high-latency networks, and intermittent data coverage daily. airpay treats offline-first as a hard constraint, not an afterthought. + +--- + +## Team + +**Team 7** + +| Name | Role | GitHub | +|------|------|--------| +| Chirag Gupta | Developer | [@cgchiraggupta](https://github.com/cgchiraggupta) | +| Sparshika | Team Member | — | +| Meet Khurana | Team Member | — | + +--- + +## Problem + +Standard UPI and digital payment flows assume a stable data connection. When that connection degrades — patchy 2G, network congestion, rural dead zones — the entire payment stack fails. Users are left with no fallback, no feedback, and no transaction. + +The infrastructure to solve this already exists in every device: GSM. airpay leverages it. + +--- + +## Solution + +airpay wraps a guided, minimal mobile interface around a GSM-assisted payment flow. The user experience is deliberately stripped down to reduce failure points. Payment information is reviewed on-device, the interaction is routed through the GSM channel, and the user receives confirmation — all without touching a data packet. + +The frontend is engineered for low-cognitive-load operation: large tap targets, clear state transitions, and no dependency on real-time API calls for core payment steps. + +--- + +## Tech Stack + +| Layer | Technology | +|-------|------------| +| Platform | Android (native) | +| Language | Kotlin | +| Payment Transport | GSM-assisted flow | +| Frontend Reference | Web (hosted on Vercel) | + +--- + +## Architecture + +``` +User Device + │ + ├── airpay Android App (Kotlin) + │ ├── Payment UI Layer ← guided screens, minimal state + │ ├── Input Validation ← on-device, no network required + │ └── GSM Interaction Module ← routes payment through carrier layer + │ + └── GSM Network (carrier infrastructure) + └── Payment Processing +``` + +The GSM interaction module handles the transport layer entirely. No REST calls. No WebSocket. No dependency on the user's data plan. + +--- + +## Payment Flow + +``` +1. Launch airpay + │ + ▼ +2. Enter payment details + (validated on-device) + │ + ▼ +3. Review transaction screen + │ + ▼ +4. Initiate GSM-assisted flow + │ + ▼ +5. Carrier routes interaction + │ + ▼ +6. Confirmation returned to device +``` + +--- + +## Links + +| Resource | URL | +|----------|-----| +| Live Demo | [airpaywebsite.vercel.app/about](https://airpaywebsite.vercel.app/about) | +| Video Demo | [YouTube Shorts](https://youtube.com/shorts/viAPPQx1r9U?si=aYHEXjK7b6zZsqi7) | +| Presentation | [Google Drive](https://drive.google.com/file/d/1iFc6vUQjXlpI-kgZgBZYivRLXWyG0q9p/view?usp=sharing) | + +--- + +## Screenshots + +

+ + + +

+ +--- + +## Running Locally + +Demo assets, APK builds, and additional technical documentation are available through the links listed above. + +Frontend reference files are located in the `app/` directory. These correspond to the public-facing website and can be served locally with any static file server: + +```bash +cd app/ +npx serve . +# or +python3 -m http.server 8080 +``` + +For the Android application, build requirements and setup instructions will be published in a follow-up release. + +--- + +## Why GSM + +| Factor | Data-dependent apps | airpay | +|--------|---------------------|--------| +| Requires internet | Yes | No | +| Works on 2G | Partial | Yes | +| Works with zero data | No | Yes | +| Rural coverage | Poor | GSM coverage | +| Failure mode | Silent / timeout | Defined fallback | + +GSM infrastructure in India covers regions where 4G and even 3G are inconsistent. By treating the carrier layer as the transport rather than IP networking, airpay inherits decades of telecom reliability. + +--- + +## Status + +Hackathon build. Core payment flow implemented. Active development ongoing. + +--- + +*Built at [Hackathon Name] · Team 7* diff --git a/submissions/team7_airpay/app/index.html b/submissions/team7_airpay/app/index.html new file mode 100644 index 0000000..2d457d8 --- /dev/null +++ b/submissions/team7_airpay/app/index.html @@ -0,0 +1,16 @@ + + + + + + + + AirPay UPI | Offline Payment Protocol + + + +
+ + + + \ No newline at end of file diff --git a/submissions/team7_airpay/app/package.json b/submissions/team7_airpay/app/package.json new file mode 100644 index 0000000..40ce796 --- /dev/null +++ b/submissions/team7_airpay/app/package.json @@ -0,0 +1,39 @@ +{ + "name": "react-example", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3000 --host=0.0.0.0", + "build": "vite build", + "preview": "vite preview", + "clean": "rm -rf dist", + "lint": "tsc --noEmit" + }, + "dependencies": { + "@google/genai": "^1.29.0", + "@tailwindcss/vite": "^4.1.14", + "@vitejs/plugin-react": "^5.0.4", + "clsx": "^2.1.1", + "dotenv": "^17.2.3", + "express": "^4.21.2", + "lenis": "^1.3.19", + "lucide-react": "^0.546.0", + "motion": "^12.23.24", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.13.2", + "recharts": "^3.8.0", + "tailwind-merge": "^3.5.0", + "vite": "^6.2.0" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^22.14.0", + "autoprefixer": "^10.4.21", + "tailwindcss": "^4.1.14", + "tsx": "^4.21.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } +} diff --git a/submissions/team7_airpay/app/public/images/image1.jpeg b/submissions/team7_airpay/app/public/images/image1.jpeg new file mode 100644 index 0000000..88bddeb Binary files /dev/null and b/submissions/team7_airpay/app/public/images/image1.jpeg differ diff --git a/submissions/team7_airpay/app/public/images/meet2.png b/submissions/team7_airpay/app/public/images/meet2.png new file mode 100644 index 0000000..2a3a3bd Binary files /dev/null and b/submissions/team7_airpay/app/public/images/meet2.png differ diff --git a/submissions/team7_airpay/app/src/App.tsx b/submissions/team7_airpay/app/src/App.tsx new file mode 100644 index 0000000..cedae2f --- /dev/null +++ b/submissions/team7_airpay/app/src/App.tsx @@ -0,0 +1,78 @@ +import { BrowserRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom'; +import { useEffect } from 'react'; +import Lenis from 'lenis'; +import { HeroSection } from './components/HeroSection'; +import { FeaturesSection } from './components/FeaturesSection'; +import { DownloadSection } from './components/DownloadSection'; +import { HowToUseSection } from './components/HowToUseSection'; +import { ContactSection } from './components/ContactSection'; +import { Footer } from './components/Footer'; +import { AboutPage } from './pages/AboutPage'; + +// This component can use useLocation because it's inside Router +function AppContent() { + const location = useLocation(); + const showAboutButton = location.pathname === '/'; + + useEffect(() => { + const lenis = new Lenis({ + duration: 1.2, + easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), + orientation: 'vertical', + gestureOrientation: 'vertical', + smoothWheel: true, + wheelMultiplier: 1, + touchMultiplier: 2, + }); + + function raf(time: number) { + lenis.raf(time); + requestAnimationFrame(raf); + } + + requestAnimationFrame(raf); + + return () => { + lenis.destroy(); + }; + }, []); + + return ( +
+ {/* Show button only on home page */} + {showAboutButton && ( +
+ + About + +
+ )} + + + + + + + + +
+ ); +} + +// Main App component just wraps everything with Router +export default function App() { + return ( + + + + ); +} \ No newline at end of file diff --git a/submissions/team7_airpay/app/src/components/ContactSection.tsx b/submissions/team7_airpay/app/src/components/ContactSection.tsx new file mode 100644 index 0000000..174e286 --- /dev/null +++ b/submissions/team7_airpay/app/src/components/ContactSection.tsx @@ -0,0 +1,114 @@ +import { Mail, Github, MessageSquare } from 'lucide-react'; +import { TextScramble } from './TextScramble'; + +export function ContactSection() { + return ( +
+
+
+

+ +

+

+ +

+
+ +
+ +
+ +
+

Email Support

+

+ Send us an email for support or inquiries +

+
+ airpay.perry@gmail.com +
+
+ + +
+ +
+

GitHub

+

+ Download APK and view documentation +

+
+ github.com/cgchiraggupta +
+
+ + +
+ +
+

Website

+

+ Visit our official website for updates +

+
+ airpaywebsite.vercel.app +
+
+
+ +
+

+ +

+ +
+
+

Is AirPay safe to use?

+

+ Yes. AirPay never automatically enters your UPI PIN. You must manually enter it during the GSM process. No sensitive information is stored or transmitted automatically. +

+
+ +
+

Which banks are supported?

+

+ AirPay works with any Indian bank that supports GSM banking (GSM service). This includes most major banks like SBI, HDFC, ICICI, Axis, and others. +

+
+ +
+

Do I need internet at all?

+

+ No internet is required for payments. The app uses GSM (Unstructured Supplementary Service Data) which works over the mobile cellular network. +

+
+ +
+

Is there any fee?

+

+ AirPay is completely free to download and use. Standard GSM charges from your mobile operator may apply (usually free or minimal cost). +

+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/submissions/team7_airpay/app/src/components/DownloadSection.tsx b/submissions/team7_airpay/app/src/components/DownloadSection.tsx new file mode 100644 index 0000000..2e0ce2a --- /dev/null +++ b/submissions/team7_airpay/app/src/components/DownloadSection.tsx @@ -0,0 +1,94 @@ +import { Download, Smartphone, Shield, ArrowDown } from 'lucide-react'; +import { TextScramble } from './TextScramble'; + +export function DownloadSection() { + return ( +
+
+
+ + Download Now +
+ +

+ +

+ +

+ +
+ +

+ +
+ + + DOWNLOAD APK (26 MB) + + + + View on GitHub + +
+ +
+
+
+ +

Requirements

+
+
    +
  • • Android 8.0+ (Oreo)
  • +
  • • SIM with GSM banking
  • +
  • • Mobile network signal
  • +
  • • 50MB free storage
  • +
+
+ +
+
+ +

Installation

+
+
    +
  • 1. Enable "Unknown Sources"
  • +
  • 2. Download APK file
  • +
  • 3. Open and install
  • +
  • 4. Grant permissions
  • +
+
+ +
+
+
+
+
+

Safety

+
+
    +
  • • No automatic PIN entry
  • +
  • • Manual PIN verification
  • +
  • • No internet required
  • +
  • • Open source code
  • +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/submissions/team7_airpay/app/src/components/FeaturesSection.tsx b/submissions/team7_airpay/app/src/components/FeaturesSection.tsx new file mode 100644 index 0000000..b7e92f6 --- /dev/null +++ b/submissions/team7_airpay/app/src/components/FeaturesSection.tsx @@ -0,0 +1,81 @@ +import { useState } from 'react'; +import { WifiOff, Shield, Smartphone, Zap } from 'lucide-react'; +import { TextScramble } from './TextScramble'; + +export function FeaturesSection() { + const [hovered, setHovered] = useState(null); + + const features = [ + { + icon: WifiOff, + title: 'Works Offline', + description: 'Make UPI payments even without internet connection using mobile network.', + metric: '0 INTERNET REQ', + }, + { + icon: Shield, + title: 'Secure', + description: 'Your UPI PIN is always entered manually. No automatic PIN entry.', + metric: 'PIN PROTECTED', + }, + { + icon: Smartphone, + title: 'Easy to Use', + description: 'Simple interface. Scan QR code or enter details manually.', + metric: 'USER FRIENDLY', + }, + { + icon: Zap, + title: 'Fast', + description: 'Complete payments in seconds through GSM banking.', + metric: 'SECONDS ONLY', + }, + ]; + + return ( +
+
+
+

+ +

+ STATUS: ACTIVE +
+ +
+ {features.map((feature, i) => { + const Icon = feature.icon; + return ( +
setHovered(i)} + onMouseLeave={() => setHovered(null)} + onTouchStart={() => setHovered(i)} + onTouchEnd={() => setHovered(null)} + className="p-6 sm:p-8 border-b md:border-b-0 md:border-r last:border-b-0 lg:last:border-r-0 border-divider bg-surface hover:bg-primary hover:text-text-primary active:bg-primary transition-colors duration-200 flex flex-col group touch-manipulation" + > +
+ FEAT.{i + 1} + +
+ +
+

+ {hovered === i ? : feature.title} +

+

+ {feature.description} +

+
+ +
+ [{feature.metric}] +
+
+ ); + })} +
+
+
+ ); +} \ No newline at end of file diff --git a/submissions/team7_airpay/app/src/components/Footer.tsx b/submissions/team7_airpay/app/src/components/Footer.tsx new file mode 100644 index 0000000..8a52c15 --- /dev/null +++ b/submissions/team7_airpay/app/src/components/Footer.tsx @@ -0,0 +1,29 @@ +import { useEffect, useState } from 'react'; + +export function Footer() { + const [showCursor, setShowCursor] = useState(true); + + useEffect(() => { + const timer = setInterval(() => { + setShowCursor((prev) => !prev); + }, 500); + return () => clearInterval(timer); + }, []); + + return ( + + ); +} diff --git a/submissions/team7_airpay/app/src/components/HeroSection.tsx b/submissions/team7_airpay/app/src/components/HeroSection.tsx new file mode 100644 index 0000000..51c025b --- /dev/null +++ b/submissions/team7_airpay/app/src/components/HeroSection.tsx @@ -0,0 +1,81 @@ +import { TextScramble } from './TextScramble'; + +export function HeroSection() { + return ( +
+
+ +
+

+ + + + + + + + + +

+ +

+ AirPay enables UPI payments even when you have no internet connection. + Simple, secure, and works anywhere with mobile network coverage. +

+ +
+ + DOWNLOAD FOR ANDROID + + + + HOW TO USE + +
+
+ + {/* Animated background element - OPTIMAL */} +
+
+ {/* Inner rings */} +
+
+ + {/* Signal strength indicator */} +
+ {[1, 2, 3, 4, 5].map((i) => ( +
+ ))} +
+ + {/* Center dot */} +
+
+
+ + {/* Status indicator */} +
+
+
+ READY FOR OFFLINE PAYMENTS +
+
+
+ ); +} \ No newline at end of file diff --git a/submissions/team7_airpay/app/src/components/HowToUseSection.tsx b/submissions/team7_airpay/app/src/components/HowToUseSection.tsx new file mode 100644 index 0000000..455660d --- /dev/null +++ b/submissions/team7_airpay/app/src/components/HowToUseSection.tsx @@ -0,0 +1,128 @@ +import { QrCode, Smartphone, Lock, CheckCircle, ChevronRight } from 'lucide-react'; +import { TextScramble } from './TextScramble'; + +export function HowToUseSection() { + const steps = [ + { + number: '01', + icon: QrCode, + title: 'Scan or Enter', + description: 'Scan a UPI QR code or manually enter payment details.', + }, + { + number: '02', + icon: Smartphone, + title: 'Confirm Details', + description: 'Review recipient, amount, and payment note.', + }, + { + number: '03', + icon: Smartphone, + title: 'GSM Process', + description: 'App automatically accesses banking GSM menu (GSM ).', + }, + { + number: '04', + icon: Lock, + title: 'Enter PIN', + description: 'Manually enter your UPI PIN when prompted (most secure step).', + }, + { + number: '05', + icon: CheckCircle, + title: 'Complete', + description: 'Transaction completes through GSM . You receive confirmation.', + }, + ]; + + return ( +
+
+
+

+ +

+

+ +

+
+ +
+ {/* Connecting line for desktop */} +
+ +
+ {steps.map((step, index) => { + const Icon = step.icon; + const isEven = index % 2 === 0; + + return ( +
+ {/* Step content */} +
+
+
+
+ {step.number} +
+ + +
+ +

+ +

+ +

+ {step.description} +

+
+
+ + {/* Center dot for desktop */} +
+
+
+ + {/* Empty spacer for alignment */} +
+
+ ); + })} +
+
+ +
+
+
+

+ +

+

+ AirPay never automatically enters your UPI PIN. You must manually enter it + when prompted during the GSM process. This ensures maximum security and + follows banking regulations. +

+
+ +
+
+ + PIN PROTECTED +
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/submissions/team7_airpay/app/src/components/TextScramble.tsx b/submissions/team7_airpay/app/src/components/TextScramble.tsx new file mode 100644 index 0000000..f6f552c --- /dev/null +++ b/submissions/team7_airpay/app/src/components/TextScramble.tsx @@ -0,0 +1,90 @@ +import { useEffect, useState } from 'react'; + +interface TextScrambleProps { + text: string; + delay?: number; + duration?: number; +} + +export function TextScramble({ text, delay = 0, duration = 800 }: TextScrambleProps) { + const [displayText, setDisplayText] = useState(''); + const [isScrambling, setIsScrambling] = useState(true); + + const chars = '!<>-_\\/[]{}—=+*^?#________'; + + useEffect(() => { + let frame = 0; + let frameRequest: number; + let startTime: number; + + const queue: Array<{ + from: string; + to: string; + start: number; + end: number; + char?: string; + }> = []; + + for (let i = 0; i < text.length; i++) { + const from = chars[Math.floor(Math.random() * chars.length)]; + const to = text[i]; + const start = Math.random() * 10; + const end = start + Math.random() * duration; + queue.push({ from, to, start, end }); + } + + const update = () => { + let output = ''; + let complete = 0; + + for (let i = 0; i < queue.length; i++) { + let { from, to, start, end, char } = queue[i]; + + if (frame >= end) { + complete++; + output += to; + } else if (frame >= start) { + if (!char || Math.random() < 0.28) { + char = chars[Math.floor(Math.random() * chars.length)]; + queue[i].char = char; + } + output += `${char}`; + } else { + output += from; + } + } + + setDisplayText(output); + + if (complete === queue.length) { + setIsScrambling(false); + } else { + frameRequest = requestAnimationFrame(update); + frame++; + } + }; + + const startAnimation = () => { + startTime = Date.now(); + frame = 0; + setIsScrambling(true); + frameRequest = requestAnimationFrame(update); + }; + + const timeoutId = setTimeout(startAnimation, delay); + + return () => { + clearTimeout(timeoutId); + if (frameRequest) { + cancelAnimationFrame(frameRequest); + } + }; + }, [text, delay, duration]); + + return ( + + ); +} \ No newline at end of file diff --git a/submissions/team7_airpay/app/src/index.css b/submissions/team7_airpay/app/src/index.css new file mode 100644 index 0000000..ca825dc --- /dev/null +++ b/submissions/team7_airpay/app/src/index.css @@ -0,0 +1,209 @@ +@import url('https://api.fontshare.com/v2/css?f[]=clash-display@200,300,400,500,600,700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&display=swap'); + +@import "tailwindcss"; + +@theme { + --color-background: #0D0D0D; + --color-surface: #1A1A1A; + --color-surface-elevated: #242424; + --color-card-background: #1E1E1E; + --color-divider: #2A2A2A; + + --color-primary: #1A1A2E; + --color-primary-variant: #16213E; + --color-secondary: #0F3460; + --color-accent: #E94560; + + --color-text-primary: #FFFFFF; + --color-text-secondary: #B0B0B0; + --color-text-hint: #666666; + + --color-success: #00C853; + --color-warning: #FFB300; + --color-error: #FF5252; + + --color-flow-shell-start: #5A45F6; + --color-flow-shell-end: #3B2FD1; + --color-flow-lime: #B9F46A; + --color-flow-lime-dark: #273112; + + --font-heading: 'Clash Display', sans-serif; + --font-mono: 'Geist Mono', monospace; +} + +body { + background-color: var(--color-background); + color: var(--color-text-primary); + font-family: var(--font-mono); + overflow-x: hidden; +} + + + +/* Hide scrollbar for Chrome, Safari and Opera */ +.no-scrollbar::-webkit-scrollbar { + display: none; +} + +/* Hide scrollbar for IE, Edge and Firefox */ +.no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.noise-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 50; + opacity: 0.05; + mix-blend-mode: overlay; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E"); +} + +.receipt-edge { + position: absolute; + top: -10px; + left: 0; + right: 0; + height: 10px; + background: radial-gradient(circle at 5px 0, transparent 5px, var(--color-surface) 6px); + background-size: 10px 10px; +} + +.gradient-shell { + background: linear-gradient(135deg, var(--color-flow-shell-start), var(--color-flow-shell-end)); +} + +.gradient-surface { + background: linear-gradient(135deg, var(--color-surface), var(--color-surface-elevated)); +} + +/* Touch-friendly elements for mobile */ +@media (max-width: 768px) { + button, + a[role="button"], + .touch-target { + min-height: 44px; + min-width: 44px; + } + + input, + select, + textarea { + font-size: 16px; /* Prevents iOS zoom on focus */ + } +} + +/* Prevent text selection on interactive elements */ +button, +a { + -webkit-tap-highlight-color: transparent; + user-select: none; +} + +/* Improve scrolling on mobile */ +html { + -webkit-overflow-scrolling: touch; +} + +/* Custom animations - OPTIMAL SPEED */ +@keyframes spin-optimal { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@keyframes spin-optimal-reverse { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); + } +} + +@keyframes pulse-optimal { + 0%, 100% { + opacity: 0.6; + } + 50% { + opacity: 1; + } +} + +@keyframes float-optimal { + 0%, 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-8px); + } +} + +@keyframes fade-in-up-optimal { + from { + opacity: 0; + transform: translateY(15px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes bounce-optimal { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-5px); + } +} + +.animate-spin-optimal { + animation: spin-optimal 12s linear infinite; +} + +.animate-spin-optimal-reverse { + animation: spin-optimal-reverse 9s linear infinite; +} + +.animate-float-optimal { + animation: float-optimal 2s ease-in-out infinite; +} + +.animate-fade-in-up-optimal { + animation: fade-in-up-optimal 0.5s ease-out forwards; +} + +.animate-pulse-optimal { + animation: pulse-optimal 1.5s ease-in-out infinite; +} + +.animate-bounce-optimal { + animation: bounce-optimal 0.8s ease-in-out infinite; +} + + + + +/* Add to index.css */ +@keyframes gradient-shift { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} \ No newline at end of file diff --git a/submissions/team7_airpay/app/src/main.tsx b/submissions/team7_airpay/app/src/main.tsx new file mode 100644 index 0000000..080dac3 --- /dev/null +++ b/submissions/team7_airpay/app/src/main.tsx @@ -0,0 +1,10 @@ +import {StrictMode} from 'react'; +import {createRoot} from 'react-dom/client'; +import App from './App.tsx'; +import './index.css'; + +createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/submissions/team7_airpay/app/src/pages/AboutPage.tsx b/submissions/team7_airpay/app/src/pages/AboutPage.tsx new file mode 100644 index 0000000..eb68f12 --- /dev/null +++ b/submissions/team7_airpay/app/src/pages/AboutPage.tsx @@ -0,0 +1,1146 @@ +// src/pages/AboutPage.tsx +import { useEffect, useRef, useState } from 'react'; +import { motion, useScroll, useTransform, useInView, useMotionValue, useSpring } from 'framer-motion'; +import { ArrowLeft, Github, Linkedin, Twitter, ExternalLink, Mail, MapPin, Calendar, Zap, Code2, Wifi, WifiOff, Signal, Shield, Smartphone, Radio, Users, Globe, ChevronRight } from 'lucide-react'; +import { Footer } from '../components/Footer'; + +// ─── 3D Floating Grid Background ───────────────────────────────────────────── +function FloatingGrid() { + const canvasRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + let animationId: number; + let time = 0; + + const resize = () => { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + }; + + resize(); + window.addEventListener('resize', resize); + + const drawGrid = () => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + const gridSize = 40; + const perspective = 800; + const rotationX = Math.sin(time * 0.0003) * 0.3; + const rotationY = Math.cos(time * 0.0002) * 0.2; + + for (let x = -20; x <= 20; x++) { + for (let z = -20; z <= 20; z++) { + const worldX = x * gridSize; + const worldZ = z * gridSize - time * 0.02; + const adjustedZ = ((worldZ % (gridSize * 40)) + gridSize * 40) % (gridSize * 40) - gridSize * 20; + + const rotatedX = worldX * Math.cos(rotationY) - adjustedZ * Math.sin(rotationY); + const rotatedZ = worldX * Math.sin(rotationY) + adjustedZ * Math.cos(rotationY); + const rotatedY = rotatedZ * Math.sin(rotationX) + 200; + const finalZ = rotatedZ * Math.cos(rotationX) + perspective; + + if (finalZ <= 0) continue; + + const screenX = (rotatedX * perspective) / finalZ + canvas.width / 2; + const screenY = (rotatedY * perspective) / finalZ + canvas.height / 2; + const size = Math.max(1, (perspective / finalZ) * 1.5); + + const alpha = Math.max(0, Math.min(0.15, 1 - finalZ / 2000)); + ctx.fillStyle = `rgba(90, 69, 246, ${alpha})`; + ctx.beginPath(); + ctx.arc(screenX, screenY, size, 0, Math.PI * 2); + ctx.fill(); + } + } + + time += 16; + animationId = requestAnimationFrame(drawGrid); + }; + + drawGrid(); + + return () => { + window.removeEventListener('resize', resize); + cancelAnimationFrame(animationId); + }; + }, []); + + return ( + + ); +} + +// ─── Magnetic Element ───────────────────────────────────────────────────────── +function MagneticWrap({ children, className = '' }: { children: React.ReactNode; className?: string }) { + const ref = useRef(null); + const x = useMotionValue(0); + const y = useMotionValue(0); + const springX = useSpring(x, { stiffness: 150, damping: 15 }); + const springY = useSpring(y, { stiffness: 150, damping: 15 }); + + const handleMouseMove = (e: React.MouseEvent) => { + const rect = ref.current?.getBoundingClientRect(); + if (!rect) return; + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + x.set((e.clientX - centerX) * 0.15); + y.set((e.clientY - centerY) * 0.15); + }; + + const handleMouseLeave = () => { + x.set(0); + y.set(0); + }; + + return ( + + {children} + + ); +} + +// ─── Glitch Text ────────────────────────────────────────────────────────────── +function GlitchText({ text, className = '' }: { text: string; className?: string }) { + const [displayText, setDisplayText] = useState(text); + const [isGlitching, setIsGlitching] = useState(false); + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%&*'; + + const triggerGlitch = () => { + if (isGlitching) return; + setIsGlitching(true); + let iterations = 0; + const maxIterations = text.length; + + const interval = setInterval(() => { + setDisplayText( + text + .split('') + .map((char, i) => { + if (i < iterations) return text[i]; + if (char === ' ') return ' '; + return chars[Math.floor(Math.random() * chars.length)]; + }) + .join('') + ); + + iterations += 1 / 2; + + if (iterations >= maxIterations) { + clearInterval(interval); + setDisplayText(text); + setIsGlitching(false); + } + }, 30); + }; + + return ( + + {displayText} + + ); +} + +// ─── Signal Line Animation ──────────────────────────────────────────────────── +function SignalLine() { + return ( +
+
+ +
+ ); +} + +// ─── Terminal Block ─────────────────────────────────────────────────────────── +function TerminalBlock({ children, title = 'terminal' }: { children: React.ReactNode; title?: string }) { + const ref = useRef(null); + const isInView = useInView(ref, { once: true, margin: '-100px' }); + + return ( + +
+
+
+
+
+
+ {title} +
+
+ {children} +
+ + ); +} + +// ─── Orbit Ring ─────────────────────────────────────────────────────────────── +function OrbitRing({ delay = 0 }: { delay?: number }) { + return ( + +
+ + ); +} + +// ─── Stat Counter ───────────────────────────────────────────────────────────── +function AnimatedStat({ value, label, suffix = '' }: { value: string; label: string; suffix?: string }) { + const ref = useRef(null); + const isInView = useInView(ref, { once: true }); + + return ( + +
+ {value}{suffix} +
+
+ {label} +
+
+ ); +} + +// ─── GSM Flow Visualization ────────────────────────────────────────────────── +function GSMFlowVisualization() { + const ref = useRef(null); + const isInView = useInView(ref, { once: true, margin: '-50px' }); + + const steps = [ + { icon: Smartphone, label: 'Scan QR / Enter UPI ID', color: 'text-flow-shell-start' }, + { icon: Radio, label: 'Dial GSM Code', color: 'text-warning' }, + { icon: Signal, label: 'Navigate GSM Menu', color: 'text-flow-lime' }, + { icon: Shield, label: 'User Enters MPIN', color: 'text-accent' }, + { icon: Zap, label: 'Transaction Complete', color: 'text-success' }, + ]; + + return ( +
+
+ {steps.map((step, i) => ( + + {/* Connector line */} + {i < steps.length - 1 && ( + +
+ +
+ +
+ )} + +
+ +
+ + {step.label} + + 0{i + 1} +
+ ))} +
+
+ ); +} + +// ─── Founder Card ───────────────────────────────────────────────────────────── +// ─── Founder Card (with image support) ───────────────────────────────────────────── +function FounderCard({ + name, + role, + bio, + funFact, + links, + index, + skills, + location, + tagline, + imageUrl, // <-- new prop +}: { + name: string; + role: string; + bio: string; + funFact: string; + links: { github?: string; linkedin?: string; twitter?: string; email?: string }; + index: number; + skills: string[]; + location: string; + tagline: string; + imageUrl: string; // path to image, e.g. "/images/chirag.jpg" +}) { + const ref = useRef(null); + const isInView = useInView(ref, { once: true, margin: '-50px' }); + const [isHovered, setIsHovered] = useState(false); + + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > +
+ + +
+
+ +
+ {/* Orbit rings */} + + + + {/* Avatar container with image */} + + {name} + + + {/* Scanning line overlay */} + + + {/* Status badge */} +
+
+ BUILDING +
+
+ + +

+ +

+ +
+ + {role} +
+ +

"{tagline}"

+ +

+ {bio} +

+
+ +
+
Tech Stack
+
+ {skills.map((skill, i) => ( + + {skill} + + ))} +
+
+ +
+
+ + What Drives Them +
+

+ {'> '} + {funFact} +

+
+ +
+ {links.github && ( + + + + + + )} + {links.linkedin && ( + + + + + + )} + {links.twitter && ( + + + + + + )} + {links.email && ( + + + + + + )} +
+
+ +
+
+ + {location} +
+
+ + 3rd Year Engineering +
+
+
+
+ ); +} +// ─── Timeline Node ──────────────────────────────────────────────────────────── +function TimelineNode({ + year, + title, + description, + index, +}: { + year: string; + title: string; + description: string; + index: number; +}) { + const ref = useRef(null); + const isInView = useInView(ref, { once: true, margin: '-50px' }); + + return ( + +
+ + +
+ +
+
{year}
+

{title}

+

{description}

+
+
+ ); +} + +// ─── Architecture Card ──────────────────────────────────────────────────────── +function ArchCard({ icon: Icon, title, description, index }: { icon: any; title: string; description: string; index: number }) { + const ref = useRef(null); + const isInView = useInView(ref, { once: true }); + + return ( + +
+ +
+

{title}

+

{description}

+
+ ); +} + +// ─── Team Member Badge ──────────────────────────────────────────────────────── +function TeamBadge({ name, role, index }: { name: string; role: string; index: number }) { + const ref = useRef(null); + const isInView = useInView(ref, { once: true }); + + return ( + +
+ + {name.split(' ').map(n => n[0]).join('')} + +
+
+
{name}
+
{role}
+
+
+ ); +} + +// ─── Main About Page ────────────────────────────────────────────────────────── +export function AboutPage() { + const containerRef = useRef(null); + const { scrollYProgress } = useScroll({ target: containerRef }); + const progressWidth = useTransform(scrollYProgress, [0, 1], ['0%', '100%']); + + const founders = [ + { + name: 'Chirag Gupta', + role: 'Co-Founder & Developer', + tagline: '18, engineer. I like to build.', + bio: '', + funFact: 'Believes the best way to learn a technology is to build something real with it.', + skills: ['React', 'TypeScript', 'Android', 'System Design', 'Node.js', 'GSM Protocols'], + location: 'Delhi, India', + imageUrl: "/images/image1.jpeg", + links: { + github: 'https://github.com/cgchiraggupta', + twitter: '#', + email: 'cg077593@gmail.com', + }, + }, + { + name: 'Meet Khurana', + role: 'Co-Founder & Developer', + tagline: 'person who genuinely loves tech', + bio: '', + funFact: 'Favorite feedback isn`t great code, its this actually made my day easier', + skills: ['Python', 'Java', 'React', 'Flask', 'MongoDB', 'PostgreSQL', 'C++', 'Firebase'], + location: 'Gurgaon, India', + imageUrl: "/images/meet2.png", + links: { + github: 'https://github.com/meetkhurana04', + linkedin: '#', + email: 'airpay.perry@gmail.com', + }, + }, + ]; + + const timeline = [ + + ]; + + return ( +
+ +
+ + {/* Scroll progress */} + + + {/* Back button */} + + + + + Back + + + + + {/* ═══════════════════════════════════════════════════════════════════ + HERO + ═══════════════════════════════════════════════════════════════════ */} +
+
+ + + Payments that don't need WiFi + + + + We Built + + + + + + Because Signals > WiFi. + + + + Two third-year engineering students from Gurgaon who got tired of payment + failures in low-signal zones. So we bridged UPI with legacy GSM signaling + and made digital payments work with just one bar of signal. + + + + No internet required. Just cellular network. That's it. + + + + Scroll to explore our story + +
+ + +
+
+ + + + {/* ═══════════════════════════════════════════════════════════════════ + THE PROBLEM WE SAW + ═══════════════════════════════════════════════════════════════════ */} +
+
+ +
+ Problem // Statement +
+ + + +
+ + Every UPI transaction assumes one thing:{' '} + that you have internet. But across India, payments fail daily — in rural areas with weak signals, metros underground, highways, and during outages. + + + + Failed payments mean lost sales, reduced trust, and a fallback to cash. Meanwhile, cellular networks still work. + + + +

+ "Payments failed where calls worked. That gap didn’t make sense." +

+

— Chirag & Meet, AirPay

+
+
+
+ + {/* Problem cards */} +
+ {[ + { icon: WifiOff, label: 'Rural Areas', desc: 'Weak or no connectivity' }, + { icon: Signal, label: 'Underground', desc: 'Metros & tunnels' }, + { icon: Globe, label: 'Remote Highways', desc: 'Connectivity drops' }, + { icon: Zap, label: 'Emergencies', desc: 'Disasters & outages' }, + ].map((item, i) => ( + + +
{item.label}
+
{item.desc}
+
+ ))} +
+
+
+ + + + {/* ═══════════════════════════════════════════════════════════════════ + THE SOLUTION — HOW AIRPAY WORKS + ═══════════════════════════════════════════════════════════════════ */} +
+
+ +
+ Solution // How It Works +
+ + + +

+ UPI Over +

+

+ AirPay is an asynchronous, offline-tolerant payment orchestration layer. It bridges modern UPI + with legacy GSM channels, using an intelligent accessibility service to navigate + banking menus automatically. +

+
+ + {/* GSM Flow */} + + + + + {/* Architecture cards */} +
+ + + + + + +
+
+
+ + + {/* ═══════════════════════════════════════════════════════════════════ + THE FOUNDERS + ═══════════════════════════════════════════════════════════════════ */} +
+
+ +
+ The // Founders +
+ + + + Two third-year engineering students from Gurgaon who believe that if you can + make a phone call, you should be able to make a payment. + + +
+ {founders.map((founder, i) => ( + + ))} +
+
+
+ + + + + {/* ═══════════════════════════════════════════════════════════════════ + TECH STACK + ═══════════════════════════════════════════════════════════════════ */} +
+
+ +
+ Tech // Stack +
+ + +
+ {/* Frontend */} + +
+

+ + Frontend +

+ {['React Native', 'Cross-Platform (iOS & Android)', 'Responsive UI Design', 'Secure Input Handling'].map((item, i) => ( + + + {item} + + ))} +
+
+ + {/* Backend */} + +
+

+ + Backend +

+ {['Node.js Runtime', 'Express.js APIs', 'Modular Architecture', 'Offline-First Model', 'Scalable Design'].map((item, i) => ( + + + {item} + + ))} +
+
+ + {/* Security */} + +
+

+ + Security +

+ {['End-to-End Encryption', 'Hardware-Backed Keys', 'Zero Credential Storage', 'Fraud Detection', 'Sandboxed Services'].map((item, i) => ( + + + {item} + + ))} +
+
+
+
+
+ + + + {/* ═══════════════════════════════════════════════════════════════════ + MISSION + VISION + ═══════════════════════════════════════════════════════════════════ */} +
+
+
+ {/* Mission */} + +
+
+
+ + Mission +
+

+ +

+

+ To make digital payments accessible to every Indian — regardless of internet + connectivity, network conditions, or geography. If you have a SIM card and one bar + of signal, you should be able to pay. AirPay brings the convenience of UPI to the + 400M+ Indians who live in areas with unreliable internet. +

+
+ + + {/* Vision */} + +
+
+
+ + Vision +
+

+ +

+

+ To become the default payment layer for disconnected environments — not just + in India, but globally. We envision a world where network outages, natural disasters, + and infrastructure gaps never prevent someone from completing a transaction. + Digital payments should be as reliable as a phone call. +

+
+ +
+
+
+ + + + {/* ═══════════════════════════════════════════════════════════════════ + CTA + ═══════════════════════════════════════════════════════════════════ */} +
+
+ +

+ Payments should work{' '} + + + + . +

+

+ We're building the future of offline-first payments. Want to know more, + test the app, or just chat about GSM protocols? +

+ + + {/* GitHub link */} + + + + View on GitHub + + +
+
+
+ + {/* Footer */} +
+
+ ); +} \ No newline at end of file diff --git a/submissions/team7_airpay/app/tsconfig.json b/submissions/team7_airpay/app/tsconfig.json new file mode 100644 index 0000000..d88f175 --- /dev/null +++ b/submissions/team7_airpay/app/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "moduleResolution": "bundler", + "isolatedModules": true, + "moduleDetection": "force", + "allowJs": true, + "jsx": "react-jsx", + "paths": { + "@/*": [ + "./*" + ] + }, + "allowImportingTsExtensions": true, + "noEmit": true + } +} diff --git a/submissions/team7_airpay/app/vite.config.ts b/submissions/team7_airpay/app/vite.config.ts new file mode 100644 index 0000000..0506f1b --- /dev/null +++ b/submissions/team7_airpay/app/vite.config.ts @@ -0,0 +1,24 @@ +import tailwindcss from '@tailwindcss/vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; +import {defineConfig, loadEnv} from 'vite'; + +export default defineConfig(({mode}) => { + const env = loadEnv(mode, '.', ''); + return { + plugins: [react(), tailwindcss()], + define: { + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY), + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), + }, + }, + server: { + // HMR is disabled in AI Studio via DISABLE_HMR env var. + // Do not modify—file watching is disabled to prevent flickering during agent edits. + hmr: process.env.DISABLE_HMR !== 'true', + }, + }; +}); diff --git a/submissions/team7_airpay/demo-assets/.gitkeep b/submissions/team7_airpay/demo-assets/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/submissions/team7_airpay/demo-assets/.gitkeep @@ -0,0 +1 @@ + diff --git a/submissions/team7_airpay/public-ui/index.html b/submissions/team7_airpay/public-ui/index.html new file mode 100644 index 0000000..889d29a --- /dev/null +++ b/submissions/team7_airpay/public-ui/index.html @@ -0,0 +1,74 @@ + + + + + + airpay UI Preview + + + + + + +
+
+

Public UI Sample

+

airpay frontend preview

+

+ This is a static showcase of the product interface shared for hackathon review. + It does not include private transaction logic, backend code, or internal payment orchestration. +

+
+ +
+
+
+ 9:41 + 5G +
+
+

Quick Pay

+

Scan or pay manually

+
QR
+ + +
+
+ +
+
+ 9:41 + 5G +
+
+

Payment

+

Confirm payment details

+
+ Recipient + AirPay Demo Merchant +
+
+ Amount + Rs. 499 +
+ +
+
+ +
+
+ 9:41 + 5G +
+
+

Status

+

Payment initiated

+
+

User-friendly interface preview for the hackathon package.

+ +
+
+
+
+ + diff --git a/submissions/team7_airpay/public-ui/styles.css b/submissions/team7_airpay/public-ui/styles.css new file mode 100644 index 0000000..cdb16e8 --- /dev/null +++ b/submissions/team7_airpay/public-ui/styles.css @@ -0,0 +1,177 @@ +:root { + --bg: #f3efe7; + --panel: #fffdf8; + --ink: #112018; + --muted: #59675f; + --line: #d6ddcf; + --brand: #0f8a5f; + --brand-dark: #0b5a3e; + --accent: #f4c95d; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: "Manrope", sans-serif; + background: + radial-gradient(circle at top left, #fff7d6, transparent 28%), + linear-gradient(180deg, #eef4eb 0%, var(--bg) 100%); + color: var(--ink); +} + +.page { + width: min(1120px, calc(100% - 32px)); + margin: 0 auto; + padding: 48px 0 64px; +} + +.hero { + max-width: 700px; + margin-bottom: 32px; +} + +.eyebrow { + margin: 0 0 10px; + font-size: 12px; + font-weight: 800; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--brand-dark); +} + +.hero h1 { + margin: 0; + font-size: clamp(2.4rem, 5vw, 4.8rem); + line-height: 0.96; +} + +.copy { + max-width: 620px; + margin: 16px 0 0; + font-size: 1rem; + line-height: 1.7; + color: var(--muted); +} + +.screens { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 24px; +} + +.phone-card { + min-height: 640px; + padding: 18px; + border: 1px solid var(--line); + border-radius: 32px; + background: var(--panel); + box-shadow: 0 18px 50px rgba(17, 32, 24, 0.08); +} + +.phone-card.accent { + background: linear-gradient(180deg, #fff7df 0%, #fffdf8 100%); +} + +.status-row { + display: flex; + justify-content: space-between; + margin-bottom: 24px; + font-size: 0.9rem; + font-weight: 700; +} + +.screen-body { + display: flex; + flex-direction: column; + height: calc(100% - 32px); +} + +.label { + margin: 0 0 8px; + color: var(--brand-dark); + font-size: 0.9rem; + font-weight: 700; +} + +.screen-body h2 { + margin: 0 0 20px; + font-size: 2rem; + line-height: 1.05; +} + +.qr-box { + display: grid; + place-items: center; + width: 100%; + aspect-ratio: 1; + border: 2px dashed var(--line); + border-radius: 24px; + margin: 8px 0 20px; + background: + linear-gradient(45deg, rgba(15, 138, 95, 0.08), rgba(244, 201, 93, 0.16)); + font-size: 3rem; + font-weight: 800; +} + +.detail-block { + padding: 16px 18px; + margin-bottom: 14px; + border: 1px solid var(--line); + border-radius: 18px; + background: rgba(255, 255, 255, 0.75); +} + +.detail-block span, +.muted { + color: var(--muted); +} + +.detail-block strong { + display: block; + margin-top: 6px; + font-size: 1.05rem; +} + +.success-dot { + width: 92px; + height: 92px; + border-radius: 50%; + margin: 26px auto 18px; + background: + radial-gradient(circle at 30% 30%, #8ae1bd, var(--brand) 68%, var(--brand-dark)); + box-shadow: 0 12px 30px rgba(15, 138, 95, 0.25); +} + +button { + border: 0; + border-radius: 999px; + padding: 15px 18px; + font: inherit; + font-weight: 800; + cursor: pointer; +} + +.primary { + margin-top: auto; + background: var(--brand); + color: white; +} + +.secondary { + margin-top: 12px; + background: #ecf2eb; + color: var(--ink); +} + +@media (max-width: 900px) { + .screens { + grid-template-columns: 1fr; + } + + .phone-card { + min-height: 520px; + } +} diff --git a/submissions/team7_airpay/screenshots/.gitkeep b/submissions/team7_airpay/screenshots/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/submissions/team7_airpay/screenshots/.gitkeep @@ -0,0 +1 @@ + diff --git a/submissions/team7_airpay/screenshots/airpay-1.jpeg b/submissions/team7_airpay/screenshots/airpay-1.jpeg new file mode 100644 index 0000000..55da94a Binary files /dev/null and b/submissions/team7_airpay/screenshots/airpay-1.jpeg differ diff --git a/submissions/team7_airpay/screenshots/airpay-2.jpeg b/submissions/team7_airpay/screenshots/airpay-2.jpeg new file mode 100644 index 0000000..57e54a3 Binary files /dev/null and b/submissions/team7_airpay/screenshots/airpay-2.jpeg differ diff --git a/submissions/team7_airpay/screenshots/airpay-3.jpeg b/submissions/team7_airpay/screenshots/airpay-3.jpeg new file mode 100644 index 0000000..7a0b4ac Binary files /dev/null and b/submissions/team7_airpay/screenshots/airpay-3.jpeg differ