Restructure services into Public/Private sections, fix modals, limit terminal animation
This commit is contained in:
parent
79170ffa21
commit
1524944b52
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
113
index.html
113
index.html
|
|
@ -255,11 +255,46 @@
|
|||
<div class="section-header">
|
||||
<span class="section-badge">⚡ Available Now</span>
|
||||
<h2 class="section-title">Services</h2>
|
||||
<p class="section-desc">Self-hosted applications and services available on the network</p>
|
||||
<p class="section-desc">Self-hosted applications running on the LemonLink infrastructure</p>
|
||||
</div>
|
||||
|
||||
<!-- Public Services -->
|
||||
<div class="services-category">
|
||||
<h3 class="category-title">
|
||||
<span class="category-icon">🌐</span>
|
||||
Public Services
|
||||
<span class="category-badge">Open Access</span>
|
||||
</h3>
|
||||
<div class="services-grid">
|
||||
<a href="https://cloud.lemonlink.eu" class="service-card" target="_blank" rel="noopener">
|
||||
<a href="https://git.lemonlink.eu" class="service-card public" target="_blank" rel="noopener">
|
||||
<div class="service-glow"></div>
|
||||
<div class="service-icon" style="--icon-color: #609926;">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C6.477 0 2 4.477 2 10c0 4.42 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.341-3.369-1.341-.454-1.155-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.163 22 16.418 22 12c0-5.523-4.477-10-10-10z"/></svg>
|
||||
</div>
|
||||
<h3 class="service-name">Gitea</h3>
|
||||
<p class="service-desc">Self-hosted Git service for code repositories</p>
|
||||
<div class="service-status checking" data-status-url="https://git.lemonlink.eu/api/v1/version">
|
||||
<span class="status-dot"></span>
|
||||
<span class="status-text">Checking...</span>
|
||||
</div>
|
||||
<div class="service-arrow">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M7 17L17 7M17 7H7M17 7V17"/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Private Services -->
|
||||
<div class="services-category">
|
||||
<h3 class="category-title">
|
||||
<span class="category-icon">🔒</span>
|
||||
Private Services
|
||||
<span class="category-badge">Restricted Access</span>
|
||||
</h3>
|
||||
<div class="services-grid">
|
||||
<div class="service-card private-service" data-service="nextcloud">
|
||||
<div class="service-glow"></div>
|
||||
<div class="service-icon" style="--icon-color: #0082c9;">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
|
||||
|
|
@ -272,30 +307,12 @@
|
|||
</div>
|
||||
<div class="service-arrow">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M7 17L17 7M17 7H7M17 7V17"/>
|
||||
<path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a href="https://git.lemonlink.eu" class="service-card" target="_blank" rel="noopener">
|
||||
<div class="service-glow"></div>
|
||||
<div class="service-icon" style="--icon-color: #609926;">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.341-3.369-1.341-.454-1.155-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.163 22 16.418 22 12c0-5.523-4.477-10-10-10z"/></svg>
|
||||
</div>
|
||||
<h3 class="service-name">Gitea</h3>
|
||||
<p class="service-desc">Self-hosted Git service for code repositories</p>
|
||||
<div class="service-status online">
|
||||
<span class="status-dot"></span>
|
||||
<span>Online</span>
|
||||
</div>
|
||||
<div class="service-arrow">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M7 17L17 7M17 7H7M17 7V17"/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="https://stats.lemonlink.eu" class="service-card" target="_blank" rel="noopener">
|
||||
<div class="service-card private-service" data-service="netdata">
|
||||
<div class="service-glow"></div>
|
||||
<div class="service-icon" style="--icon-color: #00ab44;">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 3v18h18v-2H5V3H3zm11 12.5l-4-4-5 5L4 16l5-5 4 4 6.5-6.5 1.5 1.5-8 8z"/></svg>
|
||||
|
|
@ -308,10 +325,10 @@
|
|||
</div>
|
||||
<div class="service-arrow">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M7 17L17 7M17 7H7M17 7V17"/>
|
||||
<path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="service-card private-service" data-service="immich">
|
||||
<div class="service-glow"></div>
|
||||
|
|
@ -359,16 +376,14 @@
|
|||
<div class="service-status checking" data-status-url="https://login.tailscale.com">
|
||||
<span class="status-dot"></span>
|
||||
<span class="status-text">Checking...</span>
|
||||
</div>
|
||||
<span class="status-dot"></span>
|
||||
<span>Online</span>
|
||||
</div>
|
||||
<div class="service-arrow">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M7 17L17 7M17 7H7M17 7V17"/>
|
||||
<path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -632,6 +647,44 @@
|
|||
</footer>
|
||||
|
||||
<!-- Service Info Modals -->
|
||||
<div id="modal-nextcloud" class="service-modal">
|
||||
<div class="modal-content">
|
||||
<button class="modal-close">×</button>
|
||||
<h3>☁️ Nextcloud</h3>
|
||||
<p>Private cloud storage platform - your data stays under your control.</p>
|
||||
<div class="modal-details">
|
||||
<h4>Features:</h4>
|
||||
<ul>
|
||||
<li>File storage and sync</li>
|
||||
<li>Calendar and contacts</li>
|
||||
<li>Document collaboration</li>
|
||||
<li>End-to-end encryption</li>
|
||||
</ul>
|
||||
<h4>Access:</h4>
|
||||
<p>This is a <strong>private service</strong>. User account required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="modal-netdata" class="service-modal">
|
||||
<div class="modal-content">
|
||||
<button class="modal-close">×</button>
|
||||
<h3>📊 Netdata Monitoring</h3>
|
||||
<p>Real-time infrastructure monitoring with beautiful dashboards.</p>
|
||||
<div class="modal-details">
|
||||
<h4>Features:</h4>
|
||||
<ul>
|
||||
<li>Real-time metrics</li>
|
||||
<li>System performance monitoring</li>
|
||||
<li>Custom alerts</li>
|
||||
<li>Historical data</li>
|
||||
</ul>
|
||||
<h4>Access:</h4>
|
||||
<p>This is a <strong>private service</strong>. VPN or local network access required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="modal-immich" class="service-modal">
|
||||
<div class="modal-content">
|
||||
<button class="modal-close">×</button>
|
||||
|
|
|
|||
95
script.js
95
script.js
|
|
@ -323,11 +323,11 @@ function initTerminalTyping() {
|
|||
setInterval(typeCommand, 8000);
|
||||
}
|
||||
|
||||
// Initialize terminal typing when contact section is visible
|
||||
// Initialize terminal typing when contact section is visible - limited to 3 cycles
|
||||
const terminalObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
initTerminalTyping();
|
||||
initLimitedTerminalTyping();
|
||||
terminalObserver.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
|
|
@ -338,6 +338,97 @@ if (contactSection) {
|
|||
terminalObserver.observe(contactSection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Limited Terminal Typing Effect (3 cycles max)
|
||||
*/
|
||||
function initLimitedTerminalTyping() {
|
||||
const terminal = document.querySelector('.terminal-body');
|
||||
if (!terminal) return;
|
||||
|
||||
const commands = [
|
||||
{ text: 'docker ps', output: 'CONTAINER ID IMAGE STATUS\nabc123 nginx Up 3 days\ndef456 nextcloud Up 5 days' },
|
||||
{ text: 'uptime', output: 'Load: 1.06, 1.06, 1.14 | RAM: 18% of 96GB' },
|
||||
{ text: 'tailscale status', output: '100.x.x.x compute-01 linux -' }
|
||||
];
|
||||
|
||||
let currentCommand = 0;
|
||||
let cycleCount = 0;
|
||||
const maxCycles = 3;
|
||||
let isTyping = false;
|
||||
|
||||
const typeCommand = () => {
|
||||
if (isTyping || cycleCount >= maxCycles) return;
|
||||
isTyping = true;
|
||||
|
||||
const cmd = commands[currentCommand];
|
||||
const lines = terminal.querySelectorAll('.terminal-line');
|
||||
|
||||
// Remove cursor from previous line
|
||||
const cursor = terminal.querySelector('.cursor');
|
||||
if (cursor) cursor.remove();
|
||||
|
||||
// Create new command line
|
||||
const newLine = document.createElement('div');
|
||||
newLine.className = 'terminal-line';
|
||||
newLine.innerHTML = `
|
||||
<span class="prompt">➜</span>
|
||||
<span class="path">~</span>
|
||||
<span class="command"></span>
|
||||
<span class="cursor">|</span>
|
||||
`;
|
||||
terminal.appendChild(newLine);
|
||||
|
||||
const commandSpan = newLine.querySelector('.command');
|
||||
let charIndex = 0;
|
||||
|
||||
const typeChar = () => {
|
||||
if (charIndex < cmd.text.length) {
|
||||
commandSpan.textContent += cmd.text[charIndex];
|
||||
charIndex++;
|
||||
setTimeout(typeChar, 50 + Math.random() * 50);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
const outputDiv = document.createElement('div');
|
||||
outputDiv.className = 'terminal-output';
|
||||
outputDiv.innerHTML = cmd.output.replace(/\n/g, '<br>');
|
||||
terminal.appendChild(outputDiv);
|
||||
|
||||
const nextLine = document.createElement('div');
|
||||
nextLine.className = 'terminal-line';
|
||||
nextLine.innerHTML = `
|
||||
<span class="prompt">➜</span>
|
||||
<span class="path">~</span>
|
||||
<span class="cursor">|</span>
|
||||
`;
|
||||
terminal.appendChild(nextLine);
|
||||
|
||||
terminal.scrollTop = terminal.scrollHeight;
|
||||
|
||||
isTyping = false;
|
||||
currentCommand = (currentCommand + 1) % commands.length;
|
||||
|
||||
// Count completed cycles
|
||||
if (currentCommand === 0) {
|
||||
cycleCount++;
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
typeChar();
|
||||
};
|
||||
|
||||
// Start typing effect every 6 seconds, but only for 3 cycles
|
||||
const intervalId = setInterval(() => {
|
||||
if (cycleCount >= maxCycles) {
|
||||
clearInterval(intervalId);
|
||||
// Keep cursor blinking at the end
|
||||
return;
|
||||
}
|
||||
typeCommand();
|
||||
}, 6000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Network connection lines animation
|
||||
*/
|
||||
|
|
|
|||
81
styles.css
81
styles.css
|
|
@ -1518,6 +1518,17 @@ body {
|
|||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Service Modals - Hide by default
|
||||
======================================== */
|
||||
.service-modal {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.service-modal.active {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Service Status Checking
|
||||
======================================== */
|
||||
|
|
@ -1698,3 +1709,73 @@ body {
|
|||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ========================================
|
||||
Services Categories (Public/Private)
|
||||
======================================== */
|
||||
.services-category {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.category-title {
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.category-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.category-badge {
|
||||
margin-left: auto;
|
||||
padding: 0.35rem 0.75rem;
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||
border-radius: 100px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: #22c55e;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.services-category:last-child .category-badge {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* Public Service Styling */
|
||||
.service-card.public {
|
||||
border-color: rgba(34, 197, 94, 0.2);
|
||||
}
|
||||
|
||||
.service-card.public:hover {
|
||||
border-color: rgba(34, 197, 94, 0.5);
|
||||
box-shadow: 0 8px 32px rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
/* Private Service Styling */
|
||||
.service-card.private-service {
|
||||
border-color: rgba(239, 68, 68, 0.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.service-card.private-service:hover {
|
||||
border-color: rgba(239, 68, 68, 0.5);
|
||||
box-shadow: 0 8px 32px rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
.service-card.private-service .service-arrow svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue