Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Neon Portal</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&display=swap'); | |
| :root { | |
| --neon-blue: #05d9e8; | |
| --neon-pink: #ff2a6d; | |
| --neon-purple: #d300c5; | |
| --dark-bg: #05010e; | |
| --darker-bg: #0d0221; | |
| --nav-dark: #12092a; | |
| } | |
| body { | |
| font-family: 'Orbitron', sans-serif; | |
| background-color: var(--dark-bg); | |
| color: white; | |
| min-height: 100vh; | |
| background-image: var(--bg-image, none); | |
| background-size: cover; | |
| background-attachment: fixed; | |
| background-position: center; | |
| } | |
| .glitch-effect { | |
| position: relative; | |
| color: white; | |
| } | |
| .glitch-effect::before, .glitch-effect::after { | |
| content: attr(data-text); | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| opacity: 0.8; | |
| } | |
| .glitch-effect::before { | |
| color: var(--neon-blue); | |
| z-index: -1; | |
| animation: glitch-effect 3s infinite; | |
| } | |
| .glitch-effect::after { | |
| color: var(--neon-pink); | |
| z-index: -2; | |
| animation: glitch-effect 2s infinite reverse; | |
| } | |
| @keyframes glitch-effect { | |
| 0% { transform: translate(0); } | |
| 20% { transform: translate(-3px, 3px); } | |
| 40% { transform: translate(-3px, -3px); } | |
| 60% { transform: translate(3px, 3px); } | |
| 80% { transform: translate(3px, -3px); } | |
| 100% { transform: translate(0); } | |
| } | |
| .neon-text-blue { | |
| color: var(--neon-blue); | |
| text-shadow: 0 0 5px var(--neon-blue), 0 0 10px var(--neon-blue), 0 0 20px var(--neon-blue); | |
| } | |
| .neon-text-pink { | |
| color: var(--neon-pink); | |
| text-shadow: 0 0 5px var(--neon-pink), 0 0 10px var(--neon-pink), 0 0 20px var(--neon-pink); | |
| } | |
| .neon-text-purple { | |
| color: var(--neon-purple); | |
| text-shadow: 0 0 5px var(--neon-purple), 0 0 10px var(--neon-purple), 0 0 20px var(--neon-purple); | |
| } | |
| .neon-border-blue { | |
| border: 1px solid var(--neon-blue); | |
| box-shadow: 0 0 5px var(--neon-blue), inset 0 0 5px var(--neon-blue), 0 0 20px var(--neon-blue); | |
| } | |
| .neon-border-pink { | |
| border: 1px solid var(--neon-pink); | |
| box-shadow: 0 0 5px var(--neon-pink), inset 0 0 5px var(--neon-pink), 0 0 20px var(--neon-pink); | |
| } | |
| .neon-hover:hover { | |
| text-shadow: 0 0 10px currentColor, 0 0 20px currentColor, 0 0 30px currentColor; | |
| transition: text-shadow 0.3s ease; | |
| } | |
| .nav-item:hover { | |
| transform: translateY(-2px); | |
| transition: transform 0.3s ease; | |
| } | |
| .dropdown { | |
| display: none; | |
| background: var(--nav-dark); | |
| border: 1px solid var(--neon-blue); | |
| box-shadow: 0 0 20px var(--neon-blue); | |
| transition: all 0.3s ease; | |
| z-index: 100; | |
| } | |
| .group:hover .dropdown { | |
| display: block; | |
| } | |
| .portal-card { | |
| transition: all 0.3s ease; | |
| backdrop-filter: blur(10px); | |
| perspective: 1000px; | |
| transform-style: preserve-3d; | |
| } | |
| .portal-card:hover { | |
| transform: translateY(-5px) scale(1.05); | |
| box-shadow: 0 0 20px currentColor; | |
| } | |
| .portal-card-inner { | |
| transition: transform 0.6s; | |
| transform-style: preserve-3d; | |
| } | |
| .portal-card:hover .portal-card-inner { | |
| transform: rotateY(15deg) rotateX(10deg); | |
| } | |
| .modal-overlay { | |
| background: rgba(5, 1, 14, 0.9); | |
| backdrop-filter: blur(5px); | |
| animation: fadeIn 0.3s ease; | |
| } | |
| .modal-content { | |
| animation: slideIn 0.3s ease; | |
| background: var(--darker-bg); | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| @keyframes slideIn { | |
| from { transform: translateY(-50px); opacity: 0; } | |
| to { transform: translateY(0); opacity: 1; } | |
| } | |
| .color-option { | |
| width: 30px; | |
| height: 30px; | |
| border-radius: 50%; | |
| display: inline-block; | |
| margin: 5px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| } | |
| .color-option:hover { | |
| transform: scale(1.1); | |
| } | |
| .color-option.selected::after { | |
| content: "✓"; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| color: white; | |
| font-weight: bold; | |
| } | |
| .custom-color-toggle { | |
| padding: 8px; | |
| cursor: pointer; | |
| border-radius: 5px; | |
| margin-top: 10px; | |
| text-align: center; | |
| border: 1px dashed var(--neon-blue); | |
| } | |
| .custom-color-toggle:hover { | |
| background: rgba(5, 217, 232, 0.1); | |
| } | |
| .custom-color-input { | |
| width: 100%; | |
| height: 30px; | |
| border: none; | |
| background: transparent; | |
| cursor: pointer; | |
| margin-top: 10px; | |
| } | |
| .bg-preview, .icon-preview { | |
| max-width: 100%; | |
| height: auto; | |
| margin-top: 10px; | |
| border-radius: 5px; | |
| display: none; | |
| } | |
| .grid-lines { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-image: linear-gradient(to right, rgba(5, 217, 232, 0.05) 1px, transparent 1px), | |
| linear-gradient(to bottom, rgba(5, 217, 232, 0.05) 1px, transparent 1px); | |
| background-size: 30px 30px; | |
| z-index: -1; | |
| pointer-events: none; | |
| } | |
| .neon-underline { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .neon-underline::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: -5px; | |
| left: 0; | |
| width: 100%; | |
| height: 2px; | |
| background: linear-gradient(to right, var(--neon-blue), var(--neon-pink)); | |
| transform: scaleX(0); | |
| transform-origin: bottom right; | |
| transition: transform 0.5s ease-out; | |
| } | |
| .neon-underline:hover::after { | |
| transform: scaleX(1); | |
| transform-origin: bottom left; | |
| } | |
| /* Holographic effect */ | |
| .holographic-effect { | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .holographic-effect::before { | |
| content: ''; | |
| position: absolute; | |
| top: -50%; | |
| left: -50%; | |
| width: 200%; | |
| height: 200%; | |
| background: linear-gradient( | |
| to bottom right, | |
| rgba(255, 255, 255, 0) 45%, | |
| rgba(5, 217, 232, 0.2) 50%, | |
| rgba(255, 255, 255, 0) 55% | |
| ); | |
| transform: rotate(30deg); | |
| animation: holographic 6s linear infinite; | |
| } | |
| @keyframes holographic { | |
| 0% { transform: translateY(-100%) rotate(30deg); } | |
| 100% { transform: translateY(100%) rotate(30deg); } | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen relative"> | |
| <!-- Grid lines effect --> | |
| <div class="grid-lines"></div> | |
| <!-- Navbar --> | |
| <nav class="navbar fixed top-0 left-0 right-0 z-50 bg-[#05010e]/90 backdrop-blur-md border-b border-[#12092a]"> | |
| <div class="container mx-auto px-4"> | |
| <div class="flex justify-between items-center h-16"> | |
| <!-- Logo --> | |
| <div class="flex items-center"> | |
| <div class="neon-border-pink rounded-full p-2"> | |
| <i class="fas fa-terminal text-xl neon-text-pink"></i> | |
| </div> | |
| <span class="ml-3 font-bold text-xl neon-text-blue">NEON PORTAL</span> | |
| </div> | |
| <!-- Nav Items --> | |
| <div class="hidden md:flex items-center space-x-6"> | |
| <div class="nav-item relative group"> | |
| <button class="flex items-center space-x-1 neon-text-blue neon-underline"> | |
| <i class="fas fa-cog"></i> | |
| <span>Settings</span> | |
| <i class="fas fa-chevron-down text-xs"></i> | |
| </button> | |
| <div class="dropdown absolute right-0 mt-2 w-80 rounded-lg dropdown-content p-4"> | |
| <h3 class="text-sm neon-text-pink mb-3 uppercase tracking-wider">Portal Management</h3> | |
| <button onclick="showAddLinkModal()" | |
| class="w-full py-2 neon-border-blue rounded flex items-center justify-center mb-3 hover:bg-[#05d9e8] hover:text-[#0d0221] transition-all"> | |
| <i class="fas fa-plus mr-2"></i>Add New Portal | |
| </button> | |
| <h3 class="text-sm neon-text-blue mt-4 mb-3 uppercase tracking-wider">Customization</h3> | |
| <div class="space-y-4"> | |
| <div> | |
| <label for="bg-image" class="block mb-2 text-xs uppercase tracking-wider">Background Image URL</label> | |
| <div class="flex"> | |
| <input type="url" id="bg-image" class="flex-grow p-2 rounded-l-lg bg-[#12092a]" | |
| placeholder="https://example.com/image.jpg"> | |
| <button onclick="setBackgroundImage()" | |
| class="px-3 py-2 bg-[#05d9e8] text-[#0d0221] rounded-r-lg hover:bg-[#00c4d2] transition-all"> | |
| <i class="fas fa-check"></i> | |
| </button> | |
| </div> | |
| <div class="bg-upload-container"> | |
| <label class="block mt-2 text-xs uppercase tracking-wider">Upload Background</label> | |
| <input type="file" id="bg-upload" class="w-full mt-1" accept="image/*"> | |
| <img id="bg-preview" class="bg-preview" src="#" alt="Background Preview"> | |
| <button onclick="applyUploadedBackground()" | |
| class="mt-2 px-3 py-1 rounded border border-[#ff2a6d] hover:bg-[#ff2a6d]/20 transition-all text-xs"> | |
| Apply Uploaded Image | |
| </button> | |
| </div> | |
| <button onclick="resetBackgroundImage()" | |
| class="mt-2 text-xs px-3 py-1 rounded border border-[#ff2a6d] hover:bg-[#ff2a6d]/20 transition-all"> | |
| Reset Background | |
| </button> | |
| </div> | |
| <div> | |
| <label class="block mb-2 text-xs uppercase tracking-wider">Theme Color</label> | |
| <div class="color-picker-container neon-border-blue rounded-lg p-4"> | |
| <div class="color-option selected" style="background-color: #05d9e8;" | |
| onclick="selectThemeColor('#05d9e8', this)"></div> | |
| <div class="color-option" style="background-color: #ff2a6d;" | |
| onclick="selectThemeColor('#ff2a6d', this)"></div> | |
| <div class="color-option" style="background-color: #d300c5;" | |
| onclick="selectThemeColor('#d300c5', this)"></div> | |
| <div class="color-option" style="background-color: #00ff9d;" | |
| onclick="selectThemeColor('#00ff9d', this)"></div> | |
| <div class="color-option" style="background-color: #ffcc00;" | |
| onclick="selectThemeColor('#ffcc00', this)"></div> | |
| <div class="color-option" style="background-color: #ff3333;" | |
| onclick="selectThemeColor('#ff3333', this)"></div> | |
| <div class="color-option" style="background-color: #4267B2;" | |
| onclick="selectThemeColor('#4267B2', this)"></div> | |
| <div class="color-option" style="background-color: #9147ff;" | |
| onclick="selectThemeColor('#9147ff', this)"></div> | |
| <div class="color-option" style="background-color: #25F4EE;" | |
| onclick="selectThemeColor('#25F4EE', this)"></div> | |
| <div class="custom-color-toggle w-full" onclick="toggleCustomColorInput()"> | |
| Custom Color | |
| </div> | |
| <div id="custom-color-wrapper" class="custom-color-option" style="display: none;"> | |
| <input type="color" id="custom-color" class="custom-color-input"> | |
| <button class="custom-color-confirm mt-2 px-3 py-1 rounded border border-[#05d9e8] hover:bg-[#05d9e8]/20 transition-all" onclick="confirmCustomColor()">OK</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <h3 class="text-sm neon-text-blue mt-4 mb-3 uppercase tracking-wider">System Info</h3> | |
| <div class="text-xs space-y-2"> | |
| <div class="flex justify-between"> | |
| <span>Version</span> | |
| <span class="text-[#05d9e8]">v2.3.5</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span>Portals</span> | |
| <span id="portal-count" class="text-[#ff2a6d]">0</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="nav-item relative group"> | |
| <button class="flex items-center space-x-1 neon-text-blue neon-underline"> | |
| <i class="fas fa-address-card"></i> | |
| <span>Contact</span> | |
| <i class="fas fa-chevron-down text-xs"></i> | |
| </button> | |
| <div class="dropdown absolute right-0 mt-2 w-72 rounded-lg dropdown-content p-4"> | |
| <h3 class="text-sm neon-text-pink mb-3 uppercase tracking-wider">Connection Channels</h3> | |
| <div class="space-y-3"> | |
| <div class="contact-card p-3 rounded border border-[#05d9e8] hover:bg-[#05d9e8]/10 transition-all"> | |
| <div class="flex items-center"> | |
| <i class="fas fa-envelope text-lg text-[#05d9e8] mr-3"></i> | |
| <div> | |
| <h4 class="font-bold">Encrypted Mail</h4> | |
| <a href="mailto:contact@neonportal.dev" | |
| class="text-sm hover:underline block">contact@neonportal.dev</a> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="contact-card p-3 rounded border border-[#ff2a6d] hover:bg-[#ff2a6d]/10 transition-all"> | |
| <div class="flex items-center"> | |
| <i class="fab fa-discord text-lg text-[#5865F2] mr-3"></i> | |
| <div> | |
| <h4 class="font-bold">Discord</h4> | |
| <span class="text-sm">NeonPortal#0001</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="contact-card p-3 rounded border border-[#25F4EE] hover:bg-[#25F4EE]/10 transition-all"> | |
| <div class="flex items-center"> | |
| <i class="fab fa-twitter text-lg text-[#1DA1F2] mr-3"></i> | |
| <div> | |
| <h4 class="font-bold">Twitter</h4> | |
| <a href="https://twitter.com/neonportal" target="_blank" class="text-sm hover:underline block">@neonportal</a> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="contact-card p-3 rounded border border-[#9147ff] hover:bg-[#9147ff]/10 transition-all"> | |
| <div class="flex items-center"> | |
| <i class="fab fa-patreon text-lg text-[#FF424D] mr-3"></i> | |
| <div> | |
| <h4 class="font-bold">Support</h4> | |
| <a href="https://patreon.com/neonportal" target="_blank" class="text-sm hover:underline block">Patreon</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Mobile menu button --> | |
| <div class="md:hidden"> | |
| <button id="mobile-menu-button" class="neon-text-pink text-2xl focus:outline-none"> | |
| <i class="fas fa-bars"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Mobile menu --> | |
| <div id="mobile-menu" class="md:hidden hidden bg-[#05010e] border-t border-[#ff2a6d]"> | |
| <div class="px-2 py-3 space-y-1"> | |
| <button onclick="showAddLinkModal()" class="block px-3 py-2 rounded-md neon-text-blue w-full text-left hover:bg-[#05d9e8]/20"> | |
| <i class="fas fa-plus mr-2"></i>Add New Portal | |
| </button> | |
| <button class="block px-3 py-2 rounded-md neon-text-blue w-full text-left hover:bg-[#05d9e8]/20"> | |
| <i class="fas fa-cog mr-2"></i>Settings | |
| </button> | |
| <button class="block px-3 py-2 rounded-md neon-text-blue w-full text-left hover:bg-[#05d9e8]/20"> | |
| <i class="fas fa-address-card mr-2"></i>Contact | |
| </button> | |
| <div class="px-3 py-2"> | |
| <label class="block text-xs neon-text-blue mb-1">Background Image</label> | |
| <div class="flex mb-2"> | |
| <input type="url" id="bg-image-mobile" class="flex-grow p-2 rounded-l-lg bg-[#12092a]" | |
| placeholder="Image URL"> | |
| <button onclick="setBackgroundImage()" | |
| class="px-3 py-2 bg-[#05d9e8] text-[#0d0221] rounded-r-lg hover:bg-[#00c4d2] transition-all"> | |
| <i class="fas fa-check"></i> | |
| </button> | |
| </div> | |
| <button onclick="resetBackgroundImage()" | |
| class="w-full px-3 py-1.5 text-xs rounded border border-[#ff2a6d] hover:bg-[#ff2a6d]/20 transition-all"> | |
| Reset Background | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </nav> | |
| <!-- Main container --> | |
| <div class="container mx-auto px-4 py-8 relative z-10 mt-20"> | |
| <!-- Header --> | |
| <header class="text-center mb-12"> | |
| <div class="relative inline-block"> | |
| <h1 class="glitch-effect text-5xl md:text-7xl font-bold mb-4" data-text="NEON PORTAL">NEON PORTAL</h1> | |
| <span | |
| class="absolute -bottom-3 left-1/2 transform -translate-x-1/2 w-48 h-1 bg-gradient-to-r from-transparent via-[#05d9e8] to-transparent"></span> | |
| <span | |
| class="absolute -bottom-5 left-1/2 transform -translate-x-1/2 w-32 h-1 bg-gradient-to-r from-transparent via-[#ff2a6d] to-transparent"></span> | |
| </div> | |
| <p class="text-xl neon-text-purple uppercase tracking-wider mt-8">Your gateway to the digital neonverse</p> | |
| </header> | |
| <!-- Time and date display --> | |
| <div class="flex justify-center mb-12"> | |
| <div class="neon-border-blue rounded-lg p-4 text-center backdrop-blur-sm bg-[#0d0221]/50 holographic-effect"> | |
| <div id="time" class="text-3xl font-bold neon-text-blue">00:00:00</div> | |
| <div id="date" class="text-lg uppercase tracking-wider mt-1">Loading...</div> | |
| </div> | |
| </div> | |
| <!-- Portals grid --> | |
| <div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-6 mb-12" id="portals-grid"> | |
| <!-- Default portals will be added here by JavaScript --> | |
| </div> | |
| </div> | |
| <!-- Add Portal Modal --> | |
| <div id="add-portal-modal" class="fixed inset-0 flex items-center justify-center z-50 hidden modal-overlay"> | |
| <div class="modal-content neon-border-blue rounded-xl p-8 max-w-md w-full mx-4 relative"> | |
| <button id="close-portal-modal" | |
| class="absolute top-4 right-4 text-xl hover:text-[#ff2a6d] transition-all">×</button> | |
| <h2 class="text-2xl neon-text-pink mb-6 uppercase tracking-wider">Create New Portal</h2> | |
| <form id="portal-form" class="space-y-6"> | |
| <div> | |
| <label for="portal-name" class="block mb-2 neon-text-blue uppercase text-sm tracking-wider">Portal Name</label> | |
| <input type="text" id="portal-name" class="w-full p-3 rounded-lg bg-[#12092a] focus:outline-none focus:border focus:border-[#05d9e8]" required> | |
| </div> | |
| <div> | |
| <label for="portal-url" | |
| class="block mb-2 neon-text-blue uppercase text-sm tracking-wider">Destination URL</label> | |
| <input type="url" id="portal-url" class="w-full p-3 rounded-lg bg-[#12092a] focus:outline-none focus:border focus:border-[#05d9e8]" placeholder="https://" | |
| required> | |
| </div> | |
| <div> | |
| <label class="block mb-2 neon-text-blue uppercase text-sm tracking-wider">Icon</label> | |
| <select id="portal-icon" class="w-full p-3 rounded-lg bg-[#0d0221] border border-[#05d9e8] focus:outline-none focus:border focus:border-[#ff2a6d]"> | |
| <option value="fab fa-youtube">YouTube</option> | |
| <option value="fab fa-twitter">Twitter</option> | |
| <option value="fab fa-github">GitHub</option> | |
| <option value="fab fa-reddit">Reddit</option> | |
| <option value="fab fa-discord">Discord</option> | |
| <option value="fab fa-facebook">Facebook</option> | |
| <option value="fab fa-google-drive">Drive</option> | |
| <option value="fas fa-envelope">Mail</option> | |
| <option value="fas fa-music">Music</option> | |
| <option value="fas fa-gamepad">Games</option> | |
| <option value="fas fa-film">Movies</option> | |
| <option value="fas fa-newspaper">News</option> | |
| <option value="fas fa-shopping-cart">Shop</option> | |
| <option value="custom-icon">Custom Icon</option> | |
| </select> | |
| <div id="custom-icon-options" class="custom-icon-option mt-3" style="display: none;"> | |
| <div class="flex space-x-4"> | |
| <div class="flex-1"> | |
| <label class="block mb-2 text-xs uppercase tracking-wider">Upload Icon</label> | |
| <input type="file" id="icon-upload" class="w-full" accept="image/*"> | |
| </div> | |
| <div class="flex-1"> | |
| <label class="block mb-2 text-xs uppercase tracking-wider">Icon URL</label> | |
| <input type="url" id="icon-url" class="w-full p-2 rounded bg-[#0d0221]" | |
| placeholder="https://example.com/icon.png"> | |
| </div> | |
| </div> | |
| <img id="icon-preview" class="icon-preview" src="#" alt="Icon Preview"> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block mb-2 neon-text-blue uppercase text-sm tracking-wider">Color</label> | |
| <div class="color-picker-container neon-border-blue rounded-lg p-4"> | |
| <div class="color-option" style="background-color: #ff2a6d;" | |
| onclick="selectPortalColor('#ff2a6d', this)"></div> | |
| <div class="color-option selected" style="background-color: #05d9e8;" | |
| onclick="selectPortalColor('#05d9e8', this)"></div> | |
| <div class="color-option" style="background-color: #d300c5;" | |
| onclick="selectPortalColor('#d300c5', this)"></div> | |
| <div class="color-option" style="background-color: #00ff9d;" | |
| onclick="selectPortalColor('#00ff9d', this)"></div> | |
| <div class="color-option" style="background-color: #ffcc00;" | |
| onclick="selectPortalColor('#ffcc00', this)"></div> | |
| <div class="color-option" style="background-color: #ffffff;" | |
| onclick="selectPortalColor('#ffffff', this)"></div> | |
| <div class="color-option" style="background-color: #4267B2;" | |
| onclick="selectPortalColor('#4267B2', this)"></div> | |
| <div class="color-option" style="background-color: #9147ff;" | |
| onclick="selectPortalColor('#9147ff', this)"></div> | |
| <div class="custom-color-toggle w-full" onclick="togglePortalCustomColorInput()"> | |
| Custom Color | |
| </div> | |
| <input type="color" id="portal-custom-color" class="custom-color-input" | |
| onchange="applyCustomPortalColor(this.value)"> | |
| </div> | |
| <input type="hidden" id="portal-color" value="#05d9e8"> | |
| </div> | |
| <div class="flex justify-end space-x-3"> | |
| <button type="button" onclick="document.getElementById('add-portal-modal').classList.add('hidden')" | |
| class="px-4 py-2 rounded-lg border border-[#ff2a6d] hover:bg-[#ff2a6d] hover:text-white transition-all"> | |
| Cancel | |
| </button> | |
| <button type="submit" | |
| class="px-6 py-2 neon-border-blue rounded-lg hover:bg-[#05d9e8] hover:text-[#0d0221] transition-all font-bold"> | |
| Create Portal | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize default portals | |
| const defaultPortals = [ | |
| { name: 'YouTube', url: 'https://youtube.com', icon: 'fab fa-youtube', color: '#ff0000' }, | |
| { name: 'Twitter', url: 'https://twitter.com', icon: 'fab fa-twitter', color: '#1DA1F2' }, | |
| { name: 'GitHub', url: 'https://github.com', icon: 'fab fa-github', color: '#ffffff' }, | |
| { name: 'Reddit', url: 'https://reddit.com', icon: 'fab fa-reddit', color: '#FF5700' }, | |
| { name: 'Discord', url: 'https://discord.com', icon: 'fab fa-discord', color: '#5865F2' }, | |
| { name: 'Google Drive', url: 'https://drive.google.com', icon: 'fab fa-google-drive', color: '#34A853' }, | |
| { name: 'Gmail', url: 'https://mail.google.com', icon: 'fas fa-envelope', color: '#EA4335' }, | |
| { name: 'Spotify', url: 'https://open.spotify.com', icon: 'fas fa-music', color: '#1DB954' } | |
| ]; | |
| // DOM elements | |
| const portalsGrid = document.getElementById('portals-grid'); | |
| const portalForm = document.getElementById('portal-form'); | |
| const portalCount = document.getElementById('portal-count'); | |
| const mobileMenuButton = document.getElementById('mobile-menu-button'); | |
| const mobileMenu = document.getElementById('mobile-menu'); | |
| const timeDisplay = document.getElementById('time'); | |
| const dateDisplay = document.getElementById('date'); | |
| const portalIconSelect = document.getElementById('portal-icon'); | |
| const customIconOptions = document.getElementById('custom-icon-options'); | |
| const bgUpload = document.getElementById('bg-upload'); | |
| const bgPreview = document.getElementById('bg-preview'); | |
| const iconUpload = document.getElementById('icon-upload'); | |
| const iconPreview = document.getElementById('icon-preview'); | |
| // Current state | |
| let portals = JSON.parse(localStorage.getItem('portals')) || defaultPortals; | |
| let currentThemeColor = '#05d9e8'; // Default neon blue | |
| let currentBgImage = localStorage.getItem('bgImage') || ''; | |
| // Initialize the app | |
| document.addEventListener('DOMContentLoaded', function() { | |
| renderPortals(); | |
| updateTime(); | |
| setInterval(updateTime, 1000); | |
| loadBackgroundImage(); | |
| // Mobile menu toggle | |
| mobileMenuButton.addEventListener('click', function() { | |
| mobileMenu.classList.toggle('hidden'); | |
| }); | |
| // Event listeners for form | |
| portalForm.addEventListener('submit', handlePortalFormSubmit); | |
| // Show custom icon options when custom icon is selected | |
| portalIconSelect.addEventListener('change', function() { | |
| if (this.value === 'custom-icon') { | |
| customIconOptions.style.display = 'block'; | |
| } else { | |
| customIconOptions.style.display = 'none'; | |
| } | |
| }); | |
| // Image preview for background upload | |
| bgUpload.addEventListener('change', function(e) { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = function(event) { | |
| bgPreview.src = event.target.result; | |
| bgPreview.style.display = 'block'; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| }); | |
| // Image preview for custom icon upload | |
| iconUpload.addEventListener('change', function(e) { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = function(event) { | |
| iconPreview.src = event.target.result; | |
| iconPreview.style.display = 'block'; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| }); | |
| }); | |
| // Render all portals | |
| function renderPortals() { | |
| portalsGrid.innerHTML = ''; | |
| portals.forEach((portal, index) => { | |
| const portalElement = document.createElement('div'); | |
| // Apply the portal's color for text and border glow | |
| const textColorStyle = `color: ${portal.color}; text-shadow: 0 0 5px ${portal.color}, 0 0 10px ${portal.color};`; | |
| const borderStyle = `border: 1px solid ${portal.color}; box-shadow: 0 0 5px ${portal.color}, inset 0 0 5px ${portal.color}, 0 0 20px ${portal.color};`; | |
| portalElement.innerHTML = ` | |
| <div class="portal-card h-full" style="${borderStyle}"> | |
| <a href="${portal.url}" target="_blank" class="block h-full p-6 rounded-lg text-center relative overflow-hidden backdrop-blur-sm bg-[#0d0221]/50 transition-all duration-300"> | |
| <div class="portal-card-inner"> | |
| ${portal.icon.startsWith('http') ? | |
| `<img src="${portal.icon}" class="mx-auto h-12 w-12 mb-4" alt="${portal.name} icon">` : | |
| `<i class="${portal.icon} text-4xl mb-4" style="${textColorStyle}"></i>`} | |
| <h3 class="font-bold uppercase tracking-wider" style="${textColorStyle}">${portal.name}</h3> | |
| </div> | |
| <button onclick="deletePortal(${index}); event.preventDefault();" | |
| class="absolute top-1 right-1 text-xs text-red-500 hover:text-red-400"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </a> | |
| </div> | |
| `; | |
| portalsGrid.appendChild(portalElement); | |
| }); | |
| // Update portal count | |
| portalCount.textContent = portals.length; | |
| } | |
| // Handle form submission | |
| function handlePortalFormSubmit(e) { | |
| e.preventDefault(); | |
| const name = document.getElementById('portal-name').value; | |
| const url = document.getElementById('portal-url').value; | |
| let icon = document.getElementById('portal-icon').value; | |
| const color = document.getElementById('portal-color').value; | |
| // Handle custom icon | |
| if (icon === 'custom-icon') { | |
| const iconUrl = document.getElementById('icon-url').value; | |
| const iconFile = document.getElementById('icon-upload').files[0]; | |
| if (iconUrl) { | |
| icon = iconUrl; | |
| } else if (iconFile) { | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| // Convert image to base64 to store in local storage | |
| icon = e.target.result; | |
| addPortalToArray(name, url, icon, color); | |
| }; | |
| reader.readAsDataURL(iconFile); | |
| return; // Exit early as we'll call addPortalToArray in the callback | |
| } else { | |
| alert('Please provide an icon URL or upload an icon'); | |
| return; | |
| } | |
| } | |
| addPortalToArray(name, url, icon, color); | |
| } | |
| // Add a new portal to the array and update UI | |
| function addPortalToArray(name, url, icon, color) { | |
| const newPortal = { | |
| name: name, | |
| url: url, | |
| icon: icon, | |
| color: color | |
| }; | |
| portals.push(newPortal); | |
| savePortals(); | |
| renderPortals(); | |
| document.getElementById('add-portal-modal').classList.add('hidden'); | |
| resetPortalForm(); | |
| } | |
| // Delete a portal | |
| function deletePortal(index) { | |
| if (confirm('Are you sure you want to delete this portal?')) { | |
| portals.splice(index, 1); | |
| savePortals(); | |
| renderPortals(); | |
| } | |
| } | |
| // Reset portal form | |
| function resetPortalForm() { | |
| document.getElementById('portal-form').reset(); | |
| customIconOptions.style.display = 'none'; | |
| iconPreview.style.display = 'none'; | |
| document.getElementById('portal-color').value = '#05d9e8'; | |
| } | |
| // Save portals to localStorage | |
| function savePortals() { | |
| localStorage.setItem('portals', JSON.stringify(portals)); | |
| } | |
| // Show add portal modal | |
| function showAddLinkModal() { | |
| document.getElementById('add-portal-modal').classList.remove('hidden'); | |
| mobileMenu.classList.add('hidden'); | |
| } | |
| // Update time display | |
| function updateTime() { | |
| const now = new Date(); | |
| const timeString = now.toLocaleTimeString(); | |
| const dateString = now.toLocaleDateString(undefined, { | |
| weekday: 'long', | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric' | |
| }); | |
| timeDisplay.textContent = timeString; | |
| dateDisplay.textContent = dateString; | |
| } | |
| // Select portal color | |
| function selectPortalColor(color, element) { | |
| // Remove selected class from all options | |
| document.querySelectorAll('.color-picker-container .color-option').forEach(el => { | |
| el.classList.remove('selected'); | |
| }); | |
| // Add selected class to clicked option | |
| element.classList.add('selected'); | |
| // Update hidden input value | |
| document.getElementById('portal-color').value = color; | |
| } | |
| // Select theme color | |
| function selectThemeColor(color, element) { | |
| // Remove selected class from all options | |
| document.querySelectorAll('.dropdown .color-option').forEach(el => { | |
| el.classList.remove('selected'); | |
| }); | |
| // Add selected class to clicked option | |
| element.classList.add('selected'); | |
| // Update current theme color | |
| currentThemeColor = color; | |
| updateThemeColor(color); | |
| } | |
| // Update theme color | |
| function updateThemeColor(color) { | |
| // Update CSS variables | |
| document.documentElement.style.setProperty('--neon-blue', color); | |
| // Update all elements with the neon-blue color classes | |
| // This is a simplified version - in a real app you might need more comprehensive updates | |
| const neonElements = document.querySelectorAll('.neon-text-blue, .neon-border-blue'); | |
| neonElements.forEach(el => { | |
| if (el.classList.contains('neon-text-blue')) { | |
| el.style.color = color; | |
| el.style.textShadow = `0 0 5px ${color}, 0 0 10px ${color}, 0 0 20px ${color}`; | |
| } | |
| if (el.classList.contains('neon-border-blue')) { | |
| el.style.borderColor = color; | |
| el.style.boxShadow = `0 0 5px ${color}, inset 0 0 5px ${color}, 0 0 20px ${color}`; | |
| } | |
| }); | |
| } | |
| // Toggle custom color input in settings | |
| function toggleCustomColorInput() { | |
| const wrapper = document.getElementById('custom-color-wrapper'); | |
| wrapper.style.display = wrapper.style.display === 'none' ? 'block' : 'none'; | |
| } | |
| // Toggle custom color input in portal form | |
| function togglePortalCustomColorInput() { | |
| const input = document.getElementById('portal-custom-color'); | |
| input.style.display = input.style.display === 'none' ? 'block' : 'none'; | |
| } | |
| // Apply custom portal color | |
| function applyCustomPortalColor(color) { | |
| selectPortalColor(color, document.querySelector('.portal-form .color-option.selected')); | |
| } | |
| // Confirm custom theme color | |
| function confirmCustomColor() { | |
| const customColor = document.getElementById('custom-color').value; | |
| selectThemeColor(customColor, document.querySelector('.dropdown .color-option.selected')); | |
| document.getElementById('custom-color-wrapper').style.display = 'none'; | |
| } | |
| // Set background image from URL | |
| function setBackgroundImage() { | |
| // Check if we're on mobile or desktop | |
| const bgUrlInput = document.getElementById('bg-image-mobile') || document.getElementById('bg-image'); | |
| if (bgUrlInput && bgUrlInput.value) { | |
| currentBgImage = bgUrlInput.value; | |
| localStorage.setItem('bgImage', currentBgImage); | |
| document.body.style.backgroundImage = `url('${currentBgImage}')`; | |
| } | |
| } | |
| // Apply uploaded background | |
| function applyUploadedBackground() { | |
| if (bgPreview.src && bgPreview.src !== '#') { | |
| currentBgImage = bgPreview.src; | |
| localStorage.setItem('bgImage', currentBgImage); | |
| document.body.style.backgroundImage = `url('${currentBgImage}')`; | |
| } | |
| } | |
| // Load background image from localStorage | |
| function loadBackgroundImage() { | |
| if (currentBgImage) { | |
| document.body.style.backgroundImage = `url('${currentBgImage}')`; | |
| document.body.style.backgroundSize = 'cover'; | |
| document.body.style.backgroundAttachment = 'fixed'; | |
| document.body.style.backgroundColor = 'transparent'; | |
| } | |
| } | |
| // Reset background image | |
| function resetBackgroundImage() { | |
| currentBgImage = ''; | |
| localStorage.removeItem('bgImage'); | |
| document.body.style.backgroundImage = 'none'; | |
| document.body.style.backgroundColor = '#05010e'; | |
| // Reset input fields | |
| const bgInputs = document.querySelectorAll('[id^=bg-image]'); | |
| bgInputs.forEach(input => input.value = ''); | |
| if (bgPreview) bgPreview.style.display = 'none'; | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=Shiroaki085/hubv2" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> | |
| </html> |