/* 09-gallery - part of the former static/style.css.
*
* The stylesheet is split by area, but the split is mechanical: rules were
* moved verbatim and NOT regrouped. The numeric prefix is the load order, and
* that order is what preserves the cascade. A rule sits in the file its
* original position fell into, so an area's rules are not all in its file and
* a file is not only that area's rules.
*
* Do not reorder the tags in static/index.html, and do not move a rule
* between files: with 1,151 redeclarations and 1,886 !important in this
* cascade, either can change which declaration wins.
*
* static/index.html and the PRECACHE list in static/sw.js must agree on this
* order. tests/test_static_stylesheet_manifest.py and
* tests/test_css_computed_style_snapshot.py fail if that drifts.
*/
.email-reader-actions-inline.email-reader-actions-compact > .reader-action-overflowed {
display: none !important;
}
/* ── Archive browser ── */
.archive-list {
grid-template-columns: 1fr !important;
gap: 4px !important;
}
.archive-row {
flex-direction: row !important;
}
.archive-row .doclib-card-header {
flex: 1;
padding: 8px 10px;
}
.archive-row .doclib-card-title {
flex: 1;
min-width: 0;
}
/* Archive column layout */
.archive-header {
display: flex;
align-items: center;
padding: 4px 10px;
gap: 6px;
font-size: 9px;
font-weight: 600;
opacity: 0.35;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.archive-col-title {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-left: -4px;
}
.archive-col-msgs {
width: 40px;
flex-shrink: 0;
text-align: left;
margin-left: -8px;
}
.archive-col-model {
width: 90px;
flex-shrink: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.archive-col-time {
width: 55px;
flex-shrink: 0;
text-align: right;
}
.archive-col-menu {
width: 24px;
flex-shrink: 0;
}
.archive-menu-btn {
background: transparent;
border: none;
color: var(--fg);
opacity: 0.3;
cursor: pointer;
padding: 4px;
line-height: 0;
border-radius: 4px;
transition: background .15s, opacity .15s;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-left: 4px;
vertical-align: middle;
height: 100%;
}
.archive-menu-btn svg {
display: block;
margin-top: -6px;
}
.archive-menu-btn:hover {
background: color-mix(in srgb, var(--fg) 10%, transparent);
opacity: 1;
}
/* ── Gallery (image library) ── */
.gallery-modal-content {
width: min(820px, 94vw);
max-height: 92vh;
font-size: 12px;
}
/* Keep the Gallery frame anchored while tabs or bulk-selection controls
change the content height. Centering the frame made every shorter tab move
the whole window vertically. */
@media (min-width: 769px) {
#calendar-modal,
#gallery-modal,
#tasks-modal,
#memory-modal,
#doclib-modal,
#compare-model-overlay,
#research-overlay,
#theme-modal,
#settings-modal,
#email-lib-modal,
#cookbook-modal {
align-items: flex-start;
padding-top: 4vh;
box-sizing: border-box;
}
}
/* While the Edit tab is active the editor needs a *definite* height so
the right-side tool panel (`.ge-right-panel { overflow-y:auto }`)
scrolls INTERNALLY. Without this the modal only has `max-height`, so
`.gallery-editor { height:100% }` can't resolve — the editor grows to
its full content height, overflows the modal body, and the modal-body
scrollbar that appears can't be wheel-scrolled because the editor's
`overscroll-behavior:contain` blocks the chain. Pinning a real height
here makes the panel bounded and scrollable as designed. Scoped to
the editor view (matched via the container's inline `display:flex`)
so the Photos/Albums views keep sizing to their content. */
.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) {
height: 92vh;
}
/* The editor tab owns the modal body's scroll context. Keep the landing
chooser and the mounted editor inside the available sheet height so a long
saved-project list cannot push the launch controls below the viewport. */
.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) > .modal-body {
display: flex;
min-height: 0;
overflow: hidden;
}
.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) #gallery-editor-container {
min-height: 0;
overflow: hidden;
}
/* Photo-detail view sizing (issue #314).
The detail view is rendered as a `position:absolute; inset:0` overlay
*inside* `.gallery-images-container`, painted over the photo grid. Because
it's absolutely positioned it can't contribute to the container's height —
the container (and therefore the overlay's `inset:0` box) collapses to the
height of the grid sitting behind it. When the library only has a few
photos that grid is short, so the detail view is crushed: the image is
clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a
tiny, internally-scrolling strip. (With a large library the grid is tall,
which is why it looked fine in the demo video but cramped for users with
few photos.)
Fix: when the detail view is open, hide the grid-view siblings and drop the
overlay into normal flow. The container — and the window, up to its 92vh
max-height — then sizes to the detail's own content (image + metadata), so
nothing is clipped or squeezed regardless of how many photos exist. Scoped
via the detail element's inline `display:flex` so the grid / albums views
keep sizing to their own content. Works on both desktop and the mobile
full-screen sheet. */
#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) {
display: none !important;
}
#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail {
position: static;
}
/* Containing block for the photo-detail overlay — keeps it inside the body
so it sits below the modal header and the tab strip instead of covering them. */
.gallery-images-container {
position: relative;
}
.gallery-modal-content .modal-header h4 {
font-size: 1rem;
}
.gallery-stats {
font-size: 10px;
color: color-mix(in srgb, var(--fg) 50%, transparent);
margin-bottom: 4px;
}
.gallery-toolbar {
display: flex;
gap: 6px;
margin-bottom: 4px;
align-items: center;
}
.gallery-toolbar-break { display: none; }
/* Keep the hint anchored while independently seating the input in the row. */
.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 0; }
.gallery-search {
position: relative;
top: 4px;
flex: 1;
width: 100%;
padding: 4px 8px;
padding-right: 78px; /* room for the enter hint */
background: var(--bg);
color: var(--fg);
border: 1px solid var(--border);
border-radius: 6px;
font: inherit;
font-size: 12px;
outline: none;
}
.gallery-search-enter-hint {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 10px;
font-weight: 600;
color: var(--accent, var(--red));
pointer-events: none;
opacity: 0;
transition: opacity 0.12s;
white-space: nowrap;
}
/* Show the hint only once the user has typed something (placeholder gone). */
.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; }
.gallery-search:focus {
border-color: var(--red);
}
.gallery-model-filter,
.gallery-sort {
padding: 4px 8px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--fg);
font: inherit;
font-size: 11px;
cursor: pointer;
position: relative;
top: 4px;
}
.gallery-selection-bar {
margin-bottom: 4px;
position: relative;
top: 2px;
padding-right: 18px;
}
.gallery-tag-chips {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 4px;
}
.gallery-chip {
height: 28px;
box-sizing: border-box;
padding: 0 13px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
border-radius: 14px;
font-size: 12px;
line-height: 1;
border: 1px solid var(--border);
background: none;
color: var(--fg);
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
}
.gallery-chip:hover {
border-color: var(--red);
}
.gallery-chip.active {
background: color-mix(in srgb, var(--red) 15%, transparent);
border-color: color-mix(in srgb, var(--red) 40%, transparent);
color: var(--red);
}
/* Album chips row */
.gallery-album-chips {
display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px;
}
.gallery-chip-fav { color: var(--red); }
.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); }
.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; }
/* Active-album indicator — appears when the Photos grid is filtered to one album. */
.gallery-chip-active-album,
.gallery-active-filter-pill {
display: inline-flex; align-items: center; gap: 4px;
background: color-mix(in srgb, var(--red) 14%, transparent);
border-color: color-mix(in srgb, var(--red) 45%, transparent);
position: relative;
top: 6px;
}
.gallery-active-filter-pill {
background: color-mix(in srgb, var(--fg) 7%, transparent);
border-color: color-mix(in srgb, var(--fg) 18%, transparent);
color: color-mix(in srgb, var(--fg) 86%, transparent);
}
.gallery-clear-all-filters {
height: 28px;
border-style: dashed;
opacity: 0.72;
}
.gallery-clear-all-filters:hover {
opacity: 1;
color: var(--red);
}
.gallery-chip-clear {
background: none;
border: none;
color: inherit;
opacity: 0.6;
cursor: pointer;
padding: 0;
margin-left: 1px;
width: 12px;
height: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
line-height: 1;
position: relative;
top: -4px;
}
.gallery-chip-clear:hover { opacity: 1; }
/* Favorite button on card */
/* Video cards: