/* ==========================================================================
   Styles propres a Persolinks, par-dessus le theme Velzon.
   Tout ce qui touche a la grille de widgets vit ici plutot que dans une vue :
   la coquille peut changer sans que ces regles bougent.
   ========================================================================== */

/* ------------------------------- Identite -------------------------------- */

/* Velzon arrive avec un bleu de demonstration. On lui substitue l'indigo et
   l'ambre de la page d'accueil publique, pour qu'un visiteur qui cree son
   compte ne change pas d'univers en franchissant la connexion. */
:root {
  --vz-primary:      #4f6ef7;
  --vz-primary-rgb:  79, 110, 247;
  --pl-nuit:         #14162b;
  --pl-nuit-haute:   #2d3561;
  --pl-aube:         #ffb47a;
}

/* Le fond des ecrans de connexion est une photo de banque d'images livree
   avec le theme. On la remplace par le degrade de la page d'accueil : des
   inconnus souriants n'ont rien a faire sur un ecran de connexion. */
.auth-one-bg {
  background-image: linear-gradient(135deg, var(--pl-nuit) 0%, var(--pl-nuit-haute) 100%) !important;
}

.auth-one-bg .bg-overlay {
  opacity: .55;
  background: radial-gradient(ellipse at 50% 120%,
              rgba(255, 180, 122, .22) 0%, transparent 60%);
}

/* --------------------------- Grille de widgets --------------------------- */

.grid-stack {
  background: transparent;
  /* Un retrait de 40 px en haut : une carte posee tout en haut ne colle pas au
     bord de la fenetre. Une MARGE, pas un padding : les cartes de Gridstack
     sont en absolu et se calent sur le bord de la boite de contenu, qu'un
     padding ne deplace pas — une marge, elle, decale toute la grille (cartes
     comprises) et laisse intact le reperage cellule/pixel du glisser, qui lit
     la position ecran de la grille. L'en-tete s'aligne dessus (top: 40px). */
  margin-top: 40px;
}

/* Le bloc reserve a l'ephemeride : invisible, il ne fait que tenir la place.
   L'ephemeride, portee par l'en-tete au-dessus, s'affiche par-dessus. En
   edition, un lisere discret rappelle que le coin est pris. */
.pl-reserve-contenu {
  background: transparent;
  border: 0;
  box-shadow: none;
  outline: 0;
  pointer-events: none;
}
body.pl-edition .pl-reserve-contenu {
  outline: 1px dashed rgba(255, 255, 255, .18);
  outline-offset: 2px;
}

.pl-widget {
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.pl-widget-entete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: .55rem .85rem;
  border-bottom: 1px solid var(--vz-border-color);
  font-size: .8125rem;
  font-weight: 500;
}

/* « flex » et non « block » : un widget qui veut occuper toute la hauteur qu'on
   lui donne — pour y repartir son contenu plutot que laisser un trou en bas —
   a besoin d'un parent dont la hauteur est definie. « flex: 1 » la lui donne,
   mais « height: 100% » sur l'enfant ne s'y accroche que si le parent est
   lui-meme un conteneur flex. */
.pl-widget-corps {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: auto;
  padding: .85rem;
}

/* Le fragment d'un widget est le seul enfant du corps : il en prend toute la
   hauteur, a lui de decider s'il l'utilise. */
.pl-widget-corps > * {
  flex: 1;
  min-height: 0;
}

/* Les actions n'apparaissent qu'en mode edition : hors de ce mode, la page
   doit rester calme et se lire comme un tableau de bord, pas comme un
   editeur. */
.pl-widget-actions {
  display: none;
  gap: .25rem;
}

body.pl-edition .pl-widget-actions {
  display: flex;
}

body.pl-edition .grid-stack-item-content {
  outline: 1px dashed var(--vz-border-color);
  outline-offset: 2px;
}

/* Le curseur dit ce qu'on peut faire. En edition, une carte deplacable montre
   la main ouverte ; pendant le glisser, la main fermee. Une ancre (verrouillee)
   garde la fleche : elle ne se saisit pas. Gridstack pose « ui-draggable-dragging »
   sur l'element tire, et la regle sur <body> couvre tout le geste, y compris
   quand le curseur sort un instant de la carte. */
body.pl-edition .grid-stack-item:not(.pl-verrouille) > .grid-stack-item-content {
  cursor: grab;
}
body.pl-edition .grid-stack-item.ui-draggable-dragging,
body.pl-edition .grid-stack-item.ui-draggable-dragging > .grid-stack-item-content {
  cursor: grabbing;
}
body.pl-glisse,
body.pl-glisse * {
  cursor: grabbing !important;
}

/* Gridstack pose sa propre ombre au deplacement ; on garde la carte Velzon
   comme surface pour que widgets et reste de l'application se ressemblent. */
.grid-stack-item-content {
  overflow: visible;
}

/* Le choix de largeur ne se montre qu'en edition, comme la poignee qu'il
   complete : hors edition, l'en-tete ne porte que le nom du widget. Compact,
   pour tenir dans l'en-tete a cote des icones sans les repousser. */
.pl-largeur {
  display: none;
  width: auto;
  padding: 0.05rem 1.3rem 0.05rem 0.4rem;
  font-size: 0.7rem;
  background-position: right 0.25rem center;
  background-size: 12px 9px;
}

body.pl-edition .pl-largeur {
  display: inline-block;
}

/* Plus de decalage vertical porte par les cartes.
 *
 * Tant que la grille commencait sous un en-tete de 200 px, deux marges
 * corrigeaient l'agencement : les cartes de la banniere REMONTAIENT de 50 px
 * pour combler le vide au-dessus d'elles, et les cartes du bas DESCENDAIENT de
 * 30 px pour respirer sous la banniere. La grille demarrant desormais au sommet
 * de la page (marge negative sur .grid-stack), la rangee 0 est deja tout en
 * haut : une remontee tirerait ces cartes hors de l'ecran, et une descente
 * decalerait la rangee 0 de sa place. Les coordonnees de Gridstack font foi
 * telles quelles. La respiration sous la banniere, si elle manque, se reglera
 * par la hauteur du fond, pas en deplacant les cartes. */

/* Surlignage bref d'une carte qu'on vient d'ajouter.
 *
 * Le serveur pose la carte dans le premier trou libre sous la banniere ; sur
 * une page remplie, ce trou peut etre sous le pli. persolinks-grid.js l'amene
 * a l'ecran puis pose « pl-nouveau » deux secondes : cet anneau la designe a
 * l'arrivee, pour qu'un ajout hors du champ initial se voie tout de meme.
 * L'anneau est sur .grid-stack-item-content, la surface visible, et non sur la
 * boite Gridstack qui la deborde. */
@keyframes pl-surlignage {
  from { box-shadow: 0 0 0 3px var(--vz-primary, #6691e7), 0 0 22px 4px var(--vz-primary, #6691e7); }
  to   { box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent; }
}

.grid-stack-item.pl-nouveau > .grid-stack-item-content {
  animation: pl-surlignage 2s ease-out;
  border-radius: var(--vz-border-radius, 0.25rem);
}

@media (prefers-reduced-motion: reduce) {
  .grid-stack-item.pl-nouveau > .grid-stack-item-content { animation: none; }
}

/* ------------------------- Messages transitoires ------------------------- */

/* Alerte posee par le script de la grille quand une action echoue : elle
   s'insere en haut du contenu et disparait d'elle-meme. */
.pl-alerte {
  position: sticky;
  top: 0;
  z-index: 5;
}

/* ==========================================================================
   Page d'accueil : hero, image de fond, cartes en verre depoli
   ========================================================================== */

/* L'image et son voile sont deux calques fixes, peints derriere tout le reste.
 *
 * Pourquoi des calques plutot qu'un background sur <body> : le flou des cartes
 * repose sur backdrop-filter, qui ne floute que ce qui est *peint derriere*
 * l'element dans le meme contexte d'empilement. Un fond porte par <body> n'en
 * fait pas partie, et les cartes resteraient transparentes sans flou. */
/* Le fond est celui de la BANNIERE, pas de la page.
 *
 * Il etait pose en « fixed » sur toute la fenetre : l'image choisie s'etalait
 * derriere la page entiere, ce qui n'est pas ce qu'on demande a une banniere,
 * et la faisait suivre le regard au defilement au lieu de rester en haut.
 *
 * Ancre en absolu sur le haut du DOCUMENT et borne a la hauteur de la
 * banniere, il defile avec elle et s'arrete ou elle s'arrete. */
.pl-fond {
    position: absolute;
    inset: 0 0 auto 0;
    height: var(--pl-hero-hauteur);
    z-index: -2;
    background-image: var(--pl-fond);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

/* Le degrade choisi, pose SUR l'image et dose par le membre.
 *
 * Un calque distinct plutot qu'un second background sur .pl-fond : CSS ne sait
 * pas donner une opacite a une seule couche de background, et melanger les deux
 * obligerait a reecrire chaque degrade en rgba — impossible pour un SVG servi
 * comme image. Deux elements, une propriete « opacity » : le membre regle un
 * pourcentage et rien d'autre ne bouge. */
.pl-fond-teinte {
    position: absolute;
    inset: 0 0 auto 0;
    height: var(--pl-hero-hauteur);
    z-index: -2;
    opacity: var(--pl-fond-opacite, 1);
    background-image: var(--pl-fond-teinte);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

/* Le voile n'est pas une decoration : sans lui, un texte clair pose sur une
 * photo claire devient illisible, et l'inverse aussi. Il couvre exactement la
 * banniere, puisque c'est la seule zone ou une image est peinte. */
.pl-fond-voile {
    position: absolute;
    inset: 0 0 auto 0;
    height: var(--pl-hero-hauteur);
    z-index: -1;
    background: linear-gradient(
        to bottom,
        rgba(0, 0, 0, .34) 0,
        rgba(0, 0, 0, .12) 22rem,
        rgba(0, 0, 0, .22) 100%
    );
}

[data-bs-theme="dark"] .pl-fond-voile {
    background: linear-gradient(
        to bottom,
        rgba(0, 0, 0, .60) 0,
        rgba(0, 0, 0, .45) 22rem,
        rgba(0, 0, 0, .55) 100%
    );
}

/* Le bas de la banniere se fond dans la page.
 *
 * Les trois calques (image, teinte, voile) sont estompes ensemble par un
 * masque : opaques en haut, ils perdent leur alpha vers le bas et laissent
 * apparaitre le fond uni de la page. La transition remplace le bord net que
 * marquait le lisere d'un pixel — masque sur le calque, celui-ci disparait de
 * lui-meme dans la zone devenue transparente. Les deux prefixes : Safari et
 * les Chromium anciens ne connaissent « mask-image » que prefixe. */
.pl-fond,
.pl-fond-teinte,
.pl-fond-voile {
    -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 62%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 0, #000 62%, transparent 100%);
}

/* Aucun retrait en haut : la banniere le porte elle-meme, pour que son bord
   inferieur coincide au pixel avec celui du calque de degrade. */
.pl-page {
    position: relative;
    min-height: 100vh;
    padding: 0 clamp(1rem, 3vw, 2.5rem) 3rem;
}

/* ── Hero ── */

.pl-hero {
    margin-bottom: 1.25rem;
}

/* L'avatar reste cliquable meme si l'en-tete laisse les clics traverser :
   voir le positionnement de .pl-hero dans nocturne.css (sorti du flux, il
   survole la grille pour la laisser monter jusqu'au sommet). */
.pl-hero .pl-compte {
    pointer-events: auto;
}

.pl-hero-haut {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
}

/* L'ephemeride de la banniere.
 *
 * Elle remplace la salutation, et doit tenir dans les 200 px de l'en-tete :
 * celui-ci a une hauteur FIXE, dont depend la geometrie de la banniere
 * (200 + 5 rangees x 80 = 600). Un bloc trop haut ne pousserait pas l'en-tete,
 * il deborderait sur la grille. Les tailles ci-dessous sont donc choisies pour
 * que l'ensemble mesure environ 165 px. */
.pl-ephem {
    color: #fff;
    /* Une photo peut avoir n'importe quelle luminosite sous ce texte : l'ombre
       porte la lisibilite la ou le voile ne suffit pas. */
    text-shadow: 0 1px 12px rgba(0, 0, 0, .45);
}

.pl-ephem-date {
    margin: 0;
    color: #fff;
    font-size: clamp(1.375rem, 2.6vw, 2rem);
    font-weight: 300;
    line-height: 1.1;
}

.pl-ephem-saint {
    margin-top: .15rem;
    font-size: .9375rem;
    opacity: .9;
}

.pl-ephem-quantieme {
    margin-top: .1rem;
    font-size: .8125rem;
    opacity: .75;
}

/* La barre horizontale, reprise de l'ancienne carte : elle separait deja ce
   qui se calcule de ce qui depend d'un lieu. */
.pl-ephem-soleil {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-top: .5rem;
    padding-top: .5rem;
    border-top: 1px solid rgba(255, 255, 255, .28);
    font-size: .875rem;
    font-variant-numeric: tabular-nums;
    /* La barre ne court pas sur toute la largeur du hero : elle borne le bloc
       d'ephemeride, pas la banniere. */
    max-width: 22rem;
}

.pl-ephem-soleil i {
    opacity: .85;
    margin-right: .15rem;
}

.pl-ephem-lieu {
    margin-left: auto;
    font-size: .75rem;
    opacity: .6;
}

.pl-hero-actions {
    display: flex;
    gap: .5rem;
    margin-top: 1.25rem;
}

/* ── Avatar et menu ── */

.pl-avatar {
    width: 2.5rem;
    height: 2.5rem;
    flex: 0 0 auto;
    border: 1px solid rgba(255, 255, 255, .35);
    border-radius: 50%;
    background: rgba(255, 255, 255, .18);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: #fff;
    font-weight: 500;
    line-height: 1;
}

.pl-avatar:hover,
.pl-avatar:focus-visible {
    background: rgba(255, 255, 255, .3);
}

.pl-avatar-initiale {
    text-transform: uppercase;
}

/* ── Bouton en verre, pour ce qui se pose sur l'image ── */

.pl-btn-verre {
    border: 1px solid rgba(255, 255, 255, .35);
    background: rgba(255, 255, 255, .18);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: #fff;
}

.pl-btn-verre:hover,
.pl-btn-verre:focus-visible {
    background: rgba(255, 255, 255, .3);
    color: #fff;
}

/* ── Les cartes de widget en verre depoli ──
 *
 * Le flou s'applique a toutes les cartes, pas seulement a celles du haut : une
 * carte opaque au milieu de cartes translucides trancherait, et le membre peut
 * deplacer n'importe quel widget n'importe ou. */
.pl-accueil .card {
    background: rgba(255, 255, 255, .62);
    backdrop-filter: blur(16px) saturate(140%);
    -webkit-backdrop-filter: blur(16px) saturate(140%);
    border: 1px solid rgba(255, 255, 255, .45);
    box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .12);
}

[data-bs-theme="dark"] .pl-accueil .card {
    background: rgba(28, 32, 40, .68);
    border-color: rgba(255, 255, 255, .12);
}

/* Le navigateur qui ignore backdrop-filter doit rendre une carte lisible, pas
   une carte transparente posee sur une photo. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .pl-accueil .card { background: rgba(255, 255, 255, .95); }
    [data-bs-theme="dark"] .pl-accueil .card { background: rgba(28, 32, 40, .95); }
    .pl-avatar, .pl-btn-verre { background: rgba(0, 0, 0, .45); }
}

/* Le message de page vide se pose sur l'image comme les cartes. */
.pl-accueil #pl-vide.card {
    background: rgba(255, 255, 255, .78);
}

/* ── Choix du fond ── */

.pl-fonds-grille {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
    gap: .75rem;
}

.pl-fond-choix {
    display: block;
    width: 100%;
    padding: 0;
    border: 2px solid transparent;
    border-radius: .5rem;
    background: none;
    text-align: left;
}

.pl-fond-vignette {
    display: block;
    height: 4.5rem;
    border-radius: .375rem;
    background-size: cover;
    background-position: center;
}

.pl-fond-nom {
    display: block;
    padding: .25rem .125rem 0;
    font-size: .75rem;
    color: var(--vz-text-muted);
}

/* Le choix courant se distingue par une bordure et une coche, pas seulement
   par une nuance : deux vignettes sombres cote a cote sont indiscernables. */
.pl-fond-actif {
    border-color: var(--vz-primary);
}

.pl-fond-actif .pl-fond-nom {
    color: var(--vz-primary);
    font-weight: 500;
}

.pl-fond-actif .pl-fond-nom::after {
    content: " ✓";
}

/* Une carte peut se passer d'en-tete : une horloge n'a pas besoin qu'on lui
   dise qu'elle est une horloge. L'en-tete reparait en mode edition, sinon les
   boutons de reglage et de retrait qu'il porte seraient inatteignables. */
body:not(.pl-edition) .pl-widget[data-sans-titre] .pl-widget-entete {
  display: none;
}
