200 lines
7.5 KiB
CSS
200 lines
7.5 KiB
CSS
/*
|
||
* MasonryGallery – scoped CSS
|
||
*
|
||
* Grid column definitions (activated when React adds .is-enhanced to the root).
|
||
* Mirrors the blade @push('styles') blocks so the same rules apply whether the
|
||
* page is rendered server-side or by the React component.
|
||
*/
|
||
|
||
/* ── Masonry grid ─────────────────────────────────────────────────────────── */
|
||
[data-nova-gallery].is-enhanced [data-gallery-grid] {
|
||
display: grid;
|
||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||
grid-auto-rows: 8px;
|
||
gap: 1rem;
|
||
}
|
||
|
||
@media (min-width: 768px) {
|
||
[data-nova-gallery].is-enhanced [data-gallery-grid] {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
/* Spec §5: 4 columns desktop, scaling up for very wide screens */
|
||
@media (min-width: 1024px) {
|
||
[data-nova-gallery] [data-gallery-grid] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||
[data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||
}
|
||
|
||
@media (min-width: 1600px) {
|
||
[data-nova-gallery] [data-gallery-grid] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||
[data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||
}
|
||
|
||
@media (min-width: 2200px) {
|
||
[data-nova-gallery] [data-gallery-grid] { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
||
[data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
||
}
|
||
|
||
[data-nova-gallery].is-enhanced [data-gallery-grid] > .nova-card { margin: 0 !important; }
|
||
|
||
/* ── Fallback aspect-ratio for cards without stored dimensions ───────────── */
|
||
/*
|
||
* When ArtworkCard has no width/height data it renders the img as h-auto,
|
||
* meaning the container height is 0 until the image loads. Setting a
|
||
* default aspect-ratio here reserves approximate space immediately and
|
||
* prevents applyMasonry from calculating span=1 → then jumping on load.
|
||
* Cards with an inline aspect-ratio style (from real dimensions) override this.
|
||
*/
|
||
[data-nova-gallery] [data-gallery-grid] .nova-card-media {
|
||
aspect-ratio: 3 / 2;
|
||
width: 100%; /* prevent aspect-ratio + max-height from shrinking the column width */
|
||
}
|
||
|
||
/* Override: when an inline aspect-ratio is set by ArtworkCard those values */
|
||
/* take precedence naturally (inline style > class). No extra selector needed. */
|
||
|
||
/* ── Card max-height cap ──────────────────────────────────────────────────── */
|
||
/*
|
||
* Limits any single card to the height of 2 stacked 16:9 images in its column.
|
||
* Formula: 2 × (col_width × 9/16) = col_width × 9/8
|
||
*
|
||
* 5-col (lg+): col_width = (100vw - 80px_padding - 4×24px_gaps) / 5
|
||
* = (100vw - 176px) / 5
|
||
* max-height = (100vw - 176px) / 5 × 9/8
|
||
* = (100vw - 176px) × 0.225
|
||
*
|
||
* 2-col (md): col_width = (100vw - 80px - 1×24px) / 2
|
||
* = (100vw - 104px) / 2
|
||
* max-height = (100vw - 104px) / 2 × 9/8
|
||
* = (100vw - 104px) × 0.5625
|
||
*
|
||
* 1-col mobile: uncapped – portrait images are fine filling the full width.
|
||
*/
|
||
|
||
/* Global selector covers both the React-rendered gallery and the blade fallback */
|
||
[data-nova-gallery] [data-gallery-grid] .nova-card-media {
|
||
overflow: hidden; /* ensure img is clipped at max-height */
|
||
}
|
||
|
||
@media (min-width: 1024px) {
|
||
[data-nova-gallery] [data-gallery-grid] .nova-card-media {
|
||
/* 5-column layout: 2 × (col_width × 9/16) = col_width × 9/8 */
|
||
max-height: calc((100vw - 176px) * 9 / 40);
|
||
}
|
||
/* Wide (2-col spanning) cards get double the column width */
|
||
[data-nova-gallery] [data-gallery-grid] .nova-card--wide .nova-card-media {
|
||
max-height: calc((100vw - 176px) * 9 / 20);
|
||
}
|
||
}
|
||
|
||
@media (min-width: 768px) and (max-width: 1023px) {
|
||
[data-nova-gallery] [data-gallery-grid] .nova-card-media {
|
||
/* 2-column layout */
|
||
max-height: calc((100vw - 104px) * 9 / 16);
|
||
}
|
||
[data-nova-gallery] [data-gallery-grid] .nova-card--wide .nova-card-media {
|
||
/* 2-col span fills full width on md breakpoint */
|
||
max-height: calc((100vw - 104px) * 9 / 8);
|
||
}
|
||
}
|
||
|
||
/* Image is positioned absolutely inside the container so it always fills
|
||
the capped box (max-height), cropping top/bottom via object-fit: cover. */
|
||
[data-nova-gallery] [data-gallery-grid] .nova-card-media > .nova-card-main-image {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
/* ── Skeleton ─────────────────────────────────────────────────────────────── */
|
||
.nova-skeleton-card {
|
||
border-radius: 1rem;
|
||
min-height: 180px;
|
||
background: linear-gradient(
|
||
110deg,
|
||
rgba(255, 255, 255, 0.06) 8%,
|
||
rgba(255, 255, 255, 0.12) 18%,
|
||
rgba(255, 255, 255, 0.06) 33%
|
||
);
|
||
background-size: 200% 100%;
|
||
animation: novaShimmer 1.2s linear infinite;
|
||
}
|
||
|
||
@keyframes novaShimmer {
|
||
to { background-position-x: -200%; }
|
||
}
|
||
|
||
/* ── Card enter animation (appended by infinite scroll) ───────────────────── */
|
||
.nova-card-enter { opacity: 0; transform: translateY(8px); }
|
||
.nova-card-enter-active {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
transition: opacity 200ms ease-out, transform 200ms ease-out;
|
||
}
|
||
|
||
/* ── Card hover: bottom glow pulse ───────────────────────────────────────── */
|
||
.nova-card > a {
|
||
will-change: transform, box-shadow;
|
||
}
|
||
.nova-card:hover > a {
|
||
box-shadow:
|
||
0 8px 30px rgba(0, 0, 0, 0.6),
|
||
0 0 0 1px rgba(255, 255, 255, 0.08),
|
||
0 0 20px rgba(224, 122, 33, 0.07);
|
||
}
|
||
|
||
/* ── Quick action buttons ─────────────────────────────────────────────────── */
|
||
/*
|
||
* .nb-card-actions – absolutely positioned at top-right of .nova-card.
|
||
* Fades in + slides down slightly when the card is hovered.
|
||
* Requires .nova-card to have position:relative (set inline by ArtworkCard.jsx).
|
||
*/
|
||
.nb-card-actions {
|
||
position: absolute;
|
||
top: 0.5rem;
|
||
right: 0.5rem;
|
||
z-index: 30;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
opacity: 0;
|
||
transform: translateY(-4px);
|
||
transition: opacity 200ms ease-out, transform 200ms ease-out;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.nova-card:hover .nb-card-actions,
|
||
.nova-card:focus-within .nb-card-actions {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.nb-card-action-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 2rem;
|
||
height: 2rem;
|
||
border-radius: 0.5rem;
|
||
background: rgba(10, 14, 20, 0.75);
|
||
backdrop-filter: blur(6px);
|
||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||
color: rgba(255, 255, 255, 0.85);
|
||
font-size: 0.875rem;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
text-decoration: none;
|
||
transition: background 150ms ease, transform 150ms ease, color 150ms ease;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.nb-card-action-btn:hover {
|
||
background: rgba(224, 122, 33, 0.85);
|
||
color: #fff;
|
||
transform: scale(1.1);
|
||
}
|