feat: creative footer, card tags, selection bar theme, i18n fixes
Build and Deploy Verso / deploy (push) Successful in 15m23s

- Footer: dark navy (#0d1b24) with noise texture, teal→blue gradient
  top stripe; serif author credit on the left, monospace/uppercase
  meta links on the right; same design on login page
- Thumbnail: larger inset (14px top, 12px sides) so more background
  gradient shows around the preview; subtle drop shadow added
- Card tags: projects now show their label chips on the card (colored
  dot + name, read-only, max 3, no close button inside the link)
- Selection bar: btn-secondary recoloured to Lumière palette; dropdown
  menus rounded with teal accent on hover
- i18n fr.json: add n_projects_selected, n_projects_selected_plural,
  toolbar_selected_projects (×4 variants)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
claude
2026-06-12 12:54:22 +00:00
parent 194ffe54db
commit 7df583a3b2
4 changed files with 211 additions and 25 deletions
@@ -5,6 +5,7 @@ import { Project } from '../../../../../types/project/dashboard/api'
import { getOwnerName } from '../util/project' import { getOwnerName } from '../util/project'
import { fromNowDate } from '../../../utils/dates' import { fromNowDate } from '../../../utils/dates'
import { ProjectCompiler } from '../../../../../types/project-settings' import { ProjectCompiler } from '../../../../../types/project-settings'
import { getTagColor } from '../util/tag'
import getMeta from '@/utils/meta' import getMeta from '@/utils/meta'
import DefaultNavbar from '@/shared/components/navbar/default-navbar' import DefaultNavbar from '@/shared/components/navbar/default-navbar'
import Footer from '@/shared/components/footer/footer' import Footer from '@/shared/components/footer/footer'
@@ -57,10 +58,14 @@ const ProjectCard = memo(function ProjectCard({
}: { }: {
project: Project project: Project
}) { }) {
const { tags } = useProjectListContext()
const variant = getFormatVariant(project.compiler, project.quartoFlavor) const variant = getFormatVariant(project.compiler, project.quartoFlavor)
const ownerName = getOwnerName(project) const ownerName = getOwnerName(project)
const date = fromNowDate(project.lastUpdated) const date = fromNowDate(project.lastUpdated)
const initial = project.name.charAt(0).toUpperCase() || '?' const initial = project.name.charAt(0).toUpperCase() || '?'
const projectTags = tags
.filter(tag => tag.project_ids?.includes(project.id))
.slice(0, 3)
return ( return (
<div className="lumiere-card-wrapper"> <div className="lumiere-card-wrapper">
@@ -99,6 +104,30 @@ const ProjectCard = memo(function ProjectCard({
</span> </span>
)} )}
</div> </div>
{projectTags.length > 0 && (
<div className="lumiere-card-tags">
{projectTags.map(tag => {
const color = getTagColor(tag)
return (
<span
key={tag._id}
className="lumiere-card-tag"
style={{
backgroundColor: color + '22',
color,
borderColor: color + '55',
}}
>
<span
className="lumiere-card-tag-dot"
style={{ backgroundColor: color }}
/>
{tag.name}
</span>
)
})}
</div>
)}
<span className="lumiere-card-date">{date}</span> <span className="lumiere-card-date">{date}</span>
</div> </div>
</a> </a>
@@ -52,23 +52,58 @@
} }
// ── Login page footer — Lumière override ────────────────────────────────────── // ── Login page footer — Lumière override ──────────────────────────────────────
// Verso always renders ThinFooter (footer.site-footer), not .fat-footer. // Same dark-navy treatment as the project page footer.
body:has(.login-page) footer.site-footer { body:has(.login-page) footer.site-footer {
background-color: #c8e4de !important; position: relative;
color: #1a2e3b !important; background-color: #0d1b24 !important;
border-top-color: rgba(42, 157, 143, 0.2) !important; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.28'/%3E%3C/svg%3E") !important;
background-size: 200px 200px !important;
background-repeat: repeat !important;
border-top: none !important;
color: rgba(255, 255, 255, 0.38) !important;
font-size: 0.78rem;
letter-spacing: 0.03em;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, #2a9d8f 0%, #3d7ebf 100%);
pointer-events: none;
}
a { a {
color: #64748b !important; color: rgba(255, 255, 255, 0.38) !important;
text-decoration: none;
transition: color 0.15s ease;
&:hover { &:hover {
color: #2a9d8f !important; color: #2a9d8f !important;
} }
} }
--link-color: #64748b; .col-lg-9 .site-footer-items > li:first-child a {
font-family: Georgia, 'Times New Roman', 'DejaVu Serif', serif;
color: rgba(255, 255, 255, 0.55) !important;
}
.col-lg-3 {
font-family: ui-monospace, 'SFMono-Regular', 'Fira Code', Consolas, monospace;
font-size: 0.70rem;
text-transform: uppercase;
letter-spacing: 0.07em;
}
.text-muted {
color: rgba(255, 255, 255, 0.16) !important;
}
--link-color: rgba(255, 255, 255, 0.38);
--link-hover-color: #2a9d8f; --link-hover-color: #2a9d8f;
--link-visited-color: #64748b; --link-visited-color: rgba(255, 255, 255, 0.38);
} }
.login-lumiere-card { .login-lumiere-card {
@@ -415,8 +415,8 @@ $lum-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' wi
// selected (:has(input:checked) on the grid). // selected (:has(input:checked) on the grid).
.lumiere-card-checkbox { .lumiere-card-checkbox {
position: absolute; position: absolute;
top: 8px; top: 10px;
left: 8px; left: 10px;
z-index: 2; z-index: 2;
opacity: 0.35; opacity: 0.35;
transition: opacity 0.15s ease; transition: opacity 0.15s ease;
@@ -544,17 +544,18 @@ $lum-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' wi
// the card rises beneath it (matching the card's translateY(-3px) lift). // the card rises beneath it (matching the card's translateY(-3px) lift).
.lumiere-card-thumb-img { .lumiere-card-thumb-img {
position: absolute; position: absolute;
top: 6px; top: 14px;
left: 6px; left: 12px;
right: 6px; right: 12px;
bottom: 0; bottom: 0;
width: calc(100% - 12px); width: calc(100% - 24px);
height: calc(100% - 6px); height: calc(100% - 14px);
object-fit: cover; object-fit: cover;
object-position: top center; object-position: top center;
border-radius: 4px 4px 0 0; border-radius: 5px 5px 0 0;
z-index: 1; z-index: 1;
transition: transform 0.18s ease; transition: transform 0.18s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
} }
.lumiere-card:hover .lumiere-card-thumb-img, .lumiere-card:hover .lumiere-card-thumb-img,
@@ -660,28 +661,143 @@ $lum-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' wi
background: rgba(#ede9fe, 0.9); background: rgba(#ede9fe, 0.9);
color: #7c4dff; color: #7c4dff;
} }
// ── Card tags ─────────────────────────────────────────────────────────────
.lumiere-card-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 4px;
}
.lumiere-card-tag {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.65rem;
font-weight: 500;
padding: 2px 6px;
border-radius: 999px;
border: 1px solid;
max-width: 80px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.lumiere-card-tag-dot {
flex-shrink: 0;
width: 6px;
height: 6px;
border-radius: 50%;
}
// ── Selection bar — tool buttons ──────────────────────────────────────────
.lumiere-selection-bar {
.btn-toolbar { gap: 4px; }
.btn-group { gap: 2px; }
.btn-secondary,
.btn.btn-secondary {
background-color: rgba(255, 255, 255, 0.7) !important;
border-color: rgba($lum-teal, 0.28) !important;
color: $lum-text !important;
border-radius: 8px !important;
font-size: 0.82rem;
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
&:hover,
&:focus,
&.show {
background-color: rgba($lum-teal, 0.12) !important;
border-color: rgba($lum-teal, 0.45) !important;
color: $lum-teal !important;
}
}
.dropdown-menu {
border: 1px solid $lum-border;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
.dropdown-header {
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.07em;
text-transform: uppercase;
color: $lum-teal;
padding-top: 0.6rem;
}
.dropdown-item:hover,
.dropdown-item:focus {
background-color: rgba($lum-teal, 0.07);
color: $lum-teal;
}
}
}
} }
// ── Footer — Lumière override ───────────────────────────────────────────────── // ── Footer — Lumière override ─────────────────────────────────────────────────
// Verso always renders a ThinFooter (site-footer) because showThinFooter is // Dark-navy footer with noise texture, gradient accent stripe, and two type
// forced true for non-SaaS instances. Target footer.site-footer, not .fat-footer. // treatments: serif copyright on the left, monospace meta links on the right.
// !important beats the dark theme rule: // !important beats :root [data-theme='default'] .project-ds-nav-page footer.site-footer.
// :root [data-theme='default'] .project-ds-nav-page footer.site-footer (0,3,1).
.project-list-lumiere footer.site-footer { .project-list-lumiere footer.site-footer {
background-color: #c8e4de !important; position: relative;
color: $lum-text !important; background-color: #0d1b24 !important;
border-top-color: $lum-border !important; background-image: #{$lum-noise} !important;
background-size: 200px 200px !important;
background-repeat: repeat !important;
border-top: none !important;
color: rgba(255, 255, 255, 0.38) !important;
font-size: 0.78rem;
letter-spacing: 0.03em;
// Teal-to-blue gradient stripe matching the navbar accent
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, $lum-teal 0%, $lum-blue 100%);
pointer-events: none;
}
a { a {
color: $lum-text-sub !important; color: rgba(255, 255, 255, 0.38) !important;
text-decoration: none;
transition: color 0.15s ease;
&:hover { &:hover {
color: $lum-teal !important; color: $lum-teal !important;
} }
} }
--link-color: #{$lum-text-sub}; // Author credit — warm serif for a human touch
.col-lg-9 .site-footer-items > li:first-child a {
font-family: Georgia, 'Times New Roman', 'DejaVu Serif', serif;
color: rgba(255, 255, 255, 0.55) !important;
}
// Meta links (AGPL, source code) — subtle monospace/uppercase treatment
.col-lg-3 {
font-family: ui-monospace, 'SFMono-Regular', 'Fira Code', Consolas, monospace;
font-size: 0.70rem;
text-transform: uppercase;
letter-spacing: 0.07em;
}
// Pipe separators — nearly invisible
.text-muted {
color: rgba(255, 255, 255, 0.16) !important;
}
--link-color: rgba(255, 255, 255, 0.38);
--link-hover-color: #{$lum-teal}; --link-hover-color: #{$lum-teal};
--link-visited-color: #{$lum-text-sub}; --link-visited-color: rgba(255, 255, 255, 0.38);
} }
+6
View File
@@ -769,6 +769,8 @@
"no_pdf_error_title": "Pas de PDF", "no_pdf_error_title": "Pas de PDF",
"no_planned_maintenance": "Il ny a pas de maintenance prévue pour le moment", "no_planned_maintenance": "Il ny a pas de maintenance prévue pour le moment",
"no_preview_available": "Désolé, aucune prévisualisation possible.", "no_preview_available": "Désolé, aucune prévisualisation possible.",
"n_projects_selected": "__count__ projet sélectionné",
"n_projects_selected_plural": "__count__ projets sélectionnés",
"no_projects": "Aucun projet", "no_projects": "Aucun projet",
"no_search_results": "Aucun résultat pour la recherche", "no_search_results": "Aucun résultat pour la recherche",
"no_selection_select_file": "Aucun fichier sélectionné. Veuillez sélectionner un fichier depuis larborescence.", "no_selection_select_file": "Aucun fichier sélectionné. Veuillez sélectionner un fichier depuis larborescence.",
@@ -1125,6 +1127,10 @@
"too_many_requests": "Trop de requêtes ont été reçues sur une courte période. Veuillez patienter quelques instants puis réessayer.", "too_many_requests": "Trop de requêtes ont été reçues sur une courte période. Veuillez patienter quelques instants puis réessayer.",
"too_recently_compiled": "Ce projet a été compilé très récemment, cette compilation a donc été passée.", "too_recently_compiled": "Ce projet a été compilé très récemment, cette compilation a donc été passée.",
"tooltip_hide_pdf": "Cliquez pour cacher le PDF", "tooltip_hide_pdf": "Cliquez pour cacher le PDF",
"toolbar_selected_projects": "Projets sélectionnés",
"toolbar_selected_projects_management_actions": "Actions de gestion des projets sélectionnés",
"toolbar_selected_projects_remove": "Supprimer les projets sélectionnés",
"toolbar_selected_projects_restore": "Restaurer les projets sélectionnés",
"tooltip_show_pdf": "Cliquez pour afficher le PDF", "tooltip_show_pdf": "Cliquez pour afficher le PDF",
"total_words": "Total des mots", "total_words": "Total des mots",
"tr": "Turque", "tr": "Turque",