| <!DOCTYPE html> |
| <html lang="ru"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Site Switcher</title> |
| <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=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet"> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <style> |
| :root { |
| --bg: #0a0a0b; |
| --fg: #f0f0f2; |
| --muted: #6b6b70; |
| --accent: #00d4aa; |
| --accent-dim: rgba(0, 212, 170, 0.15); |
| --card: #161618; |
| --card-hover: #1e1e21; |
| --border: #2a2a2d; |
| --glow: rgba(0, 212, 170, 0.4); |
| } |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Space Grotesk', sans-serif; |
| background: var(--bg); |
| overflow: hidden; |
| height: 100vh; |
| width: 100vw; |
| } |
| |
| |
| .site-frame { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| border: none; |
| z-index: 1; |
| background: var(--bg); |
| } |
| |
| |
| .switcher { |
| position: fixed; |
| bottom: 24px; |
| left: 50%; |
| transform: translateX(-50%); |
| z-index: 1000; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| } |
| |
| |
| .switcher-menu { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 8px; |
| padding: 12px; |
| background: var(--card); |
| border: 1px solid var(--border); |
| border-radius: 16px; |
| margin-bottom: 12px; |
| opacity: 0; |
| visibility: hidden; |
| transform: translateY(10px) scale(0.95); |
| transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); |
| min-width: 200px; |
| } |
| |
| .switcher-menu.active { |
| opacity: 1; |
| visibility: visible; |
| transform: translateY(0) scale(1); |
| } |
| |
| .menu-item { |
| width: 100%; |
| padding: 12px 16px; |
| background: transparent; |
| border: 1px solid transparent; |
| border-radius: 10px; |
| cursor: pointer; |
| transition: all 0.15s ease; |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| color: var(--fg); |
| font-size: 14px; |
| font-weight: 500; |
| text-decoration: none; |
| } |
| |
| .menu-item:hover { |
| background: var(--card-hover); |
| border-color: var(--border); |
| } |
| |
| .menu-item.active { |
| background: var(--accent-dim); |
| border-color: var(--accent); |
| color: var(--accent); |
| } |
| |
| .menu-item .status-dot { |
| width: 8px; |
| height: 8px; |
| border-radius: 50%; |
| background: var(--muted); |
| transition: all 0.15s ease; |
| flex-shrink: 0; |
| } |
| |
| .menu-item.active .status-dot { |
| background: var(--accent); |
| box-shadow: 0 0 8px var(--glow); |
| } |
| |
| .menu-item:focus-visible { |
| outline: 2px solid var(--accent); |
| outline-offset: 2px; |
| } |
| |
| |
| .toggle-btn { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| padding: 12px 20px; |
| background: var(--card); |
| border: 1px solid var(--border); |
| border-radius: 100px; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| color: var(--fg); |
| font-size: 13px; |
| font-weight: 500; |
| box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4); |
| } |
| |
| .toggle-btn:hover { |
| background: var(--card-hover); |
| border-color: var(--accent); |
| box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5), 0 0 20px var(--glow); |
| } |
| |
| .toggle-btn:focus-visible { |
| outline: 2px solid var(--accent); |
| outline-offset: 2px; |
| } |
| |
| .toggle-btn .chevron { |
| width: 16px; |
| height: 16px; |
| transition: transform 0.25s ease; |
| } |
| |
| .toggle-btn.active .chevron { |
| transform: rotate(180deg); |
| } |
| |
| .current-domain { |
| color: var(--accent); |
| font-weight: 600; |
| } |
| |
| |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(20px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| .switcher { |
| animation: fadeIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.3s both; |
| } |
| |
| @media (prefers-reduced-motion: reduce) { |
| .switcher, |
| .switcher-menu, |
| .menu-item, |
| .toggle-btn { |
| animation: none; |
| transition: none; |
| } |
| } |
| |
| |
| @media (max-width: 480px) { |
| .switcher { |
| bottom: 16px; |
| } |
| |
| .toggle-btn { |
| padding: 10px 16px; |
| font-size: 12px; |
| } |
| |
| .switcher-menu { |
| min-width: 180px; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| |
| <iframe id="siteFrame" class="site-frame" title="Embedded website"></iframe> |
|
|
| |
| <div class="switcher"> |
| <div id="menu" class="switcher-menu" role="menu" aria-label="Выбор сайта"> |
| |
| </div> |
| |
| <button id="toggleBtn" class="toggle-btn" aria-expanded="false" aria-controls="menu"> |
| <span class="current-domain" id="currentDomain">—</span> |
| <svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <polyline points="6 9 12 15 18 9"></polyline> |
| </svg> |
| </button> |
| </div> |
|
|
| <script> |
| |
| |
| |
| const SITES = [ |
| { url: 'https://z.ai/', name: null }, |
| { url: 'https://chat.qwen.ai/', name: null }, |
| { url: 'https://chat.deepseek.com/', name: null }, |
| |
| |
| ]; |
| |
| |
| |
| function extractDomain(url) { |
| try { |
| const urlObj = new URL(url); |
| return urlObj.hostname.replace('www.', ''); |
| } catch (e) { |
| return url; |
| } |
| } |
| |
| |
| let currentSiteIndex = 0; |
| let isMenuOpen = false; |
| |
| |
| const siteFrame = document.getElementById('siteFrame'); |
| const menu = document.getElementById('menu'); |
| const toggleBtn = document.getElementById('toggleBtn'); |
| const currentDomainEl = document.getElementById('currentDomain'); |
| |
| |
| function renderMenu() { |
| menu.innerHTML = ''; |
| |
| SITES.forEach((site, index) => { |
| const domain = extractDomain(site.url); |
| const displayName = site.name || domain; |
| |
| const item = document.createElement('button'); |
| item.className = `menu-item ${index === currentSiteIndex ? 'active' : ''}`; |
| item.setAttribute('role', 'menuitem'); |
| item.setAttribute('tabindex', isMenuOpen ? '0' : '-1'); |
| item.innerHTML = ` |
| <span class="status-dot"></span> |
| <span>${displayName}</span> |
| `; |
| |
| item.addEventListener('click', () => selectSite(index)); |
| menu.appendChild(item); |
| }); |
| } |
| |
| |
| function selectSite(index) { |
| currentSiteIndex = index; |
| const site = SITES[index]; |
| const domain = extractDomain(site.url); |
| |
| siteFrame.src = site.url; |
| currentDomainEl.textContent = domain; |
| |
| renderMenu(); |
| closeMenu(); |
| } |
| |
| |
| function toggleMenu() { |
| isMenuOpen ? closeMenu() : openMenu(); |
| } |
| |
| function openMenu() { |
| isMenuOpen = true; |
| menu.classList.add('active'); |
| toggleBtn.classList.add('active'); |
| toggleBtn.setAttribute('aria-expanded', 'true'); |
| |
| |
| const firstItem = menu.querySelector('.menu-item'); |
| if (firstItem) firstItem.focus(); |
| } |
| |
| function closeMenu() { |
| isMenuOpen = false; |
| menu.classList.remove('active'); |
| toggleBtn.classList.remove('active'); |
| toggleBtn.setAttribute('aria-expanded', 'false'); |
| } |
| |
| |
| toggleBtn.addEventListener('click', toggleMenu); |
| |
| |
| document.addEventListener('click', (e) => { |
| if (!e.target.closest('.switcher') && isMenuOpen) { |
| closeMenu(); |
| } |
| }); |
| |
| |
| document.addEventListener('keydown', (e) => { |
| if (!isMenuOpen) return; |
| |
| const items = menu.querySelectorAll('.menu-item'); |
| const currentIndex = Array.from(items).findIndex(item => item === document.activeElement); |
| |
| if (e.key === 'Escape') { |
| closeMenu(); |
| toggleBtn.focus(); |
| } else if (e.key === 'ArrowDown') { |
| e.preventDefault(); |
| const nextIndex = (currentIndex + 1) % items.length; |
| items[nextIndex].focus(); |
| } else if (e.key === 'ArrowUp') { |
| e.preventDefault(); |
| const prevIndex = (currentIndex - 1 + items.length) % items.length; |
| items[prevIndex].focus(); |
| } else if (e.key === 'Enter' && currentIndex >= 0) { |
| selectSite(currentIndex); |
| } |
| }); |
| |
| |
| renderMenu(); |
| selectSite(0); |
| </script> |
| </body> |
| </html> |