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:[email protected]" | |
class="text-sm hover:underline block">[email protected]</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> |