/* Portfolio - Sotis AI Design System */

/* ==========================================
   Portfolio Container
   ========================================== */

.portfolio-container {
    max-width: 1200px;
    margin: 0 auto;
    padding: var(--space-md);
    position: relative;
    z-index: 1;
}

/* `header` is a bare-element selector in style.css and reaches every <header>,
   including this one, so the inherited chrome has to be reset explicitly. */
.portfolio-header {
    margin-bottom: var(--space-md);
    padding: 0 0 var(--space-sm);
    border-bottom: 1px solid var(--border-color);
    background: none;
    position: static;
    box-shadow: none;
    color: inherit;
}

.portfolio-header h1 {
    color: var(--text-primary);
    margin-bottom: 0.4rem;
    font-size: var(--text-3xl);
    letter-spacing: var(--tracking-tighter);
}

.portfolio-subtitle {
    color: var(--text-secondary);
    font-size: var(--text-md);
    max-width: 62ch;
}

/* ==========================================
   Note de provenance
   ========================================== */

/* Une phrase, une icone, un encadre teinte : d'ou vient ce qui est expose ici.
   Elle repond a une question qu'un prospect se pose en parcourant du travail
   livre pour d'autres, et qu'il ne posera jamais a voix haute.

   ELLE CONSTATE, ELLE NE PROMET PAS. Ecrite comme un engagement (« vos projets
   ne seront jamais publies sans votre accord »), elle repondrait a une question
   que personne n'a posee et installerait le doute qu'elle pretend lever. Le
   lecteur tire la conclusion tout seul.

   POURQUOI ICI ET NON DANS style.css. Le composant y a vecu une demi-journee,
   le temps qu'une jumelle existe sur la page d'accueil ; celle-ci a ete retiree,
   et un composant global a consommateur unique est une regle en trop sur les
   dix-huit autres pages. Si une deuxieme app en a besoin un jour, il remonte
   dans style.css : une app ne lie jamais la feuille d'une autre.

   PAS LA PALETTE --warning. Un objet en forme d'alerte, sur une page qui vend,
   dit « il y a un probleme ici ». La teinte de marque dit « une remarque de
   l'auteur », ce qui est exactement le registre voulu. Une taille de texte en
   dessous de la copie voisine : chromatiquement distincte, physiquement
   demotee, le registre d'une note de bas de contrat. */
.provenance-note {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    /* HORS de #portfolio-grid, dont updateProjectGrid() reecrit le contenu par
       innerHTML a chaque clic de filtre : a l'interieur, la note disparaitrait
       au premier clic. D'ou la marge posee depuis l'en-tete. */
    margin-top: 0.8rem;
    padding: 0.7rem 0.9rem;
    background: var(--violet-wash);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    /* --text-secondary et jamais --text-tertiary : a cette taille, tertiary
       mesure 4.11:1 contre le fond la ou AA demande 4.5:1, et cette phrase est
       tout l'objet de l'element. */
    color: var(--text-secondary);
    font-size: var(--text-base);
    line-height: var(--leading-normal);
    max-width: 62ch;
}

/* Decorative : la phrase porte tout, et le gabarit masque l'icone aux lecteurs
   d'ecran. Lucide REMPLACE le <i> par un <svg> a l'execution, donc la taille se
   pose sur le svg, comme pour .filter-btn. Avant cet echange le <i> est une
   boite de taille nulle : flex-shrink et la taille explicite sont ce qui evite
   que la phrase ne saute au moment ou les icones s'initialisent. */
.provenance-note-icon {
    flex-shrink: 0;
    display: flex;
    /* Alignement optique sur la hauteur de capitale de la premiere ligne. */
    margin-top: 0.1rem;
    color: var(--violet-primary);
}

.provenance-note-icon svg {
    width: 16px;
    height: 16px;
}

/* Le libelle mono au-dessus de la phrase. `.eyebrow` vient de style.css. */
.provenance-note .eyebrow {
    display: block;
    margin-bottom: 0.2rem;
    color: var(--violet-primary);
}

@media (max-width: 640px) {
    .provenance-note {
        padding: 0.6rem 0.7rem;
        font-size: var(--text-sm);
        max-width: none;
    }
}

/* ==========================================
   Filters
   ========================================== */

.portfolio-filters {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: var(--space-md);
}

/* Hairline chips, not pills with 2px brand borders. The active state is a
   filled neutral, so the eye lands on it without a second saturated colour
   competing next to it. */
.filter-btn {
    padding: 0.4rem 0.8rem;
    border: 1px solid var(--border-color);
    background: transparent;
    color: var(--text-secondary);
    border-radius: var(--radius-full);
    cursor: pointer;
    font-family: inherit;
    font-size: var(--text-base);
    font-weight: var(--font-weight-normal);
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    transition: color var(--transition-fast), background-color var(--transition-fast),
                border-color var(--transition-fast);
}

.filter-btn:hover {
    color: var(--text-primary);
    border-color: var(--border-strong);
    background: rgba(255, 255, 255, 0.04);
}

.filter-btn.active {
    background: var(--text-primary);
    border-color: var(--text-primary);
    color: var(--bg-primary);
}

.filter-btn:focus-visible {
    outline: 2px solid var(--violet-primary);
    outline-offset: 2px;
}

/* The favourites filter is the one place yellow is spent on this page. */
.filter-btn-favorites {
    border-color: rgba(var(--yellow-primary-rgb), 0.35);
    color: var(--yellow-primary);
    background: transparent;
}

.filter-btn-favorites:hover {
    background: rgba(var(--yellow-primary-rgb), 0.10);
    border-color: var(--yellow-primary);
    color: var(--yellow-primary);
}

.filter-btn-favorites.active {
    background: var(--yellow-primary);
    border-color: var(--yellow-primary);
    color: var(--dark-text);
}

/* ==========================================
   Lucide Icons
   ========================================== */

/* Filter buttons */
.filter-btn svg {
    width: 14px;
    height: 14px;
    vertical-align: middle;
    margin-right: 0.25rem;
}

/* Project links */
.project-link svg {
    width: 12px;
    height: 12px;
    vertical-align: middle;
    margin-right: 0.2rem;
}

/* Project links detail page */
.project-link-detail svg,
.link-icon svg {
    width: 14px;
    height: 14px;
}

/* Back link */
.back-link svg {
    width: 14px;
    height: 14px;
}

/* Carousel buttons */
.carousel-btn svg {
    width: 16px;
    height: 16px;
}

/* Zoom icon */
.zoom-icon svg {
    width: 24px;
    height: 24px;
}

/* ==========================================
   Project Grid
   ========================================== */

.portfolio-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: var(--space-sm);
    width: 100%;
    min-height: 400px;
}

.portfolio-empty {
    grid-column: 1 / -1;
    text-align: center;
    padding: var(--space-xl);
    color: var(--text-secondary);
}

/* ==========================================
   Project Card
   ========================================== */

.project-card {
    background: var(--card-bg);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    overflow: hidden;
    transition: var(--transition-base);
    display: flex;
    flex-direction: column;
    position: relative;
}

.project-card:hover {
    border-color: var(--border-strong);
    background: var(--surface-raised);
}

/* Featured used to be a 2px yellow frame around the whole card, which on a grid
   of five made the page read as a warning. It is now a small corner mark. */
.project-card.featured::before {
    content: '★';
    position: absolute;
    top: 0.6rem;
    right: 0.6rem;
    width: 22px;
    height: 22px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(10, 10, 12, 0.75);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    border: 1px solid rgba(var(--yellow-primary-rgb), 0.35);
    color: var(--yellow-primary);
    border-radius: var(--radius-sm);
    font-size: 0.7rem;
    z-index: 1;
}

.project-image-link {
    display: block;
    cursor: pointer;
}

.project-image {
    width: 100%;
    height: 180px;
    overflow: hidden;
    position: relative;
    border-bottom: 1px solid var(--border-color);
}

.project-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Raw full-contrast screenshots on a near-black page are the loudest thing
       in the grid. Dimmed at rest, restored on hover. */
    opacity: 0.72;
    transition: opacity var(--transition-base), transform var(--transition-base);
}

.project-image-link:hover .project-image img,
.project-card:hover .project-image img {
    opacity: 1;
    transform: scale(1.03);
}

.project-content {
    padding: var(--space-sm);
    display: flex;
    flex-direction: column;
    flex-grow: 1;
}

/* La carte est un squelette FIGÉ : image 180px, badges 1 rangée, résumé
   élastique, outils 2 rangées, liens 2 rangées. La grille étire les cartes d'une
   rangée à la même hauteur et `.project-highlights` absorbe tout le mou, donc la
   position des blocs du bas ne dépend que de LEUR propre hauteur. Tant que ces
   trois blocs mesuraient 1, 2 ou 3 rangées selon leur contenu, les tags
   partaient jusqu'à 72 px plus haut d'une carte à sa voisine, et le filet
   séparateur 39 px. Ne pas rendre ces hauteurs à leur contenu. */
.project-meta {
    display: flex;
    gap: var(--space-xs);
    margin-bottom: var(--space-xs);
    flex-wrap: wrap;
    align-items: center;
}

.project-category {
    background: var(--violet-wash);
    color: var(--violet-primary);
    padding: 0.15rem 0.5rem;
    border-radius: var(--radius-sm);
    font-size: var(--text-xs);
    font-weight: var(--font-weight-medium);
}

/* Le compte des catégories restantes. Filaire et en encre tertiaire : plein et
   violet comme ses voisins, « +2 » se lirait comme un nom de catégorie. */
.project-category.more {
    background: transparent;
    color: var(--text-tertiary);
    border: 1px solid var(--border-color);
    padding: calc(0.15rem - 1px) calc(0.5rem - 1px);
}

.project-year {
    font-family: var(--font-mono);
    color: var(--text-tertiary);
    font-size: var(--text-xs);
    display: flex;
    align-items: center;
}

.project-title {
    font-size: var(--text-md);
    margin-bottom: var(--space-xs);
    line-height: 1.3;
}

.project-title a {
    color: var(--text-primary);
    text-decoration: none;
    transition: var(--transition-fast);
}

.project-title a:hover {
    color: var(--violet-primary);
}

/* Le seul bloc élastique de la carte : c'est LUI qui absorbe la différence de
   hauteur entre une carte et la plus haute de sa rangée. Lui retirer `flex-grow`
   ferait tomber le mou entre les outils et les liens, au milieu du bas de carte. */
.project-highlights {
    color: var(--text-secondary);
    font-size: var(--text-base);
    line-height: 1.5;
    margin-bottom: var(--space-sm);
    flex-grow: 1;
}

/* ==========================================
   Tool Tags
   ========================================== */

.project-tools {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: var(--space-sm);
    /* Deux rangées, toujours (la boîte mesurait 25, 57 ou 89 px selon la
       longueur des noms d'outils). `align-content` vaut `stretch` par défaut :
       sur un conteneur qui enveloppe et dont la hauteur est contrainte, une
       rangée unique s'étirerait à toute la boîte, et les pastilles avec elle.
       `overflow: hidden` coupe à la rangée entière, jamais au milieu d'une
       pastille ; ce qui empêche une pastille « +12 » de disparaître dans cette
       coupe est un budget de caractères, gardé par `CardToolRowsTest`.

       Le pixel en plus n'est pas une marge de confort : la boîte au calcul
       exact (56,4 px) est arrondie EN BAS par le navigateur alors que son
       contenu l'est EN HAUT, et la deuxième rangée était rognée de 0,4 px sur
       13 fiches. */
    height: calc(2 * 25px + 0.4rem + 1px);
    align-content: flex-start;
    overflow: hidden;
}

.tool-tag {
    background: transparent;
    color: var(--text-tertiary);
    padding: 0.15rem 0.45rem;
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    border: 1px solid var(--border-color);
}

.tool-tag.more {
    background: var(--violet-wash);
    color: var(--violet-primary);
    border-color: transparent;
}

/* ==========================================
   Project Links
   ========================================== */

/* Deux rangées réservées : 9 fiches sur 31 en ont réellement deux (GitHub +
   Journal + Thèse + « Voir plus » ne tient pas sur une ligne), et le filet
   séparateur remontait de 39 px sur celles-là. Les 22 autres gagnent une rangée
   vide, c'est le prix d'un filet à la même hauteur sur toute la grille.
   Pas de `margin-top: auto` ici : il n'a jamais rien distribué, `.project-highlights`
   consommant déjà tout le mou, et il laissait croire à un second mécanisme de calage. */
.project-links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    padding-top: var(--space-sm);
    border-top: 1px solid var(--border-color);
    min-height: calc(2 * 32px + var(--space-xs) + var(--space-sm) + 1px);
    align-content: flex-start;
    position: relative;
}

/* Five filled violet buttons per card turned every card into a control panel.
   They are links: hairline by default, the "Voir plus" one carries the accent. */
.project-link {
    padding: 0.3rem 0.6rem;
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    text-decoration: none;
    transition: color var(--transition-fast), background-color var(--transition-fast),
                border-color var(--transition-fast);
    background: transparent;
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}

.project-link:hover {
    background: rgba(255, 255, 255, 0.05);
    border-color: var(--border-strong);
    color: var(--text-primary);
}

/* « Voir plus » est SORTI DU FLUX, épinglé au coin bas-droit du pied. Laissé
   dans le flux avec un `margin-left: auto`, il se posait au bout de la rangée
   des liens externes, donc en HAUT du pied sur les 27 fiches qui n'en ont
   qu'une, et en bas sur les 4 qui en ont deux : l'action principale de la carte
   changeait de place d'une carte à sa voisine. Il ne peut pas percuter les
   liens externes, leur dernière rangée gardant 100 px de dégagement à droite à
   la colonne la plus étroite (`ProjectLinksLayoutTest`). */
.project-link.details {
    background: transparent;
    color: var(--violet-primary);
    border-color: transparent;
    position: absolute;
    right: 0;
    bottom: 0;
}

.project-link.details:hover {
    background: var(--violet-wash);
    color: var(--violet-primary);
}

/* ==========================================
   Project Detail Page
   ========================================== */

.portfolio-detail-container {
    max-width: 900px;
    margin: 0 auto;
    padding: var(--space-md);
}

/* Breadcrumb */
.breadcrumb {
    margin-bottom: var(--space-md);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--text-tertiary);
}

.breadcrumb a {
    color: var(--text-secondary);
    text-decoration: none;
}

.breadcrumb a:hover {
    text-decoration: underline;
}

.breadcrumb .separator {
    margin: 0 0.5rem;
    color: var(--text-secondary);
}

.breadcrumb .current {
    color: var(--text-primary);
}

/* Project Detail Header.
   Was a solid violet card carrying white text: a saturated block at the top of
   every project page, and the only one on the site. It is now the same
   hairline-ruled header the rest of the site uses. */
.project-detail-header {
    margin-bottom: var(--space-md);
    padding: 0 0 var(--space-sm);
    border-bottom: 1px solid var(--border-color);
    background: none;
    position: static;
    box-shadow: none;
}

.project-detail-meta {
    display: flex;
    gap: var(--space-xs);
    margin-bottom: var(--space-sm);
    flex-wrap: wrap;
    align-items: center;
}

.project-featured-badge {
    background: transparent;
    border: 1px solid rgba(var(--yellow-primary-rgb), 0.35);
    color: var(--yellow-primary);
    padding: 0.15rem 0.5rem;
    border-radius: var(--radius-sm);
    font-size: var(--text-xs);
    font-weight: var(--font-weight-medium);
}

.project-detail-title {
    font-size: var(--text-3xl);
    color: var(--text-primary);
    letter-spacing: var(--tracking-tighter);
    margin-bottom: 0.4rem;
}

.project-organizations {
    color: var(--text-secondary);
    font-size: var(--text-base);
}

/* Project Detail Image */
.project-detail-image {
    margin-bottom: var(--space-lg);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.project-detail-image img {
    width: 100%;
    height: auto;
    display: block;
}

/* Project Sections */
.project-section {
    margin-bottom: var(--space-lg);
}

.project-section h2 {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--text-tertiary);
    margin-bottom: var(--space-sm);
    padding-bottom: var(--space-xs);
    border-bottom: 1px solid var(--border-color);
}

/* Ces blocs viennent de `|linebreaks`, qui rend UN paragraphe par ligne vide du
   champ. Sans marge, deux paragraphes se collent et le decoupage voulu par le
   texte devient invisible : la fiche du copilote documentaire separe son amorce
   de ses points de detail, et les deux se lisaient d'un seul tenant. La remise a
   zero globale des marges vient du reset de style.css. */
.project-section p {
    color: var(--text-secondary);
    line-height: var(--leading-relaxed);
    max-width: 68ch;
    margin-bottom: var(--space-sm);
}

.project-section p:last-child {
    margin-bottom: 0;
}

/* ==========================================
   Appel a l'action de fin de fiche
   ========================================== */

/* Surface surelevee et non un aplat de couleur : la fiche vient d'etablir un
   registre factuel, et un bandeau colore a cet endroit se lirait comme une
   publicite intercalee entre le contenu et les projets similaires. */
.project-cta {
    margin: var(--space-lg) 0;
    padding: var(--space-md);
    background: var(--surface-raised);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--ring-inset);
}

/* Le titre echappe volontairement au `.project-section h2` d'a cote, qui est
   une etiquette monospace en majuscules pour nommer un champ. Celui-ci est une
   phrase adressee au lecteur. */
.project-cta h2 {
    color: var(--text-primary);
    font-size: var(--text-xl);
    letter-spacing: var(--tracking-tight);
    margin-bottom: var(--space-xs);
}

.project-cta p {
    color: var(--text-secondary);
    line-height: var(--leading-relaxed);
    max-width: 62ch;
    margin-bottom: var(--space-md);
}

.project-cta-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
}

.project-tools-detail {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.project-tools-detail .tool-tag {
    font-size: var(--text-sm);
    padding: 0.3rem 0.6rem;
    color: var(--text-secondary);
}

/* Project Links Detail */
.project-links-detail {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
}

.project-link-detail {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.9rem;
    border-radius: var(--radius-sm);
    text-decoration: none;
    transition: color var(--transition-fast), background-color var(--transition-fast),
                border-color var(--transition-fast);
    font-size: var(--text-base);
    font-weight: var(--font-weight-medium);
    background: transparent;
    border: 1px solid var(--border-strong);
    color: var(--text-primary);
}

.project-link-detail:hover {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.22);
}

.link-icon {
    font-size: 1.2rem;
}

/* ==========================================
   Related Projects
   ========================================== */

.related-projects {
    margin-top: var(--space-xl);
    padding-top: var(--space-lg);
    border-top: 1px solid var(--border-color);
}

.related-projects h2 {
    font-size: 1.5rem;
    color: var(--text-primary);
    margin-bottom: var(--space-md);
}

.related-projects-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: var(--space-md);
}

.related-project-card {
    background: var(--card-bg);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    overflow: hidden;
    transition: var(--transition-fast);
}

.related-project-card:hover {
    border-color: var(--violet-primary);
    transform: translateY(-2px);
}

.related-project-image {
    height: 120px;
    overflow: hidden;
}

.related-project-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.related-project-content {
    padding: var(--space-sm);
}

.related-project-year {
    font-size: 0.8rem;
    color: var(--text-secondary);
}

.related-project-content h3 {
    font-size: 1rem;
    margin-top: 0.25rem;
}

.related-project-content a {
    color: var(--text-primary);
    text-decoration: none;
}

.related-project-content a:hover {
    color: var(--violet-primary);
}

/* ==========================================
   Back Link
   ========================================== */

.back-to-portfolio {
    margin-top: var(--space-lg);
    padding-top: var(--space-md);
}

.back-link {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--violet-primary);
    text-decoration: none;
    font-weight: 500;
    transition: var(--transition-fast);
}

.back-link:hover {
    color: var(--violet-primary);
}

/* ==========================================
   Responsive
   ========================================== */

@media (max-width: 768px) {
    .portfolio-header h1 {
        font-size: 2rem;
    }

    .portfolio-grid {
        grid-template-columns: 1fr;
    }

    /* Une seule colonne : chaque carte est seule sur sa rangée, donc il n'y a
       plus rien à aligner, et les hauteurs réservées ne seraient que du vide. */
    .project-tools {
        height: auto;
        overflow: visible;
    }

    .project-links {
        min-height: 0;
    }

    /* Le pied n'a plus de hauteur réservée : hors du flux, « Voir plus » se
       poserait sur la rangée des liens externes au lieu de sous elle. */
    .project-link.details {
        position: static;
        margin-left: auto;
    }

    .portfolio-filters {
        gap: 0.4rem;
    }

    .filter-btn {
        padding: 0.5rem 0.8rem;
        font-size: 0.85rem;
    }

    .project-detail-title {
        font-size: 1.8rem;
    }

    .project-links-detail {
        flex-direction: column;
    }

    .project-link-detail {
        justify-content: center;
    }

    .related-projects-grid {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 480px) {
    .portfolio-container,
    .portfolio-detail-container {
        padding: var(--space-sm);
    }

    .project-content {
        padding: var(--space-xs);
    }

    .project-title {
        font-size: 1.1rem;
    }
}

/* ==========================================
   Clickable Images
   ========================================== */

.clickable-image {
    position: relative;
    cursor: pointer;
}

.clickable-image .image-overlay {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    opacity: 0;
    transition: var(--transition-fast);
    color: var(--white);
    font-size: 0.9rem;
}

.clickable-image:hover .image-overlay {
    opacity: 1;
}

.clickable-image .zoom-icon {
    display: flex;
    align-items: center;
    justify-content: center;
}

/* ==========================================
   Lightbox Modal
   ========================================== */

.lightbox {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.95);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 9999;
    opacity: 0;
    visibility: hidden;
    transition: var(--transition-base);
}

.lightbox.active {
    opacity: 1;
    visibility: visible;
}

.lightbox-close {
    position: absolute;
    top: 1rem;
    right: 1.5rem;
    background: none;
    border: none;
    color: var(--white);
    font-size: 3rem;
    cursor: pointer;
    z-index: 10000;
    line-height: 1;
    transition: var(--transition-fast);
}

.lightbox-close:hover {
    color: var(--violet-primary);
    transform: scale(1.1);
}

.lightbox-content {
    max-width: 90%;
    max-height: 90%;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.lightbox-content img {
    max-width: 100%;
    max-height: 85vh;
    object-fit: contain;
    border-radius: var(--radius-sm);
    box-shadow: 0 0 50px rgba(0, 0, 0, 0.5);
}

.lightbox-caption {
    color: var(--white);
    margin-top: var(--space-sm);
    font-size: 1rem;
    text-align: center;
}

/* ==========================================
   Gallery & Carousel
   ========================================== */

.project-gallery {
    margin-bottom: var(--space-lg);
}

.project-gallery h2 {
    font-size: 1.3rem;
    color: var(--violet-primary);
    margin-bottom: var(--space-sm);
    padding-bottom: var(--space-xs);
    border-bottom: 1px solid var(--border-color);
}

.gallery-carousel {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-xs);
}

.carousel-container {
    flex: 1;
    overflow: hidden;
    border-radius: var(--radius-md);
}

.carousel-track {
    display: flex;
    transition: transform 0.5s ease-in-out;
}

.carousel-slide {
    min-width: 100%;
    position: relative;
}

.carousel-slide img {
    width: 100%;
    height: 400px;
    object-fit: contain;
    background: var(--bg-secondary);
    cursor: pointer;
    transition: var(--transition-fast);
}

.carousel-slide img:hover {
    opacity: 0.9;
}

.slide-caption {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(0, 0, 0, 0.7);
    color: var(--white);
    padding: var(--space-xs) var(--space-sm);
    font-size: 0.9rem;
    text-align: center;
}

.carousel-btn {
    background: var(--violet-fill);
    color: var(--white);
    border: none;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-full);
    cursor: pointer;
    font-size: 1.2rem;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: var(--transition-fast);
    flex-shrink: 0;
}

.carousel-btn:hover {
    background: var(--violet-primary);
    transform: scale(1.1);
}

.carousel-dots {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    margin-top: var(--space-sm);
}

.dot {
    width: 12px;
    height: 12px;
    border-radius: var(--radius-full);
    border: 2px solid var(--violet-primary);
    background: transparent;
    cursor: pointer;
    transition: var(--transition-fast);
}

.dot:hover {
    background: var(--violet-wash-strong);
}

.dot.active {
    background: var(--violet-primary);
    border-color: var(--violet-primary);
}

.gallery-count {
    text-align: center;
    color: var(--text-secondary);
    font-size: 0.85rem;
    margin-top: var(--space-xs);
}

/* Responsive Carousel */
@media (max-width: 768px) {
    .carousel-slide img {
        height: 250px;
    }

    .carousel-btn {
        width: 32px;
        height: 32px;
        font-size: 1rem;
    }
}

@media (max-width: 480px) {
    .carousel-slide img {
        height: 200px;
    }

    .carousel-btn {
        width: 28px;
        height: 28px;
    }

    .dot {
        width: 10px;
        height: 10px;
    }
}

/* ==========================================
   Sync YAML button (superuser only)
   ==========================================
   Moved here from an inline <style> in portfolio.html: a stylesheet edit that
   greps the .css files would have missed it there, which is exactly how it
   kept its light-theme colours through the previous pass. */

.sync-yaml-btn {
    position: fixed;
    bottom: 1.5rem;
    right: 1.5rem;
    padding: 0.4rem 0.8rem;
    background: var(--surface-raised);
    color: var(--text-secondary);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-family: inherit;
    font-size: var(--text-sm);
    transition: color var(--transition-fast), background-color var(--transition-fast);
    box-shadow: var(--shadow-md);
    z-index: 100;
    opacity: 0.6;
}

.sync-yaml-btn:hover {
    color: var(--text-primary);
    opacity: 1;
}

.sync-yaml-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}
