Dash
Items
public
Switch User
Custom JavaScript
Save
Cancel
/* ============================================================ HEIMDALL — CUSTOM JS Coller dans : Settings > Custom JS CONFIGURATION : modifie le bloc ci-dessous uniquement ============================================================ */ const CONFIG = { // Météo — clé API gratuite sur https://openweathermap.org/api // Laisse vide '' pour désactiver la météo weatherApiKey: '', // Ta ville pour la météo city: 'Paris', // Langue météo : 'fr', 'en', 'de', 'es'... weatherLang: 'fr', // Unités : 'metric' (°C) ou 'imperial' (°F) weatherUnits: 'metric', // Raccourci clavier pour focus sur la recherche (true/false) searchShortcut: true, // Tooltip au survol des tuiles (true/false) tooltips: true, // Animation d'entrée des tuiles (true/false) tileAnimation: true, // Message de bienvenue selon l'heure (true/false) greeting: true, }; /* ============================================================ INIT — attend que le DOM soit prêt ============================================================ */ document.addEventListener('DOMContentLoaded', () => { injectStyles(); injectWidget(); if (CONFIG.tileAnimation) animateTiles(); if (CONFIG.searchShortcut) setupSearchShortcut(); if (CONFIG.tooltips) setupTooltips(); }); /* ============================================================ STYLES INJECTÉS POUR LE WIDGET ============================================================ */ function injectStyles() { const style = document.createElement('style'); style.textContent = ` /* --- Widget horloge/météo --- */ #hd-widget { position: relative; z-index: 10; display: flex; align-items: center; justify-content: space-between; max-width: 860px; margin: 0 auto 1.8rem; padding: 0 1.5rem; gap: 16px; flex-wrap: wrap; } #hd-greeting { font-size: 13px; font-weight: 500; color: rgba(255,255,255,0.45); letter-spacing: 0.04em; text-transform: uppercase; margin-bottom: 2px; } #hd-clock { font-size: 2.6rem; font-weight: 300; color: rgba(255,255,255,0.92); letter-spacing: -0.02em; line-height: 1; font-variant-numeric: tabular-nums; } #hd-date { font-size: 13px; color: rgba(255,255,255,0.45); margin-top: 3px; text-transform: capitalize; } #hd-weather { display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.07); border: 1px solid rgba(255,255,255,0.13); border-radius: 14px; padding: 10px 18px; backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); min-width: 160px; opacity: 0; transition: opacity 0.4s ease; } #hd-weather.loaded { opacity: 1; } #hd-weather img { width: 40px; height: 40px; } #hd-weather-temp { font-size: 1.6rem; font-weight: 300; color: rgba(255,255,255,0.92); line-height: 1; } #hd-weather-desc { font-size: 11px; color: rgba(255,255,255,0.45); text-transform: capitalize; margin-top: 2px; } #hd-weather-city { font-size: 12px; color: rgba(255,255,255,0.6); font-weight: 500; } /* --- Raccourci clavier badge --- */ #hd-shortcut-badge { position: fixed; bottom: 20px; right: 20px; background: rgba(108,99,255,0.18); border: 1px solid rgba(108,99,255,0.4); border-radius: 8px; color: rgba(255,255,255,0.5); font-size: 11px; padding: 5px 10px; font-family: 'Inter', monospace; z-index: 100; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; } #hd-shortcut-badge.visible { opacity: 1; } /* --- Tooltip --- */ .hd-tooltip { position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%); background: rgba(14, 14, 28, 0.95); border: 1px solid rgba(255,255,255,0.15); border-radius: 8px; color: rgba(255,255,255,0.8); font-size: 11px; padding: 5px 10px; white-space: nowrap; pointer-events: none; z-index: 999; opacity: 0; transition: opacity 0.15s ease; backdrop-filter: blur(12px); font-family: 'Inter', sans-serif; } .hd-tooltip.show { opacity: 1; } /* --- Animation tuiles --- */ @keyframes hdSlideIn { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } } .hd-animate { animation: hdSlideIn 0.35s cubic-bezier(0.4, 0, 0.2, 1) both; } `; document.head.appendChild(style); } /* ============================================================ WIDGET HORLOGE + MÉTÉO ============================================================ */ function injectWidget() { const anchor = document.querySelector('.tags') || document.querySelector('#search-container') || document.querySelector('.appheader'); if (!anchor) return; const widget = document.createElement('div'); widget.id = 'hd-widget'; widget.innerHTML = ` <div id="hd-left"> ${CONFIG.greeting ? '<div id="hd-greeting"></div>' : ''} <div id="hd-clock">--:--</div> <div id="hd-date"></div> </div> <div id="hd-weather"> <img id="hd-weather-icon" src="" alt=""> <div> <div id="hd-weather-city">Chargement...</div> <div id="hd-weather-temp">--°</div> <div id="hd-weather-desc"></div> </div> </div> `; anchor.parentNode.insertBefore(widget, anchor); updateClock(); setInterval(updateClock, 1000); if (CONFIG.weatherApiKey) { fetchWeather(); setInterval(fetchWeather, 10 * 60 * 1000); } else { const w = document.getElementById('hd-weather'); if (w) w.style.display = 'none'; } } /* --- Horloge ------------------------------------------------ */ function updateClock() { const now = new Date(); const clock = document.getElementById('hd-clock'); if (clock) { clock.textContent = now.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', hour12: false, }); } const date = document.getElementById('hd-date'); if (date) { date.textContent = now.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', }); } if (CONFIG.greeting) { const greeting = document.getElementById('hd-greeting'); if (greeting) { const h = now.getHours(); let msg = 'Bonne nuit'; if (h >= 5 && h < 12) msg = 'Bonjour'; else if (h >= 12 && h < 18) msg = 'Bon après-midi'; else if (h >= 18 && h < 22) msg = 'Bonne soirée'; greeting.textContent = msg; } } } /* --- Météo -------------------------------------------------- */ async function fetchWeather() { try { const url = `https://api.openweathermap.org/data/2.5/weather` + `?q=${encodeURIComponent(CONFIG.city)}` + `&appid=${CONFIG.weatherApiKey}` + `&units=${CONFIG.weatherUnits}` + `&lang=${CONFIG.weatherLang}`; const res = await fetch(url); const data = await res.json(); if (data.cod !== 200) throw new Error(data.message); const temp = Math.round(data.main.temp); const desc = data.weather[0].description; const icon = data.weather[0].icon; const city = data.name; const unit = CONFIG.weatherUnits === 'metric' ? '°C' : '°F'; document.getElementById('hd-weather-temp').textContent = `${temp}${unit}`; document.getElementById('hd-weather-desc').textContent = desc; document.getElementById('hd-weather-city').textContent = city; document.getElementById('hd-weather-icon').src = `https://openweathermap.org/img/wn/${icon}@2x.png`; document.getElementById('hd-weather').classList.add('loaded'); } catch (e) { const w = document.getElementById('hd-weather'); if (w) { document.getElementById('hd-weather-city').textContent = 'Météo indispo'; w.classList.add('loaded'); } } } /* ============================================================ RACCOURCI CLAVIER Ctrl+K → focus recherche ============================================================ */ function setupSearchShortcut() { const badge = document.createElement('div'); badge.id = 'hd-shortcut-badge'; badge.textContent = 'Ctrl + K → Recherche'; document.body.appendChild(badge); setTimeout(() => { badge.classList.add('visible'); setTimeout(() => badge.classList.remove('visible'), 3000); }, 2000); document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); const input = document.querySelector('.homesearch') || document.querySelector('#searchtext') || document.querySelector('input[type="search"]') || document.querySelector('input[name="q"]'); if (input) { input.focus(); input.select(); } } if (e.key === 'Escape') { const active = document.activeElement; if (active && active.tagName === 'INPUT') active.blur(); } }); } /* ============================================================ TOOLTIPS AU SURVOL — affiche l'URL du service ============================================================ */ function setupTooltips() { const observer = new MutationObserver(() => attachTooltips()); observer.observe(document.body, { childList: true, subtree: true }); attachTooltips(); } function attachTooltips() { const tiles = document.querySelectorAll( 'ul.sortable > li:not([data-tooltip-set]), .item:not([data-tooltip-set])' ); tiles.forEach(tile => { tile.setAttribute('data-tooltip-set', '1'); tile.style.position = 'relative'; const link = tile.querySelector('a[href]'); if (!link || !link.href || link.href === '#') return; const tip = document.createElement('div'); tip.className = 'hd-tooltip'; try { const u = new URL(link.href); tip.textContent = u.host; } catch { tip.textContent = link.href; } tile.appendChild(tip); tile.addEventListener('mouseenter', () => tip.classList.add('show')); tile.addEventListener('mouseleave', () => tip.classList.remove('show')); }); } /* ============================================================ ANIMATION D'ENTRÉE DES TUILES ============================================================ */ function animateTiles() { const tiles = document.querySelectorAll('ul.sortable > li, .item, .app-card'); tiles.forEach((tile, i) => { tile.style.opacity = '0'; tile.classList.add('hd-animate'); tile.style.animationDelay = `${i * 40}ms`; tile.addEventListener('animationend', () => { tile.style.opacity = ''; tile.style.animationDelay = ''; }, { once: true }); }); }
Home dashboard
Application list
Tags list
Settings