Update: 2026 copyright, private service modals, real-time status checking
This commit is contained in:
parent
0771b37ee2
commit
0ae90b8b85
95
index.html
95
index.html
|
|
@ -266,9 +266,9 @@
|
||||||
</div>
|
</div>
|
||||||
<h3 class="service-name">Nextcloud</h3>
|
<h3 class="service-name">Nextcloud</h3>
|
||||||
<p class="service-desc">Private cloud storage, files, and collaboration</p>
|
<p class="service-desc">Private cloud storage, files, and collaboration</p>
|
||||||
<div class="service-status online">
|
<div class="service-status checking" data-status-url="https://cloud.lemonlink.eu/status.php">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span>Online</span>
|
<span class="status-text">Checking...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="service-arrow">
|
<div class="service-arrow">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
|
@ -302,9 +302,9 @@
|
||||||
</div>
|
</div>
|
||||||
<h3 class="service-name">Netdata</h3>
|
<h3 class="service-name">Netdata</h3>
|
||||||
<p class="service-desc">Real-time system monitoring and metrics</p>
|
<p class="service-desc">Real-time system monitoring and metrics</p>
|
||||||
<div class="service-status online">
|
<div class="service-status checking" data-status-url="https://stats.lemonlink.eu/api/v1/info">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span>Online</span>
|
<span class="status-text">Checking...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="service-arrow">
|
<div class="service-arrow">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
|
@ -313,50 +313,53 @@
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="https://photos.lemonlink.eu" class="service-card" target="_blank" rel="noopener">
|
<div class="service-card private-service" data-service="immich">
|
||||||
<div class="service-glow"></div>
|
<div class="service-glow"></div>
|
||||||
<div class="service-icon" style="--icon-color: #ad5c5c;">
|
<div class="service-icon" style="--icon-color: #ad5c5c;">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="service-name">Immich</h3>
|
<h3 class="service-name">Immich</h3>
|
||||||
<p class="service-desc">Self-hosted photo and video backup solution</p>
|
<p class="service-desc">Self-hosted photo and video backup solution</p>
|
||||||
<div class="service-status online">
|
<div class="service-status checking" data-status-url="https://photos.lemonlink.eu/api/server-info/ping">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span>Online</span>
|
<span class="status-text">Checking...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="service-arrow">
|
<div class="service-arrow">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
|
|
||||||
<a href="https://dash.lemonlink.eu" class="service-card" target="_blank" rel="noopener">
|
<div class="service-card private-service" data-service="homarr">
|
||||||
<div class="service-glow"></div>
|
<div class="service-glow"></div>
|
||||||
<div class="service-icon" style="--icon-color: #ff5c5c;">
|
<div class="service-icon" style="--icon-color: #ff5c5c;">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="service-name">Homarr</h3>
|
<h3 class="service-name">Homarr</h3>
|
||||||
<p class="service-desc">Customizable dashboard for all your services</p>
|
<p class="service-desc">Customizable dashboard for all your services</p>
|
||||||
<div class="service-status online">
|
<div class="service-status checking" data-status-url="https://dash.lemonlink.eu">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
<span>Online</span>
|
<span class="status-text">Checking...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="service-arrow">
|
<div class="service-arrow">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
|
|
||||||
<a href="https://vpn.lemonlink.eu" class="service-card" target="_blank" rel="noopener">
|
<div class="service-card private-service" data-service="tailscale">
|
||||||
<div class="service-glow"></div>
|
<div class="service-glow"></div>
|
||||||
<div class="service-icon" style="--icon-color: #7b68ee;">
|
<div class="service-icon" style="--icon-color: #7b68ee;">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="service-name">Tailscale</h3>
|
<h3 class="service-name">Tailscale</h3>
|
||||||
<p class="service-desc">Zero-config VPN for secure remote access</p>
|
<p class="service-desc">Zero-config VPN for secure remote access</p>
|
||||||
<div class="service-status online">
|
<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 class="status-dot"></span>
|
||||||
<span>Online</span>
|
<span>Online</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -623,11 +626,69 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-bottom">
|
<div class="footer-bottom">
|
||||||
<p>© 2024 LemonLink. All rights reserved.</p>
|
<p>© 2026 LemonLink. All rights reserved.</p>
|
||||||
<p class="footer-made">Powered by 2x Xeon E5645 | 96GB RAM | TrueNAS SCALE</p>
|
<p class="footer-made">Powered by 2x Xeon E5645 | 96GB RAM | TrueNAS SCALE</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<!-- Service Info Modals -->
|
||||||
|
<div id="modal-immich" class="service-modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<button class="modal-close">×</button>
|
||||||
|
<h3>📸 Immich Photo Server</h3>
|
||||||
|
<p>Self-hosted photo and video backup solution - a Google Photos alternative.</p>
|
||||||
|
<div class="modal-details">
|
||||||
|
<h4>Features:</h4>
|
||||||
|
<ul>
|
||||||
|
<li>Automatic mobile photo backup</li>
|
||||||
|
<li>AI-powered face recognition</li>
|
||||||
|
<li>Albums and sharing</li>
|
||||||
|
<li>RAW file support</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Access:</h4>
|
||||||
|
<p>This is a <strong>private service</strong>. Contact me for access.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="modal-homarr" class="service-modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<button class="modal-close">×</button>
|
||||||
|
<h3>🎛️ Homarr Dashboard</h3>
|
||||||
|
<p>Personal dashboard aggregating all services in one place.</p>
|
||||||
|
<div class="modal-details">
|
||||||
|
<h4>Features:</h4>
|
||||||
|
<ul>
|
||||||
|
<li>Service status monitoring</li>
|
||||||
|
<li>Quick bookmarks</li>
|
||||||
|
<li>Weather widget</li>
|
||||||
|
<li>Custom integrations</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Access:</h4>
|
||||||
|
<p>This is a <strong>private service</strong>. VPN access required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="modal-tailscale" class="service-modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<button class="modal-close">×</button>
|
||||||
|
<h3>🔒 Tailscale VPN</h3>
|
||||||
|
<p>Zero-configuration mesh VPN for secure remote access to the homelab.</p>
|
||||||
|
<div class="modal-details">
|
||||||
|
<h4>Features:</h4>
|
||||||
|
<ul>
|
||||||
|
<li>Point-to-point encrypted connections</li>
|
||||||
|
<li>No open ports required</li>
|
||||||
|
<li>Multi-device support</li>
|
||||||
|
<li>Exit nodes for secure browsing</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Access:</h4>
|
||||||
|
<p>Tailscale network invitation required. Contact me to join the tailnet.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="script.js"></script>
|
<script src="script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
139
script.js
139
script.js
|
|
@ -465,3 +465,142 @@ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||||
document.documentElement.style.setProperty('--transition-normal', '0s');
|
document.documentElement.style.setProperty('--transition-normal', '0s');
|
||||||
document.documentElement.style.setProperty('--transition-slow', '0s');
|
document.documentElement.style.setProperty('--transition-slow', '0s');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service Status Checker - Real-time health checks
|
||||||
|
*/
|
||||||
|
function initServiceStatusChecker() {
|
||||||
|
const statusElements = document.querySelectorAll('.service-status[data-status-url]');
|
||||||
|
|
||||||
|
statusElements.forEach(element => {
|
||||||
|
const url = element.getAttribute('data-status-url');
|
||||||
|
checkServiceHealth(element, url);
|
||||||
|
|
||||||
|
// Check every 60 seconds
|
||||||
|
setInterval(() => checkServiceHealth(element, url), 60000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkServiceHealth(element, url) {
|
||||||
|
const statusText = element.querySelector('.status-text');
|
||||||
|
const originalText = statusText.textContent;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try to fetch with a short timeout
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'HEAD',
|
||||||
|
mode: 'no-cors',
|
||||||
|
signal: controller.signal
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
// If we get here, service is likely online
|
||||||
|
element.className = 'service-status online';
|
||||||
|
statusText.textContent = 'Online';
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// For no-cors requests, we can't read the response
|
||||||
|
// Try an alternative approach - image load test for same-origin
|
||||||
|
testViaImage(element, url, statusText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testViaImage(element, url, statusText) {
|
||||||
|
// Extract domain from URL
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
const testUrl = `${urlObj.protocol}//${urlObj.hostname}/favicon.ico`;
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
element.className = 'service-status online';
|
||||||
|
statusText.textContent = 'Online';
|
||||||
|
};
|
||||||
|
img.onerror = () => {
|
||||||
|
// Could be offline or just no favicon
|
||||||
|
// Check if it's a private/internal service
|
||||||
|
if (element.closest('.private-service')) {
|
||||||
|
element.className = 'service-status online';
|
||||||
|
statusText.textContent = 'Restricted';
|
||||||
|
} else {
|
||||||
|
element.className = 'service-status offline';
|
||||||
|
statusText.textContent = 'Offline';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
img.src = testUrl + '?t=' + Date.now();
|
||||||
|
|
||||||
|
// Timeout after 3 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!img.complete) {
|
||||||
|
img.src = '';
|
||||||
|
if (element.closest('.private-service')) {
|
||||||
|
element.className = 'service-status online';
|
||||||
|
statusText.textContent = 'Restricted';
|
||||||
|
} else {
|
||||||
|
element.className = 'service-status maintenance';
|
||||||
|
statusText.textContent = 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize status checker
|
||||||
|
document.addEventListener('DOMContentLoaded', initServiceStatusChecker);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service Info Modals
|
||||||
|
*/
|
||||||
|
function initServiceModals() {
|
||||||
|
const privateServices = document.querySelectorAll('.private-service');
|
||||||
|
const modals = document.querySelectorAll('.service-modal');
|
||||||
|
const closeButtons = document.querySelectorAll('.modal-close');
|
||||||
|
|
||||||
|
// Open modal when clicking private service
|
||||||
|
privateServices.forEach(service => {
|
||||||
|
service.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const serviceName = service.getAttribute('data-service');
|
||||||
|
const modal = document.getElementById(`modal-${serviceName}`);
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.add('active');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal when clicking close button
|
||||||
|
closeButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const modal = button.closest('.service-modal');
|
||||||
|
modal.classList.remove('active');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal when clicking outside
|
||||||
|
modals.forEach(modal => {
|
||||||
|
modal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === modal) {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close on Escape key
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
modals.forEach(modal => {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
});
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize modals
|
||||||
|
document.addEventListener('DOMContentLoaded', initServiceModals);
|
||||||
|
|
|
||||||
184
styles.css
184
styles.css
|
|
@ -1512,3 +1512,187 @@ body {
|
||||||
background: rgba(234, 179, 8, 0.3);
|
background: rgba(234, 179, 8, 0.3);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
/* Selection */
|
||||||
|
::selection {
|
||||||
|
background: rgba(234, 179, 8, 0.3);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Service Status Checking
|
||||||
|
======================================== */
|
||||||
|
.service-status.checking {
|
||||||
|
background: rgba(107, 114, 128, 0.1);
|
||||||
|
border: 1px solid rgba(107, 114, 128, 0.3);
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-status.checking .status-dot {
|
||||||
|
background: #6b7280;
|
||||||
|
animation: pulse-dot 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-status.online {
|
||||||
|
background: rgba(34, 197, 94, 0.1);
|
||||||
|
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||||
|
color: #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-status.online .status-dot {
|
||||||
|
background: #22c55e;
|
||||||
|
animation: pulse-dot 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-status.offline {
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-status.offline .status-dot {
|
||||||
|
background: #ef4444;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-status.maintenance {
|
||||||
|
background: rgba(245, 158, 11, 0.1);
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.3);
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-status.maintenance .status-dot {
|
||||||
|
background: #f59e0b;
|
||||||
|
animation: pulse-dot 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private Services */
|
||||||
|
.private-service {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.private-service .service-arrow svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Service Info Modals
|
||||||
|
======================================== */
|
||||||
|
.service-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-modal.active {
|
||||||
|
display: flex;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: var(--color-bg-card);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-xl);
|
||||||
|
padding: 2.5rem;
|
||||||
|
max-width: 500px;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
transform: translateY(20px);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-modal.active .modal-content {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover {
|
||||||
|
background: rgba(239, 68, 68, 0.2);
|
||||||
|
border-color: rgba(239, 68, 68, 0.5);
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content h3 {
|
||||||
|
font-family: 'Space Grotesk', sans-serif;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background: var(--gradient-primary);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content p {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-details h4 {
|
||||||
|
font-family: 'Space Grotesk', sans-serif;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin: 1.5rem 0 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-details ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-details li {
|
||||||
|
padding: 0.35rem 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-details li::before {
|
||||||
|
content: '✓';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--color-primary);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-details strong {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.service-modal {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue