archy/Android/app/src/main/assets/connect.html

493 lines
16 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Archipelago</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: #000;
color: #f5f5f5;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
padding-top: calc(24px + env(safe-area-inset-top, 0px));
overflow: hidden;
-webkit-tap-highlight-color: transparent;
}
/* --- Intro Screen --- */
#intro {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 20px;
animation: fadeIn 0.6s ease;
}
#intro.hidden, #connect.hidden, #connecting.hidden { display: none; }
.logo-container {
width: 88px;
height: 88px;
border-radius: 20px;
overflow: hidden;
border: 2px solid rgba(255,255,255,0.12);
background: #030202;
}
.logo-container svg { width: 100%; height: 100%; }
.logo-square {
opacity: 0;
animation: squareIn 3s ease-out infinite;
}
@keyframes squareIn {
0% { opacity: 0; }
15% { opacity: 1; }
100% { opacity: 1; }
}
.brand-name {
font-size: 12px;
font-weight: 600;
letter-spacing: 6px;
color: #F7931A;
text-transform: uppercase;
}
h1 {
font-size: 28px;
font-weight: 600;
line-height: 1.3;
color: #f5f5f5;
margin-top: 16px;
}
.subtitle {
font-size: 16px;
color: #666;
line-height: 1.6;
max-width: 320px;
}
/* --- Glass Button --- */
.glass-button {
width: 100%;
max-width: 340px;
padding: 16px 24px;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
border: none;
transition: all 0.15s ease;
-webkit-tap-highlight-color: transparent;
}
.glass-button:active { transform: scale(0.97); }
.glass-button-primary {
background: #F7931A;
color: #000;
}
.glass-button-primary:disabled {
background: rgba(247,147,26,0.3);
color: rgba(0,0,0,0.5);
}
.glass-button-outline {
background: rgba(255,255,255,0.06);
color: #f5f5f5;
border: 1px solid rgba(255,255,255,0.12);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
/* --- Connect Screen --- */
#connect {
width: 100%;
max-width: 400px;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
animation: fadeIn 0.4s ease;
}
.glass-card {
width: 100%;
padding: 20px;
border-radius: 16px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
}
.form-group { margin-bottom: 16px; }
.form-group:last-child { margin-bottom: 0; }
label {
display: block;
font-size: 13px;
font-weight: 500;
color: rgba(255,255,255,0.5);
margin-bottom: 8px;
}
input[type="text"] {
width: 100%;
padding: 14px 16px;
border-radius: 12px;
border: 1px solid rgba(255,255,255,0.12);
background: transparent;
color: #f5f5f5;
font-size: 16px;
font-family: inherit;
outline: none;
transition: border-color 0.2s;
}
input[type="text"]:focus {
border-color: #F7931A;
}
input[type="text"]::placeholder {
color: rgba(255,255,255,0.25);
}
.port-row {
display: flex;
gap: 12px;
align-items: flex-end;
}
.port-input { width: 120px; }
.toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 0;
}
.toggle-label {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: rgba(255,255,255,0.7);
}
.toggle-label svg { width: 18px; height: 18px; opacity: 0.5; }
/* Toggle switch */
.toggle {
position: relative;
width: 48px;
height: 28px;
-webkit-appearance: none;
appearance: none;
background: rgba(255,255,255,0.12);
border-radius: 14px;
border: none;
cursor: pointer;
transition: background 0.2s;
}
.toggle:checked { background: #F7931A; }
.toggle::before {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 22px;
height: 22px;
border-radius: 50%;
background: #fff;
transition: transform 0.2s;
}
.toggle:checked::before { transform: translateX(20px); }
/* Error */
.error-msg {
width: 100%;
padding: 12px 16px;
border-radius: 12px;
background: rgba(239,68,68,0.12);
border: 1px solid rgba(239,68,68,0.25);
color: #ef4444;
font-size: 14px;
display: none;
}
.error-msg.visible { display: block; }
/* Saved servers */
.saved-title {
font-size: 11px;
font-weight: 600;
letter-spacing: 1px;
color: rgba(255,255,255,0.3);
text-transform: uppercase;
}
.saved-item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
border-radius: 12px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
cursor: pointer;
transition: background 0.15s;
}
.saved-item:active { background: rgba(255,255,255,0.08); }
.saved-addr {
font-size: 14px;
color: #f5f5f5;
}
.saved-remove {
background: none;
border: none;
color: rgba(255,255,255,0.3);
font-size: 18px;
cursor: pointer;
padding: 4px 8px;
}
/* Connecting overlay */
#connecting {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
animation: fadeIn 0.3s ease;
}
.spinner {
width: 32px;
height: 32px;
border: 3px solid rgba(247,147,26,0.2);
border-top-color: #F7931A;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
/* Hide scrollbar */
::-webkit-scrollbar { display: none; }
</style>
</head>
<body>
<!-- Intro -->
<div id="intro">
<div class="logo-container">
<svg viewBox="0 0 1024 1024" fill="none">
<rect width="1024" height="1024" fill="#030202"/>
<rect x="357.614" y="318" width="71.007" height="70.936" fill="white" class="logo-square" style="animation-delay:0ms"/>
<rect x="436.152" y="318" width="72.082" height="70.936" fill="white" class="logo-square" style="animation-delay:100ms"/>
<rect x="515.766" y="318" width="72.082" height="70.936" fill="white" class="logo-square" style="animation-delay:200ms"/>
<rect x="595.379" y="318" width="71.007" height="70.936" fill="white" class="logo-square" style="animation-delay:300ms"/>
<rect x="595.379" y="396.46" width="71.007" height="72.011" fill="white" class="logo-square" style="animation-delay:400ms"/>
<rect x="673.917" y="396.46" width="72.083" height="72.011" fill="white" class="logo-square" style="animation-delay:500ms"/>
<rect x="278" y="475.994" width="72.083" height="72.012" fill="white" class="logo-square" style="animation-delay:600ms"/>
<rect x="357.614" y="475.994" width="71.007" height="72.012" fill="white" class="logo-square" style="animation-delay:700ms"/>
<rect x="436.152" y="475.994" width="72.082" height="72.012" fill="white" class="logo-square" style="animation-delay:800ms"/>
<rect x="515.766" y="475.994" width="72.082" height="72.012" fill="white" class="logo-square" style="animation-delay:900ms"/>
<rect x="595.379" y="475.994" width="71.007" height="72.012" fill="white" class="logo-square" style="animation-delay:1000ms"/>
<rect x="673.917" y="475.994" width="72.083" height="72.012" fill="white" class="logo-square" style="animation-delay:1100ms"/>
<rect x="278" y="555.529" width="72.083" height="70.936" fill="white" class="logo-square" style="animation-delay:1200ms"/>
<rect x="357.614" y="555.529" width="71.007" height="70.936" fill="white" class="logo-square" style="animation-delay:1300ms"/>
<rect x="595.379" y="555.529" width="71.007" height="70.936" fill="white" class="logo-square" style="animation-delay:1400ms"/>
<rect x="673.917" y="555.529" width="72.083" height="70.936" fill="white" class="logo-square" style="animation-delay:1500ms"/>
<rect x="357.614" y="633.989" width="71.007" height="72.011" fill="white" class="logo-square" style="animation-delay:1600ms"/>
<rect x="436.152" y="633.989" width="72.082" height="72.011" fill="white" class="logo-square" style="animation-delay:1700ms"/>
<rect x="515.766" y="633.989" width="72.082" height="72.011" fill="white" class="logo-square" style="animation-delay:1800ms"/>
<rect x="595.379" y="633.989" width="71.007" height="72.011" fill="white" class="logo-square" style="animation-delay:1900ms"/>
</svg>
</div>
<span class="brand-name">Archipelago</span>
<h1>Your Sovereign<br>Personal Server</h1>
<p class="subtitle">Bitcoin node, app platform, and private cloud — all in one box you control.</p>
<button class="glass-button glass-button-primary" onclick="showConnect()" style="margin-top:16px">Get Started</button>
</div>
<!-- Connect -->
<div id="connect" class="hidden">
<div class="logo-container" style="width:56px;height:56px;border-radius:14px">
<svg viewBox="0 0 1024 1024" fill="none">
<rect width="1024" height="1024" fill="#030202"/>
<rect x="357.614" y="318" width="71.007" height="70.936" fill="white"/>
<rect x="436.152" y="318" width="72.082" height="70.936" fill="white"/>
<rect x="515.766" y="318" width="72.082" height="70.936" fill="white"/>
<rect x="595.379" y="318" width="71.007" height="70.936" fill="white"/>
<rect x="595.379" y="396.46" width="71.007" height="72.011" fill="white"/>
<rect x="673.917" y="396.46" width="72.083" height="72.011" fill="white"/>
<rect x="278" y="475.994" width="72.083" height="72.012" fill="white"/>
<rect x="357.614" y="475.994" width="71.007" height="72.012" fill="white"/>
<rect x="436.152" y="475.994" width="72.082" height="72.012" fill="white"/>
<rect x="515.766" y="475.994" width="72.082" height="72.012" fill="white"/>
<rect x="595.379" y="475.994" width="71.007" height="72.012" fill="white"/>
<rect x="673.917" y="475.994" width="72.083" height="72.012" fill="white"/>
<rect x="278" y="555.529" width="72.083" height="70.936" fill="white"/>
<rect x="357.614" y="555.529" width="71.007" height="70.936" fill="white"/>
<rect x="595.379" y="555.529" width="71.007" height="70.936" fill="white"/>
<rect x="673.917" y="555.529" width="72.083" height="70.936" fill="white"/>
<rect x="357.614" y="633.989" width="71.007" height="72.011" fill="white"/>
<rect x="436.152" y="633.989" width="72.082" height="72.011" fill="white"/>
<rect x="515.766" y="633.989" width="72.082" height="72.011" fill="white"/>
<rect x="595.379" y="633.989" width="71.007" height="72.011" fill="white"/>
</svg>
</div>
<h1 style="font-size:22px">Connect to Server</h1>
<p class="subtitle" style="font-size:14px">Enter your Archipelago server IP or hostname</p>
<div class="glass-card">
<div class="form-group">
<label>Server Address</label>
<input type="text" id="address" placeholder="192.168.1.100" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
</div>
<div class="form-group">
<div class="port-row">
<div class="port-input">
<label>Port (optional)</label>
<input type="text" id="port" placeholder="80" inputmode="numeric" pattern="[0-9]*">
</div>
</div>
</div>
<div class="form-group">
<div class="toggle-row">
<span class="toggle-label">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>
Use HTTPS
</span>
<input type="checkbox" id="https" class="toggle">
</div>
</div>
</div>
<div id="error" class="error-msg"></div>
<button class="glass-button glass-button-primary" id="connectBtn" onclick="doConnect()" disabled>Connect</button>
<div id="savedServers"></div>
</div>
<!-- Connecting -->
<div id="connecting" class="hidden">
<div class="spinner"></div>
<p style="color:rgba(255,255,255,0.6);font-size:14px">Connecting…</p>
</div>
<script>
var STORAGE_KEY = 'archipelago_servers';
var ACTIVE_KEY = 'archipelago_active';
function showConnect() {
document.getElementById('intro').classList.add('hidden');
document.getElementById('connect').classList.remove('hidden');
document.getElementById('address').focus();
renderSaved();
}
// Enable button when address has content
document.getElementById('address').addEventListener('input', function() {
document.getElementById('connectBtn').disabled = !this.value.trim();
});
// Enter to connect
document.getElementById('address').addEventListener('keydown', function(e) {
if (e.key === 'Enter' && this.value.trim()) doConnect();
});
document.getElementById('port').addEventListener('keydown', function(e) {
if (e.key === 'Enter') doConnect();
});
function buildUrl() {
var addr = document.getElementById('address').value.trim();
var port = document.getElementById('port').value.trim();
var https = document.getElementById('https').checked;
var scheme = https ? 'https' : 'http';
var portSuffix = port ? ':' + port : '';
return scheme + '://' + addr + portSuffix;
}
function doConnect() {
var addr = document.getElementById('address').value.trim();
if (!addr) return;
var url = buildUrl();
document.getElementById('connect').classList.add('hidden');
document.getElementById('connecting').classList.remove('hidden');
document.getElementById('error').classList.remove('visible');
// Save and navigate directly — no XHR test needed,
// the WebView error handler catches failures
saveServer(url);
localStorage.setItem(ACTIVE_KEY, url);
AndroidBridge.onConnected(url);
}
function saveServer(url) {
var saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
if (saved.indexOf(url) === -1) saved.push(url);
localStorage.setItem(STORAGE_KEY, JSON.stringify(saved));
}
function removeServer(url) {
var saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
saved = saved.filter(function(s) { return s !== url; });
localStorage.setItem(STORAGE_KEY, JSON.stringify(saved));
renderSaved();
}
function connectSaved(url) {
document.getElementById('intro').classList.add('hidden');
document.getElementById('connect').classList.add('hidden');
document.getElementById('connecting').classList.remove('hidden');
localStorage.setItem(ACTIVE_KEY, url);
AndroidBridge.onConnected(url);
}
function renderSaved() {
var saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
var container = document.getElementById('savedServers');
if (!saved.length) { container.innerHTML = ''; return; }
var html = '<p class="saved-title" style="margin-top:8px;margin-bottom:8px">Saved Servers</p>';
saved.forEach(function(url) {
html += '<div class="saved-item" onclick="connectSaved(\'' + url + '\')">' +
'<span class="saved-addr">' + url.replace(/^https?:\/\//, '') + '</span>' +
'<button class="saved-remove" onclick="event.stopPropagation();removeServer(\'' + url + '\')">&times;</button>' +
'</div>';
});
container.innerHTML = html;
}
// On load: check if already connected
(function() {
var active = localStorage.getItem(ACTIVE_KEY);
if (active) {
connectSaved(active);
}
})();
</script>
</body>
</html>