Fix translations, center logo/footer, add tile zoom control
Build and Deploy Verso / deploy (push) Successful in 14m8s

- i18n: unwrap webpack module object on dynamic JSON import (lang.default ?? lang)
  so French bundle keys are correctly registered in the i18next store
- Login logo: use flex centering on wrapper instead of display:block + margin:auto
- Footer (project list + login): align-items:center on .row for vertical centering
- Tile zoom: S/M/L control in header with CSS custom property (--lum-card-scale)
  that scales grid column width and card thumbnail height; persisted in localStorage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
claude
2026-06-12 17:20:21 +00:00
co-authored by Claude Sonnet 4.6
parent d08e834f49
commit 592b4d3dad
5 changed files with 127 additions and 8 deletions
@@ -1,4 +1,4 @@
import { memo, useCallback, useEffect, useRef } from 'react'
import React, { memo, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useProjectListContext } from '../context/project-list-context'
import { Project } from '../../../../../types/project/dashboard/api'
@@ -28,6 +28,46 @@ import { CompileAndDownloadProjectPDFButtonTooltip } from './table/cells/action-
import { ArchiveProjectButtonTooltip } from './table/cells/action-buttons/archive-project-button'
import { TrashProjectButtonTooltip } from './table/cells/action-buttons/trash-project-button'
// ── Tile zoom ─────────────────────────────────────────────────────────────────
type ZoomLevel = 0.75 | 1 | 1.35
const ZOOM_OPTIONS: { value: ZoomLevel; label: string }[] = [
{ value: 0.75, label: 'S' },
{ value: 1, label: 'M' },
{ value: 1.35, label: 'L' },
]
const ZOOM_STORAGE_KEY = 'lumiere-card-scale'
function useLumiereCardScale(): [ZoomLevel, (z: ZoomLevel) => void] {
const [scale, setScale] = useState<ZoomLevel>(() => {
try {
const stored = localStorage.getItem(ZOOM_STORAGE_KEY)
if (stored) {
const val = parseFloat(stored)
if ((ZOOM_OPTIONS.map(o => o.value) as number[]).includes(val)) {
return val as ZoomLevel
}
}
} catch (_) {
// storage unavailable
}
return 1
})
const updateScale = useCallback((z: ZoomLevel) => {
setScale(z)
try {
localStorage.setItem(ZOOM_STORAGE_KEY, String(z))
} catch (_) {
// ignore
}
}, [])
return [scale, updateScale]
}
// ── Format helpers ─────────────────────────────────────────────────────────────
type FormatVariant = 'latex' | 'typst' | 'quarto' | 'quarto-slides'
function getFormatVariant(
@@ -137,6 +177,7 @@ export function ProjectListLumiere() {
const navbarProps = getMeta('ol-navbar')
const footerProps = getMeta('ol-footer')
const { t } = useTranslation()
const [cardScale, setCardScale] = useLumiereCardScale()
const {
error,
visibleProjects,
@@ -206,6 +247,23 @@ export function ProjectListLumiere() {
filter={filter}
selectedTag={selectedTag}
/>
<div
className="lumiere-zoom-control"
role="group"
aria-label="Taille des cartes"
>
{ZOOM_OPTIONS.map(({ value, label }) => (
<button
key={value}
type="button"
className={`lumiere-zoom-btn${cardScale === value ? ' active' : ''}`}
onClick={() => setCardScale(value)}
aria-pressed={cardScale === value}
>
{label}
</button>
))}
</div>
<NewProjectButton
id="lumiere-new-project-button"
showAddAffiliationWidget
@@ -241,7 +299,11 @@ export function ProjectListLumiere() {
{visibleProjects.length === 0 ? (
<p className="lumiere-empty">{t('no_projects')}</p>
) : (
<div className="lumiere-card-grid">
<div
className="lumiere-card-grid"
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
style={{ '--lum-card-scale': cardScale } as React.CSSProperties}
>
{visibleProjects.map(project => (
<ProjectCard key={project.id} project={project} />
))}