This commit is contained in:
2024-11-08 18:31:50 +03:00
parent c65caa5bcc
commit aa84cc8793
40 changed files with 3655 additions and 0 deletions

11
poc/components/faq.html Normal file
View File

@@ -0,0 +1,11 @@
<section class="faq-container">
<div class="markdown-content">
[[{{ config["name"] }}]]
</div>
<div class="faq-section">
<h4>Frequently Asked Questions</h4>
[[{{ config["section_name"] }}]]
</div>
</section>

203
poc/components/footer.html Normal file
View File

@@ -0,0 +1,203 @@
{% include 'components/login.html' %}
<footer class="tf_footer">
<div class="tf_footer_container">
<div class="tf_footer_section">
<h3>ThreeFold</h3>
<p>Building a decentralized internet <br>for a better world.</p>
</div>
<div class="tf_footer_section">
<h4>Links</h4>
<div class="footer_links">
<a href="#">About</a>
<a href="#">Technology</a>
<a href="#">Community</a>
<a href="#">Contact</a>
</div>
</div>
<div class="tf_footer_section">
<h4>Resources</h4>
<div class="footer_links">
<a href="#">Documentation</a>
<a href="#">Blog</a>
<a href="#">FAQ</a>
<a href="#">Support</a>
</div>
</div>
<div class="tf_footer_section">
<h4>Connect</h4>
<div class="footer_links">
<a href="#">Twitter</a>
<a href="#">Telegram</a>
<a href="#">GitHub</a>
<a href="#">Discord</a>
</div>
</div>
</div>
<div class="tf_footer_bottom">
<p>&copy; 2024 ThreeFold. All rights reserved.</p>
</div>
</footer>
<style>
:root {
/* Light theme variables */
--footer-background-light: #FFFFFFF2;
--footer-text-light: #333;
--footer-link-light: #385bb5;
--footer-link-hover-light: #2a4494;
--footer-border-light: #eee;
/* Dark theme variables */
--footer-background-dark: #282C34F2;
--footer-text-dark: #fff;
--footer-link-dark: #7a9bff;
--footer-link-hover-dark: #a8beff;
--footer-border-dark: #444;
/* Default to dark theme */
--footer-background: var(--footer-background-dark);
--footer-text: var(--footer-text-dark);
--footer-link: var(--footer-link-dark);
--footer-link-hover: var(--footer-link-hover-dark);
--footer-border: var(--footer-border-dark);
}
/* Light theme class */
.light-theme {
--footer-background: var(--footer-background-light);
--footer-text: var(--footer-text-light);
--footer-link: var(--footer-link-light);
--footer-link-hover: var(--footer-link-hover-light);
--footer-border: var(--footer-border-light);
}
.tf_footer {
background-color: var(--footer-background);
color: var(--footer-text);
padding: 1.5rem 0 0.5rem;
margin-top: auto;
transition: background-color 0.3s, color 0.3s;
max-height: 160px;
box-sizing: border-box;
}
.tf_footer_container {
max-width: 1200px;
margin: 0 auto;
padding: 0 0rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 0.8rem;
}
.tf_footer_section {
display: flex;
flex-direction: column;
}
.tf_footer_section h3 {
font-size: 1.1rem;
margin: 0 0 0.4rem;
color: var(--footer-text);
}
.tf_footer_section h4 {
font-size: 0.9rem;
margin: 0 0 0.3rem;
color: var(--footer-text);
padding-left: 0.5rem;
}
.tf_footer_section p {
margin: 0;
line-height: 1.3;
font-size: 0.8rem;
color: var(--footer-text);
}
.footer_links {
display: flex;
flex-direction: column;
gap: 0.2rem;
padding-left: 1rem;
}
.footer_links a {
color: var(--footer-link);
text-decoration: none;
transition: color 0.3s;
font-size: 0.8rem;
line-height: 1.2;
}
.footer_links a:hover {
color: var(--footer-link-hover);
}
.tf_footer_bottom {
margin-top: 1rem;
padding-top: 0.5rem;
text-align: center;
border-top: 1px solid var(--footer-border);
}
.tf_footer_bottom p {
margin: 0;
font-size: 0.75rem;
color: var(--footer-text);
}
@media (max-width: 768px) {
.tf_footer_container {
grid-template-columns: repeat(2, 1fr);
gap: 0.8rem;
}
.tf_footer {
padding: 1rem 0 0.4rem;
}
}
@media (max-width: 480px) {
.tf_footer_container {
grid-template-columns: repeat(2, 1fr);
}
.tf_footer {
padding: 0.8rem 0 0.3rem;
}
.tf_footer_section {
font-size: 0.75rem;
}
.tf_footer_section h4 {
padding-left: 0.3rem;
}
.footer_links {
padding-left: 0.6rem;
}
}
</style>
<script>
// Ensure theme changes are applied consistently across all components
function applyTheme(theme) {
document.documentElement.className = theme;
localStorage.setItem('theme', theme);
}
// Initialize theme from localStorage or default to dark
document.addEventListener('DOMContentLoaded', () => {
const savedTheme = localStorage.getItem('theme') || '';
applyTheme(savedTheme);
});
// Override the toggleTheme function from nav.html
function toggleTheme() {
const isLight = document.documentElement.classList.contains('light-theme');
applyTheme(isLight ? '' : 'light-theme');
}
</script>

62
poc/components/globe.html Normal file
View File

@@ -0,0 +1,62 @@
<div class="globe-container">
<canvas
id="cobe"
style="width: 500px; height: 500px"
width="1000"
height="1000"
></canvas>
</div>
<style>
.globe-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
padding: 20px 0;
}
</style>
<script type="module">
import createGlobe from 'https://cdn.skypack.dev/cobe'
let phi = 0
let canvas = document.getElementById("cobe")
// Detect system dark mode
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const globe = createGlobe(canvas, {
devicePixelRatio: 2,
width: 1000,
height: 1000,
phi: 0,
theta: 0.3,
dark: isDarkMode ? 1 : 0,
diffuse: 1.2,
scale: 1,
mapSamples: 16000,
mapBrightness: isDarkMode ? 15 : 4,
baseColor: isDarkMode ? [0.8, 0.8, 0.8] : [0.3, 0.3, 0.3],
markerColor: [0.1, 0.8, 1],
glowColor: isDarkMode ? [0.6, 0.6, 0.6] : [0.2, 0.2, 0.2],
offset: [0, 0],
markers: [
{ location: [37.7595, -122.4367], size: 0.03 },
{ location: [40.7128, -74.006], size: 0.1 },
],
onRender: (state) => {
state.phi = phi
phi += 0.005
},
})
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
const newIsDarkMode = e.matches
globe.dark = newIsDarkMode ? 1 : 0
globe.baseColor = newIsDarkMode ? [0.8, 0.8, 0.8] : [0.3, 0.3, 0.3]
globe.mapBrightness = newIsDarkMode ? 15 : 4
globe.glowColor = newIsDarkMode ? [0.6, 0.6, 0.6] : [0.2, 0.2, 0.2]
})
</script>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en" x-data>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Decentralized Internet</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;450;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<link rel="stylesheet" href="/static/ourworld.css">
<link rel="stylesheet" href="/static/menu.css">
<link rel="stylesheet" href="/static/login.css">
<link rel="stylesheet" href="/static/faq.css">
<!-- Load Alpine.js from CDN -->
<script defer src="https://unpkg.com/alpinejs@3.13.3/dist/cdn.min.js"></script>
</head>

View File

@@ -0,0 +1,133 @@
<div class="hero2_container" x-data="{ isZoomingIn: true }" x-init="setInterval(() => isZoomingIn = !isZoomingIn, 15000)">
<!-- Text Box -->
<div class="hero2_text-box">
<h2 class="hero2_title"> {{config["title"]}} </h2>
<p class="hero2_subtitle"> {{config["subtitle"]}} </p>
</div>
<!-- Image Wrapper -->
<div class="hero2_image-wrapper">
<img src="{{config["image"]}}"
class="hero2_floating-image"
:style="isZoomingIn ? 'transform: scale(1.5)' : 'transform: scale(1)'"
style="transition: transform 20s ease-in">
</div>
</div>
<style>
main {
background-color: var(--body-background);
transition: background-color 0.3s;
}
.hero2_container {
position: relative;
padding: 3rem;
background-color: var(--modal-background);
border-radius: 1.5rem;
box-shadow: 0 8px 32px #00000026;
min-height: 400px;
width: 100%;
max-width: 1200px;
overflow: hidden;
display: flex;
align-items: center;
margin: 2rem auto;
transition: all 0.3s ease;
}
.hero2_text-box {
position: relative;
z-index: 3;
max-width: 500px;
padding-right: 2rem;
}
.hero2_title {
font-size: clamp(1.5rem, 5vw, 2.5rem);
font-weight: bold;
color: var(--text-color);
margin: 0;
text-shadow: 0 1px 2px #0000001A;
line-height: 1.2;
transition: color 0.3s;
}
.hero2_subtitle {
margin-top: 1rem;
background-color: #385bb5;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
display: inline-block;
box-shadow: 0 2px 4px #0000001A;
font-size: clamp(0.875rem, 2vw, 1rem);
max-width: 100%;
transition: all 0.3s ease;
}
.light-theme .hero2_subtitle {
background-color: #385bb5;
color: white;
}
.hero2_image-wrapper {
position: absolute;
top: 50%;
right: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
z-index: 2;
pointer-events: none;
transform: translateY(-50%);
}
.hero2_floating-image {
width: 1000px;
max-width: 100%;
height: auto;
transition: transform 15s ease-in;
margin-right: -100px;
transform-origin: center center;
}
[x-cloak] {
display: none !important;
}
@media (max-width: 768px) {
.hero2_container {
padding: 2rem;
min-height: 250px;
width: 90%;
margin: 1rem auto;
}
.hero2_text-box {
padding-right: 0;
max-width: 100%;
}
.hero2_image-wrapper {
opacity: 0.4;
}
.hero2_floating-image {
margin-right: -200px;
}
.hero2_subtitle {
margin-top: 0.75rem;
}
}
@media (max-width: 480px) {
.hero2_container {
padding: 1.2rem;
min-height: 200px;
width: 85%;
}
.hero2_floating-image {
margin-right: -200px;
}
}
</style>

View File

@@ -0,0 +1,98 @@
<div class="hero1">
<div class="hero1_box">
<h1 class="hero1_title"></h1>
<p class="hero1_subtitle">[[{{ config["subtitle"] }}]]</p>
</div>
<div class="hero1_banner"> </div>
</div>
<style>
.hero1 {
position: relative;
padding: 2.5rem;
background-color: var(--hero-background);
border-radius: 1.2rem;
box-shadow: 0 8px 32px #0000004D;
min-height: 400px;
width: 100%;
max-width: 1200px;
overflow: hidden;
display: flex;
align-items: center;
gap: 2.5rem;
margin: 2rem auto;
color: var(--hero-text);
}
.hero1_box {
position: relative;
z-index: 3;
max-width: 500px;
flex-shrink: 1;
}
.hero1_title {
font-size: clamp(1.5rem, 5vw, 2.5rem);
font-weight: bold;
color: var(--hero-text);
margin: 0;
text-shadow: 0 2px 4px #00000033;
line-height: 1.2;
}
.hero1_subtitle {
margin-top: 1rem;
background-color: var(--hero-subtitle-background);
color: var(--hero-subtitle-text);
padding: 0.4rem 0.8rem;
border-radius: 0.4rem;
display: inline-block;
text-shadow: 0 1px 2px #00000033;
box-shadow: 0 2px 4px #00000033;
font-size: clamp(0.875rem, 2vw, 1rem);
max-width: 100%;
}
.hero1_banner {
flex-grow: 1;
background-color: var(--hero-banner-background);
padding: 1.3rem;
border-radius: 0.8rem;
line-height: 1.4;
font-size: 1rem;
min-height: 300px;
color: var(--hero-text);
}
@media (max-width: 768px) {
.hero1 {
padding: 1.6rem;
min-height: 250px;
width: 90%;
flex-direction: column;
}
.hero1_box {
padding-right: 0;
max-width: 100%;
}
.hero1_subtitle {
margin-top: 0.75rem;
}
.hero1_banner {
padding: 1.2rem;
}
}
@media (max-width: 480px) {
.hero1_banner {
font-size: 0.9rem;
padding: 1rem;
}
.hero1 {
padding: 1rem;
min-height: 200px;
width: 85%;
}
}
</style>

View File

@@ -0,0 +1,217 @@
<div class="modal-overlay" id="identifyModal">
<div class="modal-container">
<div class="modal-header">
<h2>Identify Yourself</h2>
<p class="modal-description">Enter your secret password to access the key management system.</p>
</div>
<form class="modal-form" id="identifyForm" onsubmit="return handleIdentify(event)">
<div class="form-group">
<label for="secret">Secret Password</label>
<input type="password" id="secret" name="secret" required
placeholder="Enter your secret password"
autocomplete="current-password">
<div class="error-message" id="errorMessage"></div>
</div>
<div class="modal-buttons">
<button type="button" class="btn btn-secondary" onclick="closeIdentifyModal()">Cancel</button>
<button type="submit" class="btn btn-primary" id="identifyButton">Identify</button>
</div>
</form>
</div>
</div>
<script>
function showIdentifyModal() {
document.getElementById('identifyModal').style.display = 'block';
document.getElementById('secret').focus();
}
function closeIdentifyModal() {
document.getElementById('identifyModal').style.display = 'none';
document.getElementById('identifyForm').reset();
document.getElementById('errorMessage').style.display = 'none';
}
async function handleIdentify(event) {
event.preventDefault();
const secret = document.getElementById('secret').value;
const errorMessage = document.getElementById('errorMessage');
const identifyButton = document.getElementById('identifyButton');
// Disable button during processing
identifyButton.disabled = true;
identifyButton.textContent = 'Processing...';
try {
if (!window.kvs) {
throw new Error('Key management system not initialized');
}
// Set the password in session storage
window.kvs.setPassword(secret);
// Try to generate/retrieve the private key to verify the password
await window.kvs.generate_private_key();
// If successful, close the modal
closeIdentifyModal();
// Dispatch an event to notify that identification was successful
window.dispatchEvent(new CustomEvent('userIdentified'));
} catch (error) {
console.error('Identification error:', error);
errorMessage.textContent = error.message || 'Invalid secret password';
errorMessage.style.display = 'block';
window.kvs.clearSession();
} finally {
// Re-enable button
identifyButton.disabled = false;
identifyButton.textContent = 'Identify';
}
return false;
}
// Check session validity periodically
const sessionCheckInterval = setInterval(() => {
if (window.kvs && !window.kvs.isSessionValid()) {
window.kvs.clearSession();
showIdentifyModal();
}
}, 60000); // Check every minute
// Cleanup interval when page unloads
window.addEventListener('unload', () => {
clearInterval(sessionCheckInterval);
});
// Initial check when the page loads
window.addEventListener('DOMContentLoaded', () => {
if (!window.kvs || !window.kvs.isSessionValid()) {
showIdentifyModal();
}
});
</script>
<style>
.modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
backdrop-filter: blur(3px);
}
.modal-container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
z-index: 1001;
width: 90%;
max-width: 400px;
}
.modal-header {
margin-bottom: 1.5rem;
text-align: center;
}
.modal-description {
color: #666;
margin-top: 0.5rem;
font-size: 0.9rem;
}
.modal-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-group label {
font-weight: bold;
color: #333;
}
.form-group input {
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.2s;
}
.form-group input:focus {
border-color: #007bff;
outline: none;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.modal-buttons {
display: flex;
justify-content: flex-end;
gap: 1rem;
margin-top: 1.5rem;
}
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: all 0.2s;
font-weight: 500;
}
.btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover:not(:disabled) {
background-color: #545b62;
}
.error-message {
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.5rem;
display: none;
padding: 0.5rem;
background-color: #fff5f5;
border-radius: 4px;
border: 1px solid #ffebee;
}
</style>

123
poc/components/login.html Normal file
View File

@@ -0,0 +1,123 @@
<!-- Login Modal -->
<div id="loginModal" class="modal">
<div class="modal-content">
<div class="login-box">
<div class="login-header">
<svg class="logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
<h2>Welcome Back</h2>
<p>Sign in to your ThreeFold account</p>
</div>
<form id="login-form" onsubmit="return validateLoginForm(event)">
<div class="form-group">
<label for="login-email">Email</label>
<input
type="email"
id="login-email"
name="email"
required
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
placeholder="Enter your email"
autocomplete="email"
>
<span class="error-message" id="login-emailError"></span>
</div>
<div class="form-group">
<label for="login-password">Password</label>
<input
type="password"
id="login-password"
name="password"
required
minlength="8"
placeholder="Enter your password"
autocomplete="current-password"
>
<span class="error-message" id="login-passwordError"></span>
</div>
<div class="form-footer">
<label class="remember-me">
<input type="checkbox" name="remember"> Remember me
</label>
<a href="#" class="forgot-password">Forgot Password?</a>
</div>
<button type="submit" class="submit-button">Sign In</button>
</form>
<div class="signup-link">
Don't have an account? <a href="#" onclick="openSignupModal(); closeLoginModal();">Sign up</a>
</div>
<button class="close-button" onclick="closeLoginModal()">&times;</button>
</div>
</div>
</div>
<script>
function openLoginModal() {
const modal = document.getElementById('loginModal');
modal.style.display = 'flex';
document.body.style.overflow = 'hidden';
}
function closeLoginModal() {
const modal = document.getElementById('loginModal');
modal.style.display = 'none';
document.body.style.overflow = 'auto';
}
// Close modal when clicking outside
window.onclick = function(event) {
const modal = document.getElementById('loginModal');
if (event.target === modal) {
closeLoginModal();
}
}
function validateLoginForm(event) {
event.preventDefault();
const email = document.getElementById('login-email');
const emailError = document.getElementById('login-emailError');
const password = document.getElementById('login-password');
const passwordError = document.getElementById('login-passwordError');
// Reset error messages
emailError.textContent = '';
passwordError.textContent = '';
let isValid = true;
// Email validation
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(email.value)) {
emailError.textContent = 'Please enter a valid email address';
isValid = false;
}
// Password validation
if (password.value.length < 8) {
passwordError.textContent = 'Password must be at least 8 characters long';
isValid = false;
}
if (isValid) {
// Here you would typically send the form data to your server
console.log('Form is valid, ready to submit');
closeLoginModal();
}
return false; // Prevent form submission
}
// Real-time email validation
document.getElementById('login-email')?.addEventListener('input', function(e) {
const emailError = document.getElementById('login-emailError');
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (this.value && !emailRegex.test(this.value)) {
emailError.textContent = 'Please enter a valid email address';
} else {
emailError.textContent = '';
}
});
</script>

View File

@@ -0,0 +1,60 @@
<div class="tf_right_controls">
<button class="tf_signup_btn" onclick="openSignupModal()">Sign Up</button>
<button class="tf_login_btn" onclick="openLoginModal()">Login</button>
<button class="tf_theme_toggle" @click="document.documentElement.classList.toggle('light-theme')">
<svg class="theme-icon light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
<svg class="theme-icon dark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
</button>
</div>
<style>
.tf_right_controls {
display: flex;
align-items: center;
gap: 2rem;
margin-left: 4rem;
}
.tf_signup_btn {
background: transparent;
border: 0.5px solid var(--text-color);
color: var(--text-color);
padding: 0 1rem;
border-radius: 4px;
font-size: 0.8125rem;
cursor: pointer;
transition: all 0.2s;
margin-right: 0.5rem;
height: 28px;
min-width: 80px;
line-height: 28px;
font-weight: normal;
}
.tf_signup_btn:hover {
background: var(--text-color);
color: var(--body-background);
transform: translateY(-1px);
box-shadow: 0 2px 4px #0000001A;
}
.tf_login_btn {
height: 28px;
margin-right: 0.5rem;
min-width: 80px;
line-height: 28px;
padding: 0 1rem;
}
</style>

25
poc/components/nav.html Normal file
View File

@@ -0,0 +1,25 @@
<div x-data="{ mobileMenuOpen: false }">
<nav class="tf_nav">
<div class="tf_nav_container">
<button class="hamburger-menu" @click="mobileMenuOpen = !mobileMenuOpen">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<a href="#" class="tf_logo">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
<span>ThreeFold</span>
</a>
<ul class="tf_menu" :class="{ 'active': mobileMenuOpen }">
[[navcontent]]
</ul>
{% include 'components/login_lightswitch.html' %}
</div>
</nav>
<div class="mobile-overlay" :class="{ 'active': mobileMenuOpen }" @click="mobileMenuOpen = false"></div>
</div>

149
poc/components/roadmap.html Normal file
View File

@@ -0,0 +1,149 @@
<div class="roadmap-container">
<h1>Our Journey</h1>
<div class="timeline">
<div class="milestone">
<div class="milestone-date">2023 Q4</div>
<div class="milestone-content">
<h3>Platform Launch</h3>
<p>Initial release of our decentralized infrastructure and core services.</p>
</div>
</div>
<div class="milestone">
<div class="milestone-date">2024 Q1</div>
<div class="milestone-content">
<h3>Network Expansion</h3>
<p>Global node deployment and enhanced network capabilities.</p>
</div>
</div>
<div class="milestone">
<div class="milestone-date">2024 Q2</div>
<div class="milestone-content">
<h3>Developer Tools</h3>
<p>Release of comprehensive SDK and developer documentation.</p>
</div>
</div>
<div class="milestone">
<div class="milestone-date">2024 Q3</div>
<div class="milestone-content">
<h3>Enterprise Solutions</h3>
<p>Launch of enterprise-grade features and support services.</p>
</div>
</div>
</div>
</div>
<style>
.roadmap-container {
width: 100%;
max-width: 1200px;
margin: 2rem auto;
padding: 2rem 3rem;
background: var(--hero-background);
border-radius: 1.2rem;
box-shadow: 0 8px 32px #0000004D;
}
.roadmap-container h1 {
color: var(--hero-subtitle-background);
text-align: center;
margin-bottom: 2rem;
}
.timeline {
position: relative;
padding: 2rem 0;
}
.timeline::before {
content: '';
position: absolute;
left: 85px;
top: 0;
bottom: 0;
width: 2px;
background: #888;
}
.milestone {
display: flex;
margin-bottom: 2rem;
position: relative;
align-items: flex-start;
}
.milestone::before {
content: '';
position: absolute;
left: 85px;
top: 15px;
width: 18px;
height: 18px;
border-radius: 50%;
background: #888;
border: 2px solid var(--hero-background);
transform: translate(-50%, -50%);
box-sizing: border-box;
}
.milestone-date {
width: 70px;
padding-right: 1rem;
font-family: 'Poppins', sans-serif;
font-weight: 600;
color: var(--hero-subtitle-background);
padding-top: 8px;
white-space: nowrap;
}
.milestone-content {
flex: 1;
background: rgba(255, 255, 255, 0.05);
padding: 1.5rem 2rem;
border-radius: 8px;
margin-left: 2rem;
margin-right: 1rem;
}
.milestone-content h3 {
margin-top: 0;
color: var(--hero-subtitle-background);
}
.milestone-content p {
margin-bottom: 0;
color: var(--body-text);
}
@media (max-width: 768px) {
.roadmap-container {
width: 90%;
padding: 1.6rem;
}
.timeline::before {
left: 50px;
}
.milestone::before {
left: 50px;
}
.milestone-date {
width: 60px;
font-size: 0.9rem;
}
.milestone-content {
margin-left: 1.5rem;
margin-right: 0.5rem;
padding: 1rem 1.5rem;
}
}
@media (max-width: 480px) {
.roadmap-container {
width: 85%;
padding: 1rem;
}
}
</style>

313
poc/components/signup.html Normal file
View File

@@ -0,0 +1,313 @@
<!-- Signup Modal -->
<div id="signupModal" class="modal">
<div class="modal-content">
<div class="login-box">
<div class="login-header">
<svg class="logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
<h2>Join ThreeFold</h2>
<p>Create your ThreeFold account</p>
</div>
<form id="signup-form" onsubmit="return validateSignupForm(event)">
<div class="form-group compact">
<label for="signup-name">Full Name</label>
<input
type="text"
id="signup-name"
name="name"
required
placeholder="Enter your full name"
autocomplete="name"
>
<span class="error-message" id="signup-nameError"></span>
</div>
<div class="form-group compact">
<label for="signup-email">Email</label>
<input
type="email"
id="signup-email"
name="email"
required
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
placeholder="Enter your email"
autocomplete="email"
>
<span class="error-message" id="signup-emailError"></span>
</div>
<div class="form-group compact">
<label for="signup-password">Password</label>
<input
type="password"
id="signup-password"
name="password"
required
minlength="8"
placeholder="Enter your password"
autocomplete="new-password"
>
<span class="error-message" id="signup-passwordError"></span>
</div>
<div class="form-group compact">
<label for="signup-tel">Phone Number</label>
<input
type="tel"
id="signup-tel"
name="tel"
required
placeholder="Enter your phone number"
autocomplete="tel"
>
<span class="error-message" id="signup-telError"></span>
</div>
<div class="form-group compact">
<label for="signup-country">Country</label>
<select id="signup-country" name="country" required class="form-control">
<option value="">Select your country</option>
</select>
<span class="error-message" id="signup-countryError"></span>
</div>
<div class="form-group compact">
[[signup_interests]]
<span class="error-message" id="signup-interestsError"></span>
</div>
<button type="submit" class="submit-button">Sign Up</button>
</form>
<div class="signup-link">
Already have an account? <a href="#" onclick="openLoginModal(); closeSignupModal();">Sign in</a>
</div>
<button class="close-button" onclick="closeSignupModal()">&times;</button>
</div>
</div>
</div>
<script>
// Populate countries dropdown
[[system/countries]]
const countrySelect = document.getElementById('signup-country');
countries.forEach(country => {
const option = document.createElement('option');
option.value = country;
option.textContent = country;
countrySelect.appendChild(option);
});
function openSignupModal() {
const modal = document.getElementById('signupModal');
modal.style.display = 'flex';
document.body.style.overflow = 'hidden';
}
function closeSignupModal() {
const modal = document.getElementById('signupModal');
modal.style.display = 'none';
document.body.style.overflow = 'auto';
}
// Close modal when clicking outside
window.onclick = function(event) {
const modal = document.getElementById('signupModal');
if (event.target === modal) {
closeSignupModal();
}
}
// Real-time validation functions
function validatePassword(password) {
return password.length >= 8;
}
function validatePhoneNumber(phone) {
return /^\+?[\d\s-]{10,}$/.test(phone);
}
function validateEmail(email) {
return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email);
}
// Real-time validation event listeners
document.getElementById('signup-password')?.addEventListener('input', function(e) {
const passwordError = document.getElementById('signup-passwordError');
if (this.value && !validatePassword(this.value)) {
passwordError.textContent = 'Password must be at least 8 characters long';
} else {
passwordError.textContent = '';
}
});
document.getElementById('signup-tel')?.addEventListener('input', function(e) {
const telError = document.getElementById('signup-telError');
if (this.value && !validatePhoneNumber(this.value)) {
telError.textContent = 'Please enter a valid phone number (min 10 digits)';
} else {
telError.textContent = '';
}
});
document.getElementById('signup-email')?.addEventListener('input', function(e) {
const emailError = document.getElementById('signup-emailError');
if (this.value && !validateEmail(this.value)) {
emailError.textContent = 'Please enter a valid email address';
} else {
emailError.textContent = '';
}
});
function validateSignupForm(event) {
event.preventDefault();
const name = document.getElementById('signup-name');
const email = document.getElementById('signup-email');
const password = document.getElementById('signup-password');
const tel = document.getElementById('signup-tel');
const country = document.getElementById('signup-country');
const interests = document.querySelectorAll('input[name="interests"]:checked');
const nameError = document.getElementById('signup-nameError');
const emailError = document.getElementById('signup-emailError');
const passwordError = document.getElementById('signup-passwordError');
const telError = document.getElementById('signup-telError');
const countryError = document.getElementById('signup-countryError');
const interestsError = document.getElementById('signup-interestsError');
// Reset error messages
nameError.textContent = '';
emailError.textContent = '';
passwordError.textContent = '';
telError.textContent = '';
countryError.textContent = '';
interestsError.textContent = '';
let isValid = true;
// Name validation
if (name.value.trim().length < 2) {
nameError.textContent = 'Please enter your full name';
isValid = false;
}
// Email validation
if (!validateEmail(email.value)) {
emailError.textContent = 'Please enter a valid email address';
isValid = false;
}
// Password validation
if (!validatePassword(password.value)) {
passwordError.textContent = 'Password must be at least 8 characters long';
isValid = false;
}
// Phone validation
if (!validatePhoneNumber(tel.value)) {
telError.textContent = 'Please enter a valid phone number (min 10 digits)';
isValid = false;
}
// Country validation
if (!country.value) {
countryError.textContent = 'Please select your country';
isValid = false;
}
// Interests validation
if (interests.length === 0) {
interestsError.textContent = 'Please select at least one interest';
isValid = false;
}
if (isValid) {
// Here you would typically send the form data to your server
console.log('Form is valid, ready to submit');
closeSignupModal();
}
return false;
}
</script>
<style>
/* Additional styles for signup form */
.form-group.compact {
margin-bottom: 0.5rem;
}
.form-group.compact label {
margin-bottom: 0;
font-size: 0.85rem;
display: block;
line-height: 1.2;
}
.form-group.compact input,
.form-group.compact select {
margin-bottom: 0;
height: 28px;
padding: 0 0.5rem;
}
.checkbox-group {
display: flex;
flex-direction: column;
gap: 0.3rem;
margin-top: 1rem;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: 0.8rem;
color: var(--modal-text);
cursor: pointer;
line-height: 1;
}
.checkbox-label input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
margin: 0.2;
}
select.form-control {
width: 100%;
padding: 0 0.5rem;
border: 1px solid var(--input-border);
border-radius: 4px;
background: var(--body-background);
color: var(--modal-text);
font-size: 0.9rem;
transition: all 0.3s;
height: 35px;
cursor: pointer;
}
select.form-control:focus {
outline: none;
border-color: #1a73e8;
box-shadow: 0 0 0 2px #1A73E833;
}
.error-message {
color: #dc3545;
font-size: 0.8rem;
margin: 0;
display: block;
min-height: 1rem;
line-height: 1;
}
.modal-content {
padding: 0.8rem;
}
.login-header {
margin-bottom: 1rem;
}
.login-header h2 {
margin-bottom: 0.2rem;
}
</style>