/**
 * /app/dashboard/css/dashboard.css
 * Layout principal du dashboard g00dtime.
 * Utilise les variables CSS des thèmes (--color1..5, --colorfont, etc.)
 */

/* ═══════════════════════════════════════════════════════════════════════
   Reset & base
   ═══════════════════════════════════════════════════════════════════════ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root { --bottom-bar-height: 56px; }

html, body {
  height: 100%;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  background: var(--surface-1);
  color: var(--text);
  /* Pas de double-tap-zoom : le shell est une application, et le tapotement
     rapide sur une tuile ou un onglet était lu comme un zoom. Le reste du
     verrou est dans `/app/js/no-zoom.js` et la balise viewport. */
  touch-action: manipulation;
}

/* ═══════════════════════════════════════════════════════════════════════
   Layout principal : sidebar + content
   ═══════════════════════════════════════════════════════════════════════ */
#gt-app {
  display: flex;
  height: 100dvh;
  overflow: hidden;
  /* Le shell absorbe les marges système, une fois, pour tout le monde : ses
     enfants et ses iframes voient 0 (cf. `gt_safe_area_script()`). En dessous
     de 768 px c'est `#gt-content` qui s'en charge, il est alors `fixed`. */
  padding-top: var(--safe-top);
}

/* --------------------------------------------------------------------------
   Bouton Flash — exception §7 déclarée.
   C'est une affordance volontairement distincte : un dégradé conique animé qui
   doit attirer l'œil, pas se fondre dans le thème. Les cinq teintes vivent ici
   et nulle part ailleurs, pour rester auditables. Les convertir en --warning
   perdrait le dégradé à cinq arrêts, et donnerait un « feu » olive en thème
   nature. Tranché le 2026-08-10 : on garde tel quel.  design-tokens-ok
   -------------------------------------------------------------------------- */
:root {
  --flash-1:     #ff6b00;
  --flash-2:     #ffd000;
  --flash-3:     #ff9500;
  --flash-4:     #ffe566;
  --flash-ink:   #ffc830;
  --flash-hover: #ffe060;
}

/* ═══════════════════════════════════════════════════════════════════════
   Sidebar
   ═══════════════════════════════════════════════════════════════════════ */
#gt-sidebar {
  display: flex;
  flex-direction: column;
  width: 220px;
  min-width: 220px;
  height: 100%;
  background: var(--surface-2);
  border-inline-end: 1px solid var(--border);
  transition: width var(--t-base), min-width var(--t-base);
  overflow: hidden;
  z-index: 100;
  flex-shrink: 0;
}

#gt-sidebar.collapsed {
  width: 56px;
  min-width: 56px;
}

/* Header sidebar */
.gt-sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 10px 8px;
  border-bottom: 1px solid var(--border);
  gap: 8px;
  flex-shrink: 0;
}

.gt-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  overflow: hidden;
  flex: 1;
}

.gt-brand img {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  flex-shrink: 0;
}

.gt-brand-name {
  font-size: var(--font-base);
  font-weight: var(--fw-bold);
  color: var(--text);
  white-space: nowrap;
  opacity: 1;
  transition: opacity var(--t-fast);
}

#gt-sidebar.collapsed .gt-brand-name { opacity: 0; width: 0; }
#gt-sidebar.collapsed .gt-sidebar-header { justify-content: center; }
#gt-sidebar.collapsed #gt-sidebar-collapse { display: none; }

/* Bouton Flash drawer : caché sur desktop, affiché uniquement en mobile via media query */
#gt-drawer-flash-btn { display: none; }

/* ── Flash ⚡ — bordure électrique animée ────────────────────────────────── */
@property --flash-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@keyframes flashBorderSpin {
  to { --flash-angle: 360deg; }
}

@keyframes flashShimmer {
  0%   { transform: translateX(-180%) skewX(-20deg); }
  100% { transform: translateX(280%)  skewX(-20deg); }
}

@keyframes flashZap {
  0%, 100% { transform: scale(1);    filter: brightness(1); }
  45%       { transform: scale(1.35); filter: brightness(2.2) drop-shadow(0 0 5px var(--flash-2)); }
}

#gt-flash-section {
  padding: 8px 10px 4px;
  flex-shrink: 0;
}

#gt-flash-btn {
  position: relative;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1.5px solid transparent;
  border-radius: var(--radius-lg);
  cursor: pointer;
  font-size: var(--font-sm);
  font-weight: var(--fw-bold);
  letter-spacing: .5px;
  color: var(--flash-ink);
  /* Bordure conic-gradient rotative via background padding-box / border-box */
  background:
    linear-gradient(var(--surface-2), var(--surface-2)) padding-box,
    conic-gradient(from var(--flash-angle), var(--flash-1) 0%, var(--flash-2) 30%, var(--flash-3) 60%, var(--flash-4) 85%, var(--flash-1) 100%) border-box;
  animation: flashBorderSpin 3.5s linear infinite;
  box-shadow: 0 0 12px color-mix(in srgb, var(--flash-1) 18%, transparent), var(--shadow-2);
  overflow: hidden;
  transition: color .2s, transform .15s, box-shadow .2s;
}

/* Shimmer qui passe toutes les ~5s */
#gt-flash-btn::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  width: 55%;
  background: linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--flash-4) 18%, transparent) 50%, transparent 100%);
  animation: flashShimmer 5s ease-in-out infinite 1.2s;
  pointer-events: none;
  z-index: 1;
}

#gt-flash-btn:hover {
  color: var(--flash-hover);
  transform: translateY(-1px);
  box-shadow: 0 0 28px color-mix(in srgb, var(--flash-1) 55%, transparent), var(--shadow-2);
}
#gt-flash-btn:hover svg { animation: flashZap .38s ease forwards; }
#gt-flash-btn:active    { transform: scale(var(--scale-press)); }

/* Contenu au-dessus du shimmer */
#gt-flash-btn svg,
#gt-flash-btn .gt-flash-label { position: relative; z-index: 2; }

/* Mode collapsed : icône seule */
#gt-sidebar.collapsed #gt-flash-section { padding: 8px 6px 4px; }
#gt-sidebar.collapsed #gt-flash-btn     { padding: 10px; width: auto; }
#gt-sidebar.collapsed .gt-flash-label   { display: none; }

/* Bouton collapse */
#gt-sidebar-collapse {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: var(--space-1);
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  flex-shrink: 0;
  transition: color var(--t-fast), background var(--t-fast);
}
#gt-sidebar-collapse:hover { color: var(--text); background: var(--surface-3); }

/* Expand button (visible only when collapsed, bottom of sidebar) */
#gt-sidebar-expand {
  display: none;
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: var(--space-2);
  width: 100%;
  align-items: center;
  justify-content: center;
  transition: color var(--t-fast);
}
#gt-sidebar.collapsed #gt-sidebar-expand { display: flex; }
#gt-sidebar-expand:hover { color: var(--text); }

/* Liste des modules */
#gt-sidebar-modules {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 8px 6px;
  list-style: none;
  scrollbar-width: thin;
  scrollbar-color: var(--surface-3) transparent;
}

.gt-sidebar-item {
  position: relative;
  border-radius: var(--radius-md);
  margin-bottom: 2px;
  list-style: none;
  transition: opacity var(--t-fast);
}

.gt-sidebar-item.dragging { opacity: .4; }
.gt-sidebar-item.drag-over > .gt-sidebar-link { background: var(--surface-3); }
.gt-sidebar-item.hidden-module { display: none; }

.gt-sidebar-link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-2);
  border-radius: var(--radius-md);
  text-decoration: none;
  color: var(--text-muted);
  font-size: var(--font-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
  white-space: nowrap;
  overflow: hidden;
  position: relative;
  user-select: none;
  -webkit-user-select: none;
}

.gt-sidebar-link:hover {
  background: var(--surface-3);
  color: var(--text);
}

.gt-sidebar-link.active {
  background: var(--surface-4);
  color: var(--text);
}

.gt-sidebar-link.active .gt-sidebar-icon svg { stroke: var(--primary); }

/* Icône */
.gt-sidebar-icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gt-sidebar-icon svg {
  width: 18px;
  height: 18px;
  stroke: currentColor;
  stroke-width: 1.8;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Label */
.gt-sidebar-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity .15s, width .15s;
}

#gt-sidebar.collapsed .gt-sidebar-label { opacity: 0; width: 0; }

/* Badge */
.gt-sidebar-badge {
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--primary);
  color: var(--on-primary);
  font-size: 10px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: opacity .15s;
}

.gt-sidebar-badge[hidden] { display: none !important; }

#gt-sidebar.collapsed .gt-sidebar-badge {
  position: absolute;
  top: 4px;
  inset-inline-end: 6px;
  min-width: 14px;
  height: 14px;
  font-size: 8px;
  padding: 0 3px;
}

/* Tooltip en mode collapsed */
#gt-sidebar.collapsed .gt-sidebar-link::after {
  content: attr(data-label);
  position: absolute;
  inset-inline-start: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%);
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-md);
  font-size: var(--font-xs);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  z-index: 1000;
  box-shadow: var(--shadow-2);
  transition: opacity var(--t-fast) .1s;
}

#gt-sidebar.collapsed .gt-sidebar-link:hover::after { opacity: 1; }

/* Section separator */
.gt-sidebar-section-label {
  font-size: var(--font-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--text-muted);
  padding: var(--space-3) var(--space-2) var(--space-1);
  white-space: nowrap;
  overflow: hidden;
  transition: opacity var(--t-fast);
}

#gt-sidebar.collapsed .gt-sidebar-section-label { opacity: 0; }

/* Drag handle */
.gt-drag-handle {
  position: absolute;
  inset-inline-end: 6px;
  top: 50%;
  transform: translateY(-50%);
  opacity: 0;
  color: var(--text-muted);
  cursor: grab;
  transition: opacity .15s;
  display: flex;
}

.gt-sidebar-item:hover .gt-drag-handle { opacity: 1; }
@media (max-width: 768px) { .gt-drag-handle { display: none; } }

/* … sauf quand un pointeur survole. La poignée est masquée en mise en page
   étroite parce qu'elle ne sert à rien au DOIGT, pas parce qu'elle gênerait :
   une fenêtre de bureau rétrécie reçoit cette mise en page tout en gardant sa
   souris, et le glisser-déposer HTML5 qu'annonce la poignée y fonctionne déjà —
   rien ne le montrait. Elle reste en `opacity: 0` hors survol, comme au bureau.
   Après la règle qu'elle lève, à spécificité égale : c'est l'ordre qui tranche. */
@media (max-width: 768px) and (hover: hover) and (pointer: fine) {
  .gt-drag-handle { display: flex; }
}

/* Footer sidebar */
.gt-sidebar-footer {
  border-top: 1px solid var(--border);
  padding: 6px;
  flex-shrink: 0;
}

.gt-sidebar-footer .gt-sidebar-link {
  padding: 8px 10px;
}

/* Bouton Déconnexion */
.gt-logout-link {
  color: var(--danger) !important;
  opacity: .75;
  transition: opacity var(--t-fast), background var(--t-fast);
}
.gt-logout-link:hover { opacity: 1; background: color-mix(in srgb, var(--danger) 10%, transparent) !important; }
.gt-logout-link .gt-sidebar-icon svg { stroke: currentColor; }

/* User avatar */
.gt-user-avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--surface-4);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  color: var(--primary);
  flex-shrink: 0;
  overflow: hidden;
}

.gt-user-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ═══════════════════════════════════════════════════════════════════════
   Zone de contenu principale
   ═══════════════════════════════════════════════════════════════════════ */
#gt-content {
  flex: 1;
  overflow: hidden;
  position: relative;
  display: flex;
  flex-direction: column;
}

/* Vues (home, frames, native) */
.gt-view {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s;
}

.gt-view.active {
  opacity: 1;
  pointer-events: all;
  position: relative;
}

/* Slot des iframes */
#gt-frame-slot {
  position: absolute;
  inset: 0;
}

.gt-frame {
  width: 100%;
  height: 100%;
  border: none;
  background: var(--surface-1);
  display: none;
  position: absolute;
  inset: 0;
}

.gt-frame.active { display: block; }

/* Spinner de chargement (sibling de .gt-frame, caché par défaut) */
.gt-frame-loading {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: var(--surface-1);
  pointer-events: none;
  opacity: 1;
  transition: opacity .3s;
  z-index: 2;
}
.gt-frame.active + .gt-frame-loading,
.gt-frame-loading.visible { display: flex; }
.gt-frame-loading.done { opacity: 0; }

/* Slot des modules natifs */
#gt-native-slot {
  position: absolute;
  inset: 0;
  overflow: auto;
}

/* ═══════════════════════════════════════════════════════════════════════
   Home — Widget Grid
   ═══════════════════════════════════════════════════════════════════════ */
#gt-home {
  overflow-y: auto;
  background: var(--surface-1);
  padding: var(--space-5);
}

.gt-home-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-5);
}

.gt-home-greeting {
  font-size: var(--font-xl);
  font-weight: var(--fw-bold);
  color: var(--text);
}

.gt-home-date {
  font-size: var(--font-xs);
  color: var(--text-muted);
  margin-top: 2px;
}

/* Bouton « Personnaliser » de l'en-tête d'accueil. Les règles étaient en
   `style=` sur le bouton ; elles reviennent ici parce que le SVG vient
   maintenant de gt_icon('customize'), qui ne porte pas d'attribut de taille —
   c'est la feuille de style qui la donne, comme pour .gt-sidebar-icon svg. */
#gt-customize-btn {
  background: none;
  border: none;
  color: var(--colorfontalt);
  cursor: pointer;
  padding: 6px;
  border-radius: 8px;
  transition: color .15s;
  display: flex;
  align-items: center;
}

#gt-customize-btn svg {
  width: 18px;
  height: 18px;
  stroke-width: 2;
}

/* Grid */
.gt-widget-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-4);
}

/* Widget card */
.gt-widget {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  cursor: pointer;
  transition: border-color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
  position: relative;
  overflow: hidden;
  min-height: 110px;
}

.gt-widget:hover {
  border-color: var(--surface-4);
  transform: translateY(-1px);
  box-shadow: var(--shadow-3);
}

.gt-widget-large { grid-column: span 2; }

/* Widget drag-to-reorder */
.gt-widget[draggable] { cursor: grab; }
.gt-widget[draggable]:active { cursor: grabbing; }
.gt-widget.dragging  { opacity: .35; transform: scale(.97); }
.gt-widget.wg-drag-over {
  outline: 2px dashed var(--surface-4);
  outline-offset: 2px;
}

.gt-widget-drag-hint {
  margin-inline-start: auto;
  font-size: 14px;
  color: var(--text-muted);
  opacity: 0;
  transition: opacity .15s;
  user-select: none;
  padding-inline-start: 4px;
}
.gt-widget:hover .gt-widget-drag-hint { opacity: 1; }

.gt-widget-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.gt-widget-icon {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--primary) 13%, transparent);
  color: var(--primary-fg);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.gt-widget-icon svg {
  width: 16px;
  height: 16px;
  stroke: currentColor;
  stroke-width: 2;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.gt-widget-title {
  font-size: var(--font-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-muted);
}

.gt-widget-value {
  font-size: var(--font-3xl);
  font-weight: var(--fw-bold);
  color: var(--text);
  line-height: var(--leading-tight);
  margin-bottom: var(--space-1);
}

.gt-widget-subtitle {
  font-size: var(--font-2xs);
  color: var(--text-muted);
}

/* ── Tuile radio ───────────────────────────────────────────────────────────
   La pastille « en écoute ». Elle bat, parce que c'est la seule tuile de la
   grille dont le contenu est vivant — mais elle s'immobilise dès que le
   système demande moins de mouvement : un point qui clignote sans fin est
   exactement ce que ce réglage veut faire cesser. */
.wg-radio-live {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--danger, #e5484d);
  flex: none;
  animation: wg-radio-pulse 2s ease-in-out infinite;
}
@keyframes wg-radio-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .35; }
}
@media (prefers-reduced-motion: reduce) {
  .wg-radio-live { animation: none; }
}

/* Le bouton d'arrêt/reprise (L3). Il vit dans une tuile qui navigue au clic :
   il doit se lire comme un bouton, pas comme un morceau de la carte. */
.wg-radio-btn {
  margin-top: var(--space-2);
  padding: 4px 10px;
  font-size: var(--font-2xs);
  align-self: flex-start;
}

/* Items liste dans widget */
.gt-widget-items {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.gt-widget-item {
  font-size: 12.5px;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 4px 0;
  border-bottom: 1px solid var(--border);
}

.gt-widget-item:last-child { border-bottom: none; }

.gt-widget-item-sub {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 1px;
}

/* Skeleton loader */
.gt-skeleton {
  background: linear-gradient(90deg,
    var(--surface-3) 25%,
    var(--surface-4) 50%,
    var(--surface-3) 75%
  );
  background-size: 200% 100%;
  animation: gt-shimmer 1.4s infinite;
  border-radius: 6px;
}

@keyframes gt-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.gt-skeleton-line {
  height: 12px;
  margin-bottom: 8px;
  border-radius: 6px;
}

.gt-skeleton-line.short { width: 60%; }
.gt-skeleton-line.medium { width: 80%; }
.gt-skeleton-line.tall { height: 32px; width: 50%; }

/* Widget error state */
.gt-widget-error {
  color: var(--danger);
  font-size: 12px;
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Accent strip */
.gt-widget::before {
  content: '';
  position: absolute;
  top: 0;
  inset-inline-start: 0;
  inset-inline-end: 0;
  height: 2px;
  /* `--widget-color` était injectée par les couleurs d'identité de module,
     retirées au lot 2a : le repli était devenu la seule valeur possible. */
  background: var(--primary);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  opacity: .6;
}

/* ═══════════════════════════════════════════════════════════════════════
   Panel de personnalisation (overlay)
   ═══════════════════════════════════════════════════════════════════════ */
#gt-customize-panel {
  position: fixed;
  inset: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-base);
}

#gt-customize-panel.open {
  opacity: 1;
  pointer-events: all;
}

.gt-panel-backdrop {
  position: absolute;
  inset: 0;
  background: var(--overlay-bg);
  backdrop-filter: blur(4px);
}

.gt-panel-box {
  position: relative;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  width: min(460px, 90vw);
  max-height: 80vh;
  overflow-y: auto;
  padding: var(--space-5);
  box-shadow: var(--shadow-3);
}

.gt-panel-title {
  font-size: var(--font-lg);
  font-weight: var(--fw-bold);
  color: var(--text);
  margin-bottom: var(--space-5);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.gt-panel-close {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 20px;
  line-height: 1;
  padding: var(--space-1);
  border-radius: var(--radius-sm);
}
.gt-panel-close:hover { color: var(--text); }

/* Panel sections */
.gt-panel-section {
  margin-top: var(--space-5);
  border-top: 1px solid var(--border);
  padding-top: var(--space-5);
}
.gt-panel-section-first {
  margin-top: 0;
  border-top: none;
  padding-top: 0;
}

.gt-panel-section-title {
  font-size: var(--font-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-muted);
  margin-bottom: var(--space-2);
}

.gt-panel-hint {
  font-size: var(--font-xs);
  color: var(--text-muted);
  margin-bottom: var(--space-3);
}

/* Renvoi « le choix des modules se fait dans Réglages » (#gt-customize-more) */
.gt-customize-more-note {
  font-size: var(--font-xs);
  color: var(--text-muted);
  margin-bottom: var(--space-3);
}

/* Liste de tri des modules */
.gt-mod-sort-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.gt-mod-sort-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 7px var(--space-3);
  border-radius: var(--radius-md);
  background: var(--surface-3);
  border: 1px solid var(--border);
  cursor: grab;
  transition: background var(--t-fast), opacity var(--t-fast);
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;   /* pas de menu contextuel sur appui long (iOS) */
}
.gt-mod-sort-item:active { cursor: grabbing; }
.gt-mod-sort-item.dragging { opacity: .4; }
.gt-mod-sort-item.drag-over { border-color: var(--primary); background: var(--surface-4); }

.gt-mod-sort-drag {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  flex-shrink: 0;
  /* Zone de prise au doigt + geste réservé au tri (pas de scroll du panneau) */
  min-width: 28px;
  min-height: 28px;
  margin-inline-start: -6px;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* Pendant un tri tactile : aucune sélection de texte possible dans le panneau */
body.gt-sorting,
body.gt-sorting * {
  -webkit-user-select: none !important;
  user-select: none !important;
  -webkit-touch-callout: none !important;
}
.gt-mod-sort-drag svg { width: 14px; height: 14px; stroke: currentColor; fill: none; }

.gt-mod-sort-icon {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--primary) 13%, transparent);
  color: var(--primary-fg);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.gt-mod-sort-icon svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

.gt-mod-sort-label {
  flex: 1;
  font-size: var(--font-md);
  font-weight: var(--fw-medium);
  color: var(--text);
}

/* Module éteint par l'utilisateur. Il reste dans la liste, à sa place — c'est
   ici qu'on le rallume, et l'ordre est conservé pour son retour ; il garde donc
   sa poignée de glissement et son curseur "grab".

   Seuls l'icône et le libellé s'estompent, pas la ligne entière :
   l'interrupteur est le dernier élément actionnable de la rangée, l'estomper
   le ferait passer pour verrouillé — ce que dit le cadenas, et lui seul. */
.gt-mod-sort-item.gt-mod-off { background: transparent; }
.gt-mod-sort-item.gt-mod-off .gt-mod-sort-icon {
  background: color-mix(in srgb, var(--text-muted) 13%, transparent);
  color: var(--text-muted);
}
.gt-mod-sort-item.gt-mod-off .gt-mod-sort-label { color: var(--text-muted); }

/* Bouton discret « Disposition par défaut » sous la liste de tri */
.gt-panel-reset-btn {
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-muted);
  font-size: var(--font-xs);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: color var(--t-fast), border-color var(--t-fast), background var(--t-fast);
}
.gt-panel-reset-btn:hover {
  color: var(--text);
  border-color: var(--primary);
  background: var(--surface-3);
}
.gt-panel-reset-btn:disabled { opacity: .5; cursor: not-allowed; }

/* Interrupteur d'activation d'un module */
.gt-mod-switch {
  flex-shrink: 0;
  width: 38px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 11px;
  background: var(--surface-3);
  cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast);
}
.gt-mod-switch > span {
  display: block;
  width: 16px;
  height: 16px;
  margin-inline-start: 2px;
  border-radius: 50%;
  background: var(--text-muted);
  transition: transform var(--t-fast), background var(--t-fast);
}
.gt-mod-switch[aria-checked="true"] {
  background: var(--primary);
  border-color: var(--primary);
}
.gt-mod-switch[aria-checked="true"] > span {
  transform: translateX(16px);
  background: var(--on-primary);
}
.gt-mod-switch:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* État imposé par l'administrateur : informatif, non actionnable */
.gt-mod-lock {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  color: var(--text-muted);
}
.gt-mod-lock svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 1.8; }

/* Listes de préférences (worldclock / météo) */
.gt-pref-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: var(--space-3);
}

.gt-pref-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px var(--space-3);
  border-radius: var(--radius-md);
  background: var(--surface-3);
  border: 1px solid var(--border);
  font-size: var(--font-sm);
}

.gt-pref-item-label {
  flex: 1;
  color: var(--text);
}

.gt-pref-item-remove {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  padding: 0 2px;
  border-radius: var(--radius-sm);
  transition: color var(--t-fast);
}
.gt-pref-item-remove:hover { color: var(--danger); }

.gt-pref-empty {
  font-size: var(--font-xs);
  color: var(--text-muted);
  padding: 4px var(--space-1);
}

/* Select "Ajouter un fuseau" */
.gt-pref-add-row { display: flex; gap: var(--space-2); }

.gt-pref-add-select {
  flex: 1;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text);
  font-size: var(--font-sm);
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.4)' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-inline-end: 28px;
}
.gt-pref-add-select option { background: var(--surface-2); }
.gt-pref-add-select:focus { outline: none; border-color: var(--primary); }

/* Recherche météo */
.gt-pref-search-row {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.gt-pref-search-input {
  flex: 1;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text);
  font-size: var(--font-sm);
}
.gt-pref-search-input:focus { outline: none; border-color: var(--primary); }
.gt-pref-search-input::placeholder { color: var(--text-muted); }

.gt-pref-search-btn {
  padding: var(--space-2) var(--space-3);
  background: var(--primary);
  border: none;
  border-radius: var(--radius-md);
  color: var(--on-primary);
  font-size: var(--font-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: opacity var(--t-fast);
  white-space: nowrap;
}
.gt-pref-search-btn:hover { opacity: .85; }
.gt-pref-search-btn:disabled { opacity: .5; cursor: not-allowed; }

.gt-pref-result {
  padding: 7px var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--font-sm);
  color: var(--text);
  cursor: pointer;
  transition: background var(--t-fast);
}
.gt-pref-result:hover { background: var(--surface-4); }
.gt-pref-result.added { color: var(--text-muted); cursor: default; }

.gt-pref-error {
  font-size: var(--font-xs);
  color: var(--danger);
  margin-bottom: var(--space-2);
  padding: 0 var(--space-1);
}

.gt-default-view-select {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text);
  font-size: var(--font-md);
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.4)' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-inline-end: 32px;
}

.gt-default-view-select:focus {
  outline: none;
  border-color: var(--primary);
}

.gt-default-view-select option {
  background: var(--surface-2);
  color: var(--text);
}

/* ═══════════════════════════════════════════════════════════════════════
   Mobile — Bottom Bar + Drawer
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
  html, body { background: var(--surface-2); }
  input, textarea, select { font-size: max(16px, 1em) !important; }

  #gt-app { flex-direction: column; }

  /* Contenu principal : position fixe entre safe-area-top et bottom-bar.
     Le shell est le SEUL à réserver les marges système : ses iframes reçoivent
     `--safe-*: 0` (inc/pwa_include.php), sans quoi WebKit les leur répète. */
  #gt-content {
    position: fixed !important;
    top: var(--safe-top);
    inset-inline-start: 0;
    inset-inline-end: 0;
    bottom: calc(var(--bottom-bar-height) + var(--safe-bottom));
    height: auto !important;
    padding-bottom: 0;
  }

  /* `#gt-content` vient d'absorber les deux marges : ce qu'il contient ne doit
     pas les reprendre à son compte. Vaut pour les modules natifs, dont
     l'en-tête `.gt-page-header` vit dans le document du shell — les iframes,
     elles, sont servies par `gt_safe_area_script()`. */
  .gt-view { --safe-top: 0px; --safe-bottom: 0px; }

  /* #gt-home remplit tout #gt-content */
  #gt-home {
    position: absolute !important;
    inset: 0;
    overflow-y: auto;
    height: auto !important;
    padding: 16px;
  }
  .gt-widget-grid { grid-template-columns: 1fr; }
  .gt-widget-large { grid-column: span 1; }

  /* ── Sidebar = drawer qui monte du bas ─────────────────────────────── */
  #gt-sidebar {
    position: fixed;
    bottom: 0;
    inset-inline-start: 0;
    inset-inline-end: 0;
    width: 100% !important;
    min-width: 100% !important;
    height: auto;
    max-height: 75dvh;
    flex-direction: column;
    border-inline-end: none;
    border-bottom: none;
    border-top: 1px solid var(--border);
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    transform: translateY(100%);
    transition: transform var(--t-base) cubic-bezier(.32,.72,0,1);
    z-index: 200;
    backdrop-filter: blur(16px);
    /* Le tiroir est un cadre, pas un rouleau : c'est la grille du milieu qui
       défile, en-tête et pied restant collés à leurs bords. En `overflow-y:
       auto` ici, le tiroir était un SECOND conteneur de défilement autour du
       premier — le pied partait vers le haut avec le contenu, et « Déconnexion »
       ne s'atteignait qu'en faisant défiler. */
    overflow: hidden;
    /* Le drawer monte du bord de l'écran : son pied s'arrête au-dessus de
       l'indicateur d'accueil, pas dessous. */
    padding-bottom: var(--safe-bottom);
  }

  #gt-sidebar.mobile-open {
    transform: translateY(0);
  }

  /* Header du drawer */
  .gt-sidebar-header {
    order: unset;
    border-bottom: 1px solid var(--border);
    border-top: none;
    justify-content: space-between;
    padding: 10px 16px;
    flex-shrink: 0;
  }

  /* Logo seul dans le header du drawer (pas le texte) */
  .gt-brand-name { display: none; }

  /* Bouton Flash dans le header du drawer */
  #gt-drawer-flash-btn {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-md);
    border: 1.5px solid transparent;
    background:
      linear-gradient(var(--surface-2), var(--surface-2)) padding-box,
      conic-gradient(from var(--flash-angle), var(--flash-1) 0%, var(--flash-2) 30%, var(--flash-3) 60%, var(--flash-4) 85%, var(--flash-1) 100%) border-box;
    animation: flashBorderSpin 3.5s linear infinite;
    color: var(--flash-ink);
    font-size: 13px;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    letter-spacing: .02em;
  }

  #gt-drawer-flash-btn svg { flex-shrink: 0; }

  /* Cacher la section Flash séparée (elle est intégrée au header) */
  #gt-flash-section { display: none; }

  /* Grille de modules dans le drawer.
     Une vraie grille, et non un `flex-wrap` : en flex, chaque tuile prenait la
     largeur de son libellé, si bien qu'« Horloge mondiale » en occupait deux et
     que les rangées tombaient à 5, 5, 5, 4 puis 3 éléments alignés sur rien.
     `auto-fill` donne 5 colonnes sur 402 pt — la densité d'avant, au carré. */
  #gt-sidebar-modules,
  .gt-sidebar-footer {
    order: unset;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
    gap: 4px;
    padding: 8px;
    align-content: start;
  }

  /* Le milieu élastique du cadre : il prend ce qui reste entre l'en-tête et le
     pied, et lui seul défile.
     - `flex: 1 1 auto` et non `flex: 1` (bureau) : la base reste le contenu.
       Avec `flex-basis: 0`, la hauteur du tiroir dépendait d'un calcul de
       contribution `max-content` — d'où du vide résiduel qu'on ne s'expliquait
       pas. Ici, tiroir court = hauteur du contenu ; tiroir plafonné à 75dvh =
       la grille se comprime et défile.
     - `min-height: 0` est ce qui autorise cette compression : sans lui, un
       élément flex en colonne refuse de descendre sous la hauteur de son
       contenu, déborde du cadre, et pousse le pied hors de l'écran. C'est le
       défaut classique, et invisible tant que la liste tient. */
  #gt-sidebar-modules {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .gt-sidebar-header,
  .gt-sidebar-footer { flex: none; }

  .gt-sidebar-footer { border-top: 1px solid var(--border); }

  /* La tuile est une cellule de grille, plus un élément flex. */
  .gt-sidebar-item { flex: none; margin-bottom: 0; }

  /* `collapsed` est une préférence de BUREAU, mémorisée dans les prefs et
     réappliquée au chargement quelle que soit la largeur. Sur mobile le tiroir
     n'a pas de mode replié : la classe restait posée et masquait tous les
     libellés (icônes nues), plus le nom du produit et les titres de section.
     On la neutralise ici — même spécificité `#id.class` que les règles du
     mode replié, et postérieure dans la feuille. */
  #gt-sidebar.collapsed .gt-sidebar-label,
  #gt-sidebar.collapsed .gt-brand-name { opacity: 1; width: 100%; }
  #gt-sidebar.collapsed .gt-brand-name { width: auto; }
  #gt-sidebar.collapsed .gt-sidebar-section-label { opacity: 1; }
  #gt-sidebar.collapsed .gt-sidebar-header { justify-content: space-between; }
  #gt-sidebar.collapsed .gt-sidebar-link::after { content: none; }
  #gt-sidebar.collapsed #gt-sidebar-expand { display: none; }
  #gt-sidebar.collapsed .gt-sidebar-badge { top: 2px; inset-inline-end: 4px; }

  .gt-sidebar-link {
    flex-direction: column;
    justify-content: flex-start;
    gap: 4px;
    padding: 8px 4px;
    font-size: 10px;
    min-width: 0;
    height: 100%;
    text-align: center;
    white-space: normal;
  }

  /* Deux lignes réservées à tout le monde : « Horloge mondiale » cesse
     d'écarter sa colonne, et toutes les tuiles font la même hauteur — c'est ce
     qui rend les rangées droites. `break-word` et non `anywhere` : on coupe à
     l'espace, on ne casse pas « SexyCaptain » qui tient sur une ligne. */
  .gt-sidebar-label {
    flex: none;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: break-word;
    text-overflow: clip;
    line-height: 1.2;
    min-height: 2.4em;
    width: 100%;
  }

  .gt-sidebar-icon { width: 24px; height: 24px; }
  .gt-sidebar-icon svg { width: 22px; height: 22px; }
  .gt-sidebar-badge { position: absolute; top: 2px; inset-inline-end: 4px; }

  /* Boutons desktop cachés sur mobile */
  #gt-sidebar-collapse { display: none; }
  #gt-sidebar-expand   { display: none; }

  /* ── Bottom navigation bar ─────────────────────────────────────────── */
  #gt-bottom-bar {
    position: fixed;
    bottom: 0;
    inset-inline-start: 0;
    inset-inline-end: 0;
    /* La barre descend jusqu'au bord de l'écran et pousse ses icônes au-dessus
       de l'indicateur d'accueil : c'est son fond qui habille la bande, pas du
       vide. `--safe-bottom` vaut 0 partout ailleurs qu'en PWA iOS. */
    height: calc(var(--bottom-bar-height) + var(--safe-bottom));
    padding-bottom: var(--safe-bottom);
    display: none; /* affiché via JS sur mobile */
    flex-direction: row;
    align-items: center;
    background: var(--surface-2);
    border-top: 1px solid var(--border);
    z-index: 201;
    backdrop-filter: blur(20px);
    padding-inline-start: 4px;
    padding-inline-end: 4px;
    /* La bande du haut appartient à la poignée : sans elle, la poignée était
       posée sur les icônes, et deux choses distinctes qui se touchent se lisent
       comme une seule. */
    padding-top: 4px;
    gap: 0;
  }

  /* Bouton Flash dans la bottom bar */
  /* 38 px et non 50 : l'icône fait 20 px, les 15 px de marge intérieure de
     chaque côté se lisaient comme un trou entre le bouton et le premier onglet.
     La zone tactile reste à 38 × 40, au-dessus du minimum confortable. */
  #gt-bottom-flash {
    flex: 0 0 38px;
    height: 40px;
    border-radius: 12px;
    border: none;
    background: var(--surface-2);
    animation: none;
    color: var(--flash-ink);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    flex-shrink: 0;
  }

  #gt-bottom-flash::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(105deg, transparent 40%, color-mix(in srgb, var(--flash-4) 35%, transparent) 50%, transparent 60%);
    transform: translateX(-180%) skewX(-20deg);
    animation: flashShimmer 5s ease-in-out 1.5s infinite;
    pointer-events: none;
  }

  #gt-bottom-flash svg { width: 20px; height: 20px; position: relative; z-index: 1; }
  #gt-bottom-flash:active { opacity: .7; }

  /* Onglets modules dans la bottom bar */
  #gt-bottom-tabs {
    flex: 1;
    display: flex;
    flex-direction: row;
    align-items: stretch;
    min-width: 0;
  }

  .gt-bottom-tab {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 4px 2px;
    color: var(--text-muted);
    background: none;
    border: none;
    font-size: 9px;
    font-family: inherit;
    cursor: pointer;
    transition: color .15s;
    position: relative;
    min-width: 0;
    text-decoration: none;
  }

  .gt-bottom-tab.active { color: var(--primary); }
  .gt-bottom-tab:active { opacity: .7; }

  .gt-bottom-tab .gt-sidebar-icon {
    width: 22px;
    height: 22px;
    transition: transform var(--t-fast);
  }
  .gt-bottom-tab .gt-sidebar-icon svg { width: 20px; height: 20px; }

  /* ── L'onglet sélectionné, et les autres ─────────────────────────────────
     Au REPOS, tout le monde est à taille pleine : une barre de navigation doit
     d'abord se lire. La rétraction n'apparaît que pendant qu'on PARCOURT la
     rangée (`.is-browsing`, posée par `_setupBarSwipe()`), et se relâche cinq
     secondes après le dernier mouvement, ou tout de suite si l'on ouvre un
     module. C'est une aide au parcours, pas un état permanent.

     85 % et non 70 % : à 70 les libellés devenaient illisibles, ce qui les
     transformait en texture. À 85 on lit encore, et l'onglet sélectionné se
     détache quand même — il garde sa pastille, qui fait le plus gros du travail.

     Par `transform` et `opacity`, jamais par `width` : la sélection change à
     chaque événement de défilement, et une mise en page qui se recalcule à
     chaque image ferait sauter la rangée sous le doigt. Une transformation ne
     coûte qu'une composition, et surtout elle ne déplace personne. */
  .gt-bottom-tab .gt-sidebar-icon {
    transition: transform var(--t-fast);
  }

  /* `top` comme origine, sinon le libellé rétréci remonterait vers l'icône. */
  .gt-bottom-tab-label {
    transform-origin: center top;
    transition: transform var(--t-fast), opacity var(--t-fast);
  }

  #gt-bottom-bar.is-browsing .gt-bottom-tab .gt-sidebar-icon { transform: scale(.85); }
  #gt-bottom-bar.is-browsing .gt-bottom-tab-label { transform: scale(.85); opacity: .6; }

  /* `.is-current` suit le DÉFILEMENT (l'onglet sous le repère de sélection) ;
     `.active` marque le module OUVERT. Au repos les deux coïncident, la barre
     amenant l'onglet actif sous le repère à chaque navigation. Pendant qu'on
     fait glisser ils se séparent, et c'est voulu : la taille annonce ce qu'on
     est en train de viser, la couleur rappelle d'où l'on vient. */
  #gt-bottom-bar.is-browsing .gt-bottom-tab.is-current .gt-sidebar-icon { transform: scale(1); }
  #gt-bottom-bar.is-browsing .gt-bottom-tab.is-current .gt-bottom-tab-label {
    transform: scale(1);
    opacity: 1;
  }

  /* Le fond est posé sur l'onglet lui-même et non sur un `::before` en
     `z-index: -1` : un pseudo-élément négatif passe DERRIÈRE le fond de son
     parent, donc derrière celui de la barre — la pastille aurait disparu. */
  .gt-bottom-tab.is-current {
    background: var(--surface-3);
    border-radius: var(--radius-md);
  }

  /* Le choix explicite « pas d'animations » passe par `--t-fast: 0s` posé sur
     `<html>` (a11y.js) : la transition ci-dessus s'annule alors d'elle-même.
     Le réglage « comme mon système » (`data-motion="auto"`, ou pas d'attribut
     du tout avant que a11y.js n'ait tourné) n'est lu par aucun jeton — d'où ce
     média query, sans quoi le système serait le seul à ne pas être écouté. */
  @media (prefers-reduced-motion: reduce) {
    :root[data-motion="auto"] .gt-bottom-tab .gt-sidebar-icon,
    :root:not([data-motion]) .gt-bottom-tab .gt-sidebar-icon,
    :root[data-motion="auto"] .gt-bottom-tab-label,
    :root:not([data-motion]) .gt-bottom-tab-label { transition: none; }
  }

  /* `max-width: 54px` pour une fente de 48 : les libellés se chevauchaient d'un
     onglet à l'autre. La contrainte suit maintenant la fente, quelle qu'elle
     soit. */
  .gt-bottom-tab-label {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
  }

  .gt-bottom-tab-badge {
    position: absolute;
    top: 2px;
    inset-inline-end: 4px;
    background: var(--danger);
    color: var(--on-danger);
    font-size: 8px;
    min-width: 14px;
    height: 14px;
    border-radius: 7px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 3px;
    line-height: 1;
  }

  /* L'attribut hidden doit prendre le dessus sur display:flex */
  .gt-bottom-tab-badge[hidden] { display: none !important; }

  /* ── Poignée de dépli ──────────────────────────────────────────────────
     Posée en absolu au-dessus de la rangée : elle ne prend pas de place dans
     le flux, donc pas une place d'onglet. Zone tactile de 24 px de haut pour
     un trait de 4 px — on vise la poignée, pas le pixel. */
  #gt-bottom-grabber {
    position: absolute;
    top: 0;
    inset-inline-start: 50%;
    transform: translateX(-50%);
    width: 72px;
    /* 16 px de haut pour 4 px de trait : la zone tactile déborde de 4 px sur le
       haut des onglets, qui en font 44 — assez pour attraper la poignée au
       pouce, trop peu pour voler un appui destiné à un onglet. */
    height: 16px;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    background: none;
    border: none;
    padding: 4px 0 0;
    cursor: pointer;
    z-index: 1;
  }

  /* Le trait est plus étroit que sa zone tactile : on vise la poignée avec le
     pouce, mais on ne la regarde pas — 36 px la faisaient lire comme un
     élément de l'interface plutôt que comme une prise. */
  #gt-bottom-grabber span {
    display: block;
    width: 28px;
    height: 4px;
    border-radius: 2px;
    background: var(--text-muted);
    opacity: .35;
    transition: opacity var(--t-fast);
  }

  #gt-bottom-grabber:active span { opacity: .7; }

  /* ── Mode « défilement » ───────────────────────────────────────────────
     Tous les modules et le pied de menu tiennent dans la rangée, qui défile au
     doigt. Largeur fixe par onglet : en `flex: 1` ils se seraient partagé la
     largeur de l'écran, donc comprimés à mesure qu'on en ajoute — l'inverse de
     ce qu'on cherche. */
  #gt-bottom-tabs {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    /* Pas d'accrochage. `scroll-snap-align: center` rendait le premier et le
       dernier onglet inatteignables — pas assez de course pour les amener au
       centre —, et le corriger par des cales aux deux bouts remplissait de vide
       la gauche de la rangée dès qu'on était sur Accueil. C'est le POINT DE
       SÉLECTION qui se déplace maintenant (cf. `_focusedTab()`), pas la rangée
       qui s'allonge. */
    /* Ne pas déclencher le retour arrière du navigateur en butée de rangée. */
    overscroll-behavior-inline: contain;
    /* Le masque dit qu'il y a une suite, des deux côtés. */
    /* Le fondu de bord dit qu'il y a une suite. Il commençait à 12 px, donc il
       délavait le premier onglet alors même qu'il est collé au bord — ce qui
       ajoutait au trou après le bouton Flash. 6 px suffisent à suggérer la
       coupure sans manger d'onglet. */
    mask-image: linear-gradient(to right, transparent 0, #000 6px, #000 calc(100% - 6px), transparent 100%);
  }

  #gt-bottom-tabs::-webkit-scrollbar { display: none; }

  /* Glissement à la souris (cf. `_setupBarDragSouris()`).
     Au REPOS le curseur reste `pointer` : la rangée est faite d'onglets qui
     s'ouvrent au clic, et un `grab` posé dessus mentirait sur le geste premier.
     Il ne devient `grabbing` qu'une fois le seuil franchi — sur toute la
     descendance, sinon l'onglet survolé garderait son `pointer` au milieu du
     geste. `user-select` en renfort de `removeAllRanges()` : celui-ci nettoie
     après coup, celui-là empêche la surbrillance de clignoter pendant. */
  @media (hover: hover) and (pointer: fine) {
    #gt-bottom-tabs.is-grabbing,
    #gt-bottom-tabs.is-grabbing * { cursor: grabbing; user-select: none; }
  }


  /* 48 px et non 56 : les onglets étant rétractés en permanence, la fente n'a
     plus à loger une icône pleine taille. Avec vingt modules la rangée passe de
     1120 à 960 px — 14 % de moins à parcourir, tout le temps. */
  .gt-bottom-tab {
    flex: 0 0 48px;
  }

  .gt-bottom-tab--danger { color: var(--danger); opacity: .75; }

  /* ── Tiroir court (poignée) ────────────────────────────────────────────
     Le tiroir, habillé plus discret : moitié moins haut et sans en-tête. Le
     pied reste, sans quoi le geste donnerait moins que le bouton « Plus » —
     et ce serait un troisième état qui retire au lieu d'ajouter. */
  #gt-sidebar.bar-expanded {
    max-height: 55dvh;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  }

  #gt-sidebar.bar-expanded .gt-sidebar-header { display: none; }

  /* Masquer le bottom bar quand l'utilisateur compose un message dans une iframe */
  /* display:none + bottom:0 sont instantanés : pas de transition intermédiaire qui
     décalerait le calcul --kb-translate dans l'iframe. L'iframe reçoit la resize
     immédiatement et applyViewportVars() corrige kb avec le heightGain. */
  body.gt-composing #gt-bottom-bar { display: none !important; }
  body.gt-composing #gt-content    { bottom: 0 !important; }

  /* Backdrop */
  #gt-mobile-backdrop {
    position: fixed;
    inset: 0;
    background: var(--overlay-bg);
    z-index: 199;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--t-base);
  }

  #gt-mobile-backdrop.show {
    opacity: 1;
    pointer-events: all;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   Loader initial
   ═══════════════════════════════════════════════════════════════════════ */
#gt-loading {
  position: fixed;
  inset: 0;
  background: var(--surface-1);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  transition: opacity .3s;
}

#gt-loading.hidden {
  opacity: 0;
  pointer-events: none;
}

.gt-spinner {
  width: 36px;
  height: 36px;
  border: 3px solid var(--surface-3);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: gt-spin .7s linear infinite;
}

@keyframes gt-spin {
  to { transform: rotate(360deg); }
}
/* ─── Panel Annonces ─────────────────────────────────────────────── */
#gt-announce-panel {
  position: fixed;
  inset: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-base);
}
#gt-announce-panel.open {
  opacity: 1;
  pointer-events: all;
}
#gt-announce-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 60vh;
  overflow-y: auto;
  padding: 4px 0;
}
.gt-ann-state {
  padding: var(--space-5) 0;
  text-align: center;
  color: var(--text-muted);
  font-size: var(--font-md);
}
.gt-ann-error { color: var(--danger-fg); }
.gt-ann-item {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--surface-3);
  cursor: default;
  transition: background var(--t-fast);
}
.gt-ann-item:hover { background: var(--surface-4); }
.gt-ann-archived { opacity: .6; }
.gt-ann-item-header {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 4px;
}
.gt-ann-title {
  font-size: var(--font-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
  flex: 1;
}
.gt-ann-badges { display: flex; gap: 4px; flex-shrink: 0; }
.gt-ann-badge {
  font-size: var(--font-2xs);
  font-weight: var(--fw-semibold);
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.gt-ann-badge-new  { background: var(--primary); color: var(--on-primary); }
.gt-ann-badge-arch { background: var(--surface-4); color: var(--text-muted); }
.gt-ann-summary {
  font-size: var(--font-xs);
  color: var(--text-muted);
  margin-bottom: var(--space-1);
  line-height: var(--leading-snug);
}
.gt-ann-body {
  font-size: var(--font-xs);
  color: var(--text);
  line-height: var(--leading-normal);
  padding: var(--space-2) 0 var(--space-1);
  border-top: 1px solid var(--surface-3);
  margin-top: var(--space-1);
}
.gt-ann-body a { color: var(--primary); }
.gt-ann-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 8px;
}
.gt-ann-date {
  font-size: 11px;
  color: var(--text-muted);
}
.gt-ann-toggle {
  background: none;
  border: none;
  font-size: 12px;
  color: var(--primary);
  cursor: pointer;
  padding: 2px 0;
}
.gt-ann-toggle:hover { text-decoration: underline; }
.gt-ann-cta {
  display: inline-block;
  margin-top: 10px;
  font-size: 13px;
  font-weight: 600;
  color: var(--primary);
  text-decoration: none;
}
.gt-ann-cta:hover { text-decoration: underline; }

/* ═══════════════════════════════════════════════════════════════════════
   Accueil invité (D17)
   Les widgets de la vue membre interrogent des API qui refusent l'anonyme :
   un invité verrait une page de squelettes qui ne se remplissent jamais.
   On lui montre à la place ce qu'il peut faire, et ce qui manque sans compte.
   ═══════════════════════════════════════════════════════════════════════ */
.gt-guest-home {
  max-width: 78ch;
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 14px);
  background: var(--surface-2);
}

.gt-guest-kicker {
  margin: 0 0 var(--space-2);
  font-size: var(--font-xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent, var(--text-muted));
}

.gt-guest-home h2 {
  margin: 0 0 var(--space-3);
  font-size: var(--font-lg);
  color: var(--text);
}

.gt-guest-lede {
  margin: 0 0 var(--space-3);
  color: var(--text-muted);
  line-height: 1.55;
}

.gt-guest-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.gt-guest-btn {
  display: inline-block;
  padding: 10px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md, 10px);
  color: var(--text);
  background: var(--surface-3);
  text-decoration: none;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.gt-guest-btn:hover {
  background: var(--surface-4);
}

.gt-guest-btn-primary {
  background: var(--accent, var(--surface-4));
  border-color: transparent;
  color: var(--on-accent);
  font-weight: var(--fw-bold);
}

@media (prefers-reduced-motion: reduce) {
  .gt-guest-btn { transition: none; }
}

/* Vitrine des modules dans l'accueil invité (D17). Le mode invité ne montre
   qu'un module ; sans cette liste, un visiteur repart en croyant qu'habask est
   un site de quiz. */
.gt-guest-section {
  margin: var(--space-5) 0 var(--space-3);
  font-size: var(--font-xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.gt-guest-modules {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
}

@media (min-width: 720px) {
  .gt-guest-modules { grid-template-columns: 1fr 1fr; }
}

.gt-guest-modules li {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md, 10px);
  background: var(--surface-3);
}

.gt-guest-modules strong {
  color: var(--text);
  font-size: var(--font-sm);
}

.gt-guest-modules span {
  color: var(--text-muted);
  font-size: var(--font-xs);
  line-height: 1.5;
}

.gt-guest-promise {
  margin: var(--space-4) 0 0;
  padding: var(--space-3);
  border-inline-start: 3px solid var(--accent, var(--border));
  color: var(--text-muted);
  line-height: 1.55;
}

/* ═══════════════════════════════════════════════════════════════════════════
   CENTRE DE NOTIFICATIONS (037)
   La cloche vit dans l'en-tête de la barre latérale, à côté du bouton Flash.
   Tout est en tokens : ce bloc doit tenir sur les sept thèmes, dont trois
   clairs, et pas seulement sur le nocturne où on le regarde en l'écrivant.
   ═══════════════════════════════════════════════════════════════════════════ */

.gt-notif-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border: none;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background var(--t-fast, .15s), color var(--t-fast, .15s);
}
.gt-notif-btn:hover {
  background: color-mix(in oklab, var(--text) 8%, transparent);
  color: var(--text);
}
.gt-notif-btn:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}
/* Non lues : la cloche prend la couleur d'accent, pour que la pastille ne soit
   pas le seul signal — un daltonisme ou une pastille masquée par le pouce ne
   doivent pas faire perdre l'information. */
.gt-notif-btn.has-unread { color: var(--primary); }

.gt-notif-badge {
  position: absolute;
  top: 1px;
  inset-inline-end: 1px;
  min-width: 15px;
  height: 15px;
  padding: 0 4px;
  border-radius: var(--radius-pill);
  background: var(--danger, #ef4444);
  color: var(--on-danger, #fff);
  font-size: var(--font-2xs);
  font-weight: var(--fw-bold);
  line-height: 15px;
  text-align: center;
  box-sizing: border-box;
  pointer-events: none;
}
.gt-notif-badge[hidden] { display: none; }

/* Le panneau réutilise .gt-panel-* de « Personnaliser » : même boîte, même
   fond, même fermeture. Seule la liste est propre au centre. */
/* Centré, comme « Personnaliser » — et non collé en haut.
   En `display: block`, la boîte se posait contre le bord supérieur du
   conteneur, donc sur iPhone derrière l'heure et l'encoche. Le patron de
   `#gt-customize-panel` (flex + centrage) est repris tel quel, avec en plus les
   marges de sécurité : `inset: 0` couvre l'écran physique, encoche comprise,
   et rien ne les applique à notre place. */
#gt-notif-panel {
  position: fixed;
  inset: 0;
  z-index: 2400;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(env(safe-area-inset-top, 0px) + var(--space-4))
           calc(env(safe-area-inset-right, 0px) + var(--space-4))
           calc(env(safe-area-inset-bottom, 0px) + var(--space-4))
           calc(env(safe-area-inset-left, 0px) + var(--space-4));
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-base);
}

#gt-notif-panel.open {
  opacity: 1;
  pointer-events: all;
}

/* `100%` et non `80vh` : la hauteur disponible est déjà celle du conteneur,
   marges de sécurité déduites. Sur iPhone, `vh` compte la zone masquée par la
   barre du navigateur et laisse la boîte dépasser. */
#gt-notif-panel .gt-panel-box {
  max-height: 100%;
}

.gt-notif-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.gt-notif-item { margin: 0; }

.gt-notif-link {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3);
  border: none;
  border-radius: var(--radius-md);
  background: color-mix(in oklab, var(--surface-3) 95%, transparent);
  color: inherit;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--t-fast, .15s);
}
.gt-notif-link:hover {
  background: color-mix(in oklab, var(--surface-3) 85%, var(--primary) 15%);
}
.gt-notif-link:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

/* Non lue : un liseré, pas un fond criard — la liste doit rester lisible même
   quand tout y est non lu, ce qui est le cas normal en la découvrant. */
.gt-notif-item.is-unread .gt-notif-link {
  border-inline-start: 3px solid var(--primary);
  padding-inline-start: calc(var(--space-3) - 3px);
}

.gt-notif-avatar {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-circle);
  object-fit: cover;
  flex-shrink: 0;
  background: var(--surface-2);
}
.gt-notif-avatar--none {
  display: block;
  background: color-mix(in oklab, var(--primary) 25%, var(--surface-2));
}

.gt-notif-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.gt-notif-title {
  font-size: var(--font-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
}
.gt-notif-body {
  font-size: var(--font-sm);
  color: var(--text-muted);
  /* Deux lignes puis coupe : une notification est un résumé, pas un article. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.gt-notif-time {
  font-size: var(--font-2xs);
  color: var(--text-muted);
  opacity: .8;
}

/* La cloche de la barre du bas — téléphone uniquement.
   Elle reprend la géométrie de `#gt-bottom-flash`, son voisin immédiat : deux
   boutons côte à côte de tailles différentes se remarqueraient plus que ce
   qu'ils signalent. Le `overflow: visible` est nécessaire, contrairement au
   bouton Flash : la pastille déborde du coin, et le `overflow: hidden` du
   voisin la couperait. */
@media (max-width: 768px) {
  #gt-bottom-notif {
    flex: 0 0 38px;
    height: 40px;
    width: auto;
    border-radius: 12px;
    border: none;
    background: var(--surface-2);
    color: var(--text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    position: relative;
    overflow: visible;
    flex-shrink: 0;
  }
  #gt-bottom-notif svg { width: 20px; height: 20px; }
  #gt-bottom-notif.has-unread { color: var(--primary); }
  #gt-bottom-notif:active { filter: brightness(1.15); }
}

/* Et l'inverse : la cloche de la barre latérale n'a rien à faire au téléphone,
   où son en-tête est masqué en tiroir court. La laisser afficherait deux
   cloches dès qu'on déplie le tiroir complet. */
@media (max-width: 768px) {
  #gt-notif-btn { display: none; }
}

/* ── Feuille de partage du lien d'invitation (partage, lot 2) ──────────────
 *
 * Le REPLI de `navigator.share()`, montré aux ordinateurs et aux navigateurs
 * sans Web Share. Sur téléphone, c'est la feuille du système qui s'ouvre et
 * rien de ceci ne sert — d'où la mise en page : centrée sur grand écran,
 * collée en bas sur petit, là où la main tient l'appareil.
 */

.gt-invite-back {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(0, 0, 0, 0.55); /* design-tokens-ok: un voile d'ombre, pas une couleur d'interface — il doit valoir pour les sept thèmes */
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: var(--space-4);
}

.gt-invite-sheet {
  width: 100%;
  max-width: 460px;
  box-sizing: border-box;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-3);
  padding: var(--space-5);
  /* Sous l'encoche et la barre de geste, cf. les jetons `--safe-*`. */
  margin-bottom: var(--safe-bottom, 0);
}

.gt-invite-title {
  margin: 0 0 var(--space-2);
  font-size: var(--font-xl);
}

.gt-invite-lede {
  margin: 0 0 var(--space-5);
  color: var(--text-muted);
  font-size: var(--font-md);
  line-height: var(--leading-snug);
}

.gt-invite-targets {
  display: grid;
  /* 84 et non 96 : les quatre cibles tiennent alors sur UNE rangée dans la
     largeur de la feuille (4 × 84 + 3 × 12 = 372 < 412). À 96, « E-mail »
     partait seul à la ligne. En dessous de 372 px utiles, la grille se replie
     d'elle-même. */
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

.gt-invite-target {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-2);
  border-radius: var(--radius-lg);
  background: var(--surface-3);
  color: var(--text);
  text-decoration: none;
  font-size: var(--font-sm);
  text-align: center;
  transition: transform var(--t-fast);
}

.gt-invite-target:hover { transform: scale(1.04); }
.gt-invite-emoji { font-size: var(--font-xl); line-height: 1; }

.gt-invite-field {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.gt-invite-field .gt-input {
  flex: 1;
  min-width: 0; /* sans lui, un lien long pousse le bouton « Copier » hors de la feuille */
}

.gt-invite-note {
  margin: 0 0 var(--space-4);
  font-size: var(--font-xs);
  color: var(--text-muted);
  line-height: var(--leading-snug);
}

.gt-invite-close { width: 100%; }

@media (min-width: 560px) {
  .gt-invite-back { align-items: center; }
}
