Files
Verso/services/web/frontend/stylesheets/pages/project-list-lumiere.scss
T
claude 0bbc07e0b9
Build and Deploy Verso / deploy (push) Successful in 15m20s
fix: improve thumbnail quality and show gradient border around preview
Increase pdftocairo output from 190px/q50 to 380px/q82 — 2× resolution
for crisp rendering on retina displays, higher quality to eliminate
visible compression artefacts.

Inset the thumbnail image 6px from the tile edges (inset: 6px) with a
4px border-radius so the card's colour gradient is visible as a frame
around the document preview.

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

680 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.
.lumiere-card-thumb-img {
position: absolute;
inset: 6px;
object-fit: cover;
object-position: top center;
border-radius: 4px;
z-index: 1;
}
.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 ─────────────────────────────────────────────────
// The dark theme rule is :root [data-theme='default'] .project-ds-nav-page
// .fat-footer with specificity (0,4,0), which beats a plain class selector.
// We use !important on actual properties to guarantee the light-teal look
// regardless of the user's theme preference.
.project-list-lumiere .fat-footer {
background: #c8e4de !important;
color: $lum-text !important;
border-top: 1px solid $lum-border;
.footer-section-heading {
color: $lum-teal !important;
}
.fat-footer-base {
color: $lum-text-muted !important;
#language-picker-toggle {
color: $lum-text-sub !important;
}
}
// Links override
--link-color: #{$lum-text-sub};
--link-hover-color: #{$lum-teal};
--link-visited-color: #{$lum-text-sub};
}