...
@@ -7,7 +7,14 @@ import os
 | 
			
		||||
import markdown
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
sources_dir = os.path.expanduser("~/code/git.ourworld.tf/tfgrid/www_projectmycelium/poc")
 | 
			
		||||
# Get BASE_DIR from environment variables with a default fallback
 | 
			
		||||
BASE_DIR = os.environ.get('BASE_DIR', os.path.dirname(os.path.abspath(__file__)))
 | 
			
		||||
 | 
			
		||||
# Check if BASE_DIR exists
 | 
			
		||||
if not os.path.exists(BASE_DIR):
 | 
			
		||||
    raise RuntimeError(f"The BASE_DIR '{BASE_DIR}' does not exist.")
 | 
			
		||||
 | 
			
		||||
sources_dir = os.path.expanduser(f"{BASE_DIR}/poc")
 | 
			
		||||
if not os.path.exists(sources_dir):
 | 
			
		||||
    raise RuntimeError(f"The source directory '{sources_dir}' does not exist.")
 | 
			
		||||
static_dir = f"{sources_dir}/static"
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 602 KiB After Width: | Height: | Size: 602 KiB  | 
| 
		 Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB  | 
| 
		 Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 345 B  | 
| 
		 Before Width: | Height: | Size: 293 KiB After Width: | Height: | Size: 293 KiB  | 
| 
		 Before Width: | Height: | Size: 702 KiB After Width: | Height: | Size: 702 KiB  | 
| 
		 Before Width: | Height: | Size: 387 KiB After Width: | Height: | Size: 387 KiB  | 
| 
		 Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB  | 
							
								
								
									
										11
									
								
								poc_threefold/components/faq.html
									
									
									
									
									
										Normal 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>
 | 
			
		||||
							
								
								
									
										206
									
								
								poc_threefold/components/footer.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,206 @@
 | 
			
		||||
{% 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>© 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;
 | 
			
		||||
        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: 1rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .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 {
 | 
			
		||||
    background-color: var(--footer-background);
 | 
			
		||||
    margin-top: 1rem;
 | 
			
		||||
    padding-top: 0.5rem;
 | 
			
		||||
    /* Add flexbox properties */
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center; /* Horizontally center */
 | 
			
		||||
    align-items: center;     /* Vertically center */
 | 
			
		||||
    border-top: 0px 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_threefold/components/globe.html
									
									
									
									
									
										Normal 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>
 | 
			
		||||
							
								
								
									
										18
									
								
								poc_threefold/components/header.html
									
									
									
									
									
										Normal 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/img/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/css/ourworld.css">
 | 
			
		||||
    <link rel="stylesheet" href="/static/css/menu.css">
 | 
			
		||||
    <link rel="stylesheet" href="/static/css/login.css">
 | 
			
		||||
    <link rel="stylesheet" href="/static/css/faq.css">
 | 
			
		||||
    <!-- Load Alpine.js from CDN -->
 | 
			
		||||
    <script defer src="https://unpkg.com/alpinejs@3.13.3/dist/cdn.min.js"></script>
 | 
			
		||||
</head>
 | 
			
		||||
							
								
								
									
										133
									
								
								poc_threefold/components/hero_image.html
									
									
									
									
									
										Normal 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>
 | 
			
		||||
							
								
								
									
										98
									
								
								poc_threefold/components/hero_text.html
									
									
									
									
									
										Normal 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>
 | 
			
		||||
							
								
								
									
										217
									
								
								poc_threefold/components/identify_modal.html
									
									
									
									
									
										Normal 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_threefold/components/login.html
									
									
									
									
									
										Normal 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()">×</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>
 | 
			
		||||
							
								
								
									
										60
									
								
								poc_threefold/components/login_lightswitch.html
									
									
									
									
									
										Normal 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_threefold/components/nav.html
									
									
									
									
									
										Normal 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>
 | 
			
		||||
							
								
								
									
										150
									
								
								poc_threefold/components/roadmap.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,150 @@
 | 
			
		||||
<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: 96px;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    width: 2px;
 | 
			
		||||
    background: #888;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.milestone {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    margin-bottom: 2rem;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    align-items: center; /* Use center alignment */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.milestone::before {
 | 
			
		||||
    content: '';
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: 88px;
 | 
			
		||||
    top: 50%; /* Center the circle vertically */
 | 
			
		||||
    transform: translateY(-50%); /* Adjust to align perfectly */
 | 
			
		||||
    width: 18px;
 | 
			
		||||
    height: 18px;
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    background: #888;
 | 
			
		||||
    border: 2px solid var(--hero-background);
 | 
			
		||||
    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: 0; /* Remove padding to align vertically */
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    text-align: right; /* Align the date to the right */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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_threefold/components/signup.html
									
									
									
									
									
										Normal 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()">×</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>
 | 
			
		||||
							
								
								
									
										26
									
								
								poc_threefold/content/navcontent.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,26 @@
 | 
			
		||||
 | 
			
		||||
<li class="tf_menu_item">
 | 
			
		||||
    <a href="../index.html" class="tf_menu_link" @click="mobileMenuOpen = false">Home</a>
 | 
			
		||||
</li>
 | 
			
		||||
<li class="tf_menu_item">
 | 
			
		||||
    <a href="#" class="tf_menu_link" @click="mobileMenuOpen = false">Why</a>
 | 
			
		||||
    <div class="tf_dropdown">
 | 
			
		||||
        <a href="#" class="tf_dropdown_item">Sustainable</a>
 | 
			
		||||
        <a href="#" class="tf_dropdown_item">Secure</a>
 | 
			
		||||
        <a href="#" class="tf_dropdown_item">Scalable</a>
 | 
			
		||||
    </div>
 | 
			
		||||
</li>
 | 
			
		||||
<li class="tf_menu_item">
 | 
			
		||||
    <a href="#" class="tf_menu_link" @click="mobileMenuOpen = false">Products</a>
 | 
			
		||||
    <div class="tf_dropdown">
 | 
			
		||||
        <a href="#" class="tf_dropdown_item">Fungistor</a>
 | 
			
		||||
        <a href="#" class="tf_dropdown_item">ThreeFold</a>
 | 
			
		||||
        <a href="#" class="tf_dropdown_item">Magic Cloud</a>
 | 
			
		||||
    </div>
 | 
			
		||||
</li>
 | 
			
		||||
<li class="tf_menu_item">
 | 
			
		||||
    <a href="#" class="tf_menu_link" @click="mobileMenuOpen = false">Info</a>
 | 
			
		||||
    <div class="tf_dropdown">
 | 
			
		||||
        <a href="/roadmap" class="tf_dropdown_item">Roadmap</a>
 | 
			
		||||
    </div>
 | 
			
		||||
</li>
 | 
			
		||||
							
								
								
									
										35
									
								
								poc_threefold/content/signup_interests.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,35 @@
 | 
			
		||||
<label><strong>Interests</strong> (check all which matters)</label>
 | 
			
		||||
<div class="checkbox-group">
 | 
			
		||||
    <label class="checkbox-label">
 | 
			
		||||
        <input type="checkbox" name="interests" value="provider"> 
 | 
			
		||||
        Become an cloud/internet provider (farmer)
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="checkbox-label">
 | 
			
		||||
        <input type="checkbox" name="interests" value="vm"> 
 | 
			
		||||
        Use cloud capacity for virtual machines
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="checkbox-label">
 | 
			
		||||
        <input type="checkbox" name="interests" value="ai"> 
 | 
			
		||||
        Use cloud capacity for AI
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="checkbox-label">
 | 
			
		||||
        <input type="checkbox" name="interests" value="blockchain"> 
 | 
			
		||||
        Use cloud capacity for Blockchain
 | 
			
		||||
    </label>                        
 | 
			
		||||
    <label class="checkbox-label">
 | 
			
		||||
        <input type="checkbox" name="interests" value="data"> 
 | 
			
		||||
        Use cloud capacity for Data
 | 
			
		||||
    </label>      
 | 
			
		||||
    <label class="checkbox-label">
 | 
			
		||||
        <input type="checkbox" name="interests" value="kubernetes"> 
 | 
			
		||||
        Use cloud capacity for Kubernetes
 | 
			
		||||
    </label>             
 | 
			
		||||
    <label class="checkbox-label">
 | 
			
		||||
        <input type="checkbox" name="interests" value="partner"> 
 | 
			
		||||
        Become a preferred Partner.
 | 
			
		||||
    </label>                                                     
 | 
			
		||||
    <label class="checkbox-label">
 | 
			
		||||
        <input type="checkbox" name="interests" value="development"> 
 | 
			
		||||
        Develop applications (Web4).
 | 
			
		||||
    </label>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										24
									
								
								poc_threefold/content/system/countries.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
			
		||||
    const countries = [
 | 
			
		||||
        "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", "Armenia", "Australia", 
 | 
			
		||||
        "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", 
 | 
			
		||||
        "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", 
 | 
			
		||||
        "Cambodia", "Cameroon", "Canada", "Cape Verde", "Central African Republic", "Chad", "Chile", "China", "Colombia", 
 | 
			
		||||
        "Comoros", "Congo", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", 
 | 
			
		||||
        "Dominican Republic", "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", 
 | 
			
		||||
        "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Greece", "Grenada", 
 | 
			
		||||
        "Guatemala", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Honduras", "Hungary", "Iceland", "India", "Indonesia", 
 | 
			
		||||
        "Iran", "Iraq", "Ireland", "Israel", "Italy", "Ivory Coast", "Jamaica", "Japan", "Jordan", "Kazakhstan", "Kenya", 
 | 
			
		||||
        "Kiribati", "North Korea", "South Korea", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", 
 | 
			
		||||
        "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", 
 | 
			
		||||
        "Mali", "Malta", "Marshall Islands", "Mauritania", "Mauritius", "Mexico", "Micronesia", "Moldova", "Monaco", 
 | 
			
		||||
        "Mongolia", "Montenegro", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", 
 | 
			
		||||
        "New Zealand", "Nicaragua", "Niger", "Nigeria", "Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", 
 | 
			
		||||
        "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Qatar", "Romania", "Russia", "Rwanda", "Saint Kitts and Nevis", 
 | 
			
		||||
        "Saint Lucia", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", 
 | 
			
		||||
        "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", 
 | 
			
		||||
        "South Africa", "South Sudan", "Spain", "Sri Lanka", "Sudan", "Suriname", "Swaziland", "Sweden", "Switzerland", 
 | 
			
		||||
        "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Togo", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", 
 | 
			
		||||
        "Turkmenistan", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "Uruguay", 
 | 
			
		||||
        "Uzbekistan", "Vanuatu", "Vatican City", "Venezuela", "Vietnam", "Yemen", "Zambia", "Zimbabwe"
 | 
			
		||||
    ];
 | 
			
		||||
    
 | 
			
		||||
							
								
								
									
										21
									
								
								poc_threefold/content/threefold/faq_tf.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
## About Our Platform
 | 
			
		||||
 | 
			
		||||
ThreeFold provides a revolutionary Internet Infrastructure that enables:
 | 
			
		||||
 | 
			
		||||
- Decentralized cloud, data & network capacity with reward opportunities for contributors
 | 
			
		||||
- Global deployment of web2, web3, AI, edge, and blockchain applications
 | 
			
		||||
- Enhanced application access through distributed infrastructure
 | 
			
		||||
- Creation of resilient, uncensorable peer-to-peer networks
 | 
			
		||||
 | 
			
		||||
### Why ThreeFold?
 | 
			
		||||
 | 
			
		||||
- **First platform in the world to seamlessly combine cloud, data & network capabilities**
 | 
			
		||||
- Global Access: Bridging the digital divide for 50% of the world lacking quality Internet infrastructure
 | 
			
		||||
- Economic Sovereignty: Enabling countries to build and control their own Internet infrastructure
 | 
			
		||||
- Sustainable Growth: Reducing environmental impact through efficient edge computing
 | 
			
		||||
 | 
			
		||||
### Our Mission & Values
 | 
			
		||||
 | 
			
		||||
- Planet First: Committed to sustainable and eco-friendly technology solutions
 | 
			
		||||
- People Empowerment: Democratizing Internet infrastructure access and ownership
 | 
			
		||||
- Digital Sovereignty: Ensuring data privacy and infrastructure independence
 | 
			
		||||
							
								
								
									
										92
									
								
								poc_threefold/content/threefold/faq_tf_intro.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,92 @@
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>Is this a separate new Internet?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        No ThreeFold is a complementary Internet and lives with and on top of
 | 
			
		||||
        the current Internet. From out of ThreeFold you can still use the
 | 
			
		||||
        current Internet and interact with it.
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>Why do we need a new Internet?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        The Internet used to be a peer to peer network, but has become fragile
 | 
			
		||||
        and too centralized. There are so many problems with the current
 | 
			
		||||
        Internet, such as authenticity, privacy, security, and sustainability
 | 
			
		||||
        that we believe a fundamental new approach is needed.
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>This sounds too big, are you guys are faking it?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        We have been working on this for over 30 years and ThreeFold is the
 | 
			
		||||
        result of that work. We have a working product and a growing community
 | 
			
		||||
        of farmers, users, and partners. We are real and we are here to stay.
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>You have 2 tokens, TFT and INCA, why?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        Thanks to our community there are +60,000 virtual cpu's and +17,000 GB
 | 
			
		||||
        of storage available on the network.
 | 
			
		||||
        <a
 | 
			
		||||
            href="https://dashboard.grid.tf/#/tf-grid/node-statistics/"
 | 
			
		||||
            target="_blank"
 | 
			
		||||
            >Checkout our stats on our dashboard.</a
 | 
			
		||||
        >
 | 
			
		||||
        TFT is our token which was used to build generation 1,2 and 3 of the
 | 
			
		||||
        ThreeFold Grid of capacity. TFT is the reward for our loyal community.
 | 
			
		||||
        There can never be more than 1 billion TFT. We are now building
 | 
			
		||||
        generation 4 of the ThreeFold Grid of capacity and we need a new token
 | 
			
		||||
        to build this new generation. There will never be more than 3 billion
 | 
			
		||||
        INCA. Our partners will start selling new ThreeFold Nodes end Nov 2024
 | 
			
		||||
        with a new reward scheme and ready to grow to millions of nodes.
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>How can I participate?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        You can participate by becoming a farmer, a user, a partner or by developing a web4 app.
 | 
			
		||||
        <ul>
 | 
			
		||||
            <li><a href="/farmer">Provide capacity to the ThreeFold Grid</a></li>
 | 
			
		||||
            <li><a href="/user">Use capacity off the ThreeFold Grid</a></li>
 | 
			
		||||
            <li><a href="/builder">Build solutions</a></li>
 | 
			
		||||
            <li><a href="/hero_coder">Develop applications for Web4</a></li>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>What is Web4?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        Web4 is the next generation of the Internet, where users are in 100% in
 | 
			
		||||
        control of their data. No centralized services are needed. Blockchain
 | 
			
		||||
        was the first step to Web3, ThreeFold is the next step to Web4.
 | 
			
		||||
        ThreeFold is ofcourse fully compatible with Web2 and Web3. In Web4 each
 | 
			
		||||
        user has a personal Virtual Digital Assistent which is called a Hero.
 | 
			
		||||
        This hero manages your digital life and on your behalf can interact with
 | 
			
		||||
        all versions of the Web.
 | 
			
		||||
        <a href="/hero_intro">Checkout more info about our Hero</a>.
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>How secure and private is my data?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        ThreeFold is designed to be secure and private by default. We use
 | 
			
		||||
        end-to-end encryption to protect your data and ensure that only you have
 | 
			
		||||
        access to your data.
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>Who should use the ThreeFold Grid. ?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        Individuals, businesses, and organizations who want to be sovereign and have full control over their data and applications.
 | 
			
		||||
        Security is a very big problem today, Technology as used by ThreeFold has the potential to resolve this if used properly.
 | 
			
		||||
        We already work with Governements, NGO's, and Individuals. We are building a channel of solution providers and integrators who want to build on top of ThreeFold.
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
							
								
								
									
										43
									
								
								poc_threefold/content/threefold/faq_tf_web4.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,43 @@
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>What is Web4?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        Web4 is the next generation of the Internet, where users are in 100% in
 | 
			
		||||
        control of their data. No centralized services are needed. Blockchain
 | 
			
		||||
        was the first step to Web3, ThreeFold is the next step to Web4.
 | 
			
		||||
        ThreeFold is ofcourse fully compatible with Web2 and Web3. In Web4 each
 | 
			
		||||
        user has a personal Virtual Digital Assistent which is called a Hero.
 | 
			
		||||
        This hero manages your digital life and on your behalf can interact with
 | 
			
		||||
        all versions of the Web.
 | 
			
		||||
        <a href="/hero_intro">Checkout more info about our Hero</a>.
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>How secure and private is my data?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        ThreeFold is designed to be secure and private by default. We use
 | 
			
		||||
        end-to-end encryption to protect your data and ensure that only you have
 | 
			
		||||
        access to your data.
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>How secure and private is my data?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        ThreeFold is designed to be secure and private by default. We use
 | 
			
		||||
        end-to-end encryption to protect your data and ensure that only you have
 | 
			
		||||
        access to your data.
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>Who should care bout Web4. ?</summary>
 | 
			
		||||
    <p>
 | 
			
		||||
        While blockchain is a very good first step, we believe its time to move
 | 
			
		||||
        to the next level. Web4 is the next generation of the Internet, where
 | 
			
		||||
        users are in 100% in control of their data. This hero manages your
 | 
			
		||||
        digital life and on your behalf can interact with all versions of the
 | 
			
		||||
        Web. Enterprises, Governments, Software Developers and Individuals
 | 
			
		||||
        should care about Web4.
 | 
			
		||||
    </p>
 | 
			
		||||
</details>
 | 
			
		||||
							
								
								
									
										23
									
								
								poc_threefold/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,23 @@
 | 
			
		||||
{% include 'components/header.html' %}
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    {% include 'components/nav.html' %}
 | 
			
		||||
    <main>
 | 
			
		||||
        {% include 'components/globe.html' %}
 | 
			
		||||
 | 
			
		||||
        {% set config = { 
 | 
			
		||||
                "title": "THREEFOLD AUTONOMOUS INTERNET.",
 | 
			
		||||
                "subtitle": "Building a decentralized internet.",
 | 
			
		||||
                "image": "static/img/wave.png" } 
 | 
			
		||||
            %}           
 | 
			
		||||
        {% include 'components/hero_image.html' %}
 | 
			
		||||
 | 
			
		||||
        {% set config = { "name": "threefold/faq_tf", "section_name": "threefold/faq_tf_intro" } %}
 | 
			
		||||
        {% include 'components/faq.html' %}
 | 
			
		||||
 | 
			
		||||
    </main>
 | 
			
		||||
    {% include 'components/footer.html' %}
 | 
			
		||||
    {% include 'components/login.html' %}
 | 
			
		||||
    {% include 'components/signup.html' %}
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										283
									
								
								poc_threefold/login2222.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,283 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>Login - ThreeFold</title>
 | 
			
		||||
    <link rel="stylesheet" href="components/nav.html">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <div class="login-container">
 | 
			
		||||
        <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>
 | 
			
		||||
                <h1>Welcome Back</h1>
 | 
			
		||||
                <p>Sign in to your ThreeFold account</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <form id="loginForm" onsubmit="return validateForm(event)">
 | 
			
		||||
                <div class="form-group">
 | 
			
		||||
                    <label for="email">Email</label>
 | 
			
		||||
                    <input 
 | 
			
		||||
                        type="email" 
 | 
			
		||||
                        id="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="emailError"></span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-group">
 | 
			
		||||
                    <label for="password">Password</label>
 | 
			
		||||
                    <input 
 | 
			
		||||
                        type="password" 
 | 
			
		||||
                        id="password" 
 | 
			
		||||
                        name="password" 
 | 
			
		||||
                        required
 | 
			
		||||
                        minlength="8"
 | 
			
		||||
                        placeholder="Enter your password"
 | 
			
		||||
                        autocomplete="current-password"
 | 
			
		||||
                    >
 | 
			
		||||
                    <span class="error-message" id="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="login-button">Sign In</button>
 | 
			
		||||
            </form>
 | 
			
		||||
            <div class="signup-link">
 | 
			
		||||
                Don't have an account? <a href="#">Sign up</a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <style>
 | 
			
		||||
        :root {
 | 
			
		||||
            --primary-color: #1a73e8;
 | 
			
		||||
            --error-color: #dc3545;
 | 
			
		||||
            --text-color: #333;
 | 
			
		||||
            --border-color: #ddd;
 | 
			
		||||
            --background-color: #f5f5f5;
 | 
			
		||||
            --box-shadow: 0 2px 10px #0000001A;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        body {
 | 
			
		||||
            margin: 0;
 | 
			
		||||
            padding: 0;
 | 
			
		||||
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
 | 
			
		||||
            background: var(--background-color);
 | 
			
		||||
            color: var(--text-color);
 | 
			
		||||
            min-height: 100vh;
 | 
			
		||||
            display: flex;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
            justify-content: center;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-container {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            max-width: 400px;
 | 
			
		||||
            padding: 10px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-box {
 | 
			
		||||
            background: white;
 | 
			
		||||
            border-radius: 8px;
 | 
			
		||||
            box-shadow: var(--box-shadow);
 | 
			
		||||
            padding: 2rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-header {
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            margin-bottom: 1.5rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .logo {
 | 
			
		||||
            width: 48px;
 | 
			
		||||
            height: 48px;
 | 
			
		||||
            margin-bottom: 1rem;
 | 
			
		||||
            color: var(--primary-color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-header h1 {
 | 
			
		||||
            margin: 0;
 | 
			
		||||
            font-size: 1.75rem;
 | 
			
		||||
            color: var(--text-color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-header p {
 | 
			
		||||
            margin: 0.5rem 0 0;
 | 
			
		||||
            color: #666;
 | 
			
		||||
            font-size: 0.95rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .form-group {
 | 
			
		||||
            margin-bottom: 0.5rem;  /* Reduced from 1rem */
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        label {
 | 
			
		||||
            display: block;
 | 
			
		||||
            margin-bottom: 0.5rem;
 | 
			
		||||
            color: var(--text-color);
 | 
			
		||||
            font-size: 0.9rem;
 | 
			
		||||
            font-weight: 500;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        input[type="email"],
 | 
			
		||||
        input[type="password"] {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            padding: 0.75rem;
 | 
			
		||||
            border: 1px solid var(--border-color);
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            font-size: 1rem;
 | 
			
		||||
            transition: border-color 0.3s, box-shadow 0.3s;
 | 
			
		||||
            box-sizing: border-box;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        input[type="email"]:focus,
 | 
			
		||||
        input[type="password"]:focus {
 | 
			
		||||
            outline: none;
 | 
			
		||||
            border-color: var(--primary-color);
 | 
			
		||||
            box-shadow: 0 0 0 2px #1A73E833;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .error-message {
 | 
			
		||||
            display: block;
 | 
			
		||||
            color: var(--error-color);
 | 
			
		||||
            font-size: 0.85rem;
 | 
			
		||||
            margin-top: 0.25rem;
 | 
			
		||||
            min-height: 1.25em;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .form-footer {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            justify-content: space-between;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
            margin-bottom: 0.5rem;  /* Reduced from 1rem */
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .remember-me {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
            gap: 0.5rem;
 | 
			
		||||
            font-size: 0.9rem;
 | 
			
		||||
            color: #666;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .forgot-password {
 | 
			
		||||
            font-size: 0.9rem;
 | 
			
		||||
            color: var(--primary-color);
 | 
			
		||||
            text-decoration: none;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .forgot-password:hover {
 | 
			
		||||
            text-decoration: underline;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-button {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            padding: 0.75rem;
 | 
			
		||||
            background: var(--primary-color);
 | 
			
		||||
            color: white;
 | 
			
		||||
            border: none;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            font-size: 1rem;
 | 
			
		||||
            font-weight: 500;
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
            transition: background-color 0.3s, transform 0.3s;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-button:hover {
 | 
			
		||||
            background: #1557b0;
 | 
			
		||||
            transform: translateY(-1px);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .login-button:active {
 | 
			
		||||
            transform: translateY(0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .signup-link {
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            margin-top: 1rem;
 | 
			
		||||
            font-size: 0.9rem;
 | 
			
		||||
            color: #666;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .signup-link a {
 | 
			
		||||
            color: var(--primary-color);
 | 
			
		||||
            text-decoration: none;
 | 
			
		||||
            font-weight: 500;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .signup-link a:hover {
 | 
			
		||||
            text-decoration: underline;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @media (max-width: 480px) {
 | 
			
		||||
            .login-container {
 | 
			
		||||
                padding: 10px;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .login-box {
 | 
			
		||||
                padding: 1.5rem;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
        function validateForm(event) {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            
 | 
			
		||||
            const email = document.getElementById('email');
 | 
			
		||||
            const emailError = document.getElementById('emailError');
 | 
			
		||||
            const password = document.getElementById('password');
 | 
			
		||||
            const passwordError = document.getElementById('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');
 | 
			
		||||
                // For demo purposes, we'll just log the email
 | 
			
		||||
                console.log('Email:', email.value);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return isValid;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Real-time email validation
 | 
			
		||||
        document.getElementById('email').addEventListener('input', function(e) {
 | 
			
		||||
            const emailError = document.getElementById('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>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										299
									
								
								poc_threefold/logintest.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,299 @@
 | 
			
		||||
{% include 'components/header.html' %}
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    {% include 'components/nav.html' %}
 | 
			
		||||
    <main>
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <h1>Identity Test Page</h1>
 | 
			
		||||
            
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div id="status" class="status unauthenticated">
 | 
			
		||||
                    Not identified
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="actions">
 | 
			
		||||
                    <button class="btn-identify" onclick="showIdentifyModal()">Identify</button>
 | 
			
		||||
                    <button class="btn-clear" onclick="clearAllAndReload()">Clear All & Reload</button>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div id="keyInfo" class="key-info" style="display: none;">
 | 
			
		||||
                    <h3>Key Information</h3>
 | 
			
		||||
                    <div class="key-details">
 | 
			
		||||
                        <div id="keyStatus"></div>
 | 
			
		||||
                        <div id="publicKey" class="public-key"></div>
 | 
			
		||||
                        <div id="generateKeySection" style="display: none;" class="generate-key-section">
 | 
			
		||||
                            <div class="warning-message">
 | 
			
		||||
                                ⚠️ No private key found. You need to generate a private key to use the system.
 | 
			
		||||
                                <strong>WARNING: Losing your private key will result in permanent loss of access to your digital life in ThreeFold.</strong>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <button onclick="generatePrivateKey()" class="btn-generate">Generate Private Key</button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div id="testActions" class="test-actions" style="display: none;">
 | 
			
		||||
                    <h3>Test Actions</h3>
 | 
			
		||||
                    <div class="test-container">
 | 
			
		||||
                        <div class="test-group">
 | 
			
		||||
                            <input type="text" id="testKey" placeholder="Key name">
 | 
			
		||||
                            <input type="text" id="testValue" placeholder="Value">
 | 
			
		||||
                            <button onclick="setTestValue()">Set Value</button>
 | 
			
		||||
                            <div id="setResult" class="set-result"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="test-group">
 | 
			
		||||
                            <input type="text" id="retrieveKey" placeholder="Key to retrieve">
 | 
			
		||||
                            <button onclick="getTestValue()">Get Value</button>
 | 
			
		||||
                            <div id="retrievedValue" class="retrieved-value"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="test-group">
 | 
			
		||||
                            <button onclick="listAllValues()" class="btn-list">List All Values</button>
 | 
			
		||||
                            <div id="allValues" class="all-values"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </main>
 | 
			
		||||
 | 
			
		||||
    <!-- Identity Modal -->
 | 
			
		||||
    <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';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function clearAllAndReload() {
 | 
			
		||||
            sessionStorage.clear();
 | 
			
		||||
            location.reload();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async function generatePrivateKey() {
 | 
			
		||||
            try {
 | 
			
		||||
                const privateKey = await window.kvs.generateNewPrivateKey();
 | 
			
		||||
                const publicKey = window.kvs.getPublicKey(privateKey);
 | 
			
		||||
                const publicKeyBase58 = bs58.encode(publicKey);
 | 
			
		||||
                
 | 
			
		||||
                document.getElementById('generateKeySection').style.display = 'none';
 | 
			
		||||
                document.getElementById('keyStatus').textContent = '🔑 New private key generated';
 | 
			
		||||
                document.getElementById('publicKey').innerHTML = 
 | 
			
		||||
                    `<strong>Public Key (Base58):</strong><br>${publicKeyBase58}`;
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                console.error('Error generating private key:', error);
 | 
			
		||||
                document.getElementById('keyStatus').textContent = 
 | 
			
		||||
                    '❌ Error generating private key: ' + error.message;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async function updateStatus() {
 | 
			
		||||
            const status = document.getElementById('status');
 | 
			
		||||
            const keyInfo = document.getElementById('keyInfo');
 | 
			
		||||
            const testActions = document.getElementById('testActions');
 | 
			
		||||
            const keyStatus = document.getElementById('keyStatus');
 | 
			
		||||
            const generateKeySection = document.getElementById('generateKeySection');
 | 
			
		||||
            const publicKeyDiv = document.getElementById('publicKey');
 | 
			
		||||
            
 | 
			
		||||
            if (window.kvs.isSessionValid()) {
 | 
			
		||||
                try {
 | 
			
		||||
                    status.className = 'status authenticated';
 | 
			
		||||
                    status.textContent = 'Identified successfully';
 | 
			
		||||
                    
 | 
			
		||||
                    keyInfo.style.display = 'block';
 | 
			
		||||
                    testActions.style.display = 'block';
 | 
			
		||||
 | 
			
		||||
                    const hasKey = await window.kvs.hasPrivateKey();
 | 
			
		||||
                    if (!hasKey) {
 | 
			
		||||
                        generateKeySection.style.display = 'block';
 | 
			
		||||
                        keyStatus.textContent = '⚠️ No private key found';
 | 
			
		||||
                        publicKeyDiv.innerHTML = '';
 | 
			
		||||
                        testActions.style.display = 'none';
 | 
			
		||||
                    } else {
 | 
			
		||||
                        generateKeySection.style.display = 'none';
 | 
			
		||||
                        const privateKey = await window.kvs.getPrivateKey();
 | 
			
		||||
                        const publicKey = window.kvs.getPublicKey(privateKey);
 | 
			
		||||
                        const publicKeyBase58 = bs58.encode(publicKey);
 | 
			
		||||
                        
 | 
			
		||||
                        keyStatus.textContent = '🔑 Private key loaded';
 | 
			
		||||
                        publicKeyDiv.innerHTML = `<strong>Public Key (Base58):</strong><br>${publicKeyBase58}`;
 | 
			
		||||
                    }
 | 
			
		||||
                } catch (error) {
 | 
			
		||||
                    status.className = 'status unauthenticated';
 | 
			
		||||
                    status.textContent = 'Error accessing keys: ' + error.message;
 | 
			
		||||
                    keyInfo.style.display = 'none';
 | 
			
		||||
                    testActions.style.display = 'none';
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                status.className = 'status unauthenticated';
 | 
			
		||||
                status.textContent = 'Not identified';
 | 
			
		||||
                keyInfo.style.display = 'none';
 | 
			
		||||
                testActions.style.display = 'none';
 | 
			
		||||
                showIdentifyModal();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
                
 | 
			
		||||
                // 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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async function setTestValue() {
 | 
			
		||||
            const key = document.getElementById('testKey').value;
 | 
			
		||||
            const value = document.getElementById('testValue').value;
 | 
			
		||||
            const setResult = document.getElementById('setResult');
 | 
			
		||||
            const retrieveKey = document.getElementById('retrieveKey');
 | 
			
		||||
            
 | 
			
		||||
            if (!key || !value) {
 | 
			
		||||
                setResult.innerHTML = '<div class="error">Please enter both key and value</div>';
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                await window.kvs.set(key, value);
 | 
			
		||||
                setResult.innerHTML = '<div class="success">Value set successfully!</div>';
 | 
			
		||||
                document.getElementById('testKey').value = '';
 | 
			
		||||
                document.getElementById('testValue').value = '';
 | 
			
		||||
                
 | 
			
		||||
                // Auto-fill the retrieve key field
 | 
			
		||||
                retrieveKey.value = key;
 | 
			
		||||
                // Automatically get the value
 | 
			
		||||
                await getTestValue();
 | 
			
		||||
                // Refresh the list if it's visible
 | 
			
		||||
                const allValues = document.getElementById('allValues');
 | 
			
		||||
                if (allValues.innerHTML !== '') {
 | 
			
		||||
                    await listAllValues();
 | 
			
		||||
                }
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                setResult.innerHTML = `<div class="error">Error setting value: ${error.message}</div>`;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async function getTestValue() {
 | 
			
		||||
            const key = document.getElementById('retrieveKey').value;
 | 
			
		||||
            const retrievedValueDiv = document.getElementById('retrievedValue');
 | 
			
		||||
            
 | 
			
		||||
            if (!key) {
 | 
			
		||||
                retrievedValueDiv.innerHTML = '<div class="error">Please enter a key to retrieve</div>';
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                const value = await window.kvs.get(key);
 | 
			
		||||
                retrievedValueDiv.innerHTML = value ? 
 | 
			
		||||
                    `<div class="success">Retrieved value: ${value}</div>` : 
 | 
			
		||||
                    '<div class="info">No value found for this key</div>';
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                retrievedValueDiv.innerHTML = `<div class="error">Error: ${error.message}</div>`;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async function listAllValues() {
 | 
			
		||||
            const allValuesDiv = document.getElementById('allValues');
 | 
			
		||||
            try {
 | 
			
		||||
                const results = await window.kvs.listAll();
 | 
			
		||||
                if (results.length === 0) {
 | 
			
		||||
                    allValuesDiv.innerHTML = '<div class="no-values">No stored values found</div>';
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const html = results.map(({key, value, encrypted}) => `
 | 
			
		||||
                    <div class="stored-value ${encrypted ? 'encrypted' : 'not-encrypted'}">
 | 
			
		||||
                        <div class="stored-key">${key}</div>
 | 
			
		||||
                        <div class="stored-value-content">${value}</div>
 | 
			
		||||
                        <div class="encryption-status">
 | 
			
		||||
                            ${encrypted ? 
 | 
			
		||||
                                '<span class="status-icon">🔒</span> Encrypted' : 
 | 
			
		||||
                                '<span class="status-icon">⚠️</span> Not encrypted'}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                `).join('');
 | 
			
		||||
 | 
			
		||||
                allValuesDiv.innerHTML = html;
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                allValuesDiv.innerHTML = `<div class="error">Error listing values: ${error.message}</div>`;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check session validity periodically
 | 
			
		||||
        const sessionCheckInterval = setInterval(() => {
 | 
			
		||||
            if (window.kvs && !window.kvs.isSessionValid()) {
 | 
			
		||||
                window.kvs.clearSession();
 | 
			
		||||
                updateStatus();
 | 
			
		||||
            }
 | 
			
		||||
        }, 60000); // Check every minute
 | 
			
		||||
 | 
			
		||||
        // Cleanup interval when page unloads
 | 
			
		||||
        window.addEventListener('unload', () => {
 | 
			
		||||
            clearInterval(sessionCheckInterval);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Initial check when the page loads
 | 
			
		||||
        window.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
            updateStatus();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Update status when user is identified
 | 
			
		||||
        window.addEventListener('userIdentified', updateStatus);
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										9
									
								
								poc_threefold/page1.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,9 @@
 | 
			
		||||
{% include 'components/header.html' %}
 | 
			
		||||
<body>
 | 
			
		||||
    {% include 'components/nav.html' %}
 | 
			
		||||
    <main>
 | 
			
		||||
        {% include 'components/hero1.html' %}
 | 
			
		||||
    </main>
 | 
			
		||||
    {% include 'components/footer.html' %}
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										10
									
								
								poc_threefold/page2.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
 | 
			
		||||
{% include 'components/header.html' %}
 | 
			
		||||
<body>
 | 
			
		||||
    {% include 'components/nav.html' %}
 | 
			
		||||
    <main>
 | 
			
		||||
        {% include 'components/faq.html' %}
 | 
			
		||||
    </main>
 | 
			
		||||
    {% include 'components/footer.html' %}
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										1
									
								
								poc_threefold/remember.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
{{/* <script src="https://cdn.jsdelivr.net/npm/tweetnacl@1.0.3/nacl.min.js"></script> */}}
 | 
			
		||||
							
								
								
									
										21
									
								
								poc_threefold/roadmap.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{% include 'components/header.html' %}
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    {% include 'components/nav.html' %}
 | 
			
		||||
    <main>
 | 
			
		||||
 | 
			
		||||
        {% set config = { 
 | 
			
		||||
                "title": "ThreeFold INTERNET ROADMAP",
 | 
			
		||||
                "subtitle": "Building a decentralized internet, for a better world",
 | 
			
		||||
                "image": "static/img/questions.png" } 
 | 
			
		||||
            %}           
 | 
			
		||||
        {% include 'components/hero_image.html' %}
 | 
			
		||||
 | 
			
		||||
        {% set config_roadmap = { "name": "threefold/faq_tf", "section_name": "threefold/faq_tf_section" } %}
 | 
			
		||||
        {% include 'components/roadmap.html' %}
 | 
			
		||||
 | 
			
		||||
    </main>
 | 
			
		||||
    {% include 'components/footer.html' %}
 | 
			
		||||
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										189
									
								
								poc_threefold/server.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,189 @@
 | 
			
		||||
from fastapi import FastAPI, Request, HTTPException, WebSocket
 | 
			
		||||
from fastapi.responses import HTMLResponse, FileResponse
 | 
			
		||||
from fastapi.templating import Jinja2Templates
 | 
			
		||||
from fastapi.staticfiles import StaticFiles
 | 
			
		||||
from jinja2 import Environment, FileSystemLoader, select_autoescape, TemplateNotFound
 | 
			
		||||
import os
 | 
			
		||||
import markdown
 | 
			
		||||
import re
 | 
			
		||||
from watchdog.observers import Observer
 | 
			
		||||
from watchdog.events import FileSystemEventHandler
 | 
			
		||||
import asyncio
 | 
			
		||||
from typing import List
 | 
			
		||||
import time
 | 
			
		||||
from contextlib import asynccontextmanager
 | 
			
		||||
 | 
			
		||||
# Get BASE_DIR from environment variables with a default fallback
 | 
			
		||||
BASE_DIR = os.environ.get('BASE_DIR', os.path.dirname(os.path.abspath(__file__)))
 | 
			
		||||
 | 
			
		||||
# Check if BASE_DIR exists
 | 
			
		||||
if not os.path.exists(BASE_DIR):
 | 
			
		||||
    raise RuntimeError(f"The BASE_DIR '{BASE_DIR}' does not exist.")
 | 
			
		||||
 | 
			
		||||
sources_dir = os.path.expanduser(f"{BASE_DIR}/poc_threefold")
 | 
			
		||||
if not os.path.exists(sources_dir):
 | 
			
		||||
    raise RuntimeError(f"The source directory '{sources_dir}' does not exist.")
 | 
			
		||||
static_dir = f"{sources_dir}/static"
 | 
			
		||||
content_dir = f"{sources_dir}/content"
 | 
			
		||||
 | 
			
		||||
# Store active WebSocket connections
 | 
			
		||||
active_connections: List[WebSocket] = []
 | 
			
		||||
 | 
			
		||||
# Store the main event loop reference
 | 
			
		||||
main_loop = None
 | 
			
		||||
 | 
			
		||||
class FileChangeHandler(FileSystemEventHandler):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.last_modified = 0
 | 
			
		||||
 | 
			
		||||
    def on_modified(self, event):
 | 
			
		||||
        if event.is_directory:
 | 
			
		||||
            return
 | 
			
		||||
        
 | 
			
		||||
        current_time = time.time()
 | 
			
		||||
        if current_time - self.last_modified > 0.5:  # Debounce multiple events
 | 
			
		||||
            self.last_modified = current_time
 | 
			
		||||
            if main_loop is not None:
 | 
			
		||||
                asyncio.run_coroutine_threadsafe(broadcast_reload(), main_loop)
 | 
			
		||||
 | 
			
		||||
async def broadcast_reload():
 | 
			
		||||
    for connection in active_connections[:]:  # Create a copy of the list to iterate over
 | 
			
		||||
        try:
 | 
			
		||||
            await connection.send_text("reload")
 | 
			
		||||
        except:
 | 
			
		||||
            if connection in active_connections:
 | 
			
		||||
                active_connections.remove(connection)
 | 
			
		||||
 | 
			
		||||
def get_content(name: str) -> str:
 | 
			
		||||
    """Get content by name from either HTML or markdown files in content directory"""
 | 
			
		||||
    # Remove any leading/trailing slashes
 | 
			
		||||
    name = name.strip('/')
 | 
			
		||||
 | 
			
		||||
    # Check for file with .html extension
 | 
			
		||||
    html_path = os.path.join(content_dir, f"{name}.html")
 | 
			
		||||
    if os.path.exists(html_path):
 | 
			
		||||
        with open(html_path, 'r') as f:
 | 
			
		||||
            return f.read()
 | 
			
		||||
 | 
			
		||||
    # Check for file with .md extension
 | 
			
		||||
    md_path = os.path.join(content_dir, f"{name}.md")
 | 
			
		||||
    if os.path.exists(md_path):
 | 
			
		||||
        with open(md_path, 'r') as f:
 | 
			
		||||
            content = f.read()
 | 
			
		||||
            return markdown.markdown(content)
 | 
			
		||||
 | 
			
		||||
    return f"[[{name} not found]]"
 | 
			
		||||
 | 
			
		||||
def process_content(content: str) -> str:
 | 
			
		||||
    """Process content and replace [[name]] with corresponding HTML content"""
 | 
			
		||||
    def replace_content(match):
 | 
			
		||||
        name = match.group(1)
 | 
			
		||||
        return get_content(name)
 | 
			
		||||
 | 
			
		||||
    # Replace all [[name]] patterns
 | 
			
		||||
    content = re.sub(r'\[\[(.*?)\]\]', replace_content, content)
 | 
			
		||||
    
 | 
			
		||||
    # Inject live reload script before </body>
 | 
			
		||||
    reload_script = """
 | 
			
		||||
    <script>
 | 
			
		||||
        const ws = new WebSocket('ws://' + window.location.host + '/ws');
 | 
			
		||||
        ws.onmessage = function(event) {
 | 
			
		||||
            if (event.data === 'reload') {
 | 
			
		||||
                window.location.reload();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        ws.onclose = function() {
 | 
			
		||||
            console.log('WebSocket connection closed. Retrying...');
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                window.location.reload();
 | 
			
		||||
            }, 1000);
 | 
			
		||||
        };
 | 
			
		||||
    </script>
 | 
			
		||||
    """
 | 
			
		||||
    content = content.replace('</body>', f'{reload_script}</body>')
 | 
			
		||||
    return content
 | 
			
		||||
 | 
			
		||||
# Setup file watcher and store observer reference
 | 
			
		||||
observer = None
 | 
			
		||||
 | 
			
		||||
@asynccontextmanager
 | 
			
		||||
async def lifespan(app: FastAPI):
 | 
			
		||||
    # Startup
 | 
			
		||||
    global main_loop, observer
 | 
			
		||||
    main_loop = asyncio.get_running_loop()
 | 
			
		||||
    
 | 
			
		||||
    # Setup file watcher
 | 
			
		||||
    event_handler = FileChangeHandler()
 | 
			
		||||
    observer = Observer()
 | 
			
		||||
    observer.schedule(event_handler, sources_dir, recursive=True)
 | 
			
		||||
    observer.start()
 | 
			
		||||
    
 | 
			
		||||
    yield
 | 
			
		||||
    
 | 
			
		||||
    # Shutdown
 | 
			
		||||
    if observer:
 | 
			
		||||
        observer.stop()
 | 
			
		||||
        observer.join()
 | 
			
		||||
 | 
			
		||||
app = FastAPI(lifespan=lifespan)
 | 
			
		||||
 | 
			
		||||
if not os.path.exists(static_dir):
 | 
			
		||||
    raise RuntimeError(f"The directory '{static_dir}' does not exist.")
 | 
			
		||||
 | 
			
		||||
if not os.path.exists(sources_dir):
 | 
			
		||||
    raise RuntimeError(f"The templates dir '{sources_dir}' does not exist.")
 | 
			
		||||
 | 
			
		||||
# Mount the static files directory
 | 
			
		||||
app.mount("/static", StaticFiles(directory=static_dir), name="static")
 | 
			
		||||
 | 
			
		||||
env = Environment(
 | 
			
		||||
    loader=FileSystemLoader(sources_dir),
 | 
			
		||||
    autoescape=select_autoescape(['html', 'xml'])
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Initialize Jinja2 templates
 | 
			
		||||
templates = Jinja2Templates(directory=sources_dir)
 | 
			
		||||
 | 
			
		||||
@app.websocket("/ws")
 | 
			
		||||
async def websocket_endpoint(websocket: WebSocket):
 | 
			
		||||
    await websocket.accept()
 | 
			
		||||
    active_connections.append(websocket)
 | 
			
		||||
    try:
 | 
			
		||||
        while True:
 | 
			
		||||
            await websocket.receive_text()
 | 
			
		||||
    except:
 | 
			
		||||
        if websocket in active_connections:
 | 
			
		||||
            active_connections.remove(websocket)
 | 
			
		||||
 | 
			
		||||
@app.get("/favicon.ico")
 | 
			
		||||
async def favicon():
 | 
			
		||||
    # First try to serve from static directory
 | 
			
		||||
    favicon_path = os.path.join(static_dir, "favicon.ico")
 | 
			
		||||
    if os.path.exists(favicon_path):
 | 
			
		||||
        return FileResponse(favicon_path)
 | 
			
		||||
    # If not found, return 404
 | 
			
		||||
    raise HTTPException(status_code=404, detail="Favicon not found")
 | 
			
		||||
 | 
			
		||||
@app.get("/", response_class=HTMLResponse)
 | 
			
		||||
async def read_index(request: Request):
 | 
			
		||||
    template = env.get_template("index.html")
 | 
			
		||||
    content = template.render(request=request)
 | 
			
		||||
    return process_content(content)
 | 
			
		||||
 | 
			
		||||
@app.get("/{path:path}", response_class=HTMLResponse)
 | 
			
		||||
async def read_template(request: Request, path: str):
 | 
			
		||||
    # Add .html extension if not present
 | 
			
		||||
    if not path.endswith('.html'):
 | 
			
		||||
        path = f"{path}.html"
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        # Try to load and render the template (this will work for both direct files and templates)
 | 
			
		||||
        template = env.get_template(path)
 | 
			
		||||
        content = template.render(request=request)
 | 
			
		||||
        return process_content(content)
 | 
			
		||||
    except TemplateNotFound:
 | 
			
		||||
        raise HTTPException(status_code=404, detail=f"Template {path} not found")
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    import uvicorn
 | 
			
		||||
    uvicorn.run("server:app", host="127.0.0.1", port=8001, reload=True)
 | 
			
		||||
							
								
								
									
										171
									
								
								poc_threefold/static/css/faq.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,171 @@
 | 
			
		||||
/* Import Google Fonts - if not already imported in ourworld.css */
 | 
			
		||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
 | 
			
		||||
 | 
			
		||||
/* Container styling */
 | 
			
		||||
.faq-container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    gap: 2rem;
 | 
			
		||||
    max-width: 1200px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    padding: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Markdown content styling */
 | 
			
		||||
.markdown-content {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    padding-right: 2rem;
 | 
			
		||||
    border-right: 1px solid #a4b6ba;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Base styles for markdown content */
 | 
			
		||||
.markdown-content {
 | 
			
		||||
    font-family: 'Inter';
 | 
			
		||||
    font-weight: 200;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Heading styles */
 | 
			
		||||
.markdown-content h2,
 | 
			
		||||
.markdown-content h3 {
 | 
			
		||||
    font-family: 'Inter';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Light theme colors */
 | 
			
		||||
.light-theme .markdown-content h2,
 | 
			
		||||
.light-theme .markdown-content h3,
 | 
			
		||||
.light-theme .markdown-content p,
 | 
			
		||||
.light-theme .markdown-content ul,
 | 
			
		||||
.light-theme .markdown-content li {
 | 
			
		||||
    color: #444444 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Dark theme colors */
 | 
			
		||||
.markdown-content h2,
 | 
			
		||||
.markdown-content h3,
 | 
			
		||||
.markdown-content p,
 | 
			
		||||
.markdown-content ul,
 | 
			
		||||
.markdown-content li {
 | 
			
		||||
    color: #cccccc !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.markdown-content h2 {
 | 
			
		||||
    font-size: 1.2rem;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    line-height: 1.2;
 | 
			
		||||
    margin-bottom: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.markdown-content h3 {
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    line-height: 1.3;
 | 
			
		||||
    margin-bottom: 0.8rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Regular text styles */
 | 
			
		||||
.markdown-content p,
 | 
			
		||||
.markdown-content ul,
 | 
			
		||||
.markdown-content li {
 | 
			
		||||
    font-family: 'Inter', sans-serif;
 | 
			
		||||
    font-weight: 400;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Make list items and paragraphs consistent size */
 | 
			
		||||
.markdown-content p,
 | 
			
		||||
.markdown-content li {
 | 
			
		||||
    font-size: 0.75rem;
 | 
			
		||||
    font-weight: 200;
 | 
			
		||||
    line-height: 1.25;
 | 
			
		||||
    margin-bottom: 0.25rem;
 | 
			
		||||
    padding-left: 1rem;  /* Added indentation for paragraphs */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* List specific styling */
 | 
			
		||||
.markdown-content ul {
 | 
			
		||||
    font-weight: 200;
 | 
			
		||||
    padding-left: 2.5rem;  /* Increased padding for better alignment */
 | 
			
		||||
    margin-bottom: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* FAQ section styling */
 | 
			
		||||
.faq-section {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
details {
 | 
			
		||||
    border-bottom: 1px solid #ddd;
 | 
			
		||||
    padding: 1em 0;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
summary {
 | 
			
		||||
    font-family: 'Inter', sans-serif;
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
    font-weight: 200;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.faq-section details summary {
 | 
			
		||||
    color: #96989ead;  /* ThreeFold blue color */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.faq-section details summary:before {
 | 
			
		||||
    color: #2a2a2c;  /* Makes the arrow icon match */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
summary::marker {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
details[open] summary {
 | 
			
		||||
    color: #000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
details p {
 | 
			
		||||
    font-family: 'Inter', sans-serif;
 | 
			
		||||
    margin: 0.5em 0 0 0;
 | 
			
		||||
    color: #666;
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
    line-height: 1.6;
 | 
			
		||||
    font-weight: 400;
 | 
			
		||||
    padding-left: 1.5rem;  /* Added indentation for FAQ answers */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Links styling */
 | 
			
		||||
.markdown-content a,
 | 
			
		||||
details a {
 | 
			
		||||
    font-size: 0.75rem;  /* Match paragraph font size */
 | 
			
		||||
    font-family: 'Inter', sans-serif;
 | 
			
		||||
    font-weight: 200;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
summary:before {
 | 
			
		||||
    content: "▶";
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    transform: rotate(0deg);
 | 
			
		||||
    transition: transform 0.3s ease;
 | 
			
		||||
    margin-right: 0.5em;
 | 
			
		||||
    color: #000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
details[open] summary:before {
 | 
			
		||||
    transform: rotate(90deg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Responsive design */
 | 
			
		||||
@media (max-width: 768px) {
 | 
			
		||||
    .faq-container {
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .markdown-content {
 | 
			
		||||
        border-right: none;
 | 
			
		||||
        border-bottom: 1px solid #ddd;
 | 
			
		||||
        padding-right: 0;
 | 
			
		||||
        padding-bottom: 2rem;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										178
									
								
								poc_threefold/static/css/login.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,178 @@
 | 
			
		||||
/* Modal Styles */
 | 
			
		||||
.modal {
 | 
			
		||||
    display: none;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background: #00000080;
 | 
			
		||||
    z-index: 1001;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.modal-content {
 | 
			
		||||
    background: var(--modal-background);
 | 
			
		||||
    padding: 2rem;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    box-shadow: 0 2px 10px #00000033;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    max-width: 460px;
 | 
			
		||||
    color: var(--modal-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.login-box {
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.login-header {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin-bottom: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.logo {
 | 
			
		||||
    width: 48px;
 | 
			
		||||
    height: 48px;
 | 
			
		||||
    margin-bottom: 1rem;
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.login-header h2 {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    color: var(--modal-text);
 | 
			
		||||
    font-size: 1.3rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.login-header p {
 | 
			
		||||
    margin: 0.5rem 0 0;
 | 
			
		||||
    color: var(--hover-color);
 | 
			
		||||
    font-size: 0.85rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-group {
 | 
			
		||||
    margin-bottom: 0.3rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-group label {
 | 
			
		||||
    display: block;
 | 
			
		||||
    margin-bottom: 0.3rem;
 | 
			
		||||
    color: var(--modal-text);
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-group input {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding: 0.25rem 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: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-group input:focus {
 | 
			
		||||
    outline: none;
 | 
			
		||||
    border-color: #1a73e8;
 | 
			
		||||
    box-shadow: 0 0 0 2px #1A73E833;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.error-message {
 | 
			
		||||
    color: #dc3545;
 | 
			
		||||
    font-size: 0.75rem;
 | 
			
		||||
    margin-top: 0.25rem;
 | 
			
		||||
    display: block;
 | 
			
		||||
    min-height: 1.25em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-footer {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    margin-bottom: 1.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.remember-me {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 0.5rem;
 | 
			
		||||
    color: var(--modal-text);
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.forgot-password {
 | 
			
		||||
    color: #1a73e8;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.forgot-password:hover {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.submit-button {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding: 0.75rem;
 | 
			
		||||
    background: #1a73e8;
 | 
			
		||||
    color: white;
 | 
			
		||||
    border: none;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    font-size: 0.9rem;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    transition: all 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.submit-button:hover {
 | 
			
		||||
    background: #1557b0;
 | 
			
		||||
    transform: translateY(-1px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.signup-link {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin-top: 1.5rem;
 | 
			
		||||
    color: var(--modal-text);
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.signup-link a {
 | 
			
		||||
    color: #1a73e8;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.signup-link a:hover {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.close-button {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: -1rem;
 | 
			
		||||
    right: -1rem;
 | 
			
		||||
    width: 2rem;
 | 
			
		||||
    height: 2rem;
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    border: none;
 | 
			
		||||
    background: var(--modal-text);
 | 
			
		||||
    color: var(--modal-background);
 | 
			
		||||
    font-size: 1.5rem;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    transition: all 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.close-button:hover {
 | 
			
		||||
    transform: scale(1.1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 768px) {
 | 
			
		||||
    .modal-content {
 | 
			
		||||
        margin: 1rem;
 | 
			
		||||
        padding: 1.5rem;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										367
									
								
								poc_threefold/static/css/login_test.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,367 @@
 | 
			
		||||
 | 
			
		||||
    <style>
 | 
			
		||||
        .container {
 | 
			
		||||
            max-width: 800px;
 | 
			
		||||
            margin: 2rem auto;
 | 
			
		||||
            padding: 0 1rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .card {
 | 
			
		||||
            background: white;
 | 
			
		||||
            border-radius: 8px;
 | 
			
		||||
            padding: 2rem;
 | 
			
		||||
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .status {
 | 
			
		||||
            padding: 1rem;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            margin: 1rem 0;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            font-weight: bold;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .status.authenticated {
 | 
			
		||||
            background-color: #d4edda;
 | 
			
		||||
            color: #155724;
 | 
			
		||||
            border: 1px solid #c3e6cb;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .status.unauthenticated {
 | 
			
		||||
            background-color: #f8d7da;
 | 
			
		||||
            color: #721c24;
 | 
			
		||||
            border: 1px solid #f5c6cb;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .actions {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            gap: 1rem;
 | 
			
		||||
            margin: 1rem 0;
 | 
			
		||||
            justify-content: center;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .btn-identify, .btn-clear, .btn-list, .btn-generate {
 | 
			
		||||
            padding: 0.5rem 1rem;
 | 
			
		||||
            border: none;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
            font-weight: bold;
 | 
			
		||||
            transition: background-color 0.2s;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .btn-identify {
 | 
			
		||||
            background-color: #007bff;
 | 
			
		||||
            color: white;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .btn-identify:hover {
 | 
			
		||||
            background-color: #0056b3;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .btn-clear {
 | 
			
		||||
            background-color: #dc3545;
 | 
			
		||||
            color: white;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .btn-clear:hover {
 | 
			
		||||
            background-color: #c82333;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .btn-list {
 | 
			
		||||
            background-color: #17a2b8;
 | 
			
		||||
            color: white;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .btn-list:hover {
 | 
			
		||||
            background-color: #138496;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .btn-generate {
 | 
			
		||||
            background-color: #28a745;
 | 
			
		||||
            color: white;
 | 
			
		||||
            margin-top: 1rem;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .btn-generate:hover {
 | 
			
		||||
            background-color: #218838;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .key-info {
 | 
			
		||||
            background-color: #f8f9fa;
 | 
			
		||||
            padding: 1.5rem;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            margin: 1.5rem 0;
 | 
			
		||||
            color: #2c3e50;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .key-details {
 | 
			
		||||
            margin-top: 1rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .warning-message {
 | 
			
		||||
            background-color: #fff3cd;
 | 
			
		||||
            color: #856404;
 | 
			
		||||
            padding: 1rem;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            margin: 1rem 0;
 | 
			
		||||
            border: 1px solid #ffeeba;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .public-key {
 | 
			
		||||
            word-break: break-all;
 | 
			
		||||
            font-family: monospace;
 | 
			
		||||
            background: #ffffff;
 | 
			
		||||
            padding: 1rem;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            border: 1px solid #dee2e6;
 | 
			
		||||
            color: #2c3e50;
 | 
			
		||||
            margin-top: 1rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .test-actions {
 | 
			
		||||
            margin-top: 2rem;
 | 
			
		||||
            padding-top: 2rem;
 | 
			
		||||
            border-top: 1px solid #dee2e6;
 | 
			
		||||
            color: #2c3e50;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .test-container {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            flex-direction: column;
 | 
			
		||||
            gap: 1.5rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .test-group {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            flex-direction: column;
 | 
			
		||||
            gap: 0.5rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .test-group input {
 | 
			
		||||
            padding: 0.5rem;
 | 
			
		||||
            border: 1px solid #ced4da;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            color: #2c3e50;
 | 
			
		||||
            background: #ffffff;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .test-group button {
 | 
			
		||||
            padding: 0.5rem;
 | 
			
		||||
            background-color: #28a745;
 | 
			
		||||
            color: white;
 | 
			
		||||
            border: none;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .test-group button:hover {
 | 
			
		||||
            background-color: #218838;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .set-result, .retrieved-value {
 | 
			
		||||
            margin-top: 0.5rem;
 | 
			
		||||
            padding: 0.5rem;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            font-family: monospace;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .success {
 | 
			
		||||
            background-color: #d4edda;
 | 
			
		||||
            color: #155724;
 | 
			
		||||
            border: 1px solid #c3e6cb;
 | 
			
		||||
            padding: 0.5rem;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .error {
 | 
			
		||||
            background-color: #f8d7da;
 | 
			
		||||
            color: #721c24;
 | 
			
		||||
            border: 1px solid #f5c6cb;
 | 
			
		||||
            padding: 0.5rem;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .info {
 | 
			
		||||
            background-color: #e2e3e5;
 | 
			
		||||
            color: #383d41;
 | 
			
		||||
            border: 1px solid #d6d8db;
 | 
			
		||||
            padding: 0.5rem;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .all-values {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            flex-direction: column;
 | 
			
		||||
            gap: 1rem;
 | 
			
		||||
            margin-top: 1rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .stored-value {
 | 
			
		||||
            background: #ffffff;
 | 
			
		||||
            border: 1px solid #dee2e6;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            padding: 1rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .stored-key {
 | 
			
		||||
            font-weight: bold;
 | 
			
		||||
            color: #2c3e50;
 | 
			
		||||
            margin-bottom: 0.5rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .stored-value-content {
 | 
			
		||||
            font-family: monospace;
 | 
			
		||||
            background: #f8f9fa;
 | 
			
		||||
            padding: 0.5rem;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            margin: 0.5rem 0;
 | 
			
		||||
            word-break: break-all;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .encryption-status {
 | 
			
		||||
            font-size: 0.875rem;
 | 
			
		||||
            color: #6c757d;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .status-icon {
 | 
			
		||||
            margin-right: 0.5rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .encrypted .encryption-status {
 | 
			
		||||
            color: #28a745;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .not-encrypted .encryption-status {
 | 
			
		||||
            color: #dc3545;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .no-values {
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            color: #6c757d;
 | 
			
		||||
            padding: 1rem;
 | 
			
		||||
            background: #f8f9fa;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* Modal Styles */
 | 
			
		||||
        .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>
 | 
			
		||||
							
								
								
									
										272
									
								
								poc_threefold/static/css/menu.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,272 @@
 | 
			
		||||
.tf_nav {
 | 
			
		||||
    background: var(--body-background);
 | 
			
		||||
    padding: 0.3rem;
 | 
			
		||||
    position: sticky;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    z-index: 1000;
 | 
			
		||||
    box-shadow: 0 1px 4px #00000022;
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
    font-family: 'Inter', system-ui, -apple-system, sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_nav_container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    max-width: 1200px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    padding: 0 1rem;
 | 
			
		||||
    height: 32px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_logo {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 0.5rem;
 | 
			
		||||
    margin-right: 4rem;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    font-size: 0.95rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_logo svg {
 | 
			
		||||
    width: 20px;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_menu {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 0.75rem;
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    margin-right: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_menu_item {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_menu_link {
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    padding: 0.3rem;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    transition: color 0.2s;
 | 
			
		||||
    font-size: 0.8rem;
 | 
			
		||||
    font-weight: 450;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_menu_link:hover {
 | 
			
		||||
    color: var(--hover-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_dropdown {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 100%;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    background: var(--body-background);
 | 
			
		||||
    min-width: 150px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    box-shadow: 0 4px 12px #00000033;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
    transform: translateY(-10px);
 | 
			
		||||
    transition: all 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_menu_item:hover .tf_dropdown {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    visibility: visible;
 | 
			
		||||
    transform: translateY(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_dropdown_item {
 | 
			
		||||
    padding: 0.4rem 0.8rem;
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    display: block;
 | 
			
		||||
    transition: background-color 0.2s;
 | 
			
		||||
    font-size: 0.6rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_dropdown_item:hover {
 | 
			
		||||
    background-color: #80808019;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_right_controls {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 2rem;
 | 
			
		||||
    margin-left: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_theme_toggle {
 | 
			
		||||
    background: none;
 | 
			
		||||
    border: 1px solid var(--text-color);
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    padding: 0.3rem;
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    transition: all 0.2s;
 | 
			
		||||
    width: 28px;
 | 
			
		||||
    height: 28px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_theme_toggle:hover {
 | 
			
		||||
    background-color: #80808019;
 | 
			
		||||
    transform: scale(1.05);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.theme-icon {
 | 
			
		||||
    width: 14px;
 | 
			
		||||
    height: 14px;
 | 
			
		||||
    transition: all 0.2s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.light-theme .theme-icon.light {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.light-theme .theme-icon.dark {
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.theme-icon.light {
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.theme-icon.dark {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_login_btn {
 | 
			
		||||
    background: transparent;
 | 
			
		||||
    border: 1px solid var(--text-color);
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
    padding: 0.25rem 1rem;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    transition: all 0.2s;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    font-size: 0.8125rem;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tf_login_btn:hover {
 | 
			
		||||
    background: var(--text-color);
 | 
			
		||||
    color: var(--body-background);
 | 
			
		||||
    transform: translateY(-1px);
 | 
			
		||||
    box-shadow: 0 2px 4px #0000001A;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hamburger-menu {
 | 
			
		||||
    display: none;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    border: none;
 | 
			
		||||
    background: none;
 | 
			
		||||
    padding: 0.5rem;
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hamburger-menu svg {
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mobile-overlay {
 | 
			
		||||
    display: none;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    background: rgba(0, 0, 0, 0.5);
 | 
			
		||||
    z-index: 999;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    transition: opacity 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mobile-overlay.active {
 | 
			
		||||
    display: block;
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 768px) {
 | 
			
		||||
    .tf_menu {
 | 
			
		||||
        display: none;
 | 
			
		||||
        position: fixed;
 | 
			
		||||
        top: 48px;
 | 
			
		||||
        left: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        background: var(--body-background);
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        align-items: stretch;
 | 
			
		||||
        padding: 1rem;
 | 
			
		||||
        height: auto;
 | 
			
		||||
        box-shadow: 0 4px 12px #00000033;
 | 
			
		||||
        z-index: 1000;
 | 
			
		||||
        max-height: calc(100vh - 48px);
 | 
			
		||||
        overflow-y: auto;
 | 
			
		||||
        transform: translateY(-100%);
 | 
			
		||||
        transition: transform 0.3s ease;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .tf_menu.active {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        transform: translateY(0);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .tf_menu_item {
 | 
			
		||||
        height: auto;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        align-items: stretch;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .tf_menu_link {
 | 
			
		||||
        padding: 0.8rem;
 | 
			
		||||
        font-size: 1rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .tf_dropdown {
 | 
			
		||||
        position: static;
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
        visibility: visible;
 | 
			
		||||
        transform: none;
 | 
			
		||||
        box-shadow: none;
 | 
			
		||||
        background: transparent;
 | 
			
		||||
        margin: 0;
 | 
			
		||||
        padding-left: 1rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .tf_dropdown_item {
 | 
			
		||||
        font-size: 0.9rem;
 | 
			
		||||
        padding: 0.6rem 1rem;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .tf_logo {
 | 
			
		||||
        margin-right: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .hamburger-menu {
 | 
			
		||||
        display: block;
 | 
			
		||||
        order: -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .tf_right_controls {
 | 
			
		||||
        margin-left: auto;
 | 
			
		||||
        gap: 1rem;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										187
									
								
								poc_threefold/static/css/ourworld.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,187 @@
 | 
			
		||||
/* Import Google Fonts */
 | 
			
		||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
    /* Light theme variables */
 | 
			
		||||
    --body-background-light: #ffffff;
 | 
			
		||||
    --body-text-light: #333333;
 | 
			
		||||
    --text-color-light: #333;
 | 
			
		||||
    --hover-color-light: #666;
 | 
			
		||||
    --modal-background-light: #f5f5f5;
 | 
			
		||||
    --modal-text-light: #333;
 | 
			
		||||
    --input-border-light: #ddd;
 | 
			
		||||
    --hero-background-light: #FFFFFFF2;
 | 
			
		||||
    --hero-background2-light: #FFFFFFFB;
 | 
			
		||||
    --hero-text-light: #333;
 | 
			
		||||
    --hero-banner-background-light: #0000000D;
 | 
			
		||||
    --hero-subtitle-background-light: #385bb5;
 | 
			
		||||
    --hero-subtitle-text-light: white;
 | 
			
		||||
    --input-border-light:  var(--hero-background2-light);
 | 
			
		||||
 | 
			
		||||
    /* Dark theme variables */
 | 
			
		||||
    --body-background-dark: #1a1a1a;
 | 
			
		||||
    --body-text-dark: #ffffff;
 | 
			
		||||
    --text-color-dark: #fff;
 | 
			
		||||
    --hover-color-dark: #aaa;
 | 
			
		||||
    --modal-background-dark: #282c34;
 | 
			
		||||
    --modal-text-dark: #fff;
 | 
			
		||||
    --input-border-dark: #444;   
 | 
			
		||||
    --hero-background-dark: #282C34F2;
 | 
			
		||||
    --hero-background2-dark: #2D3139FA;
 | 
			
		||||
    --hero-text-dark: white;
 | 
			
		||||
    --hero-banner-background-dark: #FFFFFF26;
 | 
			
		||||
    --hero-subtitle-background-dark: #385bb5;
 | 
			
		||||
    --hero-subtitle-text-dark: white;
 | 
			
		||||
    --input-border-dark:  var(--hero-background2-dark);
 | 
			
		||||
 | 
			
		||||
    /* Default to dark theme */
 | 
			
		||||
    --body-background: var(--body-background-dark);
 | 
			
		||||
    --body-text: var(--body-text-dark);
 | 
			
		||||
    --text-color: var(--text-color-dark);
 | 
			
		||||
    --hover-color: var(--hover-color-dark);
 | 
			
		||||
    --modal-background: var(--modal-background-dark);
 | 
			
		||||
    --modal-text: var(--modal-text-dark);
 | 
			
		||||
    --input-border: var(--input-border-dark);
 | 
			
		||||
    --hero-background: var(--hero-background-dark);
 | 
			
		||||
    --hero-background2: var(--hero-background2-dark);
 | 
			
		||||
    --hero-text: var(--hero-text-dark);
 | 
			
		||||
    --hero-banner-background: var(--hero-banner-background-dark);
 | 
			
		||||
    --hero-subtitle-background: var(--hero-subtitle-background-dark);
 | 
			
		||||
    --hero-subtitle-text: var(--hero-subtitle-text-dark);
 | 
			
		||||
    --input-border: var(--input-border-dark);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Light theme class */
 | 
			
		||||
.light-theme {
 | 
			
		||||
    --body-background: var(--body-background-light);
 | 
			
		||||
    --body-text: var(--body-text-light);
 | 
			
		||||
    --text-color: var(--text-color-light);
 | 
			
		||||
    --hover-color: var(--hover-color-light);
 | 
			
		||||
    --modal-background: var(--modal-background-light);
 | 
			
		||||
    --modal-text: var(--modal-text-light);
 | 
			
		||||
    --input-border: var(--input-border-light);     
 | 
			
		||||
    --hero-background: var(--hero-background-light);
 | 
			
		||||
    --hero-background2: var(--hero-background2-light);
 | 
			
		||||
    --hero-text: var(--hero-text-light);
 | 
			
		||||
    --hero-banner-background: var(--hero-banner-background-light);
 | 
			
		||||
    --hero-subtitle-background: var(--hero-subtitle-background-light);
 | 
			
		||||
    --hero-subtitle-text: var(--hero-subtitle-text-light);      
 | 
			
		||||
    --input-border: var(--input-border-light); 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Heading styles - using Inter */
 | 
			
		||||
h1 {
 | 
			
		||||
    font-family: 'Inter', sans-serif;
 | 
			
		||||
    font-size: 1.2rem;
 | 
			
		||||
    margin-bottom: 1rem;
 | 
			
		||||
    font-weight: 700;
 | 
			
		||||
    letter-spacing: -0.03em;
 | 
			
		||||
    line-height: 1.1;
 | 
			
		||||
    text-transform: uppercase;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h2 {
 | 
			
		||||
    font-family: 'Inter', sans-serif;
 | 
			
		||||
    font-size: 1.1rem;
 | 
			
		||||
    margin-bottom: 1rem;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
    letter-spacing: -0.02em;
 | 
			
		||||
    line-height: 1.2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h3 {
 | 
			
		||||
    font-family: 'Inter', sans-serif;
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
    margin-bottom: 0.8rem;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
    letter-spacing: -0.01em;
 | 
			
		||||
    line-height: 1.3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h4 {
 | 
			
		||||
    font-family: 'Inter', sans-serif;
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
    margin-bottom: 0.8rem;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    letter-spacing: -0.005em;
 | 
			
		||||
    line-height: 1.4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p ul {
 | 
			
		||||
    padding-left: 1.5em;
 | 
			
		||||
    margin: 1.25em 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p li {
 | 
			
		||||
    line-height: 1.6;
 | 
			
		||||
    margin-bottom: 0.75em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Paragraph styles - using Inter */
 | 
			
		||||
p {
 | 
			
		||||
    font-family: 'Inter', sans-serif;
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
    line-height: 1.6;
 | 
			
		||||
    margin-bottom: 1.5rem;
 | 
			
		||||
    max-width: 720px;
 | 
			
		||||
    font-weight: 200;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Optional: styling for small or additional text */
 | 
			
		||||
small {
 | 
			
		||||
    font-family: 'Inter', sans-serif;
 | 
			
		||||
    font-size: 0.9rem;
 | 
			
		||||
    font-weight: 200;
 | 
			
		||||
    color: #666;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Additional styles for links within text */
 | 
			
		||||
a {
 | 
			
		||||
    color: #007acc;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    transition: color 0.2s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:hover {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Navigation styles - using Inter */
 | 
			
		||||
nav {
 | 
			
		||||
    font-family: 'Inter';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nav a {
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    letter-spacing: 0;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: var(--text-color);
 | 
			
		||||
    transition: color 0.2s ease;
 | 
			
		||||
    text-transform: uppercase;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nav a:hover {
 | 
			
		||||
    color: var(--hover-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main {
 | 
			
		||||
    background-color: var(--body-background);
 | 
			
		||||
    transition: background-color 0.3s;
 | 
			
		||||
}    
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    font-family: 'Inter';
 | 
			
		||||
    background-color: var(--body-background);
 | 
			
		||||
    color: var(--body-text);
 | 
			
		||||
    min-height: 100vh;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0.25rem 0;
 | 
			
		||||
    overflow-x: hidden;
 | 
			
		||||
    transition: background-color 0.3s, color 0.3s;
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
    line-height: 1.6;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								poc_threefold/static/img/cloud_dancing.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 602 KiB  | 
							
								
								
									
										5
									
								
								poc_threefold/static/img/favicon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,5 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
 | 
			
		||||
    <rect width="24" height="24" fill="#1a1a1a"/>
 | 
			
		||||
    <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 345 B  | 
							
								
								
									
										
											BIN
										
									
								
								poc_threefold/static/img/happy_kid.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 293 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								poc_threefold/static/img/music.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 702 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								poc_threefold/static/img/questions.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 387 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								poc_threefold/static/img/questions_kid.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.2 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								poc_threefold/static/img/wave.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 910 KiB  | 
@@ -24,3 +24,6 @@ uvicorn
 | 
			
		||||
pillow
 | 
			
		||||
pymupdf
 | 
			
		||||
markdown
 | 
			
		||||
types-markdown
 | 
			
		||||
watchdog
 | 
			
		||||
websockets
 | 
			
		||||
 
 | 
			
		||||