mirror of
https://github.com/danilo-znamerovszkij/c-atlas.git
synced 2025-12-23 22:17:53 -05:00
🖲️ form submit
This commit is contained in:
69
api/submit.ts
Normal file
69
api/submit.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { VercelRequest, VercelResponse } from '@vercel/node'
|
||||
|
||||
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||
// Only allow POST requests
|
||||
if (req.method !== 'POST') {
|
||||
return res.status(405).json({ error: 'Method not allowed' })
|
||||
}
|
||||
|
||||
try {
|
||||
const { name, email, message } = req.body
|
||||
|
||||
// Validate required fields
|
||||
if (!message || !message.trim()) {
|
||||
return res.status(400).json({ error: 'Message is required' })
|
||||
}
|
||||
|
||||
// Get environment variables
|
||||
const tgToken = process.env.TG_BOT_TOKEN
|
||||
const chatId = process.env.TG_CHAT_ID
|
||||
|
||||
// If Telegram is configured, send message
|
||||
if (tgToken && chatId) {
|
||||
const telegramMessage = `📨 New form submission from C-Atlas
|
||||
${name ? `Name: ${name}` : 'Name: Not provided'}
|
||||
${email ? `Email: ${email}` : 'Email: Not provided'}
|
||||
Message: ${message}
|
||||
|
||||
Timestamp: ${new Date().toISOString()}`
|
||||
|
||||
const telegramResponse = await fetch(`https://api.telegram.org/bot${tgToken}/sendMessage`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
chat_id: chatId,
|
||||
text: telegramMessage,
|
||||
parse_mode: 'Markdown'
|
||||
}),
|
||||
})
|
||||
|
||||
if (!telegramResponse.ok) {
|
||||
console.error('Telegram API error:', await telegramResponse.text())
|
||||
return res.status(500).json({ error: 'Failed to send notification' })
|
||||
}
|
||||
} else {
|
||||
// Log to console if Telegram is not configured
|
||||
console.log('Form submission received:', {
|
||||
name: name || 'Not provided',
|
||||
email: email || 'Not provided',
|
||||
message,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
}
|
||||
|
||||
// Return success response
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
message: 'Form submitted successfully'
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('Form submission error:', error)
|
||||
return res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
message: 'Failed to process form submission'
|
||||
})
|
||||
}
|
||||
}
|
||||
62
index.html
62
index.html
@@ -18,22 +18,30 @@
|
||||
<!-- Navigation for testing -->
|
||||
<div class="nav-buttons">
|
||||
<div id="search-container"></div>
|
||||
<button onclick="router.navigateToTheory('materialism', 'iit')">
|
||||
Load IIT Theory
|
||||
<button id="feedback-button" class="feedback-btn">
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
|
||||
/>
|
||||
</svg>
|
||||
Feedback
|
||||
</button>
|
||||
<button onclick="router.navigateToTheory('materialism', 'materialism')">
|
||||
Load Materialism Theory
|
||||
</button>
|
||||
<button onclick="router.navigateToTheory('electromagnetic', 'zhang')">
|
||||
Load Zhang Theory
|
||||
</button>
|
||||
<button onclick="router.goHome()">Home</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Item Details Panel -->
|
||||
<div id="item-details"></div>
|
||||
|
||||
<!-- Form Popup -->
|
||||
<div id="form-popup"></div>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -105,6 +113,42 @@
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
.feedback-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
color: #3b82f6;
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
border-color: rgba(59, 130, 246, 0.3);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid rgba(59, 130, 246, 0.5);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#theory-search {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
|
||||
287
src/components/FormPopup.scss
Normal file
287
src/components/FormPopup.scss
Normal file
@@ -0,0 +1,287 @@
|
||||
.form-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.form-popup {
|
||||
background: rgba(15, 23, 42, 0.95);
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
border-radius: 12px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||||
transform: scale(0.9) translateY(20px);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.form-overlay.visible & {
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.form-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24px 24px 0 24px;
|
||||
border-bottom: 1px solid rgba(59, 130, 246, 0.1);
|
||||
margin-bottom: 24px;
|
||||
|
||||
h3 {
|
||||
color: #f1f5f9;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid rgba(59, 130, 246, 0.5);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-content {
|
||||
padding: 0 24px 24px 24px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
color: #f1f5f9;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
border-radius: 8px;
|
||||
color: #f1f5f9;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
transition: all 0.2s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(59, 130, 246, 0.6);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: rgba(59, 130, 246, 0.4);
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
&:invalid {
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
font-family: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 24px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.form-cancel,
|
||||
.form-submit {
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 100px;
|
||||
justify-content: center;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.form-cancel {
|
||||
background: transparent;
|
||||
border-color: rgba(107, 114, 128, 0.3);
|
||||
color: #9ca3af;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: rgba(107, 114, 128, 0.1);
|
||||
border-color: rgba(107, 114, 128, 0.5);
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid rgba(107, 114, 128, 0.5);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-submit {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border-color: rgba(59, 130, 246, 0.2);
|
||||
color: #3b82f6;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
border-color: rgba(59, 130, 246, 0.3);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid rgba(59, 130, 246, 0.5);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
svg {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.form-error,
|
||||
.form-success {
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-error {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
.form-success {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
border: 1px solid rgba(34, 197, 94, 0.2);
|
||||
color: #86efac;
|
||||
}
|
||||
|
||||
// Mobile responsiveness
|
||||
@media (max-width: 768px) {
|
||||
.form-popup {
|
||||
width: 95%;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
padding: 20px 20px 0 20px;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.form-cancel,
|
||||
.form-submit {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.form-popup {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
padding: 16px 16px 0 16px;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
padding: 0 16px 16px 16px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
248
src/components/FormPopup.ts
Normal file
248
src/components/FormPopup.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import { mockApiSubmit, isApiAvailable } from '@/utils/apiMock'
|
||||
|
||||
export class FormPopup {
|
||||
private container: HTMLElement
|
||||
private overlay: HTMLElement
|
||||
private form: HTMLFormElement
|
||||
private isVisible: boolean = false
|
||||
|
||||
constructor(containerId: string) {
|
||||
const element = document.getElementById(containerId)
|
||||
if (!element) {
|
||||
throw new Error(`Container with id '${containerId}' not found`)
|
||||
}
|
||||
this.container = element
|
||||
this.render()
|
||||
this.attachEventListeners()
|
||||
}
|
||||
|
||||
private render() {
|
||||
this.container.innerHTML = `
|
||||
<div class="form-overlay" id="form-overlay">
|
||||
<div class="form-popup">
|
||||
<div class="form-header">
|
||||
<h3>Send Feedback</h3>
|
||||
<button class="form-close" id="form-close" aria-label="Close form">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form class="form-content" id="feedback-form">
|
||||
<div class="form-group">
|
||||
<label for="feedback-name">Name (optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
id="feedback-name"
|
||||
name="name"
|
||||
placeholder="Your name"
|
||||
aria-label="Your name"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="feedback-email">Email (optional)</label>
|
||||
<input
|
||||
type="email"
|
||||
id="feedback-email"
|
||||
name="email"
|
||||
placeholder="your@email.com"
|
||||
aria-label="Your email"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="feedback-message">Message *</label>
|
||||
<textarea
|
||||
id="feedback-message"
|
||||
name="message"
|
||||
placeholder="Share your thoughts, suggestions, or questions..."
|
||||
rows="4"
|
||||
required
|
||||
aria-label="Your message"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" class="form-cancel" id="form-cancel">Cancel</button>
|
||||
<button type="submit" class="form-submit" id="form-submit">
|
||||
<span class="submit-text">Send Message</span>
|
||||
<span class="submit-loading" style="display: none;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 12a9 9 0 11-6.219-8.56"/>
|
||||
</svg>
|
||||
Sending...
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
this.overlay = this.container.querySelector('#form-overlay') as HTMLElement
|
||||
this.form = this.container.querySelector('#feedback-form') as HTMLFormElement
|
||||
}
|
||||
|
||||
private attachEventListeners() {
|
||||
// Close button
|
||||
const closeBtn = this.container.querySelector('#form-close') as HTMLElement
|
||||
closeBtn.addEventListener('click', () => this.hide())
|
||||
|
||||
// Cancel button
|
||||
const cancelBtn = this.container.querySelector('#form-cancel') as HTMLElement
|
||||
cancelBtn.addEventListener('click', () => this.hide())
|
||||
|
||||
// Overlay click to close
|
||||
this.overlay.addEventListener('click', (e) => {
|
||||
if (e.target === this.overlay) {
|
||||
this.hide()
|
||||
}
|
||||
})
|
||||
|
||||
// Form submission
|
||||
this.form.addEventListener('submit', (e) => this.handleSubmit(e))
|
||||
|
||||
// Escape key to close
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && this.isVisible) {
|
||||
this.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private async handleSubmit(e: Event) {
|
||||
e.preventDefault()
|
||||
|
||||
const submitBtn = this.container.querySelector('#form-submit') as HTMLButtonElement
|
||||
const submitText = this.container.querySelector('.submit-text') as HTMLElement
|
||||
const submitLoading = this.container.querySelector('.submit-loading') as HTMLElement
|
||||
|
||||
// Get form data
|
||||
const formData = new FormData(this.form)
|
||||
const data = {
|
||||
name: formData.get('name') as string || '',
|
||||
email: formData.get('email') as string || '',
|
||||
message: formData.get('message') as string
|
||||
}
|
||||
|
||||
// Validate required message
|
||||
if (!data.message.trim()) {
|
||||
this.showError('Please enter a message')
|
||||
return
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
submitBtn.disabled = true
|
||||
submitText.style.display = 'none'
|
||||
submitLoading.style.display = 'flex'
|
||||
|
||||
try {
|
||||
let response: any
|
||||
|
||||
if (isApiAvailable()) {
|
||||
// Use real API when deployed
|
||||
const apiResponse = await fetch('/api/submit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
|
||||
if (apiResponse.ok) {
|
||||
response = await apiResponse.json()
|
||||
} else {
|
||||
throw new Error('Failed to send message')
|
||||
}
|
||||
} else {
|
||||
// Use mock API in development
|
||||
response = await mockApiSubmit(data)
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
this.showSuccess('Message sent successfully!')
|
||||
this.form.reset()
|
||||
setTimeout(() => this.hide(), 1500)
|
||||
} else {
|
||||
throw new Error('Failed to send message')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Form submission error:', error)
|
||||
this.showError('Failed to send message. Please try again.')
|
||||
} finally {
|
||||
// Reset button state
|
||||
submitBtn.disabled = false
|
||||
submitText.style.display = 'flex'
|
||||
submitLoading.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
private showError(message: string) {
|
||||
// Remove existing error messages
|
||||
const existingError = this.container.querySelector('.form-error')
|
||||
if (existingError) {
|
||||
existingError.remove()
|
||||
}
|
||||
|
||||
// Create error message
|
||||
const errorDiv = document.createElement('div')
|
||||
errorDiv.className = 'form-error'
|
||||
errorDiv.textContent = message
|
||||
|
||||
// Insert after form
|
||||
this.form.parentNode?.insertBefore(errorDiv, this.form.nextSibling)
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (errorDiv.parentNode) {
|
||||
errorDiv.remove()
|
||||
}
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
private showSuccess(message: string) {
|
||||
// Remove existing messages
|
||||
const existingMessage = this.container.querySelector('.form-success')
|
||||
if (existingMessage) {
|
||||
existingMessage.remove()
|
||||
}
|
||||
|
||||
// Create success message
|
||||
const successDiv = document.createElement('div')
|
||||
successDiv.className = 'form-success'
|
||||
successDiv.textContent = message
|
||||
|
||||
// Insert after form
|
||||
this.form.parentNode?.insertBefore(successDiv, this.form.nextSibling)
|
||||
}
|
||||
|
||||
show() {
|
||||
this.isVisible = true
|
||||
this.overlay.classList.add('visible')
|
||||
document.body.style.overflow = 'hidden'
|
||||
|
||||
// Focus first input
|
||||
const firstInput = this.form.querySelector('input, textarea') as HTMLElement
|
||||
if (firstInput) {
|
||||
setTimeout(() => firstInput.focus(), 100)
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.isVisible = false
|
||||
this.overlay.classList.remove('visible')
|
||||
document.body.style.overflow = ''
|
||||
|
||||
// Clear form
|
||||
this.form.reset()
|
||||
|
||||
// Remove any error/success messages
|
||||
const errorMsg = this.container.querySelector('.form-error')
|
||||
const successMsg = this.container.querySelector('.form-success')
|
||||
if (errorMsg) errorMsg.remove()
|
||||
if (successMsg) successMsg.remove()
|
||||
}
|
||||
|
||||
isOpen(): boolean {
|
||||
return this.isVisible
|
||||
}
|
||||
}
|
||||
@@ -468,7 +468,7 @@ export const getChartOptions = (): EChartsOption => {
|
||||
return {
|
||||
backgroundColor: 'transparent',
|
||||
title: {
|
||||
text: 'C-Atlas Chart',
|
||||
// text: 'C-Atlas Chart',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
|
||||
33
src/data/GlobalWorkspace.json
Normal file
33
src/data/GlobalWorkspace.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"theoryTitle": "Global Workspace Theory",
|
||||
"category": "Cognitive Science",
|
||||
"summary": "",
|
||||
"overview": {
|
||||
"purpose": "",
|
||||
"focus": "",
|
||||
"approach": ""
|
||||
},
|
||||
"components": {
|
||||
"ontologicalStatus": "",
|
||||
"explanatoryIdentity": "",
|
||||
"functionAndEvolution": {
|
||||
"function": "",
|
||||
"evolution": ""
|
||||
},
|
||||
"causation": "",
|
||||
"location": "",
|
||||
"arguments": []
|
||||
},
|
||||
"bigQuestions": {
|
||||
"ultimateMeaning": "",
|
||||
"aiConsciousness": "",
|
||||
"virtualImmortality": "",
|
||||
"survivalBeyondDeath": ""
|
||||
},
|
||||
"philosophicalFocus": {
|
||||
"mindBodyProblem": "",
|
||||
"consciousnessNature": "",
|
||||
"primitiveVsEmergent": "",
|
||||
"reductionism": ""
|
||||
}
|
||||
}
|
||||
33
src/data/IntegratedWorldModel.json
Normal file
33
src/data/IntegratedWorldModel.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"theoryTitle": "Integrated World Model Theory",
|
||||
"category": "Cognitive Science",
|
||||
"summary": "",
|
||||
"overview": {
|
||||
"purpose": "",
|
||||
"focus": "",
|
||||
"approach": ""
|
||||
},
|
||||
"components": {
|
||||
"ontologicalStatus": "",
|
||||
"explanatoryIdentity": "",
|
||||
"functionAndEvolution": {
|
||||
"function": "",
|
||||
"evolution": ""
|
||||
},
|
||||
"causation": "",
|
||||
"location": "",
|
||||
"arguments": []
|
||||
},
|
||||
"bigQuestions": {
|
||||
"ultimateMeaning": "",
|
||||
"aiConsciousness": "",
|
||||
"virtualImmortality": "",
|
||||
"survivalBeyondDeath": ""
|
||||
},
|
||||
"philosophicalFocus": {
|
||||
"mindBodyProblem": "",
|
||||
"consciousnessNature": "",
|
||||
"primitiveVsEmergent": "",
|
||||
"reductionism": ""
|
||||
}
|
||||
}
|
||||
33
src/data/QuantumMind.json
Normal file
33
src/data/QuantumMind.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"theoryTitle": "Quantum Mind Theory",
|
||||
"category": "Quantum Consciousness",
|
||||
"summary": "",
|
||||
"overview": {
|
||||
"purpose": "",
|
||||
"focus": "",
|
||||
"approach": ""
|
||||
},
|
||||
"components": {
|
||||
"ontologicalStatus": "",
|
||||
"explanatoryIdentity": "",
|
||||
"functionAndEvolution": {
|
||||
"function": "",
|
||||
"evolution": ""
|
||||
},
|
||||
"causation": "",
|
||||
"location": "",
|
||||
"arguments": []
|
||||
},
|
||||
"bigQuestions": {
|
||||
"ultimateMeaning": "",
|
||||
"aiConsciousness": "",
|
||||
"virtualImmortality": "",
|
||||
"survivalBeyondDeath": ""
|
||||
},
|
||||
"philosophicalFocus": {
|
||||
"mindBodyProblem": "",
|
||||
"consciousnessNature": "",
|
||||
"primitiveVsEmergent": "",
|
||||
"reductionism": ""
|
||||
}
|
||||
}
|
||||
13
src/main.ts
13
src/main.ts
@@ -5,10 +5,12 @@ import { SVGRenderer } from 'echarts/renderers'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { ChartExample } from '@/components/ChartExample'
|
||||
import { SearchBar } from '@/components/SearchBar'
|
||||
import { FormPopup } from '@/components/FormPopup'
|
||||
import { Router } from '@/utils/routing'
|
||||
|
||||
// Import styles
|
||||
import './components/ItemDetailsPanel.scss'
|
||||
import './components/FormPopup.scss'
|
||||
|
||||
// Register the required components
|
||||
echarts.use([TitleComponent, SunburstChart, SVGRenderer, CanvasRenderer])
|
||||
@@ -27,6 +29,17 @@ const itemDetailsPanel = chartExample.getItemDetailsPanel()
|
||||
// Initialize search bar
|
||||
new SearchBar('search-container', router)
|
||||
|
||||
// Initialize form popup
|
||||
const formPopup = new FormPopup('form-popup')
|
||||
|
||||
// Connect feedback button
|
||||
const feedbackButton = document.getElementById('feedback-button')
|
||||
if (feedbackButton) {
|
||||
feedbackButton.addEventListener('click', () => {
|
||||
formPopup.show()
|
||||
})
|
||||
}
|
||||
|
||||
// Set up close callback to navigate to home when panel is closed
|
||||
itemDetailsPanel.setCloseCallback(() => {
|
||||
router.goHome()
|
||||
|
||||
@@ -10,7 +10,8 @@ body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #1b1b1b;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
@@ -83,22 +84,22 @@ button:focus-visible {
|
||||
color: #1e293b;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
background: #f8fafc;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
|
||||
#main {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
button {
|
||||
background-color: rgba(59, 130, 246, 0.1);
|
||||
color: #1d4ed8;
|
||||
border-color: rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
|
||||
button:hover {
|
||||
background-color: rgba(59, 130, 246, 0.15);
|
||||
border-color: rgba(59, 130, 246, 0.5);
|
||||
|
||||
24
src/utils/apiMock.ts
Normal file
24
src/utils/apiMock.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// Mock API for development when not using Vercel CLI
|
||||
export const mockApiSubmit = async (data: { name: string; email: string; message: string }) => {
|
||||
// Simulate API delay
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// Log to console for development
|
||||
console.log('📨 Mock form submission:', {
|
||||
name: data.name || 'Not provided',
|
||||
email: data.email || 'Not provided',
|
||||
message: data.message,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
|
||||
// Simulate success response
|
||||
return {
|
||||
success: true,
|
||||
message: 'Form submitted successfully (mock)'
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're in development and API is not available
|
||||
export const isApiAvailable = () => {
|
||||
return typeof window !== 'undefined' && window.location.hostname !== 'localhost'
|
||||
}
|
||||
Reference in New Issue
Block a user