public Switch User
System
Label Value Edit
Version 2.8.1
Language English (English)
Support Discord | Github | Blog
Donate Paypal
Appearance
Label Value Edit
Background Image - not set -
Trianglify No
Trianglify Random Seed heimdall
Treat Tags As: Folders
Miscellaneous
Label Value Edit
Homepage Search No
Default Search Provider - not set -
Link opens in Open in the same tab
Advanced
Label Value Edit
Custom CSS
/* ============================================================
   HEIMDALL — THÈME MODERNE v3
   Coller dans : Settings > Custom CSS
   ============================================================ */

/* --- GOOGLE FONTS ------------------------------------------ */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');

/* --- VARIABLES --------------------------------------------- */
:root {
  --glass-bg:     rgba(255, 255, 255, 0.07);
  --glass-border: rgba(255, 255, 255, 0.14);
  --glass-hover:  rgba(255, 255, 255, 0.13);
  --accent:       #6C63FF;
  --accent-glow:  rgba(108, 99, 255, 0.35);
  --text-primary: rgba(255, 255, 255, 0.95);
  --text-muted:   rgba(255, 255, 255, 0.55);
  --radius-card:  18px;
  --radius-btn:   12px;
  --blur:         blur(18px) saturate(160%);
  --transition:   all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

/* ============================================================
   FOND & BODY
   ============================================================ */
body {
  font-family: 'Inter', system-ui, -apple-system, sans-serif !important;
  /* Pour une image de fond, décommente et adapte :
     background-image: url('https://ton-image.com/bg.jpg');
     background-size: cover; background-position: center; */
  background: linear-gradient(135deg, #0d0d1a 0%, #111827 50%, #0d1117 100%) !important;
  background-attachment: fixed !important;
  min-height: 100vh;
}

/* Texture points subtile */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  background-image: radial-gradient(rgba(255,255,255,0.04) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
  z-index: 0;
}

/* ============================================================
   HEADER / LOGO
   ============================================================ */
.appheader {
  background: transparent !important;
  border-bottom: none !important;
  padding-top: 2.5rem !important;
}

.logo {
  filter: brightness(1.2) drop-shadow(0 0 18px var(--accent-glow)) !important;
}

/* ============================================================
   BARRE DE RECHERCHE
   ============================================================ */
#search-form,
form[action*="search"] {
  margin: 1.5rem auto 2rem !important;
  max-width: 520px;
}

.homesearch,
.homesearch input,
#search,
input[type="search"],
input[name="q"] {
  background: rgba(255, 255, 255, 0.85) !important;
  backdrop-filter: var(--blur) !important;
  -webkit-backdrop-filter: var(--blur) !important;
  border: 1px solid var(--glass-border) !important;
  border-radius: 50px !important;
  color: #111111 !important;
  font-family: 'Inter', sans-serif !important;
  font-size: 15px !important;
  font-weight: 400 !important;
  padding: 13px 22px !important;
  width: 100% !important;
  transition: var(--transition) !important;
  outline: none !important;
  box-shadow: none !important;
}

.homesearch:focus,
#search:focus,
input[type="search"]:focus,
input[name="q"]:focus {
  border-color: var(--accent) !important;
  background: rgba(255, 255, 255, 0.92) !important;
  box-shadow: 0 0 0 3px var(--accent-glow) !important;
  color: #111111 !important;
}

.homesearch::placeholder,
#search::placeholder,
input[type="search"]::placeholder {
  color: #666666 !important;
}

/* Combobox Tiles / moteur de recherche */
#searchprovider,
.homesearch ~ select,
form[action*="search"] select,
select#searchprovider {
  background: rgba(255, 255, 255, 0.85) !important;
  border: 1px solid var(--glass-border) !important;
  border-radius: 50px !important;
  color: #111111 !important;
  font-family: 'Inter', sans-serif !important;
  font-size: 13px !important;
  padding: 8px 14px !important;
  outline: none !important;
  cursor: pointer !important;
  -webkit-appearance: none !important;
  appearance: none !important;
}

#searchprovider option,
select#searchprovider option {
  background: #1a1b2e !important;
  color: #ffffff !important;
}

/* ============================================================
   FLÈCHE "→ Accéder" — MASQUÉE
   classe SVG FontAwesome : fa-arrow-alt-to-right
   ============================================================ */
.fa-arrow-alt-to-right,
svg.fa-arrow-alt-to-right,
.svg-inline--fa.fa-arrow-alt-to-right,
.appLink,
a.appLink,
.item a::after,
.app-card a::after,
ul.sortable > li a::after,
ul.sortable > li .appLink {
  display: none !important;
}

/* ============================================================
   TUILES (app cards)
   ============================================================ */
.item,
.app-card,
.sortable-item {
  background: var(--glass-bg) !important;
  backdrop-filter: var(--blur) !important;
  -webkit-backdrop-filter: var(--blur) !important;
  border: 1px solid var(--glass-border) !important;
  border-radius: var(--radius-card) !important;
  transition: var(--transition) !important;
  overflow: hidden !important;
  position: relative !important;
  cursor: pointer !important;
}

.item:hover,
.app-card:hover,
.sortable-item:hover {
  background: var(--glass-hover) !important;
  border-color: rgba(255, 255, 255, 0.28) !important;
  transform: translateY(-4px) scale(1.02) !important;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.4), 0 0 0 1px var(--glass-border) !important;
}

/* Trait accent en haut au hover */
.item::before,
.app-card::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--accent), #a78bfa);
  opacity: 0;
  transition: var(--transition);
}

.item:hover::before,
.app-card:hover::before {
  opacity: 1;
}

/* Lien dans la tuile */
.item a,
.app-card a {
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  justify-content: center !important;
  padding: 1.4rem 1rem !important;
  text-decoration: none !important;
  color: var(--text-primary) !important;
  height: 100% !important;
}

/* Icône / logo */
.item .icon,
.app-card .icon,
.item img,
.app-card img {
  width: 52px !important;
  height: 52px !important;
  object-fit: contain !important;
  border-radius: 12px !important;
  margin-bottom: 10px !important;
  transition: var(--transition) !important;
}

.item:hover .icon,
.app-card:hover .icon,
.item:hover img,
.app-card:hover img {
  transform: scale(1.1) !important;
}

/* Nom de l'appli */
.item .title,
.app-card .title,
.item span,
.app-card span {
  font-size: 13px !important;
  font-weight: 500 !important;
  color: var(--text-primary) !important;
  text-align: center !important;
  letter-spacing: 0.01em !important;
  line-height: 1.3 !important;
}
Custom JavaScript
/* ============================================================
   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 });
  });
}
Default tag - not set -
Skip TLS Verification (for self-signed certificates) Yes