// 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;
}
// Search bar — wide enough to show the full placeholder (relaxed on mobile)
form.project-search .form-control {
min-width: 360px;
@media (max-width: 767px) {
min-width: 0;
}
}
.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
; 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;
}
// ── XS compact list view ─────────────────────────────────────────────────
// Activated via .lumiere-card-grid--compact (cardScale === 0).
// Uses ProjectCardCompact which reuses the classic table cell components
// (FormatCell, OwnerCell, LastUpdatedCell, ActionsCell) laid out in a
// CSS Grid so every row aligns like a proper table.
.lumiere-card-grid--compact {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-top: 0.5rem;
}
.lumiere-compact-row {
display: grid;
// checkbox | name+tags | format | owner | date | actions
grid-template-columns: 28px 1fr 96px 120px 150px auto;
align-items: center;
column-gap: 0.85rem;
padding: 0.45rem 0.75rem;
background: rgba(#edf7f5, 0.82);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba($lum-teal, 0.18);
border-radius: 7px;
box-shadow: 0 1px 4px rgba($lum-teal, 0.06);
transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
&:hover {
background: rgba(#e4f4f1, 0.96);
border-color: rgba($lum-teal, 0.35);
box-shadow: 0 2px 10px rgba($lum-teal, 0.10);
}
}
// Override classic project-format-badge colors so the XS compact view
// shows the same soft Lumière palette as the card-based views.
.project-format-badge {
border-radius: 4px;
font-size: 0.65rem;
font-weight: 600;
letter-spacing: 0.03em;
text-transform: uppercase;
padding: 0.15em 0.5em;
line-height: 1.5;
border: none;
white-space: nowrap;
}
.project-format-badge-latex { background-color: rgba(#e6f4ec, 0.9); color: #098842; border: none; }
.project-format-badge-typst { background-color: rgba(#e0f7f5, 0.9); color: #239dad; border: none; }
.project-format-badge-quarto { background-color: rgba(#e8eef5, 0.9); color: #447099; border: none; }
.project-format-badge-quarto-slides { background-color: rgba(#fde8ec, 0.9); color: #e4637c; border: none; }
.lumiere-compact-checkbox {
display: flex;
align-items: center;
input[type='checkbox'] {
width: 16px;
height: 16px;
cursor: pointer;
accent-color: $lum-teal;
}
}
.lumiere-compact-name-cell {
display: flex;
align-items: center;
gap: 0.4rem;
overflow: hidden;
min-width: 0;
}
.lumiere-compact-name {
font-size: 0.875rem;
font-weight: 600;
color: $lum-text;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:hover,
&:focus {
color: $lum-teal;
text-decoration: none;
}
}
.lumiere-compact-owner {
font-size: 0.8rem;
color: $lum-text-sub;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.lumiere-compact-date {
font-size: 0.8rem;
color: $lum-text-muted;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// On desktop, the meta wrapper is invisible to grid layout — its children
// participate as direct grid items, preserving the 6-column alignment.
.lumiere-compact-meta {
display: contents;
}
.lumiere-compact-actions {
display: flex;
align-items: center;
gap: 1px;
.action-btn {
color: $lum-text-muted !important;
border-radius: 6px !important;
opacity: 0;
transition: opacity 0.12s ease, color 0.12s ease, background-color 0.12s ease;
&:hover,
&:focus {
color: $lum-teal !important;
background: rgba($lum-teal, 0.09) !important;
}
}
// ⋮ dropdown toggle — restyled from the table variant to match Lumière
.dropdown-table-button-toggle {
padding: 0.2rem 0.35rem;
border-radius: 6px;
background: transparent;
border: none;
color: $lum-text-muted;
line-height: 1;
transition: background-color 0.15s ease, color 0.15s ease;
&:hover,
&:focus,
&[aria-expanded='true'] {
background: rgba($lum-teal, 0.09);
color: $lum-teal;
outline: none;
}
}
}
.lumiere-compact-row:hover .lumiere-compact-actions .action-btn,
.lumiere-compact-row:focus-within .lumiere-compact-actions .action-btn {
opacity: 1;
}
// ── Mobile layout overrides ────────────────────────────────────────────────
@media (max-width: 767px) {
// Stack the header vertically so nothing overflows the viewport width.
// Without this, lumiere-header-actions (flex-shrink:0) forces the flex
// container wider than the screen.
.lumiere-header {
flex-direction: column;
align-items: stretch;
gap: 0.5rem;
}
.lumiere-header-actions {
width: 100%;
flex-shrink: 1;
min-width: 0;
form.project-search {
flex: 1;
min-width: 0;
}
}
// Compact row: 2-line layout
// Row 1: [checkbox] [name+tags] [⋮ action]
// Row 2: [checkbox] [format · owner · date]
// ActionsDropdown (single ⋮ button) is shown via d-md-none; ActionsCell
// (many icon buttons) is hidden on mobile, so the auto column stays tiny.
.lumiere-compact-row {
grid-template-columns: 28px 1fr auto;
grid-template-rows: auto auto;
row-gap: 0.2rem;
.lumiere-compact-checkbox {
grid-column: 1;
grid-row: 1 / 3;
align-self: center;
}
.lumiere-compact-name-cell {
grid-column: 2;
grid-row: 1;
}
.lumiere-compact-actions {
grid-column: 3;
grid-row: 1;
align-self: center;
opacity: 1;
.action-btn { opacity: 1; }
}
// Meta wrapper becomes a flex row spanning the second line
.lumiere-compact-meta {
display: flex;
align-items: center;
gap: 0.4rem;
flex-wrap: wrap;
grid-column: 2 / 4;
grid-row: 2;
}
}
// Card tile grid: M = 2 tiles/row, L = 1 tile/row.
// --lum-card-scale inline var is suppressed on mobile (isMobile=true in JS),
// so the CSS custom property here wins for thumbnail sizing.
.lumiere-card-grid:not(.lumiere-card-grid--compact) {
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
--lum-card-scale: 0.85;
&.lumiere-card-grid--mobile-1col {
grid-template-columns: 1fr;
--lum-card-scale: 1.3;
}
}
}
// ── Touch devices: card actions always visible (no hover cursor on phones) ──
@media (hover: none) {
.lumiere-card-actions {
opacity: 1;
}
}
// ── Mobile toolbar: filter pills + zoom control ───────────────────────────
.lumiere-mobile-toolbar {
align-items: flex-start;
gap: 0.4rem 0.5rem;
width: 100%;
padding: 0.25rem 0 0.5rem;
flex-shrink: 0;
flex-wrap: wrap;
}
.lumiere-mobile-filters {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
flex: 1;
min-width: 0;
}
.lumiere-mobile-zoom {
flex-shrink: 0;
}
.lumiere-mobile-filter-pill {
flex-shrink: 0;
padding: 0.3rem 0.8rem;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 500;
border: 1.5px solid $lum-border;
background: rgba(255, 255, 255, 0.7);
color: $lum-text-sub;
cursor: pointer;
white-space: nowrap;
transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease;
line-height: 1.4;
&.active {
background: $lum-teal;
border-color: $lum-teal;
color: #fff;
font-weight: 600;
}
&:hover:not(.active),
&:focus:not(.active) {
border-color: rgba($lum-teal, 0.55);
color: $lum-teal;
background: rgba($lum-teal, 0.07);
}
}
// ── 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;
}
// ── Global Lumière rules (apply to every page when data-lumiere is set) ────
// These cover pages that don't carry .project-list-lumiere (404, settings).
// min-height on html (not body) so the gradient fills the viewport without
// adding artificial height that would push the footer below the fold.
html:has(body[data-lumiere='true']) {
min-height: 100vh;
}
[data-lumiere='true'] {
background: linear-gradient(160deg, #f0faf8 0%, #e4f4f1 50%, #daeef5 100%);
// Navbar: white background with teal accent stripe.
// Override CSS custom properties used by navbar.scss — no hard-coded
// class selectors needed, and position:absolute from base CSS is kept.
.navbar-default {
--navbar-bg: #ffffff;
--navbar-link-color: #{$lum-text-sub};
--navbar-link-hover-color: #{$lum-teal};
--navbar-link-hover-bg: rgba(42, 157, 143, 0.08);
--navbar-link-border-color: transparent;
--navbar-link-hover-border-color: rgba(42, 157, 143, 0.2);
--navbar-subdued-color: #{$lum-text};
--navbar-subdued-hover-color: #{$lum-teal};
--navbar-subdued-hover-bg: rgba(42, 157, 143, 0.08);
--navbar-title-color: #{$lum-text};
--navbar-title-color-hover: #{$lum-teal};
border-bottom: none !important;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06), 0 2px 8px rgba(0, 0, 0, 0.04);
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, $lum-teal 0%, $lum-blue 100%);
}
}
}