UNPKG

web-terminal-server

Version:

Professional web-based terminal server with persistent sessions, live sharing, smart port detection, Cloudflare tunnels, and full CLI support

1,751 lines (1,529 loc) 141 kB
<!DOCTYPE html> <html lang="he" dir="ltr"> <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"> <!-- PWA Meta Tags --> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-title" content="Terminal"> <meta name="theme-color" content="#0A84FF"> <meta name="mobile-web-app-capable" content="yes"> <meta name="application-name" content="Terminal Grid"> <!-- SEO --> <meta name="description" content="Professional web-based terminal with live sharing, port monitoring, and Cloudflare tunnels"> <meta name="keywords" content="terminal, web terminal, cloudflare tunnel, port monitoring, developer tools"> <title>Terminal Grid - Professional Terminal Interface</title> <!-- PWA Manifest --> <link rel="manifest" href="/manifest.json"> <!-- Icons --> <link rel="icon" type="image/svg+xml" href="/icons/icon.svg"> <link rel="apple-touch-icon" href="/icons/icon-192.png"> <link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152.png"> <link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192.png"> <link rel="apple-touch-icon" sizes="167x167" href="/icons/icon-192.png"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" /> <!-- Lucide Icons - Modern, Clean Icon Set --> <script src="https://unpkg.com/lucide@latest"></script> <!-- Professional Monospace Fonts --> <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=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet"> <style> :root { /* Modern App Color Palette */ --primary-bg: #000000; --secondary-bg: #1C1C1E; --tertiary-bg: #2C2C2E; --card-bg: #1C1C1E; /* Vibrant Accent Colors */ --accent-primary: #0A84FF; --accent-secondary: #5E5CE6; --accent-success: #32D74B; --accent-warning: #FF9F0A; --accent-danger: #FF453A; /* Gradients */ --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --gradient-accent: linear-gradient(135deg, #0A84FF 0%, #5E5CE6 100%); --gradient-dark: linear-gradient(180deg, #1C1C1E 0%, #000000 100%); /* Text Colors */ --text-primary: #FFFFFF; --text-secondary: #EBEBF5; --text-tertiary: #98989D; --text-muted: #636366; /* Border & Divider */ --border-light: rgba(255, 255, 255, 0.1); --border-medium: rgba(255, 255, 255, 0.15); --border-strong: rgba(255, 255, 255, 0.2); /* Shadows */ --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3); --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4); --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5); --shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.6); /* Glass Effect */ --glass-bg: rgba(28, 28, 30, 0.85); --glass-border: rgba(255, 255, 255, 0.1); } * { margin: 0; padding: 0; box-sizing: border-box; } html, body { height: 100vh; height: 100dvh; /* Dynamic viewport height for mobile */ width: 100vw; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif; background: var(--primary-bg); color: var(--text-primary); position: fixed; top: 0; left: 0; right: 0; bottom: 0; } /* CSS Grid Layout - Fixed Full Screen */ .app-grid { height: 100vh; height: 100dvh; width: 100vw; display: grid; grid-template-areas: "header" "terminal" "actions"; grid-template-rows: 60px 1fr 68px; transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1); position: fixed; top: 0; left: 0; right: 0; bottom: 0; overflow: hidden; } .app-grid.controls-active { grid-template-areas: "header" "terminal" "controls" "actions"; grid-template-rows: 60px 1fr auto 68px; } /* Header Area - Modern Glass Effect */ .header-area { grid-area: header; background: var(--glass-bg); backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%); border-bottom: 1px solid var(--border-light); display: flex; justify-content: space-between; align-items: center; padding: 0 24px; box-shadow: var(--shadow-sm); position: relative; z-index: 100; } .logo { font-size: 17px; font-weight: 700; color: var(--text-primary); display: flex; align-items: center; gap: 10px; letter-spacing: -0.3px; } .logo-icon { width: 32px; height: 32px; background: var(--gradient-accent); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; color: white; box-shadow: 0 4px 12px rgba(10, 132, 255, 0.4); position: relative; overflow: hidden; } .logo-icon::before { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.3), transparent); transform: rotate(45deg); animation: shine 3s infinite; } @keyframes shine { 0%, 100% { transform: translateX(-100%) translateY(-100%) rotate(45deg); } 50% { transform: translateX(100%) translateY(100%) rotate(45deg); } } .header-controls { display: flex; gap: 8px; } .header-btn { background: var(--tertiary-bg); border: 1px solid var(--border-light); border-radius: 12px; color: var(--text-secondary); padding: 10px 16px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; gap: 8px; position: relative; overflow: hidden; backdrop-filter: blur(10px); } .header-btn::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: var(--gradient-accent); opacity: 0; transition: opacity 0.3s ease; z-index: -1; } .header-btn:hover { border-color: var(--accent-primary); color: white; box-shadow: 0 4px 16px rgba(10, 132, 255, 0.4); transform: translateY(-2px); } .header-btn:hover::before { opacity: 1; } .header-btn:active { transform: translateY(0) scale(0.96); box-shadow: 0 2px 8px rgba(10, 132, 255, 0.3); } /* Terminal Area - Premium Design */ .terminal-area { grid-area: terminal; background: var(--primary-bg); position: relative; overflow: hidden; margin: 12px; border-radius: 20px; border: 1px solid var(--border-medium); box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(255, 255, 255, 0.05); } .terminal-container { position: absolute; top: 0; left: 0; right: 0; bottom: 0; padding: 16px; display: flex; flex-direction: column; } /* Terminal Header - Status Bar */ .terminal-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: rgba(28, 28, 30, 0.6); backdrop-filter: blur(10px); border-radius: 12px 12px 0 0; border-bottom: 1px solid var(--border-light); margin: -16px -16px 12px -16px; } .terminal-status { display: flex; gap: 12px; align-items: center; } .port-btn { display: flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 8px; font-size: 12px; font-weight: 700; cursor: pointer; transition: all 0.2s ease; letter-spacing: 0.5px; border: 1px solid; } .port-btn.unpublished { background: rgba(255, 69, 58, 0.15); border-color: rgba(255, 69, 58, 0.3); color: var(--accent-danger); } .port-btn.unpublished:hover { background: rgba(255, 69, 58, 0.25); border-color: var(--accent-danger); transform: translateY(-1px); } .port-btn.unpublished .port-indicator { background: var(--accent-danger); box-shadow: 0 0 8px var(--accent-danger); } .port-btn.published { background: rgba(50, 215, 75, 0.15); border-color: rgba(50, 215, 75, 0.3); color: var(--accent-success); } .port-btn.published:hover { background: rgba(50, 215, 75, 0.25); border-color: var(--accent-success); transform: translateY(-1px); } .port-btn.published .port-indicator { background: var(--accent-success); box-shadow: 0 0 8px var(--accent-success); } .port-indicator { width: 8px; height: 8px; border-radius: 50%; animation: portPulse 1.5s ease-in-out infinite; } @keyframes portPulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.7; transform: scale(0.9); } } .status-dot { width: 12px; height: 12px; border-radius: 50%; background: var(--accent-success); box-shadow: 0 0 12px var(--accent-success); animation: pulse 2s ease-in-out infinite; } .terminal-info { font-size: 12px; color: var(--text-tertiary); font-weight: 500; } .terminal-screen { width: 100%; height: 100%; background: linear-gradient(135deg, #0a0a0a 0%, #000000 100%); border-radius: 12px; overflow: hidden; position: relative; box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.5); } /* Terminal glow effect */ .terminal-screen::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, transparent, var(--accent-primary), transparent); opacity: 0.3; } /* Controls Area - Modern Bottom Sheet */ .controls-area { grid-area: controls; background: var(--gradient-dark); border-top: 1px solid var(--border-medium); display: none; flex-direction: column; box-shadow: var(--shadow-xl); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); position: relative; } .controls-active .controls-area { display: flex; animation: slideUpFadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1); } @keyframes slideUpFadeIn { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } /* Ambient glow for controls */ .controls-area::before { content: ''; position: absolute; top: -100px; left: 0; right: 0; height: 100px; background: radial-gradient(ellipse at bottom, rgba(10, 132, 255, 0.15) 0%, transparent 70%); pointer-events: none; } /* Controls Header */ .controls-header { background: var(--tertiary-bg); padding: 12px 20px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-color); } .controls-title { font-size: 14px; color: var(--text-primary); font-weight: 600; display: flex; align-items: center; gap: 8px; } .controls-icon { width: 16px; height: 16px; background: var(--accent-color); border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 10px; color: var(--primary-bg); } .minimize-btn { background: var(--tertiary-bg); border: 1px solid var(--border-light); border-radius: 8px; width: 32px; height: 32px; color: var(--text-tertiary); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; } .minimize-btn:hover { background: var(--accent-danger); border-color: var(--accent-danger); color: white; transform: scale(1.05); } .minimize-btn:active { transform: scale(0.95); } .minimize-btn i { width: 18px; height: 18px; } /* Controls Content */ .controls-content { padding: 20px; display: grid; grid-template-columns: 1fr 1fr; gap: 20px; max-height: 220px; overflow-y: auto; } /* Arrow Keys Section */ .arrows-section { display: flex; flex-direction: column; align-items: center; gap: 12px; } .section-title { font-size: 11px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; margin-bottom: 8px; } .arrows-grid { display: grid; grid-template-areas: ". up ." "left down right"; grid-template-columns: 40px 40px 40px; grid-template-rows: 40px 40px; gap: 6px; } .arrow-up { grid-area: up; } .arrow-down { grid-area: down; } .arrow-left { grid-area: left; } .arrow-right { grid-area: right; } /* Keys Section */ .keys-section { display: flex; flex-direction: column; gap: 12px; } .keys-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } /* Quick Keys Section */ .quick-keys-toggle { background: var(--gradient-accent); border: none; border-radius: 8px; color: white; padding: 6px 12px; font-size: 11px; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 6px; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 2px 8px rgba(10, 132, 255, 0.3); } .quick-keys-toggle:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(10, 132, 255, 0.5); } .quick-keys-toggle:active { transform: translateY(0); } .quick-keys-dropdown { margin-top: 12px; background: var(--secondary-bg); border: 1px solid var(--border-light); border-radius: 12px; padding: 12px; max-height: 240px; overflow-y: auto; animation: slideDown 0.3s cubic-bezier(0.4, 0, 0.2, 1); } @keyframes slideDown { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } .quick-keys-category { margin-bottom: 16px; } .quick-keys-category:last-child { margin-bottom: 0; } .category-title { font-size: 10px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; margin-bottom: 8px; padding-left: 4px; } .quick-keys-list { display: flex; flex-direction: column; gap: 6px; } .quick-key-item { background: var(--tertiary-bg); border: 1px solid var(--border-light); border-radius: 8px; padding: 10px 12px; color: var(--text-primary); font-size: 13px; font-weight: 500; cursor: pointer; display: flex; align-items: center; gap: 10px; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); font-family: 'JetBrains Mono', monospace; } .quick-key-item:hover { background: var(--secondary-bg); border-color: var(--accent-primary); transform: translateX(4px); box-shadow: 0 2px 8px rgba(10, 132, 255, 0.2); } .quick-key-item:active { transform: translateX(2px) scale(0.98); } .quick-key-item i { width: 16px; height: 16px; color: var(--accent-primary); flex-shrink: 0; } .add-custom-key-btn { background: var(--accent-primary); border: none; border-radius: 6px; color: white; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .add-custom-key-btn:hover { background: var(--accent-secondary); transform: scale(1.1); } .add-custom-key-btn:active { transform: scale(0.95); } /* Custom Key Modal */ .custom-key-modal { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; padding: 20px; } .modal-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(8px); animation: fadeIn 0.2s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .modal-content { position: relative; background: var(--secondary-bg); border: 1px solid var(--border-light); border-radius: 16px; width: 100%; max-width: 400px; max-height: 90vh; overflow-y: auto; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1); } @keyframes slideUp { from { opacity: 0; transform: translateY(20px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } } .modal-header { padding: 20px; border-bottom: 1px solid var(--border-light); display: flex; align-items: center; justify-content: space-between; } .modal-header h3 { margin: 0; font-size: 18px; font-weight: 600; color: var(--text-primary); } .modal-close-btn { background: transparent; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center; border-radius: 8px; transition: all 0.2s ease; } .modal-close-btn:hover { background: var(--tertiary-bg); color: var(--text-primary); } .modal-body { padding: 20px; display: flex; flex-direction: column; gap: 16px; } .modal-body .form-group { display: flex; flex-direction: column; gap: 8px; } .modal-body label { font-size: 12px; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; } .modal-body input[type="text"], .modal-body select { background: var(--tertiary-bg); border: 1px solid var(--border-light); border-radius: 8px; padding: 12px; color: var(--text-primary); font-size: 14px; font-family: inherit; transition: all 0.2s ease; } .modal-body input[type="text"]:focus, .modal-body select:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.1); } .key-combo-builder { display: flex; flex-direction: column; gap: 12px; } .modifier-keys { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } .checkbox-label { display: flex; align-items: center; gap: 8px; padding: 10px; background: var(--tertiary-bg); border: 1px solid var(--border-light); border-radius: 8px; cursor: pointer; transition: all 0.2s ease; user-select: none; } .checkbox-label:hover { background: var(--secondary-bg); border-color: var(--accent-primary); } .checkbox-label input[type="checkbox"] { width: 18px; height: 18px; cursor: pointer; accent-color: var(--accent-primary); } .checkbox-label span { font-size: 13px; font-weight: 500; color: var(--text-primary); } .key-preview { background: var(--primary-bg); border: 2px dashed var(--border-light); border-radius: 8px; padding: 12px; text-align: center; color: var(--text-secondary); font-size: 13px; font-family: 'JetBrains Mono', monospace; min-height: 44px; display: flex; align-items: center; justify-content: center; } .modal-footer { padding: 20px; border-top: 1px solid var(--border-light); display: flex; gap: 12px; justify-content: flex-end; } .modal-btn { padding: 12px 24px; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer; border: none; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); min-height: 44px; } .modal-btn-secondary { background: var(--tertiary-bg); color: var(--text-primary); border: 1px solid var(--border-light); } .modal-btn-secondary:hover { background: var(--secondary-bg); transform: translateY(-2px); } .modal-btn-primary { background: var(--gradient-accent); color: white; box-shadow: 0 4px 12px rgba(10, 132, 255, 0.3); } .modal-btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(10, 132, 255, 0.5); } .modal-btn:active { transform: translateY(0) scale(0.98); } /* Control Key Styles - iOS/Android Keyboard Inspired */ .ctrl-key { background: linear-gradient(180deg, var(--tertiary-bg) 0%, var(--secondary-bg) 100%); border: 1px solid var(--border-light); border-bottom: 2px solid var(--border-medium); border-radius: 10px; color: var(--text-primary); padding: 12px; font-size: 15px; font-weight: 600; cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; min-height: 44px; user-select: none; position: relative; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); } .ctrl-key::before { content: ''; position: absolute; inset: 0; border-radius: 10px; background: var(--gradient-accent); opacity: 0; transition: opacity 0.2s ease; } .ctrl-key:hover { border-color: var(--accent-primary); transform: translateY(-2px) scale(1.02); box-shadow: 0 6px 20px rgba(10, 132, 255, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.2); } .ctrl-key:hover::before { opacity: 0.15; } .ctrl-key:active { transform: translateY(0) scale(0.96); box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.4); border-bottom-width: 1px; } .ctrl-key span { position: relative; z-index: 1; } .ctrl-key i { position: relative; z-index: 1; } .ctrl-key.arrow { font-size: 20px; font-weight: 700; } .ctrl-key.arrow i { width: 22px; height: 22px; stroke-width: 2.5; } /* Keys with icon + text */ .ctrl-key:has(i):has(span) { gap: 6px; } .ctrl-key:has(i):has(span) i { margin-bottom: -2px; } .ctrl-key:has(i):has(span) span { font-size: 10px; font-weight: 600; opacity: 0.9; } .ctrl-key.special { background: linear-gradient(180deg, var(--accent-danger) 0%, #cc0000 100%); border-color: var(--accent-danger); color: white; box-shadow: 0 2px 12px rgba(255, 69, 58, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.2); } .ctrl-key.special:hover { background: linear-gradient(180deg, #ff5f52 0%, var(--accent-danger) 100%); transform: translateY(-2px) scale(1.02); box-shadow: 0 6px 24px rgba(255, 69, 58, 0.6); } .ctrl-key.special:active { background: linear-gradient(180deg, #cc0000 0%, #990000 100%); } /* Actions Area - Modern Tab Bar */ .actions-area { grid-area: actions; background: var(--glass-bg); backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%); border-top: 1px solid var(--border-light); display: flex; justify-content: space-around; align-items: center; padding: 0 16px; box-shadow: var(--shadow-md); position: relative; z-index: 90; } /* Active indicator track */ .actions-area::before { content: ''; position: absolute; top: 0; left: 0; width: 20%; height: 3px; background: var(--gradient-accent); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 0 0 3px 3px; } .action-btn { background: transparent; border: none; color: var(--text-tertiary); display: flex; flex-direction: column; align-items: center; gap: 6px; cursor: pointer; padding: 10px 16px; border-radius: 14px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); font-size: 12px; font-weight: 600; position: relative; flex: 1; } .action-btn::before { content: ''; position: absolute; inset: 0; background: var(--gradient-accent); opacity: 0; border-radius: 14px; transition: opacity 0.3s ease; } .action-btn:hover::before { opacity: 0.1; } .action-btn:hover { color: var(--accent-primary); transform: translateY(-3px) scale(1.05); } .action-btn.active { color: var(--accent-primary); } .action-btn.active .action-icon { animation: bounce 0.5s ease; } @keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } } .action-btn:active { transform: scale(0.92); } .action-icon { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; font-size: 20px; position: relative; z-index: 1; transition: transform 0.3s ease; } .action-btn:hover .action-icon { transform: scale(1.15); } .action-btn span:last-child { position: relative; z-index: 1; } /* Icons */ .icon { display: inline-flex; align-items: center; justify-content: center; } /* Welcome Screen - Elegant & Inviting */ .welcome { width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; padding: 40px 20px; text-align: center; overflow-y: auto; animation: fadeInUp 0.8s ease-out; } @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .welcome-icon { width: 96px; height: 96px; background: var(--gradient-accent); border-radius: 24px; display: flex; align-items: center; justify-content: center; font-size: 48px; color: white; margin: 0 auto 24px; box-shadow: 0 12px 40px rgba(10, 132, 255, 0.4); animation: floatPulse 3s ease-in-out infinite; position: relative; } .welcome-icon::before { content: ''; position: absolute; inset: -4px; background: var(--gradient-accent); border-radius: 26px; opacity: 0.3; filter: blur(12px); z-index: -1; animation: glowPulse 3s ease-in-out infinite; } @keyframes floatPulse { 0%, 100% { transform: translateY(0) scale(1); } 50% { transform: translateY(-8px) scale(1.05); } } @keyframes glowPulse { 0%, 100% { opacity: 0.3; } 50% { opacity: 0.6; } } .welcome-text { font-size: 28px; font-weight: 700; background: var(--gradient-accent); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 12px; letter-spacing: -0.5px; } .welcome-sub { font-size: 16px; color: var(--text-tertiary); font-weight: 500; } /* Toast - Modern Notification */ .toast { position: fixed; top: 80px; right: 20px; background: var(--glass-bg); backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%); color: var(--text-primary); padding: 16px 20px; border-radius: 16px; font-size: 14px; font-weight: 600; box-shadow: var(--shadow-lg), 0 0 0 1px var(--border-light); animation: toastSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1); z-index: 1000; border: 1px solid var(--border-medium); display: flex; align-items: center; gap: 12px; min-width: 200px; } .toast::before { content: ''; width: 6px; height: 6px; background: var(--accent-success); border-radius: 50%; flex-shrink: 0; box-shadow: 0 0 12px var(--accent-success); animation: pulse 2s ease-in-out infinite; } @keyframes toastSlideIn { from { transform: translateX(400px) scale(0.8); opacity: 0; } to { transform: translateX(0) scale(1); opacity: 1; } } /* Port Discovery Notification Modal */ .port-notification { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--glass-bg); backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%); border: 1px solid var(--border-light); border-radius: 20px; padding: 32px; max-width: 480px; width: 90%; box-shadow: var(--shadow-xl); z-index: 2000; animation: modalFadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .port-notification-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(4px); z-index: 1999; animation: fadeIn 0.3s ease; } @keyframes modalFadeIn { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } } .port-notification-header { display: flex; align-items: center; gap: 16px; margin-bottom: 20px; } .port-notification-icon { width: 56px; height: 56px; background: var(--gradient-accent); border-radius: 16px; display: flex; align-items: center; justify-content: center; position: relative; animation: pulse 2s ease-in-out infinite; } .port-notification-icon::before { content: ''; position: absolute; inset: -4px; background: var(--gradient-accent); border-radius: 18px; opacity: 0.3; filter: blur(12px); z-index: -1; animation: glowPulse 3s ease-in-out infinite; } .port-notification-title { flex: 1; } .port-notification-title h3 { font-size: 20px; font-weight: 700; color: var(--text-primary); margin-bottom: 4px; } .port-notification-title p { font-size: 14px; color: var(--text-tertiary); font-weight: 500; } .port-info-card { background: var(--tertiary-bg); border: 1px solid var(--border-light); border-radius: 12px; padding: 16px; margin-bottom: 20px; } .port-info-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .port-info-row:last-child { margin-bottom: 0; } .port-info-label { font-size: 13px; color: var(--text-tertiary); font-weight: 600; } .port-info-value { font-size: 15px; color: var(--text-primary); font-weight: 700; font-family: 'SF Mono', Monaco, monospace; } .port-notification-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .port-notification-btn { padding: 14px 20px; border-radius: 12px; font-size: 15px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; border: none; display: flex; align-items: center; justify-content: center; gap: 8px; } .port-notification-btn-primary { background: var(--gradient-accent); color: white; } .port-notification-btn-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(10, 132, 255, 0.4); } .port-notification-btn-secondary { background: var(--tertiary-bg); color: var(--text-secondary); border: 1px solid var(--border-light); } .port-notification-btn-secondary:hover { background: var(--secondary-bg); border-color: var(--border-medium); color: var(--text-primary); } .port-notification-btn:active { transform: scale(0.98); } .port-notification-btn.loading { pointer-events: none; opacity: 0.7; position: relative; } .port-notification-btn.loading::after { content: ''; position: absolute; width: 16px; height: 16px; border: 2px solid transparent; border-top-color: currentColor; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Loading - Premium Experience */ .loading { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: var(--primary-bg); display: flex; align-items: center; justify-content: center; z-index: 9999; transition: opacity 0.5s ease, transform 0.5s ease; } .loading.fade-out { opacity: 0; transform: scale(0.95); pointer-events: none; } .spinner { width: 60px; height: 60px; border: 4px solid rgba(255, 255, 255, 0.1); border-top: 4px solid var(--accent-primary); border-right: 4px solid var(--accent-secondary); border-radius: 50%; animation: spinGradient 1.2s cubic-bezier(0.4, 0, 0.2, 1) infinite; box-shadow: 0 0 40px rgba(10, 132, 255, 0.3); } @keyframes spinGradient { 0% { transform: rotate(0deg) scale(1); box-shadow: 0 0 40px rgba(10, 132, 255, 0.3); } 50% { transform: rotate(180deg) scale(1.1); box-shadow: 0 0 60px rgba(94, 92, 230, 0.5); } 100% { transform: rotate(360deg) scale(1); box-shadow: 0 0 40px rgba(10, 132, 255, 0.3); } } /* Mobile Responsive */ @media (max-width: 768px) { /* Better touch targets for mobile */ .header-area { padding: 0 12px; height: 60px; } .logo { font-size: 14px; } .logo-icon { width: 28px; height: 28px; font-size: 14px; } .header-controls { gap: 6px; } /* Larger touch targets - minimum 44x44px for iOS HIG */ .header-btn { min-width: 44px; min-height: 44px; padding: 8px; justify-content: center; border-radius: 10px; gap: 4px; } /* Show icons on mobile */ .header-btn i { width: 20px; height: 20px; flex-shrink: 0; } /* Keep text visible but smaller on mobile */ .header-btn .btn-text { font-size: 11px; font-weight: 700; white-space: nowrap; } /* Terminal area optimizations */ .terminal-area { margin: 8px 6px; border-radius: 16px; } .terminal-container { padding: 10px; } .terminal-screen { border-radius: 10px; } /* Controls content */ .controls-content { grid-template-columns: 1fr; gap: 20px; padding: 20px 16px; max-height: 280px; } .keys-grid { grid-template-columns: repeat(3, 1fr); gap: 10px; } /* Larger arrow keys for better touch */ .arrows-grid { grid-template-columns: 50px 50px 50px; grid-template-rows: 50px 50px; gap: 8px; } /* Larger control keys - minimum 48x48px for Android */ .ctrl-key { min-height: 48px; min-width: 48px; font-size: 13px; padding: 10px; border-radius: 8px; } .ctrl-key.arrow { font-size: 20px; } /* Actions bar optimizations */ .actions-area { padding: 0 8px; height: 70px; } .action-btn { min-width: 60px; min-height: 60px; padding: 8px 4px; font-size: 10px; } .action-icon { width: 24px; height: 24px; font-size: 20px; } /* Welcome screen */ .welcome-icon { width: 80px; height: 80px; font-size: 36px; } .welcome-text { font-size: 18px; } /* Toast notifications */ .toast { right: 10px; top: 70px; font-size: 13px; padding: 10px 14px; } /* Better section titles */ .section-title { font-size: 12px; margin-bottom: 10px; } /* Controls header */ .controls-header { padding: 14px 16px; } .controls-title { font-size: 15px; } /* Bottom sheet style for controls */ .controls-area { border-radius: 20px 20px 0 0; box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.4); background: linear-gradient(to bottom, var(--tertiary-bg), var(--secondary-bg)); } .controls-header { border-radius: 20px 20px 0 0; position: relative; } /* Add drag handle indicator */ .controls-header::before { content: ''; position: absolute; top: 8px; left: 50%; transform: translateX(-50%); width: 40px; height: 4px; background: var(--text-muted); border-radius: 2px; opacity: 0.5; } /* Keyboard-like appearance */ .ctrl-key { background: linear-gradient(to bottom, var(--tertiary-bg), var(--secondary-bg)); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.1); } .ctrl-key:active { box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3); transform: scale(0.96); } } /* Extra small devices (phones in portrait) */ @media (max-width: 480px) { .app-grid { grid-template-rows: 56px 1fr 72px; } .app-grid.controls-active { grid-template-rows: 56px 1fr auto 72px; } .header-area { padding: 0 10px; height: 56px; } .logo { font-size: 13px; } .logo-icon { width: 24px; height: 24px; font-size: 12px; } .header-btn { min-width: 40px; min-height: 40px; } .header-btn .icon { font-size: 16px; } .actions-area { height: 65px; padding: 0 4px; } .action-btn { min-width: 50px; min-height: 55px; font-size: 9px; gap: 2px; } .action-icon { width: 20px; height: 20px; font-size: 18px; } } /* Safe area support for notched devices */ @supports (padding: env(safe-area-inset-top)) { .header-area { padding-top: env(safe-area-inset-top); } .actions-area { padding-bottom: env(safe-area-inset-bottom); } } /* Scrollbar Styling */ .controls-content::-webkit-scrollbar { width: 6px; } .controls-content::-webkit-scrollbar-track { background: var(--secondary-bg); } .controls-content::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 3px; } .controls-content::-webkit-scrollbar-thumb:hover { background: var(--text-muted); } </style> </head> <body> <!-- Loading --> <div class="loading" id="loading"> <div style="text-align: center;"> <div class="spinner"></div> <div style="color: var(--accent-color); margin-top: 16px; font-size: 14px; font-weight: 500;"> Initializing Terminal Grid... </div> </div> </div> <!-- Main Grid Layout --> <div class="app-grid" id="appGrid"> <!-- Header Area --> <div class="header-area"> <div class="logo"> <div class="logo-icon"> <i data-lucide="terminal" style="width: 18px; height: 18px;"></i> </div> Terminal Grid