Files
Verso/services/web/frontend/stylesheets/pages/project-list-lumiere.scss
T
claude 6f419477df
Build and Deploy Verso / deploy (push) Successful in 1m42s
lumiere: fix mobile overflow + restore tile view with XS/M/L zoom
Overflow fix: ActionsCell (up to 9 icon buttons) was assigned to the
`auto` column in the compact row, blowing out the row to ~300px.
Replace with ActionsDropdown (single ⋮ button) on mobile via
d-md-none / d-none d-md-flex, same pattern as the classic table.

Tile view: remove the isMobile force to compact (effectiveScale=0).
Add a mobile-only toolbar row (d-flex d-md-none) combining the filter
pills and a new XS/M/L zoom control:
  - XS (scale=0)  → compact rows
  - M  (scale=1)  → 2 tiles per row  (CSS: repeat(2, 1fr))
  - L  (scale≥1.35) → 1 tile per row (CSS: 1fr, class --mobile-1col)

The --lum-card-scale inline var is suppressed on mobile so CSS media
query controls thumbnail height (0.85 for 2-col, 1.3 for 1-col).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 08:54:30 +00:00

1158 lines
35 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;
}
// 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 <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;
}
// ── 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;
}
}
}
.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) {
// 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;
}
}
}
// ── Mobile toolbar: filter pills + zoom control ───────────────────────────
.lumiere-mobile-toolbar {
align-items: center;
gap: 0.5rem;
width: 100%;
padding: 0.25rem 0 0.5rem;
flex-shrink: 0;
}
.lumiere-mobile-filters {
overflow-x: auto;
display: flex;
gap: 0.45rem;
flex: 1;
min-width: 0;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar { display: none; }
}
.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%);
}
}
}