feat: creative footer, card tags, selection bar theme, i18n fixes
Build and Deploy Verso / deploy (push) Successful in 15m23s
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:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -769,6 +769,8 @@
|
|||||||
"no_pdf_error_title": "Pas de PDF",
|
"no_pdf_error_title": "Pas de PDF",
|
||||||
"no_planned_maintenance": "Il n’y a pas de maintenance prévue pour le moment",
|
"no_planned_maintenance": "Il n’y 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 l’arborescence.",
|
"no_selection_select_file": "Aucun fichier sélectionné. Veuillez sélectionner un fichier depuis l’arborescence.",
|
||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user