...
@ -7,7 +7,14 @@ import os
|
|||||||
import markdown
|
import markdown
|
||||||
import re
|
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):
|
if not os.path.exists(sources_dir):
|
||||||
raise RuntimeError(f"The source directory '{sources_dir}' does not exist.")
|
raise RuntimeError(f"The source directory '{sources_dir}' does not exist.")
|
||||||
static_dir = f"{sources_dir}/static"
|
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
|
pillow
|
||||||
pymupdf
|
pymupdf
|
||||||
markdown
|
markdown
|
||||||
|
types-markdown
|
||||||
|
watchdog
|
||||||
|
websockets
|
||||||
|