Files
Verso/services/web/frontend/stylesheets/pages/project-list-lumiere.scss
T
claude 194ffe54db
Build and Deploy Verso / deploy (push) Successful in 14m49s
fix: target footer.site-footer (ThinFooter) in Lumière theme
Verso forces showThinFooter=true for non-SaaS instances, rendering
footer.site-footer everywhere — never .fat-footer. All previous footer
theme rules silently matched nothing. Fix both project and login page
footer selectors. Also increase login logo max-width to 520px, and
remove bottom inset/radius from thumbnail (folder-behind-card effect).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 12:23:36 +00:00

688 lines
22 KiB
SCSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Verso Lumière — modern card-based project dashboard theme.
//
// The outer element carries both project-ds-nav-page + website-redesign
// (for sidebar/navbar CSS) and project-list-lumiere (our styling hook).
// Everything here is additive or override-only — zero impact on Classic themes.
// ── Brand colours ──────────────────────────────────────────────────────────
$lum-teal: #2a9d8f;
$lum-teal-dark: #21867a;
$lum-blue: #3d7ebf;
$lum-text: #1a2e3b;
$lum-text-sub: #64748b;
$lum-text-muted: #94a3b8;
$lum-border: #e2eaf2;
// Grainy SVG noise tile (fractalNoise, greyscale, stitched for seamless tiling).
$lum-noise: 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");
.project-list-lumiere {
// ══════════════════════════════════════════════════════════════════════════
// NAVBAR
// ══════════════════════════════════════════════════════════════════════════
.navbar-default {
border-bottom: none !important;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06), 0 2px 8px rgba(0, 0, 0, 0.04);
background-color: #ffffff !important;
// Teal-to-blue gradient accent stripe at the very top
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, $lum-teal 0%, $lum-blue 100%);
z-index: 10;
}
// Nav link hover — shared base
.navbar-nav > li > .nav-link,
.navbar-nav > li > .dropdown-toggle {
border-radius: 8px !important;
transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
border: 1px solid transparent !important;
&:hover,
&:focus,
&.show {
background-color: rgba($lum-teal, 0.09) !important;
color: $lum-teal !important;
border-color: rgba($lum-teal, 0.18) !important;
}
}
// Account button — slightly more prominent (has a resting tint)
.nav-item-account > .dropdown-toggle {
background-color: rgba($lum-teal, 0.06) !important;
color: $lum-text !important;
font-weight: 500;
&:hover,
&:focus,
&.show {
background-color: rgba($lum-teal, 0.14) !important;
color: $lum-teal !important;
border-color: rgba($lum-teal, 0.25) !important;
}
}
// Admin button
.subdued > .dropdown-toggle {
color: $lum-text-sub !important;
font-weight: 500;
}
.navbar-title {
color: $lum-text !important;
font-weight: 600;
}
}
// ══════════════════════════════════════════════════════════════════════════
// SIDEBAR — shell
// ══════════════════════════════════════════════════════════════════════════
.project-list-sidebar-wrapper-react {
background: #ffffff !important;
border-right: 1px solid $lum-border;
}
// ══════════════════════════════════════════════════════════════════════════
// SIDEBAR — New Project button
// ══════════════════════════════════════════════════════════════════════════
// New Project button in the sidebar only (not in the header actions row)
.project-list-sidebar-wrapper-react .new-project-dropdown {
padding-bottom: 0.75rem;
}
// When the button appears inline next to the search bar, strip extra spacing
.lumiere-header-actions .new-project-dropdown {
padding-bottom: 0;
display: flex;
align-items: center;
}
.new-project-dropdown {
.new-project-button.btn {
width: 100%;
background: linear-gradient(135deg, $lum-teal 0%, $lum-blue 100%) !important;
border: none !important;
color: #ffffff !important;
font-weight: 600;
border-radius: 10px !important;
box-shadow: 0 2px 8px rgba($lum-teal, 0.25);
transition: box-shadow 0.18s ease, filter 0.18s ease;
&:hover,
&:focus {
filter: brightness(1.08);
box-shadow: 0 4px 14px rgba($lum-teal, 0.38) !important;
color: #ffffff !important;
}
&::after {
border-top-color: rgba(255, 255, 255, 0.8);
}
}
}
// ══════════════════════════════════════════════════════════════════════════
// SIDEBAR — filter list
// ══════════════════════════════════════════════════════════════════════════
.project-list-wrapper ul.project-list-filters {
> li > button {
border-radius: 8px;
padding: 0.45rem 0.75rem;
font-size: 0.875rem;
font-weight: 500;
color: $lum-text-sub;
transition: background-color 0.15s ease, color 0.15s ease;
}
> li:hover button {
background-color: rgba($lum-teal, 0.07) !important;
color: $lum-teal;
}
> li.active button {
background: $lum-teal !important;
color: #ffffff !important;
font-weight: 600;
}
.dropdown-header {
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: $lum-teal !important;
padding: 0.85rem 0.75rem 0.35rem;
}
> li.tag {
button.tag-name {
border-radius: 8px;
font-size: 0.83rem;
color: $lum-text-sub;
display: flex;
align-items: center;
gap: 0.45rem;
&:hover { color: $lum-teal; }
}
.tag-menu button.dropdown-toggle {
border-radius: 50%;
transition: background-color 0.15s ease;
&:hover,
&:active,
&[aria-expanded='true'] {
background-color: rgba($lum-teal, 0.1) !important;
color: $lum-teal;
}
}
}
hr { border-color: $lum-border; }
}
// ══════════════════════════════════════════════════════════════════════════
// SIDEBAR — lower section
// ══════════════════════════════════════════════════════════════════════════
.ds-nav-sidebar-lower {
border-top-color: $lum-border !important;
padding-top: 0.75rem;
}
.ds-nav-icon-dropdown .dropdown-toggle {
border-radius: 50% !important;
color: $lum-text-sub !important;
transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
&:hover {
background-color: rgba($lum-teal, 0.09) !important;
color: $lum-teal !important;
box-shadow: 0 0 0 3px rgba($lum-teal, 0.12);
}
&.show {
background-color: rgba($lum-teal, 0.15) !important;
color: $lum-teal !important;
box-shadow: 0 0 0 3px rgba($lum-teal, 0.18);
}
}
.ds-nav-verso-logo {
// No border-top here — .ds-nav-sidebar-lower already has one
padding-top: 0.6rem;
margin-top: 0.25rem;
transition: filter 0.2s ease;
&:hover {
filter: brightness(1.1) drop-shadow(0 1px 4px rgba($lum-teal, 0.2));
}
}
.theme-toggle-radios {
background-color: rgba($lum-teal, 0.07);
}
.theme-toggle-radio input:checked + label {
background-color: $lum-teal !important;
color: #ffffff;
.material-symbols { color: #ffffff; }
}
// ══════════════════════════════════════════════════════════════════════════
// NOTIFICATIONS — Lumière palette
// ══════════════════════════════════════════════════════════════════════════
.notification {
border-radius: 10px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
// Warning: solid amber-tinted white so the teal page background can't bleed
// through. !important needed to beat :root [data-theme='default'] specificity.
&.notification-type-warning {
background-color: #fffbf0 !important;
border-color: rgba(217, 119, 6, 0.35) !important;
.notification-icon { color: #b45309 !important; }
}
// Info: solid blue-tinted white for the same reason
&.notification-type-info {
background-color: #f0f6fd !important;
border-color: rgba($lum-blue, 0.28) !important;
.notification-icon { color: $lum-blue !important; }
}
// Action buttons (e.g. "Send confirmation code") — neutral slate-grey,
// rounded-square to match the Lumière button style.
.notification-cta .btn-secondary {
background-color: rgba(100, 116, 139, 0.10) !important;
border-color: rgba(100, 116, 139, 0.28) !important;
color: $lum-text !important;
border-radius: 8px !important;
&:hover,
&:focus {
background-color: rgba(100, 116, 139, 0.18) !important;
border-color: rgba(100, 116, 139, 0.38) !important;
color: $lum-text !important;
}
}
}
// ══════════════════════════════════════════════════════════════════════════
// MAIN CONTENT AREA — grainy gradient background
//
// Three layers, back to front:
// 1. Solid base colour (#fafbff — cool off-white)
// 2. Two soft radial-gradient "orbs" (teal top-right, blue bottom-left),
// rendered fixed so they stay in the corner as the user scrolls.
// 3. SVG feTurbulence noise tile (200×200, repeating, scrolls with content)
// at 6% opacity — the "grain" effect seen in Linear / Raycast / UmbrelOS.
// ══════════════════════════════════════════════════════════════════════════
.project-ds-nav-content {
// Grainy gradient: noise tile on top, two strong radial orbs below.
// Using scroll (not fixed) so the gradient renders correctly inside the
// overflow:auto scroll container.
background-image:
#{$lum-noise},
radial-gradient(circle 700px at 110% -80px, rgba($lum-teal, 0.60) 0%, transparent 100%),
radial-gradient(circle 600px at -120px 110%, rgba($lum-blue, 0.45) 0%, transparent 100%);
background-size:
200px 200px,
cover,
cover;
background-repeat:
repeat,
no-repeat,
no-repeat;
background-color: #e8f5f2;
}
// ── Page header ────────────────────────────────────────────────────────────
.lumiere-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.75rem;
flex-wrap: wrap;
}
.lumiere-header-actions {
display: flex;
align-items: center;
gap: 0.75rem;
flex-shrink: 0;
flex-wrap: wrap;
}
.lumiere-title {
font-family: Georgia, 'Times New Roman', 'DejaVu Serif', serif !important;
font-size: 2rem !important;
font-weight: 700 !important;
color: $lum-text !important;
line-height: 1.15 !important;
margin: 0 !important;
}
// ── Empty state ─────────────────────────────────────────────────────────
.lumiere-empty {
color: $lum-text-sub;
font-size: 0.95rem;
margin-top: 2rem;
}
// ── Selection bar (appears when projects are selected) ────────────────────
.lumiere-selection-bar {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.6rem 1rem;
margin-bottom: 1rem;
background: rgba($lum-teal, 0.08);
border: 1px solid rgba($lum-teal, 0.22);
border-radius: 10px;
flex-wrap: wrap;
}
.lumiere-selection-bar-left {
display: flex;
align-items: center;
gap: 0.6rem;
flex-shrink: 0;
}
.lumiere-selection-count {
font-size: 0.875rem;
font-weight: 600;
color: $lum-teal;
white-space: nowrap;
}
.lumiere-selection-deselect {
margin-left: auto;
font-size: 0.8rem;
color: $lum-text-sub;
background: none;
border: none;
padding: 0.2rem 0.4rem;
cursor: pointer;
border-radius: 4px;
flex-shrink: 0;
&:hover {
color: $lum-text;
background: rgba(0, 0, 0, 0.06);
}
}
// ── Card grid ─────────────────────────────────────────────────────────────
.lumiere-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1.5rem;
margin-top: 0.5rem;
}
// ── Card wrapper (checkbox + link) ────────────────────────────────────────
.lumiere-card-wrapper {
position: relative;
}
// Checkbox overlay: top-left corner of the card, low opacity by default.
// Becomes fully opaque on hover, on focus, and for all cards when any is
// selected (:has(input:checked) on the grid).
.lumiere-card-checkbox {
position: absolute;
top: 8px;
left: 8px;
z-index: 2;
opacity: 0.35;
transition: opacity 0.15s ease;
input[type='checkbox'] {
width: 16px;
height: 16px;
cursor: pointer;
accent-color: $lum-teal;
}
}
.lumiere-card-wrapper:hover .lumiere-card-checkbox,
.lumiere-card-wrapper:focus-within .lumiere-card-checkbox {
opacity: 1;
}
// When any card in the grid is checked, show all checkboxes fully
.lumiere-card-grid:has(input[type='checkbox']:checked) .lumiere-card-checkbox {
opacity: 1;
}
// Selected card: teal border + tint
.lumiere-card-wrapper:has(input[type='checkbox']:checked) .lumiere-card {
border-color: rgba($lum-teal, 0.5);
background: rgba($lum-teal, 0.06);
box-shadow:
0 0 0 2px rgba($lum-teal, 0.18),
0 2px 8px rgba(0, 0, 0, 0.07);
}
// ── Individual card ───────────────────────────────────────────────────────
// Card container is now a <div>; the clickable area is .lumiere-card-link
.lumiere-card {
display: flex;
flex-direction: column;
border-radius: 10px;
background: rgba(255, 255, 255, 0.82);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.6);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07);
transition: transform 0.18s ease, box-shadow 0.18s ease;
overflow: hidden;
&:hover,
&:focus-within {
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
}
// The anchor covers thumb + body, with no underline or color bleed
.lumiere-card-link {
display: flex;
flex-direction: column;
flex: 1;
text-decoration: none;
color: inherit;
&:hover,
&:focus-visible {
text-decoration: none;
color: inherit;
}
}
// Action strip — icon buttons that fade in when the card is hovered
.lumiere-card-actions {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
padding: 3px 6px 5px;
border-top: 1px solid rgba($lum-teal, 0.10);
opacity: 0;
transition: opacity 0.15s ease;
// Recolour OLIconButton's action-btn class for the Lumière palette
.action-btn {
color: $lum-text-muted !important;
border-radius: 6px !important;
&:hover,
&:focus {
color: $lum-teal !important;
background: rgba($lum-teal, 0.09) !important;
}
}
}
.lumiere-card:hover .lumiere-card-actions,
.lumiere-card:focus-within .lumiere-card-actions {
opacity: 1;
}
// ── Card thumbnail ────────────────────────────────────────────────────────
.lumiere-card-thumb {
position: relative;
height: 130px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
&::after {
content: '';
position: absolute;
top: 0;
right: 0;
width: 0;
height: 0;
border-style: solid;
border-width: 0 28px 28px 0;
border-color: transparent rgba(255, 255, 255, 0.3) transparent transparent;
z-index: 2;
}
}
// Thumbnail image — shown when the project has a cached compiled PDF page.
// Falls back to the gradient + initial when the image 404s or is loading.
// The counter-transform on hover keeps the image visually stationary while
// the card rises beneath it (matching the card's translateY(-3px) lift).
.lumiere-card-thumb-img {
position: absolute;
top: 6px;
left: 6px;
right: 6px;
bottom: 0;
width: calc(100% - 12px);
height: calc(100% - 6px);
object-fit: cover;
object-position: top center;
border-radius: 4px 4px 0 0;
z-index: 1;
transition: transform 0.18s ease;
}
.lumiere-card:hover .lumiere-card-thumb-img,
.lumiere-card:focus-within .lumiere-card-thumb-img {
transform: translateY(3px);
}
.lumiere-card-initial {
font-family: Georgia, 'Times New Roman', 'DejaVu Serif', serif;
font-size: 3rem;
font-weight: 700;
color: rgba(255, 255, 255, 0.75);
line-height: 1;
user-select: none;
position: relative;
z-index: 0;
}
.lumiere-card--latex .lumiere-card-thumb {
background: linear-gradient(135deg, #4caf7d 0%, $lum-teal 100%);
}
.lumiere-card--typst .lumiere-card-thumb {
background: linear-gradient(135deg, $lum-teal 0%, $lum-blue 100%);
}
.lumiere-card--quarto .lumiere-card-thumb {
background: linear-gradient(135deg, #7c4dff 0%, $lum-blue 100%);
}
.lumiere-card--quarto-slides .lumiere-card-thumb {
background: linear-gradient(135deg, #e67e22 0%, #e74c3c 100%);
}
// ── Card body ─────────────────────────────────────────────────────────────
.lumiere-card-body {
display: flex;
flex-direction: column;
gap: 0.35rem;
padding: 0.85rem 0.9rem 0.9rem;
flex: 1;
}
.lumiere-card-name {
font-size: 0.875rem;
font-weight: 600;
color: $lum-text;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.lumiere-card-meta {
display: flex;
align-items: center;
gap: 0.4rem;
flex-wrap: wrap;
}
.lumiere-card-owner {
font-size: 0.72rem;
color: $lum-text-sub;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100px;
}
.lumiere-card-date {
font-size: 0.72rem;
color: $lum-text-muted;
margin-top: auto;
}
// ── Format badges ─────────────────────────────────────────────────────────
.lumiere-format-badge {
display: inline-block;
font-size: 0.65rem;
font-weight: 600;
letter-spacing: 0.03em;
text-transform: uppercase;
padding: 0.15em 0.5em;
border-radius: 4px;
line-height: 1.5;
}
.lumiere-format-badge--latex {
background: rgba(#e8f5ee, 0.9);
color: $lum-teal;
}
.lumiere-format-badge--typst {
background: rgba(#e0f2fe, 0.9);
color: $lum-blue;
}
.lumiere-format-badge--quarto,
.lumiere-format-badge--quarto-slides {
background: rgba(#ede9fe, 0.9);
color: #7c4dff;
}
}
// ── Footer — Lumière override ─────────────────────────────────────────────────
// Verso always renders a ThinFooter (site-footer) because showThinFooter is
// forced true for non-SaaS instances. Target footer.site-footer, not .fat-footer.
// !important beats the dark theme rule:
// :root [data-theme='default'] .project-ds-nav-page footer.site-footer (0,3,1).
.project-list-lumiere footer.site-footer {
background-color: #c8e4de !important;
color: $lum-text !important;
border-top-color: $lum-border !important;
a {
color: $lum-text-sub !important;
&:hover {
color: $lum-teal !important;
}
}
--link-color: #{$lum-text-sub};
--link-hover-color: #{$lum-teal};
--link-visited-color: #{$lum-text-sub};
}