vibetunnel/tauri/public/welcome.html
Peter Steinberger 88de777b65 feat(tauri): Port all Mac app features to Tauri implementation
- Add session detail windows with dedicated view per session
- Enhance menu bar to show clickable session list (up to 5 sessions)
- Implement terminal window focus using AppleScript on macOS
- Add project credits to welcome flow
- Fix compilation errors in tray_menu.rs

All Mac app features are now available in the Tauri version, achieving
full feature parity while maintaining Tauri's additional capabilities.
2025-06-23 04:07:16 +02:00

638 lines
No EOL
22 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to VibeTunnel</title>
<style>
:root {
/* Light mode colors */
--bg-color: #ffffff;
--window-bg: #f5f5f7;
--text-primary: #1d1d1f;
--text-secondary: #86868b;
--accent-color: #007aff;
--accent-hover: #0051d5;
--border-color: rgba(0, 0, 0, 0.1);
--shadow-color: rgba(0, 0, 0, 0.15);
--indicator-inactive: rgba(134, 134, 139, 0.3);
}
@media (prefers-color-scheme: dark) {
:root {
/* Dark mode colors */
--bg-color: #1c1c1e;
--window-bg: #000000;
--text-primary: #f5f5f7;
--text-secondary: #98989d;
--accent-color: #0a84ff;
--accent-hover: #409cff;
--border-color: rgba(255, 255, 255, 0.1);
--shadow-color: rgba(0, 0, 0, 0.5);
--indicator-inactive: rgba(152, 152, 157, 0.3);
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', system-ui, sans-serif;
background-color: var(--bg-color);
color: var(--text-primary);
width: 100vw;
height: 100vh;
overflow: hidden;
-webkit-user-select: none;
user-select: none;
}
.container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--window-bg);
}
.content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
overflow: hidden;
}
.page {
width: 100%;
height: 100%;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
animation: slideIn 0.3s ease-out;
}
.page.active {
display: flex;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.app-icon {
width: 156px;
height: 156px;
margin-bottom: 40px;
filter: drop-shadow(0 10px 20px var(--shadow-color));
border-radius: 27.6%;
transition: all 0.3s ease;
}
.app-icon:hover {
transform: scale(1.05);
filter: drop-shadow(0 15px 30px var(--shadow-color));
}
.text-content {
text-align: center;
max-width: 480px;
}
h1 {
font-size: 40px;
font-weight: 600;
margin-bottom: 20px;
letter-spacing: -0.5px;
}
.subtitle {
font-size: 16px;
color: var(--text-secondary);
line-height: 1.5;
margin-bottom: 20px;
}
.description {
font-size: 16px;
color: var(--text-secondary);
line-height: 1.5;
}
.navigation {
height: 92px;
padding: 0 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
border-top: 1px solid var(--border-color);
background-color: var(--bg-color);
}
.indicators {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding-top: 12px;
}
.indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: var(--indicator-inactive);
border: none;
cursor: pointer;
transition: background-color 0.2s ease;
}
.indicator:hover {
background-color: var(--text-secondary);
transform: scale(1.2);
}
.indicator.active {
background-color: var(--accent-color);
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.button-container {
height: 60px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.next-button {
min-width: 80px;
padding: 8px 20px;
background-color: var(--accent-color);
color: white;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease;
}
.next-button:hover {
background-color: var(--accent-hover);
}
.next-button:active {
transform: scale(0.98);
}
/* Additional pages content */
.feature-list {
margin-top: 30px;
text-align: left;
max-width: 400px;
}
.feature-item {
display: flex;
align-items: flex-start;
margin-bottom: 16px;
color: var(--text-secondary);
font-size: 15px;
line-height: 1.5;
}
.feature-icon {
width: 20px;
height: 20px;
margin-right: 12px;
flex-shrink: 0;
color: var(--accent-color);
}
.code-block {
background-color: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 16px;
margin: 20px 0;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 13px;
color: var(--text-primary);
}
.button-group {
display: flex;
gap: 12px;
margin-top: 20px;
justify-content: center;
}
.secondary-button {
padding: 8px 20px;
background-color: transparent;
color: var(--accent-color);
border: 1px solid var(--accent-color);
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.secondary-button:hover {
background-color: var(--accent-color);
color: white;
}
.terminal-item {
transition: all 0.2s ease;
}
.terminal-item:hover {
background-color: var(--tab-bg) !important;
transform: translateX(4px);
}
.credits {
margin-top: 40px;
text-align: center;
font-size: 12px;
color: var(--text-secondary);
}
.credits p {
margin: 4px 0;
}
.credits a {
color: var(--accent-color);
text-decoration: none;
}
.credits a:hover {
text-decoration: underline;
}
.success-checkmark {
color: var(--success-color);
margin-right: 8px;
}
/* Add status colors */
:root {
--success-color: #34c759;
--error-color: #ff3b30;
}
@media (prefers-color-scheme: dark) {
:root {
--success-color: #32d74b;
--error-color: #ff453a;
}
}
</style>
</head>
<body>
<div class="container">
<div class="content">
<!-- Page 1: Welcome -->
<div class="page active" id="page-0">
<img src="icon.png" alt="VibeTunnel" class="app-icon">
<div class="text-content">
<h1>Welcome to VibeTunnel</h1>
<p class="subtitle">Turn any browser into your terminal. Command your agents on the go.</p>
<p class="description">
You'll be quickly guided through the basics of VibeTunnel.<br>
This screen can always be opened from the settings.
</p>
</div>
</div>
<!-- Page 2: VT Command -->
<div class="page" id="page-1">
<img src="icon.png" alt="VibeTunnel" class="app-icon">
<div class="text-content">
<h1>Install the VT Command</h1>
<p class="subtitle">The <code>vt</code> command lets you quickly create terminal sessions</p>
<div class="code-block">
$ vt<br>
# Creates a new terminal session in your browser
</div>
<div class="button-group">
<button class="secondary-button" onclick="installCLI()" id="installCLIBtn">Install CLI Tool</button>
</div>
<p class="description" id="cliStatus" style="margin-top: 20px; color: var(--text-secondary);"></p>
</div>
</div>
<!-- Page 3: Request Permissions -->
<div class="page" id="page-2">
<img src="icon.png" alt="VibeTunnel" class="app-icon">
<div class="text-content">
<h1>Grant Permissions</h1>
<p class="subtitle">VibeTunnel needs permissions to function properly</p>
<div class="feature-list">
<div class="feature-item">
<svg class="feature-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>Terminal Automation - To launch terminal windows</span>
</div>
<div class="feature-item">
<svg class="feature-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>Accessibility - To control terminal applications</span>
</div>
</div>
<div class="button-group">
<button class="secondary-button" onclick="requestPermissions()">Grant Permissions</button>
</div>
</div>
</div>
<!-- Page 4: Select Terminal -->
<div class="page" id="page-3">
<img src="icon.png" alt="VibeTunnel" class="app-icon">
<div class="text-content">
<h1>Select Your Terminal</h1>
<p class="subtitle">Choose your preferred terminal emulator</p>
<div class="terminal-list" id="terminalList" style="margin: 20px 0;">
<!-- Terminal options will be populated here -->
</div>
<div class="button-group">
<button class="secondary-button" onclick="testTerminal()">Test Terminal</button>
</div>
</div>
</div>
<!-- Page 5: Protect Dashboard -->
<div class="page" id="page-4">
<img src="icon.png" alt="VibeTunnel" class="app-icon">
<div class="text-content">
<h1>Protect Your Dashboard</h1>
<p class="subtitle">Security is important when accessing terminals remotely</p>
<p class="description">
We recommend setting a password for your dashboard,<br>
especially if you plan to access it from outside your local network.
</p>
<div class="button-group">
<button class="secondary-button" onclick="openSettings('dashboard')">Set Password</button>
<button class="secondary-button" onclick="skipPassword()">Skip for Now</button>
</div>
</div>
</div>
<!-- Page 6: Access Dashboard -->
<div class="page" id="page-5">
<img src="icon.png" alt="VibeTunnel" class="app-icon">
<div class="text-content">
<h1>Access Your Dashboard</h1>
<p class="subtitle">
To access your terminals from any device, create a tunnel from your device.<br><br>
This can be done via <strong>ngrok</strong> in settings or <strong>Tailscale</strong> (recommended).
</p>
<div class="button-group">
<button class="secondary-button" onclick="openDashboard()">
Open Dashboard
</button>
<button class="secondary-button" onclick="openTailscale()">
Learn about Tailscale
</button>
</div>
<div class="credits">
<p>Made with ❤️ by</p>
<p>
<a href="#" onclick="openURL('https://twitter.com/badlogic'); return false;">@badlogic</a>,
<a href="#" onclick="openURL('https://twitter.com/mitsuhiko'); return false;">@mitsuhiko</a> &
<a href="#" onclick="openURL('https://twitter.com/steipete'); return false;">@steipete</a>
</p>
</div>
</div>
</div>
<!-- Page 7: Getting Started -->
<div class="page" id="page-6">
<img src="icon.png" alt="VibeTunnel" class="app-icon">
<div class="text-content">
<h1>You're All Set!</h1>
<p class="subtitle">VibeTunnel is now running in your system tray</p>
<p class="description">
Click the VibeTunnel icon in your system tray to access settings,<br>
open the dashboard, or manage your terminal sessions.
</p>
</div>
</div>
</div>
<div class="navigation">
<div class="indicators">
<button class="indicator active" onclick="goToPage(0)"></button>
<button class="indicator" onclick="goToPage(1)"></button>
<button class="indicator" onclick="goToPage(2)"></button>
<button class="indicator" onclick="goToPage(3)"></button>
<button class="indicator" onclick="goToPage(4)"></button>
<button class="indicator" onclick="goToPage(5)"></button>
<button class="indicator" onclick="goToPage(6)"></button>
</div>
<div class="button-container">
<button class="next-button" id="nextButton" onclick="handleNext()">Next</button>
</div>
</div>
</div>
<script>
const { invoke } = window.__TAURI__.tauri;
const { open } = window.__TAURI__.shell;
const { appWindow } = window.__TAURI__.window;
let currentPage = 0;
const totalPages = 7;
function updateNextButton() {
const button = document.getElementById('nextButton');
button.textContent = currentPage === totalPages - 1 ? 'Finish' : 'Next';
}
function handleNext() {
if (currentPage < totalPages - 1) {
goToPage(currentPage + 1);
} else {
// Close the welcome window
appWindow.close();
}
}
async function openDashboard() {
try {
await open('http://127.0.0.1:4020');
} catch (error) {
console.error('Failed to open dashboard:', error);
}
}
async function openTailscale() {
try {
await open('https://tailscale.com/');
} catch (error) {
console.error('Failed to open Tailscale:', error);
}
}
async function openURL(url) {
try {
await open(url);
} catch (error) {
console.error('Failed to open URL:', error);
}
}
async function installCLI() {
const button = document.getElementById('installCLIBtn');
const status = document.getElementById('cliStatus');
button.disabled = true;
status.textContent = 'Installing CLI tool...';
try {
await invoke('install_cli');
status.textContent = '✓ CLI tool installed successfully';
status.style.color = 'var(--success-color)';
button.textContent = 'Installed';
} catch (error) {
status.textContent = '✗ Installation failed';
status.style.color = 'var(--error-color)';
button.disabled = false;
}
}
async function requestPermissions() {
try {
await invoke('request_all_permissions');
} catch (error) {
console.error('Failed to request permissions:', error);
}
}
async function loadTerminals() {
try {
const terminals = await invoke('detect_terminals');
const container = document.getElementById('terminalList');
container.innerHTML = '';
terminals.forEach(terminal => {
const item = document.createElement('label');
item.className = 'terminal-item';
item.style = 'display: flex; align-items: center; padding: 12px; margin-bottom: 8px; background: var(--bg-color); border: 1px solid var(--border-color); border-radius: 6px; cursor: pointer;';
item.innerHTML = `
<input type="radio" name="terminal" value="${terminal.id}" style="margin-right: 12px;">
<span>${terminal.name}</span>
`;
container.appendChild(item);
});
// Select first terminal by default
if (terminals.length > 0) {
container.querySelector('input[type="radio"]').checked = true;
}
} catch (error) {
console.error('Failed to load terminals:', error);
}
}
async function testTerminal() {
const selected = document.querySelector('input[name="terminal"]:checked');
if (selected) {
try {
await invoke('test_terminal', { terminal: selected.value });
} catch (error) {
console.error('Failed to test terminal:', error);
}
}
}
async function openSettings(tab) {
try {
await invoke('open_settings_window', { tab });
} catch (error) {
console.error('Failed to open settings:', error);
}
}
function skipPassword() {
// Just go to next page
goToPage(currentPage + 1);
}
// Load terminals when reaching that page
function goToPage(pageIndex) {
if (pageIndex < 0 || pageIndex >= totalPages) return;
// Hide current page
document.getElementById(`page-${currentPage}`).classList.remove('active');
document.querySelectorAll('.indicator')[currentPage].classList.remove('active');
// Show new page
currentPage = pageIndex;
document.getElementById(`page-${currentPage}`).classList.add('active');
document.querySelectorAll('.indicator')[currentPage].classList.add('active');
// Load data for specific pages
if (currentPage === 3) {
loadTerminals();
}
// Update button text
updateNextButton();
}
// Handle keyboard navigation
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
handleNext();
} else if (e.key === 'ArrowRight' && currentPage < totalPages - 1) {
goToPage(currentPage + 1);
} else if (e.key === 'ArrowLeft' && currentPage > 0) {
goToPage(currentPage - 1);
}
});
// Check CLI status on page load
window.addEventListener('DOMContentLoaded', async () => {
try {
const isInstalled = await invoke('is_cli_installed');
if (isInstalled) {
const button = document.getElementById('installCLIBtn');
const status = document.getElementById('cliStatus');
button.textContent = 'Installed';
button.disabled = true;
status.textContent = '✓ CLI tool is already installed';
status.style.color = 'var(--success-color)';
}
} catch (error) {
console.error('Failed to check CLI status:', error);
}
});
</script>
</body>
</html>