Files
Verso/services/web/frontend/stylesheets/pages/project-list-lumiere.scss
T
claude 2f4b60574c
Build and Deploy Verso / deploy (push) Successful in 15m11s
build 217: square logo on password reset, align tile colours to classic badges
Use verso-square.svg (small icon) instead of the wordmark on the
password reset page. Align card thumbnail gradients and format badge
colours to match the classic project list badge colours (LaTeX green
#098842, Typst teal #239dad, Quarto blue #447099, Quarto Slides
pink-red #e4637c). Split quarto-slides badge from quarto badge.

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

855 lines
26 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: none !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: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.75rem;
flex-wrap: wrap;
}
// Title + zoom control grouped on the left
.lumiere-title-row {
display: flex;
align-items: center;
gap: 0.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 {
--lum-card-scale: 1;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(calc(180px * var(--lum-card-scale)), 1fr));
gap: 1.5rem;
margin-top: 0.5rem;
}
// ── Zoom control (S / M / L card-size picker) ─────────────────────────────
.lumiere-zoom-control {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.6);
border: 1px solid $lum-border;
border-radius: 7px;
padding: 2px;
gap: 1px;
flex-shrink: 0;
}
.lumiere-zoom-btn {
background: none;
border: none;
border-radius: 5px;
padding: 3px 9px;
font-size: 0.72rem;
font-weight: 700;
color: $lum-text-sub;
cursor: pointer;
line-height: 1;
transition: background-color 0.12s ease, color 0.12s ease;
letter-spacing: 0.05em;
&:hover {
background: rgba($lum-teal, 0.1);
color: $lum-teal;
}
&.active {
background: #fff;
color: $lum-teal;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
}
// ── 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: 10px;
left: 10px;
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: blue ring on solid white — clearly distinct from the teal page bg
.lumiere-card-wrapper:has(input[type='checkbox']:checked) .lumiere-card {
border-color: rgba($lum-blue, 0.65);
background: rgba(255, 255, 255, 0.96);
box-shadow:
0 0 0 3px rgba($lum-blue, 0.22),
0 4px 12px rgba(0, 0, 0, 0.10);
}
// ── 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: calc(130px * var(--lum-card-scale, 1));
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: 14px;
left: 12px;
right: 12px;
bottom: 0;
width: calc(100% - 24px);
height: calc(100% - 14px);
object-fit: cover;
object-position: top center;
border-radius: 5px 5px 0 0;
z-index: 1;
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: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%, #098842 100%);
}
.lumiere-card--typst .lumiere-card-thumb {
background: linear-gradient(135deg, #4dc8bf 0%, #239dad 100%);
}
.lumiere-card--quarto .lumiere-card-thumb {
background: linear-gradient(135deg, #6b9ec3 0%, #447099 100%);
}
.lumiere-card--quarto-slides .lumiere-card-thumb {
background: linear-gradient(135deg, #f09aaa 0%, #e4637c 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(#e6f4ec, 0.9);
color: #098842;
}
.lumiere-format-badge--typst {
background: rgba(#e0f7f5, 0.9);
color: #239dad;
}
.lumiere-format-badge--quarto {
background: rgba(#e8eef5, 0.9);
color: #447099;
}
.lumiere-format-badge--quarto-slides {
background: rgba(#fde8ec, 0.9);
color: #e4637c;
}
// ── Card tags ─────────────────────────────────────────────────────────────
// Dots live inside .lumiere-card-meta so they add zero extra height.
.lumiere-card-tag-dot {
flex-shrink: 0;
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
cursor: default;
}
// ── 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;
}
}
}
}
// ── Tooltip — prevent hover-stealing from card lift animation ─────────────────
// Bootstrap tooltips are purely informational. pointer-events: none ensures
// they never steal :hover from the card beneath them (which would cause the
// translateY lift to flicker when hovering over a tag dot).
.tooltip {
pointer-events: none;
}
// ── Footer — Lumière override ─────────────────────────────────────────────────
// Pale teal canvas with noise grain, 2px teal→blue accent stripe at top.
// Left col: serif author credit. Right col: monospace/uppercase meta links.
// !important beats :root [data-theme='default'] .project-ds-nav-page footer.site-footer.
.project-list-lumiere footer.site-footer {
position: relative;
.site-footer-content > .row {
align-items: center;
}
background-color: #edf7f4 !important;
background-image: #{$lum-noise} !important;
background-size: 200px 200px !important;
background-repeat: repeat !important;
border-top: none !important;
color: #64748b !important;
// 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 {
color: #64748b !important;
text-decoration: none;
transition: color 0.15s ease;
&:hover {
color: $lum-teal !important;
}
}
// 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: $lum-text !important;
font-weight: 500;
}
// Meta links (AGPL, source code) — subtle monospace/uppercase treatment
.col-lg-3 {
font-family: ui-monospace, 'SFMono-Regular', 'Fira Code', Consolas, monospace;
text-transform: uppercase;
letter-spacing: 0.07em;
color: $lum-text-muted !important;
a {
color: $lum-text-muted !important;
&:hover {
color: $lum-teal !important;
}
}
}
// Pipe separators
.text-muted {
color: #b8d4cf !important;
}
--link-color: #64748b;
--link-hover-color: #{$lum-teal};
--link-visited-color: #64748b;
}