texlab / templates /base.html
syk101's picture
Upload 239 files
d05a9d0 verified
<!DOCTYPE html>
<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 .container-fluid {
padding-left: 20px; /* Move content slightly left for standard positioning */
padding-right: 20px;
}
.navbar-brand {
font-weight: 700;
font-size: 1.8rem;
color: white !important;
display: flex;
align-items: center;
padding-left: 100px; /* Increased padding to move brand further right */
}
.navbar-brand i {
margin-right: 10px;
}
.navbar-nav {
padding-left: 30px; /* Move navigation links further to the right */
}
.navbar-nav .nav-item {
margin-right: 20px; /* Adjust spacing between nav items */
}
.nav-link {
color: rgba(255, 255, 255, 0.85) !important;
font-weight: 500;
padding: 0.5rem 1rem !important;
border-radius: 50px;
transition: var(--transition);
margin: 0 2px; /* Reduced margin for tighter spacing */
}
.nav-link:hover, .nav-link.active {
color: white !important;
background: rgba(255, 255, 255, 0.15) !important;
}
.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: 0.5rem; /* Reduced margin for better positioning */
margin-right: 15px; /* Move sign-in/sign-out slightly to the left */
}
.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;
text-align: left; /* Left-align all footer content */
margin-left: -20px; /* Shift content to the left */
}
/* Left-align the About column specifically */
.footer-content > .footer-column:first-child {
text-align: left;
padding-left: 0;
}
/* Adjust the Quick Links column to shift content slightly left */
.footer-content > .footer-column:nth-child(2) {
padding-left: 0; /* Remove padding to shift left */
}
.footer-column h4 {
color: white;
font-size: 1.2rem;
margin-bottom: 1.5rem;
position: relative;
padding-bottom: 0.5rem;
text-align: left; /* Ensure headings are left-aligned */
}
.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: left; /* Change from center to left alignment */
color: #94a3b8;
font-size: 0.9rem;
max-width: 800px; /* Limit width for better readability */
margin: 0 auto 0 0; /* Align to the left instead of center */
}
.copyright-content {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.copyright-line {
margin: 0;
}
.copyright a {
color: #94a3b8;
text-decoration: none;
}
.copyright a:hover {
color: white;
}
/* Chat Toggle Button */
.chat-toggle {
position: fixed;
bottom: 20px;
right: 20px;
width: 60px; /* Increased size */
height: 60px; /* Increased size */
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); /* Website gradient */
border-radius: 50%;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
color: white;
font-size: 1.5rem; /* Larger icon */
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease; /* Smooth transition */
z-index: 1001;
border: none; /* Remove border */
}
.chat-toggle:hover {
transform: scale(1.1); /* Scale up on hover */
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.15);
}
/* Second chat toggle button */
#latexToTextToggle {
bottom: 100px;
width: 60px; /* Increased size */
height: 60px; /* Increased size */
background: linear-gradient(135deg, #4cc9f0 0%, #4895ef 100%); /* Different gradient for distinction */
font-size: 1.5rem; /* Larger icon */
}
/* Chat Panel */
.chat-panel {
position: fixed;
bottom: 20px;
right: 20px;
width: 500px; /* Increased width */
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
transform: translateX(200%);
transition: transform 0.3s ease;
z-index: 1000;
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 90vh; /* Increased height for better user experience */
}
.chat-panel.active {
transform: translateX(0);
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid #e9ecef;
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); /* Website gradient */
color: white;
}
.chat-header h5 {
margin: 0;
font-size: 1.3rem; /* Larger font */
color: white;
}
.chat-close {
background: none;
border: none;
color: white;
font-size: 1.5rem; /* Larger icon */
cursor: pointer;
transition: color 0.3s ease;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.chat-close:hover {
background: rgba(255, 255, 255, 0.2);
}
.chat-messages {
flex: 1;
padding: 1rem;
overflow-y: auto;
max-height: 65vh; /* Increased height for more messages */
}
.chat-messages .welcome-message {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 8px;
padding: 20px;
margin-bottom: 1rem;
text-align: center;
border: 1px solid #dee2e6;
}
.chat-messages .welcome-message h6 {
color: #4361ee;
margin-bottom: 15px;
font-size: 1.4rem;
}
.chat-messages .welcome-message p {
color: #495057;
margin-bottom: 15px;
font-size: 1rem;
line-height: 1.6;
}
.chat-messages .welcome-message ul {
text-align: left;
padding-left: 20px;
margin: 15px 0;
color: #495057;
}
.chat-messages .welcome-message li {
margin-bottom: 10px;
line-height: 1.5;
}
.chat-messages .welcome-message strong {
color: #212529;
}
.chat-messages .message {
margin-bottom: 1rem;
padding: 1rem;
border-radius: 8px;
background: #f8f9fa;
color: var(--dark);
position: relative;
}
.chat-messages .message.user-message {
background: #e9ecef;
color: var(--dark);
}
.chat-messages .message.user-message::after {
content: '';
position: absolute;
bottom: -10px;
right: 10px;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid #e9ecef;
}
.chat-messages .message.assistant-message::after {
content: '';
position: absolute;
bottom: -10px;
left: 10px;
width: 0;
height: 0;
border-left: 10px solid #f8f9fa;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
}
.chat-messages .message .message-info {
font-size: 0.8rem;
color: var(--gray);
margin-top: 0.5rem;
}
.chat-messages .message .message-timestamp {
font-size: 0.75rem;
color: #6c757d;
text-align: right;
margin-top: 8px;
font-style: italic;
}
.chat-messages .message .chat-code-block {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 15px;
min-height: 80px;
font-size: 1.1rem;
font-family: 'Segoe UI', sans-serif;
white-space: pre-wrap;
overflow-y: auto;
max-height: 150px;
text-align: center;
}
.chat-messages .message .chat-code-inline {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 0 4px;
font-size: 0.95rem;
font-family: 'Segoe UI', sans-serif;
}
.chat-messages .message .chat-image {
max-width: 70%; /* Reduced image size for better layout */
border-radius: 8px;
margin-top: 1rem;
display: block;
margin-left: auto;
margin-right: auto;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); /* Added shadow for better visibility */
}
.chat-messages .message .chat-image-preview {
max-width: 70%; /* Reduced image size for better layout */
border-radius: 8px;
margin-top: 1rem;
opacity: 0.7; /* Increased opacity for better visibility */
display: block;
margin-left: auto;
margin-right: auto;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); /* Added shadow for better visibility */
}
.chat-messages .message .loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 1rem;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
border: 1px dashed #dee2e6;
}
.chat-messages .message .loading .loading-dots {
display: flex;
gap: 8px;
margin-bottom: 15px;
}
.chat-messages .message .loading .loading-dot {
width: 12px;
height: 12px;
background: var(--primary);
border-radius: 50%;
animation: loading 1.2s infinite ease-in-out;
}
.chat-messages .message .loading .loading-dot:nth-child(2) {
animation-delay: 0.4s;
}
.chat-messages .message .loading .loading-dot:nth-child(3) {
animation-delay: 0.8s;
}
.chat-messages .message .loading .loading-text {
font-size: 0.95rem;
color: var(--gray);
margin-top: 1rem;
font-style: italic;
text-align: center;
}
@keyframes loading {
0%, 100% {
transform: scale(0);
}
50% {
transform: scale(1);
}
}
.chat-input {
padding: 1rem;
border-top: 1px solid #e9ecef;
background-color: #f8f9fa; /* Added background for better visual separation */
}
.chat-input .input-group {
display: flex;
gap: 0;
}
.chat-input .input-group textarea {
flex: 1;
border-radius: 8px 0 0 8px;
border: 1px solid #dee2e6;
padding: 15px; /* Increased padding */
font-size: 1rem; /* Larger font */
resize: vertical;
min-height: 60px; /* Increased height */
font-family: 'Segoe UI', sans-serif; /* Consistent font */
}
.chat-input .input-group textarea:focus {
outline: none;
border-color: #4361ee;
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
}
.chat-input .input-group button {
border-radius: 0 8px 8px 0;
padding: 15px; /* Increased padding */
font-size: 1rem; /* Larger font */
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); /* Website gradient */
border: none;
color: white;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.chat-input .input-group button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.chat-input .input-group button:disabled {
background: #adb5bd;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.chat-input .file-upload-wrapper {
display: flex;
gap: 10px;
margin-top: 12px;
justify-content: center; /* Center the upload buttons */
}
.chat-input .file-upload-wrapper label {
cursor: pointer;
padding: 8px 16px;
border-radius: 6px;
font-size: 0.9rem;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 5px;
}
.chat-input .file-upload-wrapper label:hover {
transform: translateY(-2px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.chat-input .file-upload-wrapper .btn-outline-primary {
border: 1px solid #4361ee;
color: #4361ee;
background: white;
}
.chat-input .file-upload-wrapper .btn-outline-primary:hover {
background: #4361ee;
color: white;
}
.chat-input .file-upload-wrapper .btn-outline-danger {
border: 1px solid #f72585;
color: #f72585;
background: white;
}
.chat-input .file-upload-wrapper .btn-outline-danger:hover {
background: #f72585;
color: white;
}
.chat-input .file-upload-wrapper input {
display: none;
}
/* LaTeX to Text Panel Styles */
#latexToTextPanel .welcome-message {
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
}
#latexToTextPanel .welcome-message h6 {
color: #4361ee;
margin-bottom: 10px;
}
#latexToTextPanel .welcome-message ul {
padding-left: 20px;
margin: 10px 0;
}
#latexToTextPanel .welcome-message code {
background: #e9ecef;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.85rem;
}
#latexInput {
transition: border-color 0.2s, box-shadow 0.2s;
padding: 15px; /* Increased padding */
font-size: 1rem; /* Larger font */
min-height: 120px; /* Increased height */
}
#latexInput:focus {
outline: none;
border-color: #4361ee;
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
}
#convertedTextOutput {
background: white;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px; /* Increased padding */
min-height: 120px; /* Increased height */
font-size: 1.2rem; /* Larger font */
font-family: 'Segoe UI', sans-serif;
white-space: pre-wrap;
overflow-y: auto;
max-height: 200px; /* Increased height */
font-size: 1.3rem; /* Slightly larger for better symbol visibility */
}
#convertedTextOutput:hover {
background-color: #f8f9fa;
}
#charCount {
transition: color 0.2s;
}
#charCount.warning {
color: #f72585;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.footer-content {
grid-template-columns: 1fr;
gap: 2rem;
}
.copyright-content {
flex-direction: column;
text-align: center;
}
/* Adjust chat panel for mobile */
.chat-panel {
width: 90vw;
right: 5vw;
bottom: 10px;
}
}
/* User avatar styling */
.user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 8px;
object-fit: cover;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
{% if request.endpoint != 'auth.login' %}
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container-fluid">
<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>
<li class="nav-item">
<a class="nav-link" href="/pricing">Pricing</a>
</li>
</ul>
<div class="d-flex align-items-center" style="margin-right: 15px;">
<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">
<div class="copyright-content">
<p class="copyright-line">&copy; 2025 TexLab. All rights reserved. Transforming handwritten content into beautiful LaTeX since 2025.</p>
<p class="copyright-line">Designed with <i class="fas fa-heart" style="color: #f72585;"></i> for mathematicians, scientists, and educators worldwide.</p>
</div>
</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><strong>New Features:</strong></p>
<ul>
<li>Upload images of math equations or tables for LaTeX conversion</li>
<li>Upload PDF files for content extraction</li>
</ul>
<p>Try asking: "How do I write a fraction in LaTeX?" or upload an image/PDF!</p>
</div>
</div>
<div class="chat-input">
<div class="input-group mb-2">
<textarea id="messageInput" placeholder="Ask me anything about LaTeX or math..." class="form-control" style="border-radius: 8px 0 0 8px;"></textarea>
<button class="chat-send btn btn-primary" id="sendButton" disabled style="border-radius: 0 8px 8px 0;">
<i class="fas fa-paper-plane"></i>
</button>
</div>
<div class="d-flex justify-content-center">
<div class="file-upload-wrapper">
<label for="imageUpload" class="btn btn-sm btn-outline-primary mb-0" style="font-size: 0.8rem; padding: 0.2rem 0.4rem;">
<i class="fas fa-image"></i>
</label>
<input type="file" id="imageUpload" accept="image/*" class="d-none">
<label for="pdfUpload" class="btn btn-sm btn-outline-danger mb-0 ms-1" style="font-size: 0.8rem; padding: 0.2rem 0.4rem;">
<i class="fas fa-file-pdf"></i>
</label>
<input type="file" id="pdfUpload" accept=".pdf" class="d-none">
</div>
</div>
</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 Rendered Equation</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 Rendered Equation</h6>
<p>Enter LaTeX code below to convert it to a rendered mathematical equation.</p>
<p><strong>Examples:</strong></p>
<ul style="text-align: left; font-size: 0.9rem;">
<li><code>\int_{0}^{\infty} e^{-x^2} dx</code></li>
<li><code>\frac{\partial f}{\partial x} + \frac{\partial f}{\partial y}</code></li>
<li><code>\sum_{i=1}^{n} i^2 = \frac{n(n+1)(2n+1)}{6}</code></li>
</ul>
</div>
</div>
<div style="padding: 1.5rem; border-top: 1px solid #e9ecef;">
<textarea id="latexInput" placeholder="Enter LaTeX code here...
Example: \int_{0}^{\infty} e^{-x^2} dx" style="width: 100%; height: 60px; padding: 12px; border-radius: 8px; border: 1px solid #ddd; resize: vertical; font-family: 'Consolas', monospace; font-size: 1rem;"></textarea>
<div style="display: flex; gap: 15px; margin-top: 15px;">
<button class="btn btn-primary-gradient" id="convertLatexBtn" style="flex: 1; padding: 12px; font-size: 1.1rem;">
<i class="fas fa-exchange-alt me-1"></i>Render Equation
</button>
<button class="btn btn-neutral" id="copyTextBtn" style="flex: 1; padding: 12px; font-size: 1.1rem;">
<i class="fas fa-copy me-1"></i>Copy LaTeX
</button>
<button class="btn btn-outline-secondary" id="clearTextBtn" style="flex: 1; padding: 12px; font-size: 1.1rem; border-radius: 6px;">
<i class="fas fa-trash me-1"></i>Clear
</button>
</div>
<div style="margin-top: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h6 style="margin-bottom: 0; color: #4361ee;">Rendered Equation:</h6>
<span id="charCount" style="font-size: 0.9rem; color: #6c757d;">0 characters</span>
</div>
<div id="convertedTextOutput" style="background: white; border: 1px solid #e9ecef; border-radius: 8px; padding: 12px; min-height: 60px; font-size: 1.3rem; font-family: 'Segoe UI', sans-serif; white-space: pre-wrap; overflow-y: auto; max-height: 120px; text-align: center;">
<div id="math-output" style="font-size: 1.5rem;">Your rendered equation will appear here...</div>
</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>
`;
});
});
// Enhanced chat functionality with file upload support
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');
const imageUpload = document.getElementById('imageUpload');
const pdfUpload = document.getElementById('pdfUpload');
// Toggle chat panel
chatToggle.addEventListener('click', () => {
chatPanel.classList.toggle('active');
// Hide the chat toggle button when panel is open
if (chatPanel.classList.contains('active')) {
chatToggle.style.display = 'none';
// Also hide the LaTeX to Text toggle button to prevent interference
const latexToTextToggle = document.getElementById('latexToTextToggle');
if (latexToTextToggle) {
latexToTextToggle.style.display = 'none';
}
} else {
// Show the chat toggle button when panel is closed
chatToggle.style.display = 'flex';
// Also show the LaTeX to Text toggle button
const latexToTextToggle = document.getElementById('latexToTextToggle');
if (latexToTextToggle) {
latexToTextToggle.style.display = 'flex';
}
}
});
// Close chat panel
chatClose.addEventListener('click', () => {
chatPanel.classList.remove('active');
// Show the chat toggle button when panel is closed
chatToggle.style.display = 'flex';
// Also show the LaTeX to Text toggle button
const latexToTextToggle = document.getElementById('latexToTextToggle');
if (latexToTextToggle) {
latexToTextToggle.style.display = 'flex';
}
});
// Also close when clicking outside the panel
document.addEventListener('click', (e) => {
if (chatPanel.classList.contains('active') &&
!chatPanel.contains(e.target) &&
e.target !== chatToggle &&
!chatToggle.contains(e.target)) {
chatPanel.classList.remove('active');
chatToggle.style.display = 'flex';
// Also show the LaTeX to Text toggle button
const latexToTextToggle = document.getElementById('latexToTextToggle');
if (latexToTextToggle) {
latexToTextToggle.style.display = 'flex';
}
}
});
// Toggle LaTeX to Text panel
const latexToTextToggle = document.getElementById('latexToTextToggle');
const latexToTextPanel = document.getElementById('latexToTextPanel');
const latexToTextClose = document.getElementById('latexToTextClose');
// Only add event listeners if elements exist
if (latexToTextToggle && latexToTextPanel && latexToTextClose) {
// Function to show the toggle button
function showLaTeXToggle() {
latexToTextToggle.style.display = 'flex';
}
// Function to hide the toggle button
function hideLaTeXToggle() {
latexToTextToggle.style.display = 'none';
}
// Function to open the LaTeX panel
function openLaTeXPanel() {
// Use a small delay to ensure DOM is ready
setTimeout(() => {
latexToTextPanel.classList.add('active');
// Force reflow to ensure the transition works
latexToTextPanel.offsetHeight;
}, 10);
// Hide the toggle button when panel is open
hideLaTeXToggle();
// Close chat panel if it's open
chatPanel.classList.remove('active');
// Also hide the chat toggle button
if (chatToggle) {
chatToggle.style.display = 'none';
}
}
// Function to close the LaTeX panel
function closeLaTeXPanel() {
latexToTextPanel.classList.remove('active');
showLaTeXToggle();
// Show the chat toggle button when LaTeX panel is closed
if (chatToggle) {
chatToggle.style.display = 'flex';
}
}
// Function to show chat toggle
function showChatToggle() {
if (chatToggle) {
chatToggle.style.display = 'flex';
}
}
// Toggle LaTeX to Text panel - keep button visible
latexToTextToggle.addEventListener('click', () => {
if (latexToTextPanel.classList.contains('active')) {
closeLaTeXPanel();
} else {
openLaTeXPanel();
}
});
// Close LaTeX to Text panel
latexToTextClose.addEventListener('click', closeLaTeXPanel);
// Also close LaTeX to Text panel when clicking outside
document.addEventListener('click', (e) => {
if (latexToTextPanel.classList.contains('active') &&
!latexToTextPanel.contains(e.target) &&
e.target !== latexToTextToggle &&
!latexToTextToggle.contains(e.target)) {
closeLaTeXPanel();
}
});
}
// 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();
// File upload handling
imageUpload.addEventListener('change', function(e) {
if (this.files && this.files[0]) {
const file = this.files[0];
if (!file.type.match('image.*')) {
alert('Please select an image file (PNG, JPG, JPEG)');
this.value = ''; // Clear the input
return;
}
uploadFile(file, 'image');
this.value = ''; // Clear the input after upload
}
});
pdfUpload.addEventListener('change', function(e) {
if (this.files && this.files[0]) {
const file = this.files[0];
if (file.type !== 'application/pdf') {
alert('Please select a PDF file');
this.value = ''; // Clear the input
return;
}
uploadFile(file, 'pdf');
this.value = ''; // Clear the input after upload
}
});
// Upload file function
async function uploadFile(file, type) {
// Add user message to chat with file preview for images
let messageContent = `📁 Uploading ${type} file: ${file.name}`;
addMessageToChat(messageContent, 'user');
// If it's an image, add a preview
if (type === 'image' && file.type.match('image.*')) {
const reader = new FileReader();
reader.onload = function(e) {
// Add the image preview to the user message
const imgPreview = document.createElement('img');
imgPreview.src = e.target.result;
imgPreview.className = 'chat-image-preview';
imgPreview.alt = file.name;
// Find the last user message and add the image preview
setTimeout(() => {
const userMessages = document.querySelectorAll('.user-message');
if (userMessages.length > 0) {
const lastUserMessage = userMessages[userMessages.length - 1];
// Check if the message content matches our upload message
if (lastUserMessage.textContent.includes(file.name)) {
lastUserMessage.insertBefore(imgPreview, lastUserMessage.lastChild);
}
}
}, 100);
};
reader.readAsDataURL(file);
}
// 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>
<div class="loading-text">Processing your ${type} file... This may take a moment.</div>
`;
chatMessages.appendChild(loadingElement);
chatMessages.scrollTop = chatMessages.scrollHeight;
try {
const formData = new FormData();
formData.append(type, file);
const response = await fetch('/chatbot/chat', {
method: 'POST',
body: formData
});
const data = await response.json();
// Remove loading indicator
document.getElementById('loadingIndicator').remove();
if (data.success) {
addMessageToChat(data.response, 'assistant');
// If LaTeX code is returned, add it to the chat in a single clean box
if (data.latex) {
// Create a single clean box for the LaTeX code with copy button
const latexContainer = document.createElement('div');
latexContainer.className = 'chat-code-block';
latexContainer.style.display = 'flex';
latexContainer.style.justifyContent = 'space-between';
latexContainer.style.alignItems = 'center';
latexContainer.style.gap = '10px';
// Add the LaTeX code
const latexText = document.createElement('div');
latexText.textContent = data.latex;
latexText.style.flex = '1';
latexText.style.overflowX = 'auto';
latexText.style.wordBreak = 'break-all';
latexContainer.appendChild(latexText);
// Add copy button
const copyBtn = document.createElement('button');
copyBtn.className = 'btn btn-sm btn-outline-primary';
copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
copyBtn.style.whiteSpace = 'nowrap';
copyBtn.style.flexShrink = '0';
copyBtn.onclick = function() {
navigator.clipboard.writeText(data.latex).then(() => {
showNotification('LaTeX code copied to clipboard!');
}).catch(err => {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = data.latex;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showNotification('LaTeX code copied to clipboard!');
} catch (err) {
showNotification('Failed to copy. Please try again.');
}
document.body.removeChild(textArea);
});
};
latexContainer.appendChild(copyBtn);
const lastMessage = chatMessages.lastChild;
lastMessage.insertBefore(latexContainer, lastMessage.lastChild);
}
// If an image is returned, display it
if (data.image) {
const imgElement = document.createElement('img');
imgElement.src = data.image;
imgElement.className = 'chat-image';
imgElement.alt = 'Processed image result';
const lastMessage = chatMessages.lastChild;
lastMessage.insertBefore(imgElement, lastMessage.lastChild);
}
} else {
addMessageToChat(`❌ ${data.response || 'Error processing file'}`, 'assistant');
}
} catch (error) {
// Remove loading indicator
if (document.getElementById('loadingIndicator')) {
document.getElementById('loadingIndicator').remove();
}
console.error('Upload error:', error);
addMessageToChat('❌ Sorry, I encountered an error processing your file. Please try again.', 'assistant');
}
}
// Enhanced 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>
<div class="loading-text">Thinking...</div>
`;
chatMessages.appendChild(loadingElement);
chatMessages.scrollTop = chatMessages.scrollHeight;
try {
const response = await fetch('/chatbot/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: message })
});
const data = await response.json();
// Remove loading indicator
document.getElementById('loadingIndicator').remove();
if (data.success) {
addMessageToChat(data.response, 'assistant');
} else {
addMessageToChat(`❌ ${data.response || 'Sorry, I encountered an error. Please try again.'}`, 'assistant');
}
} catch (error) {
// Remove loading indicator
document.getElementById('loadingIndicator').remove();
console.error('Error:', error);
addMessageToChat('❌ Sorry, I encountered an error. Please try again.', 'assistant');
}
}
// 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`;
// Process content to detect and format LaTeX code blocks
let processedContent = content;
// Handle image URLs
processedContent = processedContent.replace(/(https?:\/\/.*?\.(?:png|jpe?g|gif))/gi, '<img src="$1" class="chat-image" alt="Image from message">');
// Handle block LaTeX code (enclosed in $$...$$ or \[...\])
processedContent = processedContent.replace(/\$\$(.*?)\$\$/gs, '<div class="chat-code-block">$1</div>');
processedContent = processedContent.replace(/\\\[(.*?)\\\]/gs, '<div class="chat-code-block">$1</div>');
// Handle inline LaTeX code (enclosed in $...$ or \(...\))
processedContent = processedContent.replace(/\$(.*?)\$/g, '<span class="chat-code-inline">$1</span>');
processedContent = processedContent.replace(/\\\((.*?)\\\)/g, '<span class="chat-code-inline">$1</span>');
// Handle code blocks enclosed in triple backticks
processedContent = processedContent.replace(/```([\s\S]*?)```/g, '<div class="chat-code-block">$1</div>');
// Handle inline code enclosed in single backticks
processedContent = processedContent.replace(/`([^`]+)`/g, '<span class="chat-code-inline">$1</span>');
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
messageElement.innerHTML = `
<div>${processedContent}</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
// Update character count
latexInput.addEventListener('input', () => {
const text = latexInput.value;
charCount.textContent = `${text.length} characters`;
// Add warning class for long inputs
if (text.length > 500) {
charCount.classList.add('warning');
} else {
charCount.classList.remove('warning');
}
});
// Allow conversion with Ctrl+Enter
latexInput.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
convertLatexBtn.click();
}
});
// Toggle LaTeX to Text panel
latexToTextToggle.addEventListener('click', () => {
latexToTextPanel.classList.toggle('active');
// Hide the LaTeX to Text toggle button when panel is open
if (latexToTextPanel.classList.contains('active')) {
latexToTextToggle.style.display = 'none';
}
// Close chat panel if it's open
chatPanel.classList.remove('active');
});
// Close LaTeX to Text panel
latexToTextClose.addEventListener('click', () => {
latexToTextPanel.classList.remove('active');
// Show the LaTeX to Text toggle button when panel is closed
latexToTextToggle.style.display = 'flex';
});
// Clear button click handler
clearTextBtn.addEventListener('click', () => {
latexInput.value = '';
document.getElementById('math-output').innerHTML = 'Your rendered equation will appear here...';
convertedTextOutput.style.backgroundColor = 'white';
convertedTextOutput.style.borderColor = '#e9ecef';
charCount.textContent = '0 characters';
charCount.classList.remove('warning');
latexInput.focus();
});
// Select all text in output area when clicked
convertedTextOutput.addEventListener('click', () => {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(convertedTextOutput);
selection.removeAllRanges();
selection.addRange(range);
});
// Convert LaTeX to text function
function convertLatexToText(latexStr) {
if (!latexStr) return "";
// Make a copy to work with
let text = latexStr;
// Remove common LaTeX commands but preserve mathematical structure
text = text.replace(/\\begin\{.*?\}/g, '');
text = text.replace(/\\end\{.*?\}/g, '');
// Handle fractions: \frac{a}{b} -> a/b
text = text.replace(/\\frac\{([^}]*)\}\{([^}]*)\}/g, '($1)/($2)');
// Handle square roots: \sqrt{text} -> √(text)
text = text.replace(/\\sqrt\{([^}]*)\}/g, '√($1)');
text = text.replace(/\\sqrt/g, '√');
// Handle subscripts: _{text} -> _text or _a -> _a
text = text.replace(/_\{([^}]*)\}/g, '_$1');
text = text.replace(/_(\w)/g, '_$1');
// Handle superscripts: ^{text} -> ^text or ^a -> ^a
text = text.replace(/\^\{([^}]*)\}/g, '^$1');
text = text.replace(/\^(\w)/g, '^$1');
// Handle integrals: \int -> ∫
text = text.replace(/\\int/g, '∫');
// Handle sums: \sum -> ∑
text = text.replace(/\\sum/g, '∑');
// Handle products: \prod -> ∏
text = text.replace(/\\prod/g, '∏');
// Handle limits: \lim -> lim
text = text.replace(/\\lim/g, 'lim');
// Handle infinity: \infty -> ∞
text = text.replace(/\\infty/g, '∞');
// Handle partial derivatives: \partial -> ∂
text = text.replace(/\\partial/g, '∂');
// Handle Greek letters
const greekLetters = {
'\\\\alpha': 'α',
'\\\\beta': 'β',
'\\\\gamma': 'γ',
'\\\\delta': 'δ',
'\\\\epsilon': 'ε',
'\\\\zeta': 'ζ',
'\\\\eta': 'η',
'\\\\theta': 'θ',
'\\\\iota': 'ι',
'\\\\kappa': 'κ',
'\\\\lambda': 'λ',
'\\\\mu': 'μ',
'\\\\nu': 'ν',
'\\\\xi': 'ξ',
'\\\\pi': 'π',
'\\\\rho': 'ρ',
'\\\\sigma': 'σ',
'\\\\tau': 'τ',
'\\\\upsilon': 'υ',
'\\\\phi': 'φ',
'\\\\chi': 'χ',
'\\\\psi': 'ψ',
'\\\\omega': 'ω',
'\\\\Gamma': 'Γ',
'\\\\Delta': 'Δ',
'\\\\Theta': 'Θ',
'\\\\Lambda': 'Λ',
'\\\\Xi': 'Ξ',
'\\\\Pi': 'Π',
'\\\\Sigma': 'Σ',
'\\\\Upsilon': 'Υ',
'\\\\Phi': 'Φ',
'\\\\Psi': 'Ψ',
'\\\\Omega': 'Ω'
};
for (const [pattern, replacement] of Object.entries(greekLetters)) {
const regex = new RegExp(pattern, 'g');
text = text.replace(regex, replacement);
}
// Handle mathematical operators and symbols
const mathSymbols = {
'\\\\times': '×',
'\\\\cdot': '·',
'\\\\div': '÷',
'\\\\pm': '±',
'\\\\mp': '∓',
'\\\\leq': '≤',
'\\\\geq': '≥',
'\\\\neq': '≠',
'\\\\ne': '≠',
'\\\\approx': '≈',
'\\\\equiv': '≡',
'\\\\cong': '≅',
'\\\\sim': '∼',
'\\\\subset': '⊂',
'\\\\subseteq': '⊆',
'\\\\supset': '⊃',
'\\\\supseteq': '⊇',
'\\\\cup': '∪',
'\\\\cap': '∩',
'\\\\in': '∈',
'\\\\notin': '∉',
'\\\\forall': '∀',
'\\\\exists': '∃',
'\\\\nexists': '∄',
'\\\\emptyset': '∅',
'\\\\varnothing': '∅',
'\\\\to': '→',
'\\\\rightarrow': '→',
'\\\\Rightarrow': '⇒',
'\\\\leftrightarrow': '↔',
'\\\\Leftrightarrow': '⇔',
'\\\\mapsto': '↦',
'\\\\infty': '∞',
'\\\\angle': '∠',
'\\\\perp': '⊥',
'\\\\parallel': '∥',
'\\\\mid': '∣',
'\\\\nmid': '∤'
};
for (const [pattern, replacement] of Object.entries(mathSymbols)) {
const regex = new RegExp(pattern, 'g');
text = text.replace(regex, replacement);
}
// Handle set notation
text = text.replace(/\\mathbb\{R\}/g, 'ℝ');
text = text.replace(/\\mathbb\{N\}/g, 'ℕ');
text = text.replace(/\\mathbb\{Z\}/g, 'ℤ');
text = text.replace(/\\mathbb\{Q\}/g, 'ℚ');
text = text.replace(/\\mathbb\{C\}/g, 'ℂ');
// Handle trigonometric functions
text = text.replace(/\\sin/g, 'sin');
text = text.replace(/\\cos/g, 'cos');
text = text.replace(/\\tan/g, 'tan');
text = text.replace(/\\cot/g, 'cot');
text = text.replace(/\\sec/g, 'sec');
text = text.replace(/\\csc/g, 'csc');
// Handle logarithms
text = text.replace(/\\log/g, 'log');
text = text.replace(/\\ln/g, 'ln');
// Remove remaining braces and backslashes for cleaner output
text = text.replace(/\{/g, '(').replace(/\}/g, ')');
text = text.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) {
document.getElementById('math-output').innerHTML = 'Please enter some LaTeX code to render.';
convertedTextOutput.style.backgroundColor = '#fff3cd';
convertedTextOutput.style.borderColor = '#ffeaa7';
charCount.textContent = '0 characters';
return;
}
try {
// Render the LaTeX code using MathJax
const mathOutput = document.getElementById('math-output');
mathOutput.innerHTML = `\\(${latexCode}\\)`;
// Typeset the rendered math
if (typeof MathJax !== 'undefined' && MathJax.typeset) {
MathJax.typesetPromise([mathOutput]).then(() => {
// Update character count
charCount.textContent = `${latexCode.length} characters`;
// Style the output area
convertedTextOutput.style.backgroundColor = '#f8f9fa';
convertedTextOutput.style.borderColor = '#dee2e6';
});
} else {
// Fallback if MathJax is not available
mathOutput.innerHTML = `Rendered: ${latexCode}`;
charCount.textContent = `${latexCode.length} characters`;
convertedTextOutput.style.backgroundColor = '#f8f9fa';
convertedTextOutput.style.borderColor = '#dee2e6';
}
} catch (error) {
console.error('Rendering error:', error);
document.getElementById('math-output').innerHTML = 'An error occurred during rendering. Please check your LaTeX syntax.';
convertedTextOutput.style.backgroundColor = '#f8d7da';
convertedTextOutput.style.borderColor = '#f5c6cb';
charCount.textContent = '0 characters';
}
});
// Copy text button click handler
copyTextBtn.addEventListener('click', () => {
const latexCode = latexInput.value.trim();
if (!latexCode) {
showNotification('Nothing to copy!');
return;
}
navigator.clipboard.writeText(latexCode).then(() => {
showNotification('LaTeX code copied to clipboard!');
}).catch(err => {
console.error('Failed to copy:', err);
showNotification('Failed to copy LaTeX code. Please try again.');
});
});
}
// Removed the duplicate loadAuthStatus function and its call to prevent double user name display
</script></body>
</html>