Spaces:
Build error
Build error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Perceptra AI: Intelligent Vision. Secure Insights. Real-time Understanding.</title> | |
| <style> | |
| /* Global Reset and Base Styles */ | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif; | |
| line-height: 1.6; | |
| color: #E5E7EB; | |
| overflow-x: hidden; | |
| background: #0B1121; | |
| font-weight: 400; | |
| letter-spacing: -0.01em; | |
| } | |
| /* Hero Section */ | |
| .hero { | |
| height: 100vh; | |
| background: linear-gradient(135deg, #0B1121 0%, #1E293B 50%, #334155 100%); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .hero::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: radial-gradient(circle at 20% 80%, rgba(59, 130, 246, 0.1) 0%, transparent 50%), | |
| radial-gradient(circle at 80% 20%, rgba(99, 102, 241, 0.1) 0%, transparent 50%); | |
| pointer-events: none; | |
| } | |
| .hero-content { | |
| text-align: center; | |
| z-index: 2; | |
| position: relative; | |
| max-width: 1000px; | |
| padding: 0 2rem; | |
| } | |
| .hero h1 { | |
| font-size: clamp(2.5rem, 5vw, 4.5rem); | |
| font-weight: 700; | |
| margin-bottom: 1.5rem; | |
| color: #FFFFFF; | |
| line-height: 1.1; | |
| letter-spacing: -0.02em; | |
| } | |
| .hero p { | |
| font-size: clamp(1.1rem, 2vw, 1.3rem); | |
| color: #94A3B8; | |
| margin-bottom: 3rem; | |
| max-width: 700px; | |
| margin-left: auto; | |
| margin-right: auto; | |
| font-weight: 400; | |
| line-height: 1.7; | |
| } | |
| .cta-buttons { | |
| display: flex; | |
| gap: 1.5rem; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| } | |
| .btn { | |
| padding: 1rem 2rem; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| text-decoration: none; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| font-family: inherit; | |
| letter-spacing: -0.01em; | |
| } | |
| .btn-primary { | |
| background: #3B82F6; | |
| color: #FFFFFF; | |
| box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.25); | |
| } | |
| .btn-primary:hover { | |
| background: #2563EB; | |
| transform: translateY(-1px); | |
| box-shadow: 0 6px 20px 0 rgba(59, 130, 246, 0.35); | |
| } | |
| .btn-secondary { | |
| background: transparent; | |
| color: #E5E7EB; | |
| border: 1px solid #374151; | |
| } | |
| .btn-secondary:hover { | |
| background: #374151; | |
| border-color: #4B5563; | |
| transform: translateY(-1px); | |
| } | |
| /* Navigation */ | |
| .navbar { | |
| position: fixed; | |
| top: 0; | |
| width: 100%; | |
| background: rgba(11, 17, 33, 0.8); | |
| backdrop-filter: blur(20px); | |
| z-index: 1000; | |
| padding: 1rem 0; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.08); | |
| } | |
| .nav-container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 0 2rem; | |
| } | |
| .logo { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| color: #FFFFFF; | |
| letter-spacing: -0.02em; | |
| } | |
| .nav-links { | |
| display: flex; | |
| list-style: none; | |
| gap: 2rem; | |
| } | |
| .nav-links a { | |
| color: #94A3B8; | |
| text-decoration: none; | |
| font-weight: 500; | |
| transition: color 0.2s ease; | |
| font-size: 0.95rem; | |
| } | |
| .nav-links a:hover { | |
| color: #FFFFFF; | |
| } | |
| /* Demo Section */ | |
| .demo-section, .features-section, .metrics-section, .research-section { | |
| padding: 6rem 0; | |
| background: #0B1121; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 0 2rem; | |
| } | |
| .section-title { | |
| text-align: center; | |
| font-size: clamp(2rem, 4vw, 3rem); | |
| font-weight: 700; | |
| margin-bottom: 1rem; | |
| color: #FFFFFF; | |
| letter-spacing: -0.02em; | |
| } | |
| .section-subtitle { | |
| text-align: center; | |
| font-size: 1.1rem; | |
| color: #94A3B8; | |
| margin-bottom: 4rem; | |
| max-width: 600px; | |
| margin-left: auto; | |
| margin-right: auto; | |
| } | |
| .demo-container { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 3rem; | |
| align-items: start; | |
| } | |
| /* Cards */ | |
| .upload-area.flask-form-container h3 { | |
| color:#feca57; | |
| } | |
| .upload-area.flask-form-container, .results-panel, .feature-card, .metric-card, .research-card { | |
| background: #1E293B; | |
| border: 1px solid #334155; | |
| border-radius: 12px; | |
| padding: 2.5rem; | |
| transition: all 0.2s ease; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| } | |
| /* .upload-area.flask-form-container:hover, .results-panel:hover, .feature-card:hover, .research-card:hover { | |
| border-color: #475569; | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |
| } */ | |
| .upload-area.flask-form-container:hover, .results-panel:hover, .feature-card:hover, .research-card:hover { | |
| transform: translateY(-7px); /* Consistent lift */ | |
| border-color: #3B82F6; /* Accent blue border */ | |
| box-shadow: | |
| 0 12px 30px -5px rgba(0, 0, 0, 0.2), /* Main lift shadow */ | |
| 0 0 20px rgba(59, 130, 246, 0.4); /* Subtle blue glow */ /* NEW */ | |
| } | |
| .flask-form-container h3, .results-panel h3 { | |
| font-size: 1.5rem; | |
| color: #FFFFFF; | |
| margin-bottom: 1rem; | |
| font-weight: 600; | |
| text-align: center; | |
| } | |
| .flask-form-container p, .results-panel p { | |
| color: #94A3B8; | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| } | |
| .flask-file-input { | |
| display: block; | |
| width: 100%; | |
| padding: 0.75rem; | |
| border: 1px solid #374151; | |
| border-radius: 8px; | |
| margin-bottom: 1.5rem; | |
| font-size: 1rem; | |
| cursor: pointer; | |
| background-color: #111827; | |
| color: #E5E7EB; | |
| transition: border-color 0.2s ease; | |
| } | |
| .flask-file-input:hover { | |
| border-color: #4B5563; | |
| } | |
| .flask-file-input::file-selector-button { | |
| background-color: #3B82F6; | |
| color: #FFFFFF; | |
| border: none; | |
| padding: 0.5rem 1rem; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| margin-right: 1rem; | |
| font-weight: 500; | |
| transition: background-color 0.2s ease; | |
| } | |
| .flask-file-input::file-selector-button:hover { | |
| background-color: #2563EB; | |
| } | |
| .flask-submit-btn { | |
| width: 100%; | |
| padding: 0.875rem 1.5rem; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| background: #3B82F6; | |
| color: #FFFFFF; | |
| box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.25); | |
| } | |
| .flask-submit-btn:hover { | |
| background: #2563EB; | |
| transform: translateY(-1px); | |
| box-shadow: 0 6px 20px 0 rgba(59, 130, 246, 0.35); | |
| } | |
| .flask-result-box { | |
| background: #111827; | |
| padding: 1.5rem; | |
| border-radius: 8px; | |
| border-left: 4px solid #3B82F6; | |
| margin-top: 2rem; | |
| text-align: left; | |
| } | |
| .flask-result-box h3 { | |
| font-size: 1.25rem; | |
| color: #FFFFFF; | |
| margin-bottom: 1rem; | |
| text-align: center; | |
| font-weight: 600; | |
| } | |
| .flask-result-box p { | |
| font-size: 1rem; | |
| color: #D1D5DB; | |
| line-height: 1.6; | |
| word-wrap: break-word; | |
| } | |
| .flask-uploaded-image { | |
| max-width: 100%; | |
| height: auto; | |
| border-radius: 8px; | |
| margin: 1.5rem 0; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
| } | |
| /* Flash Messages */ | |
| .flash-message { | |
| padding: 1rem 1.5rem; | |
| margin-bottom: 1rem; | |
| border-radius: 8px; | |
| font-weight: 500; | |
| text-align: center; | |
| } | |
| .flash-success { | |
| background-color: rgba(34, 197, 94, 0.1); | |
| color: #22C55E; | |
| border: 1px solid rgba(34, 197, 94, 0.2); | |
| } | |
| .flash-error { | |
| background-color: rgba(239, 68, 68, 0.1); | |
| color: #EF4444; | |
| border: 1px solid rgba(239, 68, 68, 0.2); | |
| } | |
| /* Tabs */ | |
| .tabs { | |
| display: flex; | |
| border-bottom: 1px solid #334155; | |
| margin-bottom: 2rem; | |
| } | |
| .tab { | |
| padding: 1rem 1.5rem; | |
| cursor: pointer; | |
| font-weight: 500; | |
| color: #94A3B8; | |
| border-bottom: 2px solid transparent; | |
| transition: all 0.2s ease; | |
| font-size: 0.95rem; | |
| } | |
| .tab.active { | |
| color: #3B82F6; | |
| border-color: #3B82F6; | |
| } | |
| .tab:hover:not(.active) { | |
| color: #FFFFFF; | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* Video Captioning Section */ | |
| .video-captioning-section { | |
| padding: 6rem 0; | |
| background: linear-gradient(135deg, #1E293B 0%, #334155 100%); | |
| text-align: center; | |
| margin: 4rem 0; | |
| border-radius: 16px; | |
| } | |
| .video-captioning-section h2 { | |
| font-size: clamp(2rem, 4vw, 3rem); | |
| font-weight: 700; | |
| margin-bottom: 1rem; | |
| color: #FFFFFF; | |
| letter-spacing: -0.02em; | |
| } | |
| .video-captioning-section p { | |
| font-size: 1.1rem; | |
| color: #94A3B8; | |
| margin-bottom: 2rem; | |
| max-width: 600px; | |
| margin-left: auto; | |
| margin-right: auto; | |
| line-height: 1.7; | |
| } | |
| .video-cta-btn { | |
| padding: 1rem 2rem; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| text-decoration: none; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| background: #3B82F6; | |
| color: #FFFFFF; | |
| box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.25); | |
| } | |
| .video-cta-btn:hover { | |
| background: #2563EB; | |
| transform: translateY(-1px); | |
| box-shadow: 0 6px 20px 0 rgba(59, 130, 246, 0.35); | |
| } | |
| /* Features Grid */ | |
| .features-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); | |
| gap: 2rem; | |
| margin-top: 3rem; | |
| } | |
| .feature-icon { | |
| font-size: 2.5rem; | |
| margin-bottom: 1.5rem; | |
| display: block; | |
| color: #3B82F6; | |
| text-align: center; | |
| } | |
| .feature-card h3 { | |
| font-size: 1.25rem; | |
| color: #feca57; | |
| margin-bottom: 1rem; | |
| font-weight: 600; | |
| text-align: center; | |
| } | |
| .feature-card p { | |
| color: whitesmoke; | |
| text-align: center; | |
| line-height: 1.6; | |
| } | |
| /* Architecture Section */ | |
| .architecture-section { | |
| padding: 6rem 0; | |
| background: #111827; | |
| } | |
| .architecture-viz { | |
| background: #1E293B; | |
| border: 1px solid #334155; | |
| border-radius: 12px; | |
| padding: 3rem; | |
| margin-top: 3rem; | |
| } | |
| .network-diagram-group { | |
| margin-bottom: 3rem; | |
| padding: 2rem; | |
| border: 1px solid #374151; | |
| border-radius: 12px; | |
| background: #111827; | |
| } | |
| .network-diagram-group h4 { | |
| color: #3B82F6; | |
| margin-bottom: 2rem; | |
| font-size: 1.5rem; | |
| text-align: center; | |
| font-weight: 600; | |
| } | |
| .network-diagram { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin: 1.5rem 0; | |
| flex-wrap: wrap; | |
| gap: 1.5rem; | |
| } | |
| .network-node { | |
| background: #1E293B; | |
| border: 1px solid #334155; | |
| border-radius: 8px; | |
| padding: 1.5rem; | |
| text-align: center; | |
| transition: all 0.2s ease; | |
| cursor: pointer; | |
| min-width: 150px; | |
| flex-grow: 1; | |
| } | |
| .network-node:hover { | |
| background: #334155; | |
| border-color: #475569; | |
| transform: translateY(-2px); | |
| } | |
| .network-node h4 { | |
| font-size: 1rem; | |
| margin-bottom: 0.5rem; | |
| color: #feca57; | |
| font-weight: 600; | |
| } | |
| .network-node p { | |
| font-size: 0.875rem; | |
| color: whitesmoke; | |
| } | |
| .arrow { | |
| font-size: 1.5rem; | |
| color: #3B82F6; | |
| flex-shrink: 0; | |
| margin: 0 0.5rem; | |
| } | |
| /* Metrics Grid */ | |
| .metrics-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 2rem; | |
| margin-top: 3rem; | |
| } | |
| .metric-card { | |
| text-align: center; | |
| padding: 2.5rem 2rem; | |
| } | |
| .metric-value { | |
| font-size: 2.5rem; | |
| font-weight: 700; | |
| color: #3B82F6; | |
| margin-bottom: 0.5rem; | |
| line-height: 1; | |
| } | |
| .metric-label { | |
| color: #feca57; | |
| font-weight: 500; | |
| font-size: 0.95rem; | |
| } | |
| /* Research Grid */ | |
| .research-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); | |
| gap: 2rem; | |
| margin-top: 3rem; | |
| } | |
| .research-card h3 { | |
| color: #feca57; | |
| font-size: 1.25rem; | |
| margin-bottom: 1rem; | |
| font-weight: 700; | |
| } | |
| .research-card p { | |
| color: whitesmoke; | |
| margin-bottom: 1.5rem; | |
| line-height: 1.6; | |
| } | |
| .research-card .btn { | |
| background: #3B82F6; | |
| color: #FFFFFF; | |
| box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.25); | |
| } | |
| .research-card .btn:hover { | |
| background: #2563EB; | |
| box-shadow: 0 6px 20px 0 rgba(59, 130, 246, 0.35); | |
| } | |
| /* Footer */ | |
| .footer { | |
| background: #111827; | |
| color: white; | |
| padding: 3rem 0; | |
| text-align: center; | |
| } | |
| .footer-content { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 2rem; | |
| margin-bottom: 2rem; | |
| } | |
| .footer-section h3 { | |
| margin-bottom: 1rem; | |
| color: #feca57; | |
| font-weight: 700; | |
| font-size: 1.3rem; | |
| } | |
| /* Footer links */ | |
| .footer-section p a { | |
| color: white; /* Muted grey for text, consistent with other footer text */ | |
| text-decoration: none; | |
| transition: color 0.3s ease; /* Smooth transition for hover effect */ | |
| } | |
| .footer-section p a:hover { | |
| color: #E74C3C; /* Professional red on hover */ | |
| } | |
| .social-links { | |
| display: flex; | |
| flex-direction: column; /* Stacks the h3 and the new div vertically */ | |
| align-items: center; | |
| gap: 1rem; | |
| margin-top: 2rem; | |
| } | |
| .social-links h3 { | |
| color: #feca57; | |
| font-weight: 700; | |
| font-size: 1.3rem; | |
| } | |
| .social-link { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 50px; | |
| height: 50px; | |
| background: #3B82F6; | |
| border-radius: 50%; | |
| color: white; | |
| text-decoration: none; | |
| font-size: 1.5rem; | |
| transition: transform 0.3s ease; | |
| } | |
| .social-icons-row { | |
| display: flex; /* Makes the icons themselves display in a row */ | |
| justify-content: center; /* Centers the icons within their new div */ | |
| gap: 1rem; /* Space between the icons */ | |
| } | |
| .social-link:hover { | |
| transform: scale(1.1) rotate(360deg); | |
| } | |
| /* Responsive Design */ | |
| @media (max-width: 768px) { | |
| .hero h1 { | |
| font-size: 2.5rem; | |
| } | |
| .demo-container { | |
| grid-template-columns: 1fr; | |
| } | |
| .nav-links { | |
| display: none; | |
| } | |
| .features-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .network-diagram { | |
| flex-direction: column; | |
| } | |
| .arrow { | |
| transform: rotate(90deg); | |
| } | |
| } | |
| /* Animations */ | |
| @keyframes pulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| } | |
| .pulse { | |
| animation: pulse 2s infinite; | |
| } | |
| /* Processing Animation */ | |
| .processing { | |
| display: inline-block; | |
| width: 40px; | |
| height: 40px; | |
| border: 3px solid #374151; | |
| border-top: 3px solid #3B82F6; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| /* Progress Bar */ | |
| .progress-bar { | |
| width: 100%; | |
| height: 8px; | |
| background: #f0f0f0; | |
| border-radius: 4px; | |
| overflow: hidden; | |
| margin: 1rem 0; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(45deg, #667eea, #764ba2); | |
| width: 0%; | |
| transition: width 0.3s ease; | |
| border-radius: 4px; | |
| } | |
| /* Modal and 3D content */ | |
| .modal-content-3d { | |
| width: 100%; | |
| height: 500px; | |
| background-color: #111827; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| } | |
| .modal-content-3d canvas { | |
| display: block; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| /* Segmentation results */ | |
| .segmentation-results-display { | |
| background: #111827; | |
| padding: 1.5rem; | |
| border-radius: 8px; | |
| border-left: 4px solid #3B82F6; | |
| margin-top: 2rem; | |
| text-align: left; | |
| } | |
| .segmentation-results-display h4 { | |
| font-size: 1.25rem; | |
| color: #FFFFFF; | |
| margin-bottom: 1rem; | |
| text-align: center; | |
| font-weight: 600; | |
| } | |
| .segmentation-results-display ul { | |
| list-style-type: none; | |
| padding: 0; | |
| margin-top: 1rem; | |
| } | |
| .segmentation-results-display li { | |
| background-color: rgba(59, 130, 246, 0.1); | |
| margin-bottom: 0.5rem; | |
| padding: 0.75rem 1rem; | |
| border-radius: 6px; | |
| font-size: 0.95rem; | |
| color: #E5E7EB; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| border: 1px solid rgba(59, 130, 246, 0.2); | |
| } | |
| .segmentation-results-display li span { | |
| font-weight: 600; | |
| color: #FFFFFF; | |
| } | |
| /* Utility classes */ | |
| .text-center { text-align: center; } | |
| .text-left { text-align: left; } | |
| .text-right { text-align: right; } | |
| .mb-1 { margin-bottom: 0.25rem; } | |
| .mb-2 { margin-bottom: 0.5rem; } | |
| .mb-3 { margin-bottom: 0.75rem; } | |
| .mb-4 { margin-bottom: 1rem; } | |
| .mt-1 { margin-top: 0.25rem; } | |
| .mt-2 { margin-top: 0.5rem; } | |
| .mt-3 { margin-top: 0.75rem; } | |
| .mt-4 { margin-top: 1rem; } | |
| </style> | |
| <!-- FontAwesome for icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> | |
| <!-- Three.js Library --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <!-- OrbitControls for camera interaction --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/controls/OrbitControls.min.js"></script> | |
| </head> | |
| <body> | |
| <!-- Navigation --> | |
| <nav class="navbar"> | |
| <div class="nav-container"> | |
| <div class="logo">Perceptra AI</div> | |
| <ul class="nav-links"> | |
| <li><a href="#demo">Live Demo</a></li> | |
| <li><a href="#features">Features</a></li> | |
| <li><a href="#architecture">Architecture</a></li> | |
| <li><a href="#metrics">Performance</a></li> | |
| <li><a href="#research">Research</a></li> | |
| <li><a href="/logout">Logout</a></li> {# Added Logout link #} | |
| </ul> | |
| </div> | |
| </nav> | |
| <!-- Hero Section --> | |
| <section class="hero"> | |
| <div class="hero-content"> | |
| <h1>Perceptra AI: Unlocking Visual Intelligence</h1> | |
| <p >Transforming static images and live video streams into actionable insights. | |
| Experience custom-built AI for intelligent captioning, precise segmentation, and real-time visual understanding, | |
| all powered by advanced, secure technology.</p> | |
| <div class="cta-buttons"> | |
| <a href="#demo" class="btn btn-primary"> | |
| 🚀 Try Live Demo | |
| </a> | |
| <a href="#architecture" class="btn btn-secondary"> | |
| 🧠 Explore Architecture | |
| </a> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Demo Section (Image Captioning & Segmentation) --> | |
| <section id="demo" class="demo-section"> | |
| <div class="container"> | |
| <h2 class="section-title">Visual Intelligence Studio</h2> | |
| <div class="demo-container"> | |
| <!-- Flask Image Captioning Form --> | |
| <div class="upload-area flask-form-container"> | |
| <h3>Upload Your Image for Analysis</h3> | |
| <p>Drag & drop an image or click to browse</p> | |
| <!-- Flash Messages --> | |
| {% with messages = get_flashed_messages(with_categories=true) %} | |
| {% if messages %} | |
| <div class="mb-4"> | |
| {% for category, message in messages %} | |
| <div class="flash-message flash-{{ category }}"> | |
| {{ message }} | |
| </div> | |
| {% endfor %} | |
| </div> | |
| {% endif %} | |
| {% endwith %} | |
| <form action="/predict" method="post" enctype="multipart/form-data"> | |
| <input id="imageInput" name="file" type="file" accept="image/*" required class="flask-file-input"> | |
| <p style="font-size: 0.9em; color: #777; margin-top: 0.5rem; margin-bottom: 1rem;">PNG, JPG, JPEG, GIF formats allowed.</p> | |
| <button type="submit" class="flask-submit-btn"> | |
| Analyze Image | |
| </button> | |
| </form> | |
| </div> | |
| <!-- Results Panel --> | |
| <div class="results-panel"> | |
| <div class="tabs"> | |
| <div class="tab {% if not segmentation_image_url %}active{% endif %}" data-tab="caption">📝 Caption</div> | |
| <div class="tab {% if segmentation_image_url %}active{% endif %}" data-tab="segment">🎯 Segmentation</div> | |
| </div> | |
| <div class="tab-content {% if not segmentation_image_url %}active{% endif %}" id="caption"> | |
| <h3>Generated Caption</h3> | |
| <div id="captionResult"> | |
| {% if caption %} | |
| <div class="flask-result-box"> | |
| <h3>Your Uploaded Image:</h3> | |
| {% if uploaded_image_url %} | |
| <img src="{{ uploaded_image_url }}" alt="Uploaded Image" class="flask-uploaded-image"> | |
| {% endif %} | |
| <h3>Generated Caption:</h3> | |
| <p>"{{ caption }}"</p> | |
| </div> | |
| {% else %} | |
| <p style="color: #666; font-style: italic;">Upload an image to see the AI-generated caption...</p> | |
| {% endif %} | |
| </div> | |
| </div> | |
| <div class="tab-content {% if segmentation_image_url %}active{% endif %}" id="segment"> | |
| <h3>Segmentation Results</h3> | |
| <div id="segmentResult"> | |
| {% if segmentation_image_url %} | |
| <div class="segmentation-results-display"> | |
| <h4>Segmented Image:</h4> | |
| <img src="{{ segmentation_image_url }}" alt="Segmented Image" class="flask-uploaded-image"> | |
| {% if segmentation_metrics.num_objects is defined %} | |
| <h4>Detected Objects ({{ segmentation_metrics.num_objects }}):</h4> | |
| <ul> | |
| {% for obj in segmentation_metrics.detected_objects %} | |
| <li><span>{{ obj }}</span></li> | |
| {% endfor %} | |
| {% if segmentation_metrics.error %} | |
| <li style="color: red;">Error: {{ segmentation_metrics.error }}</li> | |
| {% endif %} | |
| </ul> | |
| {% elif segmentation_metrics.status %} | |
| <p style="color: #666;">{{ segmentation_metrics.status }}</p> | |
| {% else %} | |
| <p style="color: #666; font-style: italic;">No segmentation results available. Upload an image to analyze.</p> | |
| {% endif %} | |
| </div> | |
| {% else %} | |
| <p style="color: #666; font-style: italic;">Segmentation masks will appear here after image analysis.</p> | |
| <div style="text-align: center; margin-top: 1rem;"> | |
| <img src="https://placehold.co/400x250/cccccc/333333?text=Segmentation+Preview" alt="Segmentation Placeholder" class="flask-uploaded-image"> | |
| <p style="margin-top: 0.5rem; font-size: 0.9em; color: #777;">Placeholder image until live segmentation is ready.</p> | |
| </div> | |
| {% endif %} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Dedicated LiveSense AI: Real-time Video Captioning Section --> | |
| <section id="video-captioning" class="video-captioning-section"> | |
| <div class="container"> | |
| <h2 class="section-title">LiveSense AI: Real-time Video Understanding</h2> | |
| <p>Step into the future of dynamic vision. Our dedicated LiveSense AI platform offers instant, intelligent descriptions of live video feeds, transforming real-world events into actionable insights.</p> | |
| <a href="[Your LiveSense AI App URL Here]" target="_blank" class="video-cta-btn"> | |
| Launch LiveSense AI Application 🚀 | |
| </a> | |
| </div> | |
| </section> | |
| <!-- END Dedicated LiveSense AI: Real-time Video Captioning Section --> | |
| <!-- Features Section --> | |
| <section id="features" class="features-section"> | |
| <div class="container"> | |
| <h2 class="section-title">Core Capabilities & Innovation</h2> | |
| <div class="features-grid"> | |
| <div class="feature-card"> | |
| <span class="feature-icon">👁️</span> | |
| <h3>Intelligent Image Captioning</h3> | |
| <p>Our custom-built deep learning model accurately describes the content of static images, transforming visual data into rich, human-like narratives.</p> | |
| </div> | |
| <div class="feature-card"> | |
| <span class="feature-icon">🎯</span> | |
| <h3>Precision Image Segmentation</h3> | |
| <p>Leveraging advanced techniques, we precisely identify and segment objects within images, providing detailed insights into scene composition and object boundaries.</p> | |
| </div> | |
| <div class="feature-card"> | |
| <span class="feature-icon">⚡</span> | |
| <h3>Real-time Dynamic Vision</h3> | |
| <p>Experience instantaneous understanding of live video streams. Our optimized AI processes webcam feeds in real-time, providing continuous, intelligent descriptions and tracking of evolving scenes as they happen.</p> | |
| </div> | |
| <div class="feature-card"> | |
| <span class="feature-icon">🔐</span> | |
| <h3>Robust Biometric Security</h3> | |
| <p>Safeguard access to sensitive AI capabilities with our multi-layered authentication. Featuring secure facial recognition and traditional email/password login, we ensure unparalleled user protection and data integrity.</p> | |
| </div> | |
| <div class="feature-card"> | |
| <span class="feature-icon">🧠</span> | |
| <h3>Proprietary Deep Learning Engine</h3> | |
| <p>Driven by custom-engineered neural architectures, including bespoke CNN-LSTM for captioning and advanced segmentation networks. Developed entirely from scratch for optimized performance and unique insights.</p> | |
| </div> | |
| <div class="feature-card"> | |
| <span class="feature-icon">📊</span> | |
| <h3>Performance & Operational Intelligence</h3> | |
| <p>Designed for high-throughput and low-latency operations, our system features adaptive processing, intelligent caching, and comprehensive performance analytics, ensuring scalable and reliable AI service delivery.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Architecture Section --> | |
| <section id="architecture" class="architecture-section"> | |
| <div class="container"> | |
| <h2 class="section-title">Perceptra AI: Integrated Vision & Security Architecture</h2> | |
| <div class="architecture-viz"> | |
| <h3>Core AI & System Components Overview</h3> | |
| <div class="network-diagram-group"> | |
| <h4>1. Static Image Analysis Pipeline</h4> | |
| <div class="network-diagram"> | |
| <div class="network-node" data-info="The entry point for static images uploaded by users."> | |
| <h4>Image Input</h4> | |
| <p>Files/URLs</p> | |
| </div> | |
| <div class="arrow">→</div> | |
| <div class="network-node" data-info="Our custom-built model: ResNet50 Encoder extracts features, fed into an LSTM Decoder with Attention for generating descriptive captions."> | |
| <h4>Image Captioning Module</h4> | |
| <p>ResNet50-LSTM-Attention</p> | |
| </div> | |
| <div class="arrow">→</div> | |
| <div class="network-node" data-info="Integration of the powerful YOLOv8x-seg model for accurate object detection and precise instance segmentation."> | |
| <h4>Image Segmentation Module</h4> | |
| <p>YOLOv8x-seg</p> | |
| </div> | |
| <div class="arrow">→</div> | |
| <div class="network-node" data-info="Structured JSON outputs containing generated captions, identified objects, and segmentation masks."> | |
| <h4>Analyzed Output</h4> | |
| <p>Captions & Masks</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="network-diagram-group" style="margin-top: 3rem;"> | |
| <h4>2. Real-time Video Intelligence (LiveSense AI)</h4> | |
| <div class="network-diagram"> | |
| <div class="network-node" data-info="Captures live video streams directly from the user's webcam for instant processing."> | |
| <h4>Webcam Input</h4> | |
| <p>Live Stream</p> | |
| </div> | |
| <div class="arrow">→</div> | |
| <div class="network-node" data-info="Utilizes the BLIP model for real-time video understanding, enhanced by adaptive frame sampling, batch processing, and intelligent caching."> | |
| <h4>Dynamic Vision Core</h4> | |
| <p>BLIP & Optimizations</p> | |
| </div> | |
| <div class="arrow">→</div> | |
| <div class="network-node" data-info="Provides continuous, contextually rich descriptions of evolving scenes, displayed instantly in the UI."> | |
| <h4>Live Caption Stream</h4> | |
| <p>Real-time Output</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="network-diagram-group" style="margin-top: 3rem;"> | |
| <h4>3. Secure Identity & Application Layer</h4> | |
| <div class="network-diagram"> | |
| <div class="network-node" data-info="Supports user authentication via email/password and advanced facial recognition, capturing biometric data securely."> | |
| <h4>User Inputs</h4> | |
| <p>Biometrics & Passwords</p> | |
| </div> | |
| <div class="arrow">→</div> | |
| <div class="network-node" data-info="The core Flask backend orchestrates all AI services, handles API requests, and manages data flow."> | |
| <h4>Backend Orchestration</h4> | |
| <p>Flask API & Logic</p> | |
| </div> | |
| <div class="arrow">→</div> | |
| <div class="network-node" data-info="Securely stores user credentials, face encodings, and manages authentication states using SQLAlchemy."> | |
| <h4>User Database</h4> | |
| <p>SQLite/SQLAlchemy</p> | |
| </div> | |
| <div class="arrow">→</div> | |
| <div class="network-node" data-info="The user-facing web interface built with HTML, CSS, and JavaScript, providing interactive demos and real-time displays."> | |
| <h4>Frontend Interface</h4> | |
| <p>UI/UX</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-top: 4rem;"> | |
| <!-- <button class="btn btn-primary" onclick="showArchitectureDetails()"> | |
| 🔍 Explore Conceptual 3D Model | |
| </button> --> | |
| <p style="color: rgba(255,255,255,0.7); font-size: 0.9em; margin-top: 1rem;"> | |
| Hover over nodes for details. The 3D model provides a conceptual visualization of a core AI pipeline within our system. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Metrics Section --> | |
| <!-- Metrics Section --> | |
| <section id="metrics" class="metrics-section"> | |
| <div class="container"> | |
| <h2 class="section-title">Performance Metrics</h2> | |
| <div class="metrics-grid"> | |
| <div class="metric-card"> | |
| <div class="metric-value" id="bleuScore">10.49%</div> | |
| <div class="metric-label">BLEU-4 Score</div> | |
| <p class="metric-subtext">For custom, scratch-built model</p> | |
| </div> | |
| <div class="metric-card"> | |
| <div class="metric-value" id="ciderScore">1.03</div> | |
| <div class="metric-label">CIDEr Score</div> | |
| <p class="metric-subtext">Measures agreement with human captions</p> | |
| </div> | |
| <div class="metric-card"> | |
| <div class="metric-value" id="meteorScore">31.58%</div> | |
| <div class="metric-label">METEOR Score</div> | |
| <p class="metric-subtext">Balances precision and recall of unigrams</p> | |
| </div> | |
| <div class="metric-card"> | |
| <div class="metric-value" id="inferenceLatency">27.7 ms</div> | |
| <div class="metric-label">Avg. Inference Latency</div> | |
| <p class="metric-subtext">Time to process one image</p> | |
| </div> | |
| <div class="metric-card"> | |
| <div class="metric-value" id="processingFps">36.1 FPS</div> | |
| <div class="metric-label">Processing Throughput</div> | |
| <p class="metric-subtext">Frames processed per second for live image</p> | |
| </div> | |
| <div class="metric-card"> | |
| <div class="metric-value" id="perplexity" style="font-size: 2rem;">12.43</div> | |
| <div class="metric-label">Perplexity</div> | |
| <p class="metric-subtext">Lower indicates better language model prediction</p> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Research Section --> | |
| <section id="research" class="research-section"> | |
| <div class="container"> | |
| <h2 class="section-title">Research & Innovation</h2> | |
| <div class="research-grid"> | |
| <div class="research-card"> | |
| <h3>📚 Technical Documentation</h3> | |
| <p>Complete research paper with mathematical formulations, architecture details, and experimental results.</p> | |
| <button class="btn btn-primary" style="margin-top: 1rem;">Read Paper</button> | |
| </div> | |
| <!-- <div class="research-card"> | |
| <h3>🔬 Ablation Studies</h3> | |
| <p>Comprehensive analysis of different architectural choices and their impact on model performance.</p> | |
| <button class="btn btn-primary" style="margin-top: 1rem;">View Studies</button> | |
| </div> --> | |
| <div class="research-card"> | |
| <h3>💻 Code Repository</h3> | |
| <p>Open-source implementation with detailed comments, training scripts, and deployment guides.</p> | |
| <button class="btn btn-primary" style="margin-top: 1rem;">GitHub Repo</button> | |
| </div> | |
| <div class="research-card"> | |
| <h3>📊 Training Insights</h3> | |
| <p>Interactive dashboard showing training progress, loss curves, and hyperparameter optimization results.</p> | |
| <button class="btn btn-primary" style="margin-top: 1rem;">Training Dashboard</button> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Footer --> | |
| <footer class="footer"> | |
| <div class="container"> | |
| <div class="footer-content"> | |
| <div class="footer-section"> | |
| <h3>Perceptra AI</h3> {# Updated Brand Name #} | |
| <p style="color: #ccc; margin-top: 0.5rem; font-weight: 500;"><b>Intelligent Vision. Secure Insights. Real-time Understanding.</b></p> {# Added Tagline with styling #} | |
| </div> | |
| <div class="footer-section"> | |
| <h3>Quick Links</h3> | |
| <p><a href="#demo"><b>Live Demo</b></a></p> | |
| <p><a href="#architecture"><b>Architecture</b></a></p> | |
| <p><a href="#research"><b>Research</b></a></p> | |
| <p><a href="/main_app"><b>App Home</b></a></p> {# Added link to main app #} | |
| </div> | |
| <div class="footer-section"> | |
| <h3>Developer</h3> | |
| <p><b>Varsh Dewangan</b></p> {# Your Name #} | |
| <p><b>Data Scientist</b></p> {# Specific role and context #} | |
| </div> | |
| </div> | |
| <div class="social-links"> | |
| <h3>Connect with me!</h3> | |
| <div class="social-icons-row"> {# This new div will hold your icons in a row #} | |
| <a href="mailto:varshadewangan1605@gmail.com" class="social-link">📧</a> {# Link to your email #} | |
| <a href="https://www.linkedin.com/in/varsha-dewangan-197983256/" target="_blank" class="social-link">💼</a> {# Link to your LinkedIn #} | |
| <a href="https://github.com/Varsha-1605" target="_blank" class="social-link">🐙</a> {# Link to your GitHub #} | |
| <a href="https://www.instagram.com/varshadewangan454/" target="_blank" class="social-link">📸</a> {# Replaced Twitter with Instagram #} | |
| </div> | |
| </div> | |
| <p style="margin-top: 2rem; padding-top: 2rem; border-top: 1px solid #333; color: #ccc;"> | |
| <b>© 2024 Perceptra AI. All rights reserved.</b> | |
| </p> | |
| </div> | |
| </footer> | |
| <script> | |
| // Tab functionality (Existing, Modified to clear for Flask output) | |
| document.querySelectorAll('.tab').forEach(tab => { | |
| tab.addEventListener('click', function() { | |
| // Remove active class from all tabs and contents | |
| document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); | |
| document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); | |
| // Add active class to clicked tab | |
| this.classList.add('active'); | |
| // Show corresponding content | |
| const tabId = this.getAttribute('data-tab'); | |
| document.getElementById(tabId).classList.add('active'); | |
| // Clear previous results when switching tabs, except for the Flask-driven caption or segment tab | |
| // The Flask-driven content for caption and segment should persist until a new upload | |
| if (tabId !== 'caption' && tabId !== 'segment') { | |
| // Only clear the analysis tab | |
| document.getElementById('analysisResult').innerHTML = '<p style="color: #666; font-style: italic;">Detailed analysis coming soon from backend...</p>'; | |
| } | |
| }); | |
| }); | |
| // Auto-select the segmentation tab if segmentation_image_url is present after a Flask render | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Use a slightly different check for Flask template variables which might be 'None' string | |
| const segmentationImageUrl = "{{ segmentation_image_url }}"; | |
| const captionContentExists = document.getElementById('captionResult').innerText.trim() !== 'Upload an image to see the AI-generated caption...' && document.getElementById('captionResult').innerText.trim() !== ''; | |
| if (segmentationImageUrl && segmentationImageUrl !== 'None') { | |
| // Deactivate current active tab | |
| document.querySelector('.tab.active')?.classList.remove('active'); | |
| document.querySelector('.tab-content.active')?.classList.remove('active'); | |
| // Activate segmentation tab and content | |
| document.querySelector('.tab[data-tab="segment"]').classList.add('active'); | |
| document.getElementById('segment').classList.add('active'); | |
| } else if (captionContentExists) { | |
| // If no segmentation but caption exists, activate caption tab | |
| document.querySelector('.tab.active')?.classList.remove('active'); | |
| document.querySelector('.tab-content.active')?.classList.remove('active'); | |
| document.querySelector('.tab[data-tab="caption"]').classList.add('active'); | |
| document.getElementById('caption').classList.add('active'); | |
| } | |
| // If neither has content, the default set in HTML (caption tab active) will apply | |
| }); | |
| // The original JavaScript for file upload simulation (processImage, showResults, drag/drop) | |
| // is REMOVED as Flask handles the actual file upload and rendering. | |
| // The HTML form now directly submits to Flask. | |
| // Smooth scrolling for navigation links (Existing) | |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { | |
| anchor.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| const target = document.querySelector(this.getAttribute('href')); | |
| if (target) { | |
| target.scrollIntoView({ | |
| behavior: 'smooth', | |
| block: 'start' | |
| }); | |
| } | |
| }); | |
| }); | |
| // Navbar scroll effect (Existing) | |
| window.addEventListener('scroll', function() { | |
| const navbar = document.querySelector('.navbar'); | |
| if (window.scrollY > 100) { | |
| navbar.style.background = 'rgba(0,0,0,0.9)'; | |
| } else { | |
| navbar.style.background = 'rgba(255,255,255,0.1)'; | |
| } | |
| }); | |
| // Animate metrics when in view (Existing) | |
| function animateMetrics() { | |
| const metrics = document.querySelectorAll('.metric-value'); | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| const target = entry.target; | |
| const finalValue = parseFloat(target.textContent); | |
| let currentValue = 0; | |
| const increment = finalValue / 50; | |
| const timer = setInterval(() => { | |
| currentValue += increment; | |
| if (currentValue >= finalValue) { | |
| currentValue = finalValue; | |
| clearInterval(timer); | |
| } | |
| target.textContent = currentValue.toFixed(1); | |
| }, 30); | |
| observer.unobserve(target); | |
| } | |
| }); | |
| }); | |
| metrics.forEach(metric => observer.observe(metric)); | |
| } | |
| // Architecture details modal (UPDATED for 3D) | |
| let scene, camera, renderer, controls, animationFrameId; | |
| function showArchitectureDetails() { | |
| // Prevent multiple modals if clicked rapidly | |
| if (document.querySelector('.modal')) { | |
| return; | |
| } | |
| const modal = document.createElement('div'); | |
| modal.className = 'modal'; // Add a class for styling | |
| modal.style.cssText = ` | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0,0,0,0.8); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 9999; | |
| backdrop-filter: blur(10px); | |
| transition: opacity 0.3s ease; | |
| opacity: 0; /* Start hidden for fade-in */ | |
| `; | |
| modal.innerHTML = ` | |
| <div style="background: white; border-radius: 20px; padding: 2rem; max-width: 90%; max-height: 90vh; overflow: hidden; position: relative; display: flex; flex-direction: column;"> | |
| <button id="closeModalBtn" style="position: absolute; top: 1rem; right: 1rem; border: none; background: none; font-size: 2rem; cursor: pointer; color: #666;">×</button> | |
| <h2 style="color: #667eea; margin-bottom: 1.5rem; text-align: center; font-size: 2rem;">3D Architecture Visualization</h2> | |
| <div id="architectureCanvasContainer" class="modal-content-3d" style="flex-grow: 1; min-height: 300px; display: flex; justify-content: center; align-items: center;"> | |
| <canvas id="architectureCanvas" style="display: block;"></canvas> | |
| <p id="loadingText" style="color: #ccc; text-align: center; position: absolute;">Loading 3D model...</p> | |
| </div> | |
| <div style="display: flex; flex-wrap: wrap; justify-content: space-around; gap: 1rem; margin-top: 1.5rem; color: #666;"> | |
| <div style="flex: 1; min-width: 250px;"> | |
| <h3 style="color: #667eea; margin-bottom: 0.5rem;">Caption Branch</h3> | |
| <ul style="padding-left: 1.5rem; list-style-type: disc;"> | |
| <li>CNN Feature Extractor</li> | |
| <li>Attention Mechanism</li> | |
| <li>LSTM Decoder</li> | |
| </ul> | |
| </div> | |
| <div style="flex: 1; min-width: 250px;"> | |
| <h3 style="color: #667eea; margin-bottom: 0.5rem;">Segmentation Branch</h3> | |
| <ul style="padding-left: 1.5rem; list-style-type: disc;"> | |
| <li>U-Net Architecture</li> | |
| <li>Skip Connections</li> | |
| <li>Multi-scale Features</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <p style="text-align: center; margin-top: 1rem; font-size: 0.9em; color: #555;"> | |
| Click and drag to rotate the 3D model. Scroll to zoom. | |
| </p> | |
| </div> | |
| `; | |
| document.body.appendChild(modal); | |
| // Fade in modal | |
| setTimeout(() => modal.style.opacity = '1', 10); | |
| // Setup 3D scene after modal is in DOM | |
| const canvasContainer = document.getElementById('architectureCanvasContainer'); | |
| const canvas = document.getElementById('architectureCanvas'); | |
| const loadingText = document.getElementById('loadingText'); | |
| // 1. Scene | |
| scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x1a1a1a); // Dark background for the 3D space | |
| // 2. Camera | |
| camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000); // Set initial aspect to 1, will be updated | |
| camera.position.set(0, 0, 10); // Adjust camera position for better view | |
| // 3. Renderer | |
| renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true }); | |
| renderer.setPixelRatio(window.devicePixelRatio); | |
| // setCanvasSize(); // Initial size setup - call it after objects are added for more reliable dimensions | |
| // 4. Lights | |
| const ambientLight = new THREE.AmbientLight(0x404040); // soft white light | |
| scene.add(ambientLight); | |
| const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.7); | |
| directionalLight1.position.set(5, 5, 5).normalize(); | |
| scene.add(directionalLight1); | |
| const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.5); | |
| directionalLight2.position.set(-5, -5, -5).normalize(); | |
| scene.add(directionalLight2); | |
| // 5. Controls | |
| controls = new OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; // an animation loop is required when damping is enabled | |
| controls.dampingFactor = 0.05; | |
| controls.screenSpacePanning = false; | |
| controls.minDistance = 5; | |
| controls.maxDistance = 20; | |
| // --- Create Model Architecture Elements --- | |
| // Colors | |
| const encoderColor = 0x667eea; // Blue | |
| const attentionColor = 0xfeca57; // Yellow | |
| const decoderColor = 0x764ba2; // Purple | |
| const segmentationColor = 0x4ecdc4; // Teal | |
| const arrowColor = 0xcccccc; // Light grey | |
| const boxGeometry = new THREE.BoxGeometry(2, 2, 2); | |
| const cylinderGeometry = new THREE.CylinderGeometry(0.5, 0.5, 2, 32); | |
| const coneGeometry = new THREE.ConeGeometry(0.7, 1.5, 32); | |
| // Encoder (CNN) | |
| const encoderMaterial = new THREE.MeshPhongMaterial({ color: encoderColor }); | |
| const encoder = new THREE.Mesh(boxGeometry, encoderMaterial); | |
| encoder.position.set(-4, 0, 0); | |
| scene.add(encoder); | |
| // Attention Mechanism | |
| const attentionMaterial = new THREE.MeshPhongMaterial({ color: attentionColor, emissive: attentionColor, emissiveIntensity: 0.3 }); | |
| const attention = new THREE.Mesh(coneGeometry, attentionMaterial); | |
| attention.rotation.z = -Math.PI / 2; // Point towards decoder | |
| attention.position.set(-1.5, 0, 0); // Between encoder and decoder | |
| scene.add(attention); | |
| // Decoder (LSTM) | |
| const decoderMaterial = new THREE.MeshPhongMaterial({ color: decoderColor }); | |
| const decoder = new THREE.Mesh(boxGeometry, decoderMaterial); | |
| decoder.position.set(1.5, 1, 0); // Position slightly up for caption branch | |
| scene.add(decoder); | |
| // Segmentation Branch (U-Net inspired - another block) | |
| const segmentation = new THREE.Mesh(boxGeometry, new THREE.MeshPhongMaterial({ color: segmentationColor })); | |
| segmentation.position.set(1.5, -1, 0); // Position slightly down for segmentation branch | |
| scene.add(segmentation); | |
| // Arrows (data flow) | |
| const arrowMaterial = new THREE.MeshBasicMaterial({ color: arrowColor }); | |
| const arrow1 = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.2, 0.2), arrowMaterial); // Encoder to Attention | |
| arrow1.position.set(-2.75, 0, 0); | |
| scene.add(arrow1); | |
| const arrow2 = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.2, 0.2), arrowMaterial); // Attention to Decoder | |
| arrow2.position.set(0, 1, 0); | |
| arrow2.rotation.z = Math.PI / 4; // Angle it towards decoder | |
| scene.add(arrow2); | |
| const arrow3 = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.2, 0.2), arrowMaterial); // Shared Feature to Segmentation | |
| arrow3.position.set(0, -1, 0); | |
| arrow3.rotation.z = -Math.PI / 4; // Angle it towards segmentation | |
| scene.add(arrow3); | |
| // Output Caption Arrow/Block | |
| const outputCaption = new THREE.Mesh(new THREE.BoxGeometry(1, 0.8, 0.8), decoderMaterial); | |
| outputCaption.position.set(4, 1, 0); | |
| scene.add(outputCaption); | |
| const arrow4 = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.2, 0.2), arrowMaterial); | |
| arrow4.position.set(2.75, 1, 0); | |
| scene.add(arrow4); | |
| // Output Segmentation Arrow/Block | |
| const outputSegmentation = new THREE.Mesh(new THREE.BoxGeometry(1, 0.8, 0.8), new THREE.MeshPhongMaterial({ color: segmentationColor })); | |
| outputSegmentation.position.set(4, -1, 0); | |
| scene.add(outputSegmentation); | |
| const arrow5 = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.2, 0.2), arrowMaterial); | |
| arrow5.position.set(2.75, -1, 0); | |
| scene.add(arrow5); | |
| // --- Simple Text Labels (using CanvasTexture for better readability) --- | |
| function createTextSprite(message, color, fontSize = 60) { | |
| const canvas = document.createElement('canvas'); | |
| const context = canvas.getContext('2d'); | |
| context.font = `${fontSize}px Arial`; | |
| context.fillStyle = color; | |
| context.textAlign = 'center'; | |
| context.textBaseline = 'middle'; | |
| const metrics = context.measureText(message); | |
| canvas.width = metrics.width + 20; | |
| canvas.height = fontSize + 20; | |
| context.font = `${fontSize}px Arial`; | |
| context.fillStyle = color; | |
| context.textAlign = 'center'; | |
| context.textBaseline = 'middle'; | |
| context.fillText(message, canvas.width / 2, canvas.height / 2); | |
| const texture = new THREE.CanvasTexture(canvas); | |
| const spriteMaterial = new THREE.SpriteMaterial({ map: texture }); | |
| const sprite = new THREE.Sprite(spriteMaterial); | |
| // Adjust scale based on text length and desired size in 3D | |
| sprite.scale.set(canvas.width * 0.01, canvas.height * 0.01, 1); | |
| return sprite; | |
| } | |
| const encoderLabel = createTextSprite("Encoder", "#ffffff"); | |
| encoderLabel.position.set(encoder.position.x, encoder.position.y + 1.5, encoder.position.z); | |
| scene.add(encoderLabel); | |
| const attentionLabel = createTextSprite("Attention", "#ffffff"); | |
| attentionLabel.position.set(attention.position.x, attention.position.y + 1.2, attention.position.z); | |
| scene.add(attentionLabel); | |
| const decoderLabel = createTextSprite("Decoder", "#ffffff"); | |
| decoderLabel.position.set(decoder.position.x, decoder.position.y + 1.5, decoder.position.z); | |
| scene.add(decoderLabel); | |
| const segmentationLabel = createTextSprite("Segmentation", "#ffffff"); | |
| segmentationLabel.position.set(segmentation.position.x, segmentation.position.y - 1.5, segmentation.position.z); | |
| scene.add(segmentationLabel); | |
| const outputCaptionLabel = createTextSprite("Caption", "#ffffff"); | |
| outputCaptionLabel.position.set(outputCaption.position.x, outputCaption.position.y + 0.8, outputCaption.position.z); | |
| scene.add(outputCaptionLabel); | |
| const outputSegmentationLabel = createTextSprite("Masks", "#ffffff"); | |
| outputSegmentationLabel.position.set(outputSegmentation.position.x, outputSegmentation.position.y - 0.8, outputSegmentation.position.z); | |
| scene.add(outputSegmentationLabel); | |
| // Inside the showArchitectureDetails function | |
| // Set canvas size and hide loading text after all elements are potentially rendered | |
| setTimeout(() => { | |
| // Ensure the container is ready and has dimensions | |
| const canvasContainer = document.getElementById('architectureCanvasContainer'); | |
| const loadingText = document.getElementById('loadingText'); | |
| if (canvasContainer && canvasContainer.clientWidth > 0) { | |
| const width = canvasContainer.clientWidth; | |
| const height = canvasContainer.clientHeight; | |
| renderer.setSize(width, height); | |
| camera.aspect = width / height; | |
| camera.updateProjectionMatrix(); | |
| // Hide the loading text now that the canvas is sized | |
| if (loadingText) { | |
| loadingText.style.display = 'none'; | |
| } | |
| // Start the animation loop | |
| animate(); | |
| } else { | |
| // Fallback or error for if the container isn't ready | |
| console.error("3D canvas container not ready or has no size."); | |
| if (loadingText) { | |
| loadingText.innerText = "Error loading 3D model."; | |
| } | |
| } | |
| }, 100); // 100ms delay to allow the DOM to render the modal | |
| // 6. Animation Loop | |
| function animate() { | |
| animationFrameId = requestAnimationFrame(animate); | |
| controls.update(); // only required if controls.enableDamping or controls.autoRotate are set to true | |
| renderer.render(scene, camera); | |
| } | |
| // Cleanup function for modal close | |
| const closeModal = () => { | |
| cancelAnimationFrame(animationFrameId); // Stop the animation loop | |
| if (controls) controls.dispose(); // Dispose controls to free up event listeners | |
| if (renderer) renderer.dispose(); // Dispose renderer resources | |
| modal.remove(); // Remove modal from DOM | |
| window.removeEventListener('resize', setCanvasSize); // Remove resize listener | |
| // Clear scene to free up memory (optional but good practice) | |
| scene.traverse(object => { | |
| if (!object.isMesh) return; | |
| object.geometry.dispose(); | |
| if (object.material.isMaterial) { | |
| cleanMaterial(object.material); | |
| } else { | |
| // an array of materials | |
| for (const material of object.material) cleanMaterial(material); | |
| } | |
| }); | |
| scene = null; | |
| camera = null; | |
| renderer = null; | |
| controls = null; | |
| animationFrameId = null; | |
| }; | |
| document.getElementById('closeModalBtn').addEventListener('click', closeModal); | |
| modal.addEventListener('click', (e) => { | |
| if (e.target === modal) { // Close when clicking outside the inner content | |
| closeModal(); | |
| } | |
| }); | |
| const setCanvasSize = () => { | |
| if (canvasContainer && camera && renderer) { | |
| const width = canvasContainer.clientWidth; | |
| const height = canvasContainer.clientHeight; | |
| renderer.setSize(width, height); | |
| camera.aspect = width / height; | |
| camera.updateProjectionMatrix(); | |
| } | |
| }; | |
| window.addEventListener('resize', setCanvasSize); | |
| } | |
| // Helper to dispose materials (for cleanup) | |
| function cleanMaterial(material) { | |
| for (const key of Object.keys(material)) { | |
| const value = material[key]; | |
| if (value && typeof value === 'object' && 'dispose' in value) { | |
| value.dispose(); | |
| } | |
| } | |
| } | |
| // Network node hover effects (Existing) | |
| document.querySelectorAll('.network-node').forEach(node => { | |
| node.addEventListener('mouseenter', function() { | |
| const info = this.getAttribute('data-info'); | |
| if (info) { | |
| const tooltip = document.createElement('div'); | |
| tooltip.style.cssText = ` | |
| position: absolute; | |
| background: rgba(0,0,0,0.9); | |
| color: white; | |
| padding: 1rem; | |
| border-radius: 8px; | |
| font-size: 0.9rem; | |
| max-width: 200px; | |
| z-index: 1000; | |
| top: -60px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| pointer-events: none; | |
| `; | |
| tooltip.textContent = info; | |
| tooltip.className = 'tooltip'; | |
| this.style.position = 'relative'; | |
| this.appendChild(tooltip); | |
| } | |
| }); | |
| node.addEventListener('mouseleave', function() { | |
| const tooltip = this.querySelector('.tooltip'); | |
| if (tooltip) { | |
| tooltip.remove(); | |
| } | |
| }); | |
| }); | |
| // Initialize animations (Existing) | |
| document.addEventListener('DOMContentLoaded', function() { | |
| animateMetrics(); | |
| // Add pulse animation to CTA buttons | |
| document.querySelectorAll('.btn-primary').forEach(btn => { | |
| setInterval(() => { | |
| btn.classList.add('pulse'); | |
| setTimeout(() => btn.classList.remove('pulse'), 1000); | |
| }, 5000); | |
| }); | |
| }); | |
| // Add particle animation to hero section (Existing) | |
| function createParticles() { | |
| const hero = document.querySelector('.hero'); | |
| for (let i = 0; i < 50; i++) { | |
| const particle = document.createElement('div'); | |
| particle.style.cssText = ` | |
| position: absolute; | |
| width: 2px; | |
| height: 2px; | |
| background: rgba(255,255,255,0.5); | |
| border-radius: 50%; | |
| left: ${Math.random() * 100}%; | |
| top: ${Math.random() * 100}%; | |
| animation: float ${5 + Math.random() * 10}s infinite linear; | |
| pointer-events: none; | |
| `; | |
| hero.appendChild(particle); | |
| } | |
| } | |
| // Initialize particle animation (Existing) | |
| createParticles(); | |
| // Add typing effect to hero text (Existing) | |
| function typeWriter(element, text, speed = 50) { | |
| let i = 0; | |
| element.innerHTML = ''; | |
| function typing() { | |
| if (i < text.length) { | |
| element.innerHTML += text.charAt(i); | |
| i++; | |
| setTimeout(typing, speed); | |
| } | |
| } | |
| typing(); | |
| } | |
| // Mobile menu toggle (if needed) (Existing) | |
| function toggleMobileMenu() { | |
| const navLinks = document.querySelector('.nav-links'); | |
| navLinks.classList.toggle('active'); | |
| } | |
| // Add mobile styles (already in your original HTML, moved to style tag) (Existing) | |
| const mobileStyles = document.createElement('style'); | |
| mobileStyles.textContent = ` | |
| @media (max-width: 768px) { | |
| .nav-links.active { | |
| display: flex; | |
| flex-direction: column; | |
| position: absolute; | |
| top: 100%; | |
| left: 0; | |
| width: 100%; | |
| background: rgba(0,0,0,0.9); | |
| padding: 2rem; | |
| backdrop-filter: blur(20px); | |
| } | |
| .nav-links.active a { | |
| padding: 1rem 0; | |
| border-bottom: 1px solid rgba(255,255,255,0.1); | |
| } | |
| } | |
| `; | |
| document.head.appendChild(mobileStyles); | |
| </script> | |
| </body> | |
| </html> | |