Spaces:
Paused
Paused
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>{% block title %}TexLab - Professional LaTeX Converter{% endblock %}</title> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"> | |
| <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script> | |
| <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script> | |
| <style> | |
| :root { | |
| --primary: #4361ee; | |
| --primary-dark: #3a56d4; | |
| --secondary: #7209b7; | |
| --accent: #4cc9f0; | |
| --success: #4caf50; | |
| --dark: #212529; | |
| --light: #f8f9fa; | |
| --gray: #6c757d; | |
| --border-radius: 12px; | |
| --box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); | |
| --transition: all 0.3s ease; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| html, body { | |
| height: 100%; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; | |
| background-color: #fafbff; | |
| color: #333; | |
| line-height: 1.6; | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| /* Header & Navigation */ | |
| .navbar { | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| padding: 1rem 0; | |
| box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1); | |
| flex-shrink: 0; | |
| } | |
| .navbar-brand { | |
| font-weight: 700; | |
| font-size: 1.8rem; | |
| color: white ; | |
| display: flex; | |
| align-items: center; | |
| } | |
| .navbar-brand i { | |
| margin-right: 10px; | |
| } | |
| .nav-link { | |
| color: rgba(255, 255, 255, 0.85) ; | |
| font-weight: 500; | |
| padding: 0.5rem 1rem ; | |
| border-radius: 50px; | |
| transition: var(--transition); | |
| margin: 0 5px; | |
| } | |
| .nav-link:hover, .nav-link.active { | |
| color: white ; | |
| background: rgba(255, 255, 255, 0.15) ; | |
| } | |
| .user-info { | |
| display: flex; | |
| align-items: center; | |
| color: white; | |
| font-weight: 500; | |
| padding: 0.5rem 1rem; | |
| border-radius: 50px; | |
| background: rgba(255, 255, 255, 0.15); | |
| margin-left: 1rem; | |
| } | |
| .user-info i { | |
| margin-right: 0.5rem; | |
| } | |
| /* Main Content */ | |
| .main-content { | |
| flex: 1; | |
| padding: 2rem 0; | |
| } | |
| .page-header { | |
| text-align: center; | |
| margin-bottom: 2.5rem; | |
| padding: 0 1rem; | |
| } | |
| .page-title { | |
| font-size: 2.2rem; | |
| font-weight: 800; | |
| margin-bottom: 1rem; | |
| color: var(--dark); | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .page-subtitle { | |
| font-size: 1.1rem; | |
| color: var(--gray); | |
| max-width: 700px; | |
| margin: 0 auto; | |
| } | |
| /* Cards */ | |
| .feature-card { | |
| background: white; | |
| border-radius: var(--border-radius); | |
| padding: 2rem; | |
| box-shadow: var(--box-shadow); | |
| transition: var(--transition); | |
| height: 100%; | |
| border: 1px solid rgba(0, 0, 0, 0.05); | |
| } | |
| .feature-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12); | |
| } | |
| .feature-icon { | |
| width: 70px; | |
| height: 70px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin: 0 auto 1.5rem; | |
| font-size: 1.8rem; | |
| } | |
| .feature-title { | |
| font-size: 1.4rem; | |
| font-weight: 700; | |
| margin-bottom: 1rem; | |
| color: var(--dark); | |
| } | |
| .feature-description { | |
| color: var(--gray); | |
| margin-bottom: 1.5rem; | |
| font-size: 0.95rem; | |
| } | |
| /* Buttons */ | |
| .btn-primary-gradient { | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| color: white; | |
| border: none; | |
| padding: 10px 22px; | |
| border-radius: 50px; | |
| font-weight: 600; | |
| transition: var(--transition); | |
| font-size: 0.95rem; | |
| box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); | |
| } | |
| .btn-primary-gradient:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08); | |
| } | |
| .btn-secondary-gradient { | |
| background: linear-gradient(135deg, #f72585 0%, #b5179e 100%); | |
| color: white; | |
| border: none; | |
| padding: 10px 22px; | |
| border-radius: 50px; | |
| font-weight: 600; | |
| transition: var(--transition); | |
| font-size: 0.95rem; | |
| box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); | |
| } | |
| .btn-secondary-gradient:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08); | |
| } | |
| .btn-accent-gradient { | |
| background: linear-gradient(135deg, var(--accent) 0%, #4895ef 100%); | |
| color: white; | |
| border: none; | |
| padding: 10px 22px; | |
| border-radius: 50px; | |
| font-weight: 600; | |
| transition: var(--transition); | |
| font-size: 0.95rem; | |
| box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); | |
| } | |
| .btn-accent-gradient:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08); | |
| } | |
| .btn-dark-gradient { | |
| background: linear-gradient(135deg, #3a0ca3 0%, #250070 100%); | |
| color: white; | |
| border: none; | |
| padding: 10px 22px; | |
| border-radius: 50px; | |
| font-weight: 600; | |
| transition: var(--transition); | |
| font-size: 0.95rem; | |
| box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); | |
| } | |
| .btn-dark-gradient:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08); | |
| } | |
| /* Notification */ | |
| .notification { | |
| position: fixed; | |
| top: 20px; | |
| right: 20px; | |
| padding: 15px 25px; | |
| border-radius: 10px; | |
| background: var(--success); | |
| color: white; | |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); | |
| transform: translateX(200%); | |
| transition: transform 0.3s ease; | |
| z-index: 1000; | |
| display: flex; | |
| align-items: center; | |
| } | |
| .notification.show { | |
| transform: translateX(0); | |
| } | |
| /* Footer */ | |
| .footer { | |
| background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); | |
| color: #cbd5e1; | |
| padding: 3rem 0 2rem; | |
| flex-shrink: 0; | |
| margin-top: auto; | |
| } | |
| .footer-content { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 2rem; | |
| margin-bottom: 2rem; | |
| } | |
| .footer-column h4 { | |
| color: white; | |
| font-size: 1.2rem; | |
| margin-bottom: 1.5rem; | |
| position: relative; | |
| padding-bottom: 0.5rem; | |
| } | |
| .footer-column h4::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| width: 50px; | |
| height: 3px; | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| border-radius: 3px; | |
| } | |
| .footer-column p { | |
| margin-bottom: 1rem; | |
| line-height: 1.7; | |
| } | |
| .footer-links { | |
| list-style: none; | |
| padding: 0; | |
| } | |
| .footer-links li { | |
| margin-bottom: 0.8rem; | |
| } | |
| .footer-links a { | |
| color: #94a3b8; | |
| text-decoration: none; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| } | |
| .footer-links a:hover { | |
| color: white; | |
| transform: translateX(5px); | |
| } | |
| .footer-links a i { | |
| margin-right: 10px; | |
| font-size: 0.8rem; | |
| } | |
| .social-links { | |
| display: flex; | |
| gap: 1rem; | |
| margin-top: 1rem; | |
| } | |
| .social-link { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 50%; | |
| background: rgba(255, 255, 255, 0.05); | |
| color: white; | |
| transition: var(--transition); | |
| } | |
| .social-link:hover { | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| transform: translateY(-3px); | |
| } | |
| .copyright { | |
| border-top: 1px solid rgba(255, 255, 255, 0.1); | |
| padding-top: 2rem; | |
| text-align: center; | |
| color: #94a3b8; | |
| font-size: 0.9rem; | |
| } | |
| .copyright a { | |
| color: #94a3b8; | |
| text-decoration: none; | |
| } | |
| .copyright a:hover { | |
| color: white; | |
| } | |
| /* Chat Panel */ | |
| .chat-toggle { | |
| position: fixed; | |
| bottom: 30px; | |
| right: 30px; | |
| width: 60px; | |
| height: 60px; | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| color: white; | |
| border: none; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| z-index: 999; | |
| transition: var(--transition); | |
| } | |
| .chat-toggle:hover { | |
| transform: scale(1.1); | |
| } | |
| /* LaTeX to Text Toggle Button */ | |
| #latexToTextToggle { | |
| bottom: 100px; | |
| background: linear-gradient(135deg, #f72585 0%, #b5179e 100%); | |
| } | |
| .chat-panel { | |
| position: fixed; | |
| bottom: 100px; | |
| right: 30px; | |
| width: 380px; | |
| height: 500px; | |
| background: white; | |
| border-radius: var(--border-radius); | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); | |
| display: flex; | |
| flex-direction: column; | |
| z-index: 1000; | |
| overflow: hidden; | |
| transform: translateY(20px); | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: all 0.3s ease; | |
| } | |
| .chat-panel.active { | |
| transform: translateY(0); | |
| opacity: 1; | |
| visibility: visible; | |
| } | |
| .chat-header { | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| color: white; | |
| padding: 1rem 1.5rem; | |
| font-weight: 600; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .chat-close { | |
| background: none; | |
| border: none; | |
| color: white; | |
| font-size: 1.2rem; | |
| cursor: pointer; | |
| padding: 0; | |
| width: 30px; | |
| height: 30px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 50%; | |
| transition: background 0.2s; | |
| } | |
| .chat-close:hover { | |
| background: rgba(255, 255, 255, 0.2); | |
| } | |
| .chat-messages { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 1rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| background: #f8f9fa; | |
| } | |
| .message { | |
| max-width: 85%; | |
| padding: 12px 16px; | |
| border-radius: 18px; | |
| position: relative; | |
| font-size: 0.9rem; | |
| line-height: 1.5; | |
| } | |
| .user-message { | |
| align-self: flex-end; | |
| background: var(--primary); | |
| color: white; | |
| border-bottom-right-radius: 5px; | |
| } | |
| .assistant-message { | |
| align-self: flex-start; | |
| background: white; | |
| color: var(--dark); | |
| border: 1px solid #e9ecef; | |
| border-bottom-left-radius: 5px; | |
| } | |
| .message-info { | |
| font-size: 0.7rem; | |
| opacity: 0.7; | |
| margin-top: 5px; | |
| text-align: right; | |
| } | |
| .chat-input { | |
| display: flex; | |
| padding: 1rem; | |
| background: white; | |
| border-top: 1px solid #e9ecef; | |
| gap: 10px; | |
| } | |
| .chat-input textarea { | |
| flex: 1; | |
| padding: 12px 15px; | |
| border: 1px solid #dee2e6; | |
| border-radius: 50px; | |
| resize: none; | |
| font-family: inherit; | |
| height: 44px; | |
| font-size: 0.9rem; | |
| } | |
| .chat-input textarea:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1); | |
| } | |
| .chat-send { | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| color: white; | |
| border: none; | |
| border-radius: 50%; | |
| width: 44px; | |
| height: 44px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: transform 0.2s; | |
| } | |
| .chat-send:hover { | |
| transform: scale(1.05); | |
| } | |
| .chat-send:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .loading { | |
| display: none; | |
| align-self: flex-start; | |
| background: white; | |
| color: var(--dark); | |
| padding: 12px 16px; | |
| border-radius: 18px; | |
| border: 1px solid #e9ecef; | |
| border-bottom-left-radius: 5px; | |
| font-size: 0.9rem; | |
| } | |
| .loading-dots { | |
| display: flex; | |
| gap: 4px; | |
| } | |
| .loading-dot { | |
| width: 8px; | |
| height: 8px; | |
| background: var(--primary); | |
| border-radius: 50%; | |
| animation: bounce 1.5s infinite; | |
| } | |
| .loading-dot:nth-child(2) { | |
| animation-delay: 0.2s; | |
| } | |
| .loading-dot:nth-child(3) { | |
| animation-delay: 0.4s; | |
| } | |
| @keyframes bounce { | |
| 0%, 100% { transform: translateY(0); } | |
| 50% { transform: translateY(-5px); } | |
| } | |
| .welcome-message { | |
| text-align: center; | |
| padding: 20px; | |
| color: var(--gray); | |
| font-size: 0.9rem; | |
| } | |
| .welcome-message h6 { | |
| color: var(--dark); | |
| margin-bottom: 10px; | |
| font-size: 1.1rem; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 992px) { | |
| .page-title { | |
| font-size: 2rem; | |
| } | |
| .page-subtitle { | |
| font-size: 1rem; | |
| } | |
| } | |
| @media (max-width: 768px) { | |
| .main-content { | |
| padding: 1.5rem 0; | |
| } | |
| .page-header { | |
| margin-bottom: 2rem; | |
| } | |
| .page-title { | |
| font-size: 1.8rem; | |
| } | |
| .feature-card { | |
| padding: 1.5rem; | |
| } | |
| .chat-panel { | |
| width: calc(100% - 40px); | |
| height: 70vh; | |
| right: 20px; | |
| bottom: 90px; | |
| } | |
| .navbar-nav { | |
| text-align: center; | |
| margin-top: 1rem; | |
| } | |
| .nav-link { | |
| margin: 5px 0; | |
| } | |
| .footer-content { | |
| grid-template-columns: 1fr; | |
| gap: 1.5rem; | |
| } | |
| } | |
| @media (max-width: 576px) { | |
| .page-title { | |
| font-size: 1.6rem; | |
| } | |
| .feature-icon { | |
| width: 60px; | |
| height: 60px; | |
| font-size: 1.5rem; | |
| } | |
| .feature-title { | |
| font-size: 1.2rem; | |
| } | |
| } | |
| /* Neutral Button */ | |
| .btn-neutral { | |
| background: #f8f9fa; | |
| color: #333; | |
| border: 1px solid #ddd; | |
| padding: 12px 25px; | |
| border-radius: 50px; | |
| font-weight: 600; | |
| transition: var(--transition); | |
| font-size: 1rem; | |
| cursor: pointer; | |
| box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); | |
| } | |
| .btn-neutral:hover { | |
| background: #e9ecef; | |
| transform: translateY(-2px); | |
| box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08); | |
| } | |
| /* Buttons for LaTeX to Text panel */ | |
| #convertLatexBtn, #copyTextBtn { | |
| border: none; | |
| border-radius: 6px; | |
| font-weight: 600; | |
| transition: var(--transition); | |
| cursor: pointer; | |
| box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); | |
| } | |
| #convertLatexBtn { | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| color: white; | |
| } | |
| #convertLatexBtn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08); | |
| } | |
| #copyTextBtn:hover { | |
| background: #e9ecef; | |
| transform: translateY(-2px); | |
| box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08); | |
| } | |
| </style> | |
| {% block extra_css %}{% endblock %} | |
| </head> | |
| <body> | |
| {% if request.endpoint != 'auth.login' %} | |
| <nav class="navbar navbar-expand-lg navbar-dark"> | |
| <div class="container"> | |
| <a class="navbar-brand" href="/"> | |
| <i class="fas fa-calculator"></i> | |
| <span>TexLab</span> | |
| </a> | |
| <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> | |
| <span class="navbar-toggler-icon"></span> | |
| </button> | |
| <div class="collapse navbar-collapse" id="navbarNav"> | |
| <ul class="navbar-nav me-auto"> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="/math">Math</a> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="/scribble">Scribble</a> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="/camera">Camera</a> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="/table">Table</a> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="/pdffly">PDFly</a> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="/graph">Graph</a> | |
| </li> | |
| </ul> | |
| <div class="d-flex align-items-center"> | |
| <div id="authContainer"> | |
| <!-- Auth content will be loaded here by JavaScript --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </nav> | |
| {% endif %} | |
| <div class="container main-content"> | |
| {% block content %}{% endblock %} | |
| </div> | |
| {% if request.endpoint != 'auth.login' %} | |
| {% if request.path == '/' %} | |
| <footer class="site-footer"> | |
| <!-- Footer --> | |
| <footer class="footer" style="width: 100%; background: #1e293b; color: #fff; padding: 2rem 1rem;"> | |
| <div class="container"> | |
| <div class="footer-content"> | |
| <div class="footer-column"> | |
| <h4>About TexLab</h4> | |
| <p> | |
| TexLab is a cutting-edge platform that transforms handwritten mathematical expressions, | |
| scribbles, tables, and camera captures into clean, editable LaTeX code using advanced AI models. | |
| </p> | |
| <div class="social-links"> | |
| <a href="#" class="social-link"><i class="fab fa-twitter"></i></a> | |
| <a href="#" class="social-link"><i class="fab fa-facebook-f"></i></a> | |
| <a href="#" class="social-link"><i class="fab fa-linkedin-in"></i></a> | |
| <a href="#" class="social-link"><i class="fab fa-github"></i></a> | |
| </div> | |
| </div> | |
| <div class="footer-column"> | |
| <h4>Quick Links</h4> | |
| <ul class="footer-links"> | |
| <li><a href="/"><i class="fas fa-chevron-right"></i> Home</a></li> | |
| <li><a href="/math"><i class="fas fa-chevron-right"></i> Math Equations</a></li> | |
| <li><a href="/scribble"><i class="fas fa-chevron-right"></i> Scribble Converter</a></li> | |
| <li><a href="/camera"><i class="fas fa-chevron-right"></i> Camera Capture</a></li> | |
| <li><a href="/table"><i class="fas fa-chevron-right"></i> Table Detection</a></li> | |
| </ul> | |
| </div> | |
| <div class="footer-column"> | |
| <h4>Resources</h4> | |
| <ul class="footer-links"> | |
| <li><a href="/help"><i class="fas fa-chevron-right"></i> Documentation</a></li> | |
| <li><a href="/tutorials"><i class="fas fa-chevron-right"></i> Tutorials</a></li> | |
| <li><a href="/api"><i class="fas fa-chevron-right"></i> API Reference</a></li> | |
| <li><a href="/blog"><i class="fas fa-chevron-right"></i> Blog</a></li> | |
| <li><a href="/support"><i class="fas fa-chevron-right"></i> Support Center</a></li> | |
| </ul> | |
| </div> | |
| <div class="footer-column"> | |
| <h4>Legal</h4> | |
| <ul class="footer-links"> | |
| <li><a href="/privacy"><i class="fas fa-chevron-right"></i> Privacy Policy</a></li> | |
| <li><a href="/terms"><i class="fas fa-chevron-right"></i> Terms of Service</a></li> | |
| <li><a href="/cookies"><i class="fas fa-chevron-right"></i> Cookie Policy</a></li> | |
| <li><a href="/gdpr"><i class="fas fa-chevron-right"></i> GDPR Compliance</a></li> | |
| <li><a href="/contact"><i class="fas fa-chevron-right"></i> Contact Us</a></li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="copyright"> | |
| <p>© 2025 TexLab. All rights reserved. Transforming handwritten content into beautiful LaTeX since 2025.</p> | |
| <p>Designed with <i class="fas fa-heart" style="color: #f72585;"></i> for mathematicians, scientists, and educators worldwide.</p> | |
| </div> | |
| </div> | |
| </footer> | |
| </footer> | |
| {% endif %} | |
| {% endif %} | |
| {% if request.endpoint != 'auth.login' %} | |
| <!-- Chat Toggle Button --> | |
| <button class="chat-toggle" id="chatToggle"> | |
| <i class="fas fa-robot"></i> | |
| </button> | |
| <!-- LaTeX to Text Toggle Button --> | |
| <button class="chat-toggle" id="latexToTextToggle" style="bottom: 100px;"> | |
| <i class="fas fa-exchange-alt"></i> | |
| </button> | |
| <!-- Chat Panel --> | |
| <div class="chat-panel" id="chatPanel"> | |
| <div class="chat-header"> | |
| <h5><i class="fas fa-robot me-2"></i>TexLab Assistant</h5> | |
| <button class="chat-close" id="chatClose"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="chat-messages" id="chatMessages"> | |
| <div class="welcome-message"> | |
| <h6>Welcome to TexLab Assistant!</h6> | |
| <p>I can help you with LaTeX, mathematics, and document conversion.</p> | |
| <p>Try asking: "How do I write a fraction in LaTeX?"</p> | |
| </div> | |
| </div> | |
| <div class="chat-input"> | |
| <textarea id="messageInput" placeholder="Type your message... (Enter to send)"></textarea> | |
| <button class="chat-send" id="sendButton" disabled> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- LaTeX to Text Panel --> | |
| <div class="chat-panel" id="latexToTextPanel" style="bottom: 140px;"> | |
| <div class="chat-header"> | |
| <h5><i class="fas fa-exchange-alt me-2"></i>LaTeX to Text Converter</h5> | |
| <button class="chat-close" id="latexToTextClose"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="chat-messages" id="latexToTextMessages"> | |
| <div class="welcome-message"> | |
| <h6>Convert LaTeX to Readable Text</h6> | |
| <p>Enter LaTeX code below to convert it to human-readable text.</p> | |
| </div> | |
| </div> | |
| <div style="padding: 1rem; border-top: 1px solid #e9ecef;"> | |
| <textarea id="latexInput" placeholder="Enter LaTeX code..." style="width: 100%; height: 80px; padding: 10px; border-radius: 8px; border: 1px solid #ddd; resize: vertical; font-family: 'Consolas', monospace;"></textarea> | |
| <div style="display: flex; gap: 10px; margin-top: 10px;"> | |
| <button class="btn btn-primary-gradient" id="convertLatexBtn" style="flex: 1; padding: 8px; font-size: 0.9rem;"> | |
| <i class="fas fa-exchange-alt me-1"></i>Convert | |
| </button> | |
| <button class="btn btn-neutral" id="copyTextBtn" style="flex: 1; padding: 8px; font-size: 0.9rem;"> | |
| <i class="fas fa-copy me-1"></i>Copy Text | |
| </button> | |
| </div> | |
| <div style="margin-top: 15px;"> | |
| <h6 style="margin-bottom: 8px; color: #4361ee;">Converted Text:</h6> | |
| <div id="convertedTextOutput" style="background: white; border: 1px solid #e9ecef; border-radius: 8px; padding: 12px; min-height: 60px; font-size: 0.95rem;"> | |
| Your converted text will appear here... | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="notification" id="notification"> | |
| <i class="fas fa-check-circle me-2"></i>LaTeX code copied to clipboard! | |
| </div> | |
| {% endif %} | |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> | |
| {% block extra_js %}{% endblock %} | |
| <script> | |
| // Check if we're on the login page | |
| const isLoginPage = document.querySelector('.login-container') !== null; | |
| if (!isLoginPage) { | |
| // Load user authentication status | |
| document.addEventListener('DOMContentLoaded', function() { | |
| fetch('/auth/user') | |
| .then(response => response.json()) | |
| .then(data => { | |
| const authContainer = document.getElementById('authContainer'); | |
| if (data.authenticated) { | |
| // Show user info with appropriate icon | |
| if (data.user.email && data.user.email.includes('@')) { | |
| // Google user | |
| authContainer.innerHTML = ` | |
| <div class="user-info"> | |
| <i class="fab fa-google"></i> | |
| <span>${data.user.name}</span> | |
| </div> | |
| <a href="/auth/logout" class="btn btn-signin ms-2"> | |
| <i class="fas fa-sign-out-alt me-1"></i>Sign Out | |
| </a> | |
| `; | |
| } else { | |
| // Guest user | |
| authContainer.innerHTML = ` | |
| <div class="user-info"> | |
| <i class="fas fa-user"></i> | |
| <span>Guest: ${data.user.name}</span> | |
| </div> | |
| <a href="/auth/logout" class="btn btn-signin ms-2"> | |
| <i class="fas fa-sign-out-alt me-1"></i>Sign Out | |
| </a> | |
| `; | |
| } | |
| } else { | |
| authContainer.innerHTML = ` | |
| <a href="/auth/login" class="btn btn-signin"> | |
| <i class="fas fa-sign-in-alt me-1"></i>Sign In | |
| </a> | |
| `; | |
| } | |
| }) | |
| .catch(error => { | |
| console.error('Error checking auth status:', error); | |
| // Fallback to show sign in button | |
| document.getElementById('authContainer').innerHTML = ` | |
| <a href="/auth/login" class="btn btn-signin"> | |
| <i class="fas fa-sign-in-alt me-1"></i>Sign In | |
| </a> | |
| `; | |
| }); | |
| }); | |
| // Chat panel functionality | |
| const chatToggle = document.getElementById('chatToggle'); | |
| const chatPanel = document.getElementById('chatPanel'); | |
| const chatClose = document.getElementById('chatClose'); | |
| const chatMessages = document.getElementById('chatMessages'); | |
| const messageInput = document.getElementById('messageInput'); | |
| const sendButton = document.getElementById('sendButton'); | |
| // Toggle chat panel | |
| chatToggle.addEventListener('click', () => { | |
| chatPanel.classList.toggle('active'); | |
| }); | |
| // Close chat panel | |
| chatClose.addEventListener('click', () => { | |
| chatPanel.classList.remove('active'); | |
| }); | |
| // Auto-resize textarea | |
| messageInput.addEventListener('input', function() { | |
| this.style.height = 'auto'; | |
| this.style.height = (this.scrollHeight > 120 ? 120 : this.scrollHeight) + 'px'; | |
| }); | |
| // Enable send button when there's text | |
| messageInput.addEventListener('input', function() { | |
| sendButton.disabled = !this.value.trim(); | |
| }); | |
| // Send message on Enter key | |
| messageInput.addEventListener('keydown', function(e) { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| if (!sendButton.disabled) { | |
| sendMessage(); | |
| } | |
| } | |
| }); | |
| // Send button click | |
| sendButton.addEventListener('click', sendMessage); | |
| // Generate a simple session ID | |
| const sessionId = 'session_' + Date.now(); | |
| // Send message function | |
| async function sendMessage() { | |
| const message = messageInput.value.trim(); | |
| if (!message) return; | |
| // Clear input and disable button | |
| messageInput.value = ''; | |
| messageInput.style.height = '44px'; | |
| sendButton.disabled = true; | |
| // Add user message to chat | |
| addMessageToChat(message, 'user'); | |
| // Show loading indicator | |
| const loadingElement = document.createElement('div'); | |
| loadingElement.className = 'loading'; | |
| loadingElement.id = 'loadingIndicator'; | |
| loadingElement.innerHTML = ` | |
| <div class="loading-dots"> | |
| <div class="loading-dot"></div> | |
| <div class="loading-dot"></div> | |
| <div class="loading-dot"></div> | |
| </div> | |
| `; | |
| chatMessages.appendChild(loadingElement); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| try { | |
| // Simulate AI response (in a real implementation, you would call your AI model) | |
| // For now, we'll generate a simple response based on common LaTeX questions | |
| setTimeout(() => { | |
| // Remove loading indicator | |
| document.getElementById('loadingIndicator').remove(); | |
| // Generate response based on user input | |
| const response = generateResponse(message); | |
| // Add assistant response to chat | |
| addMessageToChat(response, 'assistant'); | |
| }, 1000); | |
| } catch (error) { | |
| // Remove loading indicator | |
| document.getElementById('loadingIndicator').remove(); | |
| console.error('Error:', error); | |
| addMessageToChat('Sorry, I encountered an error. Please try again.', 'assistant'); | |
| } | |
| } | |
| // Generate a response based on user input | |
| function generateResponse(userMessage) { | |
| const lowerMessage = userMessage.toLowerCase(); | |
| if (lowerMessage.includes('fraction') || lowerMessage.includes('frac')) { | |
| return 'To write a fraction in LaTeX, use \\\\frac{numerator}{denominator}. For example: \\\\frac{1}{2} produces ½.'; | |
| } else if (lowerMessage.includes('integral') || lowerMessage.includes('int')) { | |
| return 'To write an integral in LaTeX, use \\\\int. For example: \\\\int_0^1 x^2 dx. For definite integrals, specify limits with _ and ^.'; | |
| } else if (lowerMessage.includes('sum') || lowerMessage.includes('sigma')) { | |
| return 'To write a summation in LaTeX, use \\\\sum. For example: \\\\sum_{i=1}^{n} i. Use _ for lower limit and ^ for upper limit.'; | |
| } else if (lowerMessage.includes('limit') || lowerMessage.includes('lim')) { | |
| return 'To write a limit in LaTeX, use \\\\lim. For example: \\\\lim_{x \\\\to 0} \\\\frac{\\\\sin x}{x} = 1.'; | |
| } else if (lowerMessage.includes('matrix') || lowerMessage.includes('array')) { | |
| return 'To create a matrix in LaTeX, use \\\\begin{matrix} ... \\\\end{matrix}. For example:\\n\\\\begin{matrix}\\na & b \\\\\\nc & d\\n\\\\end{matrix}'; | |
| } else if (lowerMessage.includes('table') || lowerMessage.includes('tabular')) { | |
| return 'To create a table in LaTeX, use the tabular environment. For example:\\n\\\\begin{tabular}{|c|c|}\\n\\\\hline\\nColumn 1 & Column 2 \\\\\\n\\\\hline\\nItem 1 & Item 2 \\\\\\n\\\\hline\\n\\\\end{tabular}'; | |
| } else if (lowerMessage.includes('equation') || lowerMessage.includes('align')) { | |
| return 'To write equations in LaTeX, you can use:\\n- Inline: $E = mc^2$\\n- Display: $$E = mc^2$$\\n- Aligned: \\\\begin{align} x &= y \\\\ y &= z \\\\end{align}'; | |
| } else if (lowerMessage.includes('texlab') || lowerMessage.includes('help')) { | |
| return "I'm the TexLab Assistant! I can help you with:\\n- LaTeX syntax and commands\\n- Mathematical notation\\n- Document conversion tips\\n- Using TexLab features\\n\\nJust ask me any LaTeX or math question!"; | |
| } else { | |
| // Default response | |
| return "I'm the TexLab Assistant. I can help you with LaTeX syntax, mathematical notation, and document conversion. Try asking me something like 'How do I write a fraction in LaTeX?' or 'How do I create a matrix?'"; | |
| } | |
| } | |
| // Add message to chat UI | |
| function addMessageToChat(content, sender) { | |
| // Remove welcome message if it exists | |
| const welcomeMessage = chatMessages.querySelector('.welcome-message'); | |
| if (welcomeMessage) { | |
| welcomeMessage.remove(); | |
| } | |
| const messageElement = document.createElement('div'); | |
| messageElement.className = `message ${sender}-message`; | |
| const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | |
| messageElement.innerHTML = ` | |
| <div>${content}</div> | |
| <div class="message-info">${sender === 'user' ? 'You' : 'Assistant'} • ${time}</div> | |
| `; | |
| chatMessages.appendChild(messageElement); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| // Re-enable send button | |
| sendButton.disabled = false; | |
| } | |
| // Global notification function | |
| function showNotification(message) { | |
| const notification = document.getElementById("notification"); | |
| if (notification) { | |
| notification.innerHTML = `<i class="fas fa-check-circle me-2"></i>${message}`; | |
| notification.classList.add('show'); | |
| setTimeout(() => { | |
| notification.classList.remove('show'); | |
| }, 3000); | |
| } | |
| } | |
| // LaTeX to Text functionality | |
| const latexToTextToggle = document.getElementById('latexToTextToggle'); | |
| const latexToTextPanel = document.getElementById('latexToTextPanel'); | |
| const latexToTextClose = document.getElementById('latexToTextClose'); | |
| const latexInput = document.getElementById('latexInput'); | |
| const convertLatexBtn = document.getElementById('convertLatexBtn'); | |
| const copyTextBtn = document.getElementById('copyTextBtn'); | |
| const convertedTextOutput = document.getElementById('convertedTextOutput'); | |
| // Toggle LaTeX to Text panel | |
| latexToTextToggle.addEventListener('click', () => { | |
| latexToTextPanel.classList.toggle('active'); | |
| // Close chat panel if it's open | |
| chatPanel.classList.remove('active'); | |
| }); | |
| // Close LaTeX to Text panel | |
| latexToTextClose.addEventListener('click', () => { | |
| latexToTextPanel.classList.remove('active'); | |
| }); | |
| // Convert LaTeX to text function | |
| function convertLatexToText(latexStr) { | |
| if (!latexStr) return ""; | |
| // Make a copy to work with | |
| let text = latexStr; | |
| // Remove common LaTeX commands | |
| text = text.replace(/\\begin\{.*?\}/g, ''); | |
| text = text.replace(/\\end\{.*?\}/g, ''); | |
| // Replace common math symbols with readable text | |
| const replacements = { | |
| '\\\\alpha': 'alpha', | |
| '\\\\beta': 'beta', | |
| '\\\\gamma': 'gamma', | |
| '\\\\delta': 'delta', | |
| '\\\\epsilon': 'epsilon', | |
| '\\\\theta': 'theta', | |
| '\\\\lambda': 'lambda', | |
| '\\\\mu': 'mu', | |
| '\\\\pi': 'pi', | |
| '\\\\sigma': 'sigma', | |
| '\\\\phi': 'phi', | |
| '\\\\omega': 'omega', | |
| '\\\\infty': 'infinity', | |
| '\\\\int': 'integral', | |
| '\\\\sum': 'sum', | |
| '\\\\prod': 'product', | |
| '\\\\lim': 'limit', | |
| '\\\\sqrt': 'square root', | |
| '\\\\frac': 'fraction', | |
| '\\\\times': 'times', | |
| '\\\\cdot': 'cdot', | |
| '\\\\div': 'divided by', | |
| '\\\\pm': 'plus or minus', | |
| '\\\\mp': 'minus or plus', | |
| '\\\\leq': 'less than or equal to', | |
| '\\\\geq': 'greater than or equal to', | |
| '\\\\neq': 'not equal to', | |
| '\\\\approx': 'approximately', | |
| '\\\\equiv': 'equivalent to', | |
| '\\\\subset': 'subset of', | |
| '\\\\subseteq': 'subset or equal to', | |
| '\\\\cup': 'union', | |
| '\\\\cap': 'intersection', | |
| '\\\\in': 'element of', | |
| '\\\\notin': 'not element of', | |
| '\\\\forall': 'for all', | |
| '\\\\exists': 'there exists', | |
| '\\\\nexists': 'there does not exist', | |
| '\\\\emptyset': 'empty set', | |
| '\\\\varnothing': 'empty set', | |
| '\\\\mathbb{R}': 'real numbers', | |
| '\\\\mathbb{N}': 'natural numbers', | |
| '\\\\mathbb{Z}': 'integers', | |
| '\\\\mathbb{Q}': 'rational numbers', | |
| '\\\\mathbb{C}': 'complex numbers', | |
| '\\\\to': 'to', | |
| '\\\\rightarrow': 'rightarrow', | |
| '\\\\left': '', | |
| '\\\\right': '', | |
| '\\\\big': '', | |
| '\\\\Big': '', | |
| '\\\\bigg': '', | |
| '\\\\Bigg': '', | |
| }; | |
| for (const [pattern, replacement] of Object.entries(replacements)) { | |
| const regex = new RegExp(pattern, 'g'); | |
| text = text.replace(regex, replacement); | |
| } | |
| // Handle fractions specifically: \frac{a}{b} -> (a over b) | |
| text = text.replace(/\\frac\{([^}]*)\}\{([^}]*)\}/g, '($1 over $2)'); | |
| // Handle subscripts: _{text} -> _text or _a -> _a | |
| text = text.replace(/_\{([^}]*)\}/g, '_$1'); | |
| // Handle superscripts: ^{text} -> ^text or ^a -> ^a | |
| text = text.replace(/\^\{([^}]*)\}/g, '^$1'); | |
| // Handle square roots: \sqrt{text} -> square root of (text) | |
| text = text.replace(/\\sqrt\{([^}]*)\}/g, 'square root of ($1)'); | |
| // Remove remaining braces | |
| text = text.replace(/\{/g, '(').replace(/\}/g, ')'); | |
| // Clean up extra spaces | |
| text = text.replace(/\s+/g, ' ').trim(); | |
| return text; | |
| } | |
| // Convert button click handler | |
| convertLatexBtn.addEventListener('click', () => { | |
| const latexCode = latexInput.value.trim(); | |
| if (!latexCode) { | |
| convertedTextOutput.textContent = 'Please enter some LaTeX code to convert.'; | |
| return; | |
| } | |
| try { | |
| const convertedText = convertLatexToText(latexCode); | |
| convertedTextOutput.textContent = convertedText; | |
| } catch (error) { | |
| console.error('Conversion error:', error); | |
| convertedTextOutput.textContent = 'An error occurred during conversion. Please try again.'; | |
| } | |
| }); | |
| // Copy text button click handler | |
| copyTextBtn.addEventListener('click', () => { | |
| const text = convertedTextOutput.textContent; | |
| if (!text || text === 'Your converted text will appear here...') { | |
| showNotification('Nothing to copy!'); | |
| return; | |
| } | |
| navigator.clipboard.writeText(text).then(() => { | |
| showNotification('Text copied to clipboard!'); | |
| }).catch(err => { | |
| console.error('Failed to copy:', err); | |
| showNotification('Failed to copy text. Please try again.'); | |
| }); | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> |