This repository has been archived on 2026-05-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
imagerepo/app/static/style.css
T

1510 lines
33 KiB
CSS
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.
/* /app/static/style.css
==============================================================================
ImageRepo Global Styles
- Theme tokens
- Base & layout
- Header / Navbar
- Flash messages
- Pagination
- Gallery
- Modal viewer
- Tag overlays & editor
- Tag/Artist cards (tags_list.html)
- Settings
- Index / Landing
- Forms
==============================================================================*/
/*------------------------------------------------------------------------------
Theme tokens - Dark theme matching showcase
------------------------------------------------------------------------------*/
:root {
/* surfaces */
--bg: #0a0a0a;
--bg-elevated: #141414;
--panel: #1a1a1a;
--panel-alt: rgba(20, 20, 20, 0.95);
/* nav */
--nav-bg: rgba(0, 0, 0, 0.8);
--nav-border: rgba(255, 255, 255, 0.08);
/* text */
--text: #ffffff;
--text-dim: rgba(255, 255, 255, 0.7);
--text-muted: rgba(255, 255, 255, 0.5);
/* accents */
--link: #a78bfa;
--link-hover: #c4b5fd;
/* shadows */
--shadow-1: 0 2px 8px rgba(0, 0, 0, 0.4);
--shadow-2: 0 4px 12px rgba(0, 0, 0, 0.5);
--shadow-3: 0 8px 24px rgba(0, 0, 0, 0.6);
/* buttons */
--btn-primary: #6366f1;
--btn-primary-hover: #4f46e5;
--btn-danger: #dc2626;
--btn-danger-hover: #b91c1c;
/* flash */
--flash-info: #3b82f6;
--flash-success: #22c55e;
--flash-warning: #f59e0b;
--flash-danger: #ef4444;
/* filter pills */
--filter-bg: rgba(255, 255, 255, 0.08);
--filter-fg: #e2e8f0;
--filter-border: rgba(255, 255, 255, 0.15);
--filter-active-bg: #6366f1;
--filter-active-fg: #ffffff;
--filter-active-border: #818cf8;
}
/*------------------------------------------------------------------------------
Base & layout
------------------------------------------------------------------------------*/
* { box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 0;
color: var(--text);
background-color: var(--bg);
min-height: 100vh;
}
.content {
padding: 1rem;
max-width: 1800px;
margin: 0 auto;
}
.mainview {
max-width: 100%;
margin: 0 auto;
background-color: transparent;
min-height: 100vh;
}
h1 {
font-weight: 500;
letter-spacing: -0.02em;
}
hr {
border: none;
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin: 0;
}
/*------------------------------------------------------------------------------
Header / Navbar (sticky with gradient fade)
------------------------------------------------------------------------------*/
header {
position: sticky;
top: 0;
z-index: 1000;
background: linear-gradient(to bottom, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.6) 70%, rgba(0,0,0,0) 100%);
padding-bottom: 1rem;
}
.navbar {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.25rem;
padding: 0.75rem 1rem;
background: transparent;
border: none;
}
.nav-button {
display: inline-block;
padding: 0.5rem 1rem;
color: var(--text);
text-decoration: none;
border-radius: 6px;
font-weight: 500;
font-size: 1rem;
transition: background 0.2s ease, transform 0.1s ease;
text-shadow: 0 1px 3px rgba(0,0,0,0.5);
}
.nav-button:hover {
background: rgba(255,255,255,0.15);
transform: translateY(-1px);
}
.nav-button:active {
background: rgba(255,255,255,0.1);
transform: translateY(0);
}
/*------------------------------------------------------------------------------
Flash messages
------------------------------------------------------------------------------*/
.flash-container {
max-width: 800px;
margin: 1rem auto;
padding: 0 1rem;
}
.flash {
padding: 1rem;
margin-bottom: 1rem;
border-radius: 6px;
font-weight: 500;
color: #fff;
box-shadow: var(--shadow-1);
}
.flash-info { background-color: var(--flash-info); }
.flash-message,
.flash-success { background-color: var(--flash-success); }
.flash-warning { background-color: var(--flash-warning); }
.flash-danger,
.flash-error { background-color: var(--flash-danger); }
/*------------------------------------------------------------------------------
Pagination - Floating minimal bar
------------------------------------------------------------------------------*/
.floating-pagination {
position: fixed;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: rgba(20, 20, 20, 0.9);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 999px;
box-shadow: var(--shadow-3);
z-index: 100;
}
.fp-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
color: var(--text);
text-decoration: none;
font-size: 1.25rem;
transition: all 0.2s ease;
}
.fp-btn:hover:not(.disabled) {
background: rgba(255, 255, 255, 0.15);
color: white;
}
.fp-btn.disabled {
opacity: 0.3;
cursor: not-allowed;
pointer-events: none;
}
.fp-indicator {
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0 0.5rem;
font-size: 0.9rem;
color: var(--text-dim);
}
.fp-current {
color: var(--text);
font-weight: 500;
}
.fp-sep {
color: var(--text-muted);
}
.fp-total {
color: var(--text-muted);
}
/* Legacy pagination (kept for other pages if needed) */
.pagination-container {
text-align: center;
margin: 2rem 0;
}
.pagination-link {
display: inline-block;
margin: 0 0.2rem;
padding: 0.5rem 1rem;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
color: var(--text-dim);
text-decoration: none;
background: rgba(255, 255, 255, 0.05);
transition: all 0.2s ease;
}
.pagination-link:hover {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.25);
color: var(--text);
}
.pagination-link.active {
background: var(--btn-primary);
color: white;
border-color: var(--btn-primary);
}
.pagination-ellipsis { padding: 0 0.5rem; color: var(--text-muted); }
/*------------------------------------------------------------------------------
Gallery - Seamless grid with overlaid metadata
------------------------------------------------------------------------------*/
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 4px;
padding: 4px;
}
.gallery-item {
position: relative;
height: 320px;
background-color: var(--panel);
overflow: hidden;
cursor: pointer;
}
.gallery-thumb {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
transition: transform 0.3s ease, filter 0.3s ease;
}
.gallery-item:hover .gallery-thumb {
transform: scale(1.03);
filter: brightness(1.1);
}
/* Date overlay at bottom */
.image-date {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 6px 8px;
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.9);
background: linear-gradient(to top, rgba(0,0,0,0.6) 0%, rgba(0,0,0,0) 100%);
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
z-index: 2;
pointer-events: none;
}
.gallery-item a {
color: var(--link);
font-weight: 500;
text-decoration: none;
font-size: 0.9rem;
}
.gallery-item a:hover { text-decoration: underline; color: var(--link-hover); }
/* video play glyph sits under tags, above image */
.play-overlay {
position: absolute;
inset: 0;
display: grid;
place-items: center;
color: white;
font-size: 3rem;
text-shadow: 0 2px 8px rgba(0,0,0,0.8);
z-index: 1;
pointer-events: none;
opacity: 0.9;
}
/*------------------------------------------------------------------------------
Modal viewer
------------------------------------------------------------------------------*/
.modal {
display: none;
position: fixed;
inset: 0;
z-index: 1000;
background: rgba(0, 0, 0, 0.92);
justify-content: center;
align-items: center;
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
.modal.active { display: flex; }
.modal-content {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
}
/* Modal body - horizontal layout with image on left, tags on right */
.modal-body {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 1rem;
width: 100%;
max-width: 1600px;
max-height: 100%;
}
/* On smaller screens, stack vertically */
@media (max-width: 900px) {
.modal-body {
flex-direction: column;
align-items: center;
height: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
}
/* Mobile landscape: prioritize image, minimize tag editor */
@media (max-width: 900px) and (orientation: landscape) {
.modal-body {
flex-direction: row;
align-items: stretch;
}
.modal-image-wrapper {
max-width: calc(100% - 200px);
max-height: calc(100vh - 2rem);
height: calc(100vh - 2rem);
}
.tag-editor {
width: 180px;
min-width: 180px;
max-height: calc(100vh - 2rem);
overflow-y: auto;
}
}
/* Image wrapper & zoom */
.modal-image-wrapper {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
max-width: calc(100% - 300px); /* Account for tag editor */
height: calc(100vh - 140px);
max-height: calc(100vh - 140px);
overflow: hidden;
cursor: default;
}
.modal-image-wrapper.zoomed {
overflow: auto;
cursor: grab;
/* Enable scrolling by removing flex centering when zoomed */
display: block;
}
.modal-image-wrapper.zoomed img {
width: auto;
height: auto;
max-width: none;
max-height: none;
cursor: grab;
}
.modal-image-wrapper img {
max-width: 100%;
max-height: calc(100vh - 140px);
object-fit: contain;
border-radius: 4px;
}
.modal-image-wrapper video {
max-width: 100%;
max-height: calc(100vh - 140px);
border-radius: 4px;
}
/* Tall images */
.modal-image-wrapper.too-tall { overflow-y: auto; }
.modal-image-wrapper.too-tall img {
width: 90vw; height: auto; max-height: none;
}
/* On smaller screens (portrait), full width for image wrapper */
@media (max-width: 900px) and (orientation: portrait) {
.modal-image-wrapper {
max-width: 100%;
height: auto;
max-height: 55vh;
flex-shrink: 0;
}
.modal-image-wrapper img,
.modal-image-wrapper video {
max-height: 55vh;
}
}
/* Modal navigation buttons - cleaner pill style */
.modal-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 48px;
height: 48px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.15);
color: rgba(255, 255, 255, 0.8);
font-size: 1.25rem;
cursor: pointer;
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: all 0.2s ease;
border-radius: 50%;
}
.modal-button:hover {
background: rgba(255, 255, 255, 0.2);
color: white;
border-color: rgba(255, 255, 255, 0.3);
}
.modal-content:hover .modal-button { opacity: 1; }
.modal-prev { left: 1rem; }
.modal-next { right: 1rem; }
/* Mobile: always show nav buttons, larger touch targets */
@media (max-width: 900px) {
.modal-button {
opacity: 0.8;
width: 44px;
height: 44px;
}
.modal-prev { left: 0.5rem; }
.modal-next { right: 0.5rem; }
}
/* Close button */
.modal-close-btn {
position: absolute;
top: 1rem;
right: 1rem;
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 50%;
color: rgba(255, 255, 255, 0.8);
font-size: 1.5rem;
cursor: pointer;
z-index: 1002;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
line-height: 1;
}
.modal-close-btn:hover {
background: rgba(255, 255, 255, 0.2);
color: white;
border-color: rgba(255, 255, 255, 0.3);
}
/* Close hint - hidden on mobile since we have a button */
.modal-close-hint {
position: absolute;
top: 1.5rem;
right: 4rem;
color: rgba(255, 255, 255, 0.4);
font-size: 0.8rem;
pointer-events: none;
}
@media (max-width: 640px) {
.modal-close-hint { display: none; }
}
/*------------------------------------------------------------------------------
Tag overlays & editor
------------------------------------------------------------------------------*/
/* Pills used inside overlays and editors */
.tags {
margin-top: .4rem;
display: flex;
flex-wrap: wrap;
gap: .25rem;
}
/* Tag chips (truncate long names nicely) */
.tag-chip {
display: inline-block; /* required for ellipsis */
max-width: clamp(8rem, 22vw, 14rem); /* responsive cap */
padding: 2px 8px;
border-radius: 999px;
background: rgba(255,255,255,0.92);
color: #334155;
text-decoration: none;
border: 1px solid rgba(199,210,254,0.9);
white-space: nowrap; /* single line */
overflow: hidden; /* hide overflow */
text-overflow: ellipsis; /* … */
vertical-align: middle;
font: 12px/1.6 system-ui, sans-serif;
}
.tag-chip:hover { background: rgba(255,255,255,1); }
/* Modal tag editor - side panel layout */
.tag-editor {
width: 280px;
min-width: 280px;
max-height: calc(100vh - 40px);
overflow-y: auto;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 0.75rem;
border: 1px solid rgba(255, 255, 255, 0.1);
flex-shrink: 0;
}
/* On smaller screens (portrait), make tag editor full width and scrollable */
@media (max-width: 900px) and (orientation: portrait) {
.tag-editor {
width: 100%;
max-width: 500px;
min-width: unset;
max-height: 35vh;
overflow-y: auto;
flex-shrink: 0;
}
}
.tag-editor .tags {
margin: 0 0 0.5rem 0;
min-height: 28px;
}
/* Tag chips in modal - with remove button */
.tag-editor .tag-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.15);
color: var(--text);
}
.tag-editor .tag-chip:hover {
background: rgba(255, 255, 255, 0.18);
}
.tag-editor .tag-chip .x {
background: none;
border: none;
color: rgba(255, 255, 255, 0.5);
cursor: pointer;
padding: 0;
margin-left: 2px;
font-size: 1rem;
line-height: 1;
}
.tag-editor .tag-chip .x:hover {
color: #ef4444;
}
/* Tag form with autocomplete */
.tag-form {
display: flex;
gap: 0.5rem;
position: relative;
}
.tag-form-wrapper {
flex: 1;
position: relative;
}
.tag-form input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
background: rgba(255, 255, 255, 0.08);
color: var(--text);
font-size: 16px; /* Must be 16px+ to prevent iOS auto-zoom on focus */
}
.tag-form input::placeholder {
color: var(--text-muted);
}
.tag-form input:focus {
outline: none;
border-color: var(--btn-primary);
background: rgba(255, 255, 255, 0.12);
}
.tag-form button {
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
background: var(--btn-primary);
color: white;
cursor: pointer;
font-size: 0.9rem;
transition: background 0.2s ease;
white-space: nowrap;
}
.tag-form button:hover {
background: var(--btn-primary-hover);
}
/* Autocomplete dropdown - uses fixed positioning to escape overflow containers */
.tag-autocomplete {
position: fixed;
background: var(--panel);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
max-height: 240px;
overflow-y: auto;
z-index: 10100;
display: none;
box-shadow: var(--shadow-3);
min-width: 200px;
}
.tag-autocomplete.active {
display: block;
}
.tag-autocomplete-item {
padding: 0.5rem 0.75rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
color: var(--text);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.tag-autocomplete-item:last-child {
border-bottom: none;
}
.tag-autocomplete-item:hover,
.tag-autocomplete-item.selected {
background: rgba(255, 255, 255, 0.1);
}
.tag-autocomplete-item .tag-kind {
font-size: 0.75rem;
color: var(--text-muted);
margin-left: auto;
}
.tag-autocomplete-empty {
padding: 0.5rem 0.75rem;
color: var(--text-muted);
font-size: 0.85rem;
font-style: italic;
}
/* Tag overlay bar at the top of the image */
.tag-overlay {
position: absolute;
top: 0; left: 0; right: 0;
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 6px;
z-index: 2;
background: linear-gradient(to bottom, rgba(0,0,0,0.45), rgba(0,0,0,0));
}
/*------------------------------------------------------------------------------
Tag cards (tags_list.html) - New design
------------------------------------------------------------------------------*/
.tag-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
padding: 0.5rem;
}
.tag-card {
position: relative;
background: var(--panel);
border-radius: 8px;
overflow: hidden;
box-shadow: var(--shadow-2);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.tag-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-3);
}
.tag-card-link {
text-decoration: none;
color: inherit;
display: block;
}
.tag-preview {
display: flex;
gap: 2px;
height: 140px;
overflow: hidden;
}
.tag-thumb {
flex: 1;
min-width: 0;
height: 100%;
object-fit: cover;
background-color: var(--bg-elevated);
transition: filter 0.2s ease;
}
.tag-card:hover .tag-thumb {
filter: brightness(1.08);
}
.tag-thumb-empty {
background: linear-gradient(135deg, var(--bg-elevated) 0%, var(--panel) 100%);
}
.tag-card-info {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: linear-gradient(to top, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.2) 100%);
}
.tag-icon {
font-size: 1.1rem;
flex-shrink: 0;
}
.tag-name {
flex: 1;
font-weight: 500;
font-size: 0.95rem;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tag-count {
font-size: 0.8rem;
color: var(--text-muted);
background: rgba(255,255,255,0.1);
padding: 0.2rem 0.5rem;
border-radius: 999px;
flex-shrink: 0;
}
.tag-edit-btn {
position: absolute;
top: 0.5rem;
right: 0.5rem;
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(0,0,0,0.6);
border: 1px solid rgba(255,255,255,0.1);
color: var(--text-dim);
cursor: pointer;
opacity: 0;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
}
.tag-card:hover .tag-edit-btn {
opacity: 1;
}
.tag-edit-btn:hover {
background: rgba(0,0,0,0.8);
color: var(--text);
border-color: rgba(255,255,255,0.2);
}
.empty-state {
text-align: center;
color: var(--text-muted);
padding: 3rem 1rem;
font-size: 1.1rem;
}
/* Tag edit modal specific */
.tag-edit-modal-content {
max-width: 400px;
padding: 2rem;
background: var(--panel);
border-radius: 12px;
border: 1px solid rgba(255,255,255,0.1);
}
.tag-edit-modal-content h2 {
margin: 0 0 1.5rem 0;
text-align: center;
color: var(--text);
}
.tag-edit-form .form-group {
margin-bottom: 1rem;
}
.tag-edit-form .form-actions {
display: flex;
gap: 0.75rem;
margin-top: 1.5rem;
}
.tag-edit-form .form-actions .btn {
flex: 1;
}
/* Legacy artist grid (kept for backward compatibility) */
.artist-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 6px;
padding: 0.5rem;
}
.artist-card-link {
text-decoration: none;
color: inherit;
display: block;
transition: transform 0.3s ease;
}
.artist-card-link:hover .artist-card {
transform: scale(1.02);
box-shadow: var(--shadow-3);
}
.artist-card {
background-color: var(--panel);
padding: 0;
border-radius: 4px;
overflow: hidden;
box-shadow: var(--shadow-2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.artist-card h3 {
margin: 0;
padding: 0.75rem;
text-align: center;
color: var(--text);
font-weight: 500;
font-size: 0.95rem;
background: rgba(0,0,0,0.3);
}
.artist-preview {
display: flex;
gap: 2px;
justify-content: center;
overflow: hidden;
}
.artist-thumb {
flex: 1;
height: 280px;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
transition: transform 0.3s ease, filter 0.3s ease;
}
.artist-card-link:hover .artist-thumb {
filter: brightness(1.1);
}
/* Filter pills (top of tags_list) */
.filter-bar {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0.75rem 0 1.5rem;
justify-content: center;
}
.filter-btn {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 1rem;
border-radius: 999px;
border: 1px solid var(--filter-border);
background: var(--filter-bg);
color: var(--filter-fg);
text-decoration: none;
font: 14px/1.2 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
transition: all 0.2s ease;
}
.filter-btn:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.25);
}
.filter-btn.is-active {
background: var(--filter-active-bg);
color: var(--filter-active-fg);
border-color: var(--filter-active-border);
}
.filter-btn:focus-visible {
outline: 2px solid #6366f1;
outline-offset: 2px;
}
/*------------------------------------------------------------------------------
Settings
------------------------------------------------------------------------------*/
.settings-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
max-width: 1600px;
margin: 1.5rem auto;
padding: 0 1.5rem;
align-items: start;
}
.settings-column {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
@media (max-width: 1200px) {
.settings-grid {
grid-template-columns: repeat(2, 1fr);
}
.settings-column:first-child {
grid-column: 1 / -1;
flex-direction: row;
}
.settings-column:first-child > .settings-container {
flex: 1;
}
}
@media (max-width: 768px) {
.settings-grid {
grid-template-columns: 1fr;
padding: 0 1rem;
}
.settings-column:first-child {
flex-direction: column;
}
}
.settings-container {
padding: 1.5rem;
background: var(--panel);
border-radius: 8px;
box-shadow: var(--shadow-2);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.settings-section h2,
.settings-info h2 {
margin-bottom: 1rem;
text-align: center;
color: var(--text);
font-weight: 500;
}
.settings-actions {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: center;
}
.btn {
padding: 0.75rem 2rem;
font-size: 1rem;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
font-weight: 500;
min-width: 200px;
}
.btn:hover {
transform: translateY(-1px);
}
.primary-btn { background-color: var(--btn-primary); color: #fff; }
.primary-btn:hover { background-color: var(--btn-primary-hover); }
.warning-btn { background-color: var(--flash-warning); color: #fff; }
.warning-btn:hover { background-color: #d97706; }
.danger-btn { background-color: var(--btn-danger); color: #fff; }
.danger-btn:hover { background-color: var(--btn-danger-hover); }
.settings-info {
font-size: 0.9rem;
color: var(--text-muted);
text-align: center;
line-height: 1.5;
}
/*------------------------------------------------------------------------------
Showcase - Full-bleed Masonry Collage
------------------------------------------------------------------------------*/
.showcase-body {
margin: 0;
padding: 0;
background: #0a0a0a;
min-height: 100vh;
overflow-x: hidden;
}
/* Floating navbar overlay */
.showcase-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
flex-direction: column;
align-items: center;
padding: 0.75rem 1rem;
background: linear-gradient(to bottom, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.6) 70%, rgba(0,0,0,0) 100%);
pointer-events: none;
padding-bottom: 1rem;
}
.showcase-nav-links {
display: flex;
justify-content: center;
gap: 0.25rem;
pointer-events: auto;
}
.showcase-nav .nav-link {
pointer-events: auto;
color: #fff;
text-decoration: none;
padding: 0.5rem 1rem;
font-size: 1rem;
font-weight: 500;
border-radius: 6px;
transition: background 0.2s ease;
text-shadow: 0 1px 3px rgba(0,0,0,0.5);
}
.showcase-nav .nav-link:hover {
background: rgba(255,255,255,0.15);
}
.showcase-nav .nav-hint {
color: rgba(255,255,255,0.4);
font-size: 0.8rem;
margin-top: 0.25rem;
pointer-events: none;
}
/* Masonry container - JS-managed columns */
.masonry-container {
display: flex;
gap: 4px;
padding: 60px 4px 4px 4px; /* top padding for navbar */
min-height: 100vh;
align-items: flex-start;
max-width: 2200px; /* prevent columns from getting too wide on ultrawide */
margin: 0 auto;
}
.masonry-column {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
/* Masonry item */
.masonry-item {
position: relative;
cursor: pointer;
overflow: hidden;
background: #1a1a1a;
}
.masonry-item img {
display: block;
width: 100%;
height: auto;
transition: transform 0.3s ease, filter 0.3s ease;
}
.masonry-item:hover img {
transform: scale(1.03);
filter: brightness(1.1);
}
/* Tag overlay on masonry items */
.masonry-item .tag-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 6px;
background: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%, rgba(0,0,0,0) 100%);
display: flex;
flex-wrap: wrap;
gap: 4px;
z-index: 2;
}
/* Play overlay for videos */
.masonry-item .play-overlay {
position: absolute;
inset: 0;
display: grid;
place-items: center;
color: white;
font-size: 3rem;
text-shadow: 0 2px 8px rgba(0,0,0,0.8);
z-index: 1;
pointer-events: none;
opacity: 0.9;
}
/* Loading state for shuffle */
.masonry-container.loading {
opacity: 0.5;
pointer-events: none;
}
.masonry-container {
transition: opacity 0.3s ease;
}
/*------------------------------------------------------------------------------
Forms
------------------------------------------------------------------------------*/
.form-container {
max-width: 400px;
margin: 2rem auto;
padding: 2rem;
background-color: var(--panel);
border-radius: 8px;
box-shadow: var(--shadow-2);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.form-card { display: flex; flex-direction: column; gap: 1rem; }
.form-group { display: flex; flex-direction: column; }
.form-label { margin-bottom: 0.5rem; font-weight: 500; color: var(--text); }
.form-input {
padding: 0.75rem;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
background: rgba(255, 255, 255, 0.08);
color: var(--text);
font-size: 1rem;
}
.form-input:focus {
outline: none;
border-color: var(--btn-primary);
background: rgba(255, 255, 255, 0.12);
}
/* Select dropdowns - ensure dark theme for options */
select.form-input {
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ffffff' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.75rem center;
padding-right: 2.5rem;
}
select.form-input option,
select.form-input optgroup {
background: #1a1a1a;
color: #ffffff;
}
select.form-input optgroup {
font-weight: 600;
color: #a0a0a0;
}
.form-button {
padding: 0.75rem;
background-color: var(--btn-primary);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: background 0.2s ease;
}
.form-button:hover { background-color: var(--btn-primary-hover); }
.form-footer-text { margin-top: 1rem; text-align: center; color: var(--text-dim); }
/* Fixed-height, 2-line title area so all cards match height */
.card-title {
/* reserve exactly two lines of space for the title */
line-height: 1.3;
min-height: calc(1.3em * 2);
max-height: calc(1.3em * 2);
overflow: hidden;
margin-bottom: 0.5rem;
}
/* Multi-line clamp with ellipsis (where supported) */
.card-title-text {
margin: 0;
font-size: 1.1rem;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2; /* clamp to 2 lines */
-webkit-box-orient: vertical;
overflow: hidden;
/* good fallbacks for very long single words / no spaces */
overflow-wrap: anywhere; /* allows breaking long tokens */
word-break: break-word;
}
/*------------------------------------------------------------------------------
Gallery Infinite Scroll Layout
------------------------------------------------------------------------------*/
.gallery-infinite-container {
display: flex;
min-height: 100vh;
max-width: 1800px;
margin: 0 auto;
position: relative;
}
/* Timeline Sidebar - positioned relative to gallery container */
.timeline-sidebar {
position: sticky;
top: 60px;
height: calc(100vh - 60px);
width: 70px;
min-width: 70px;
background: rgba(0,0,0,0.3);
border-right: 1px solid rgba(255,255,255,0.08);
z-index: 50;
display: flex;
flex-direction: column;
padding: 0.5rem 0;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(255,255,255,0.2) transparent;
}
.timeline-sidebar::-webkit-scrollbar {
width: 4px;
}
.timeline-sidebar::-webkit-scrollbar-track {
background: transparent;
}
.timeline-sidebar::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.2);
border-radius: 2px;
}
.timeline-track {
display: flex;
flex-direction: column;
padding: 0 6px;
}
.timeline-loading {
font-size: 0.7rem;
color: var(--text-muted);
padding: 0.5rem;
text-align: center;
}
.timeline-year-group {
margin-bottom: 0.5rem;
}
.timeline-year {
font-size: 0.8rem;
font-weight: 600;
color: var(--text);
padding: 0.5rem 0.25rem 0.25rem;
text-align: center;
position: sticky;
top: 0;
background: linear-gradient(to bottom, rgba(10,10,10,0.95), rgba(10,10,10,0.8));
z-index: 1;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.timeline-months {
display: flex;
flex-direction: column;
gap: 2px;
padding-top: 4px;
}
.timeline-month {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.35rem 0.5rem;
font-size: 0.7rem;
color: var(--text-muted);
border-radius: 4px;
cursor: pointer;
transition: all 0.15s ease;
text-decoration: none;
}
.timeline-month:hover {
background: rgba(255,255,255,0.12);
color: var(--text);
}
.timeline-month.active {
background: var(--btn-primary);
color: white;
}
.timeline-month.loaded {
color: var(--text-dim);
}
.timeline-month-label {
font-weight: 500;
}
.timeline-month-count {
font-size: 0.6rem;
opacity: 0.7;
}
/* Main gallery area - flex child next to timeline */
.gallery-main {
flex: 1;
padding: 0 0.5rem;
min-width: 0;
}
.gallery-header {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.5rem 0 1rem;
gap: 0.5rem;
}
.gallery-header h1 {
margin: 0;
font-size: 1.5rem;
}
.active-filter {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: rgba(255,255,255,0.08);
border-radius: 999px;
font-size: 0.9rem;
color: var(--text-dim);
}
.filter-tag {
color: var(--link);
font-weight: 500;
}
.clear-filter {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(255,255,255,0.1);
color: var(--text-muted);
text-decoration: none;
font-size: 0.9rem;
line-height: 1;
transition: all 0.15s ease;
}
.clear-filter:hover {
background: rgba(255,255,255,0.2);
color: var(--text);
}
/* Gallery content area */
.gallery-infinite {
min-height: 50vh;
}
.gallery-empty {
text-align: center;
padding: 4rem 1rem;
color: var(--text-muted);
}
/* Date sections */
.date-section {
margin-bottom: 1.5rem;
}
.date-divider {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem 0.5rem;
margin: 0 0 0.5rem 0;
border-bottom: 1px solid rgba(255,255,255,0.1);
position: sticky;
top: 50px;
background: linear-gradient(to bottom, var(--bg) 0%, var(--bg) 85%, transparent 100%);
z-index: 10;
}
.date-divider-text {
font-size: 1.1rem;
font-weight: 500;
color: var(--text);
}
.date-divider-count {
font-size: 0.8rem;
color: var(--text-muted);
background: rgba(255,255,255,0.08);
padding: 0.2rem 0.6rem;
border-radius: 999px;
}
/* Loading indicator */
.gallery-loading {
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
padding: 2rem;
color: var(--text-muted);
}
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid rgba(255,255,255,0.2);
border-top-color: var(--btn-primary);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.gallery-end {
text-align: center;
padding: 2rem;
color: var(--text-muted);
font-style: italic;
border-top: 1px solid rgba(255,255,255,0.05);
}
/* Play overlay with triangle icon */
.play-overlay::before {
content: '';
width: 0;
height: 0;
border-style: solid;
border-width: 20px 0 20px 35px;
border-color: transparent transparent transparent rgba(255,255,255,0.9);
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5));
}
/* Mobile: hide timeline sidebar */
@media (max-width: 768px) {
.timeline-sidebar {
display: none;
}
.date-divider {
top: 45px;
}
}