/* ---- Journal grid — article listing rows ----
   Every row holds 3 articles of equal width (4 of the 12 grid columns
   each) — one uniform row shape, not a rotating set. Rows are fixed,
   permanent markup — journal-filter.js never creates, destroys, or
   reshapes one; it only moves articles through the same fixed slot
   sequence these rows already define (see that file's own comment). A
   slot with no article left simply isn't filled (no filler, no
   stretching), and a row with zero filled slots is hidden entirely — this
   is why the final row can end up with only 1 of 3 slots occupied.
   Headline size and excerpt visibility are NOT part of any per-slot
   variation — every card in the grid uses one uniform size, and the
   excerpt is shown (truncated) on every card. */

/* ---- Fade-in reveal (assets/js/project-fade.js's initJournalFade) ----
   Same unconditional-hidden-state technique as [data-project-fade]
   (card.css) — see that file's own comment for the full rationale. The
   <noscript> override in journal.html's <head> guarantees full visibility
   with JS disabled. */
[data-journal-fade] {
  transition:
    opacity calc(var(--duration-slow) * 2) var(--ease-standard),
    transform calc(var(--duration-slow) * 2) var(--ease-standard),
    filter calc(var(--duration-slow) * 2) var(--ease-standard);
}

[data-journal-fade=""] {
  opacity: 0;
  transform: translateY(var(--space-4));
  filter: blur(6px);
}

[data-journal-fade="visible"] {
  filter: blur(0);
}

@media (prefers-reduced-motion: reduce) {
  [data-journal-fade] {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
  }
}

/* The `hidden` attribute (used by journal-filter.js to remove a row with no
   filled slots from layout) doesn't work for free here: the browser's UA
   stylesheet supplies `[hidden] { display: none }`, but this file's own
   `display: flex`/`display: grid` on `.journal-row` is also author-origin
   with equal specificity, so source order would let it win instead. See
   card.css's identical override for Projects' own rows/cards. */
[data-journal-row][hidden] {
  display: none;
}

.journal-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-20);
}

.journal-row + .journal-row {
  margin-top: var(--space-20);
}

@media (min-width: 1024px) {
  .journal-row {
    display: grid;
    grid-template-columns: repeat(var(--grid-columns), 1fr);
    gap: var(--space-10);
    align-items: start;
  }

  .journal-row + .journal-row {
    margin-top: var(--space-32);
  }

  .journal-row > * {
    grid-column: span 4;
  }
}

.journal-card {
  display: block;
  color: inherit;
}

.journal-card:focus-visible {
  outline: 2px solid var(--color-ink);
  outline-offset: 4px;
}

.journal-card__media {
  position: relative;
  background-color: var(--color-bg-alt);
  overflow: hidden;
  /* The mask — fixed size, clips its content, never itself carries a
     transform. Hover zoom lives one level deeper, on
     .journal-card__media-zoom, not on the img: that's where scroll
     parallax (assets/js/parallax.js, this wrapper carries a bare
     data-parallax) sets its own inline transform every scroll tick, and
     GSAP inline styles always beat a stylesheet rule, so a hover transform
     on the img itself would silently never show. It also can't live on
     this outer wrapper — scaling this element would grow the box itself
     and bleed over the grid cell instead of staying masked within a fixed
     frame — same technique as .project-card__media / .project-card__media-zoom
     (card.css). */
}

.journal-card__media-zoom {
  width: 100%;
  height: 100%;
  aspect-ratio: 16 / 9;
  transition: transform var(--duration-slow) var(--ease-standard);
}

.journal-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.journal-card:hover .journal-card__media-zoom,
.journal-card:focus-visible .journal-card__media-zoom {
  transform: scale(1.06);
}

@media (prefers-reduced-motion: reduce) {
  .journal-card__media-zoom {
    transition: none;
  }
}

/* Same rhythm as .project-card__meta (card.css): space-8 from the image.
   Category, the separating dot and the date all take .project-card__
   category's own size/colour (font-size-4, brand sand) so the whole meta
   line reads as one consistent unit rather than category-plus-a-smaller
   date. */
.journal-card__meta {
  margin-top: var(--space-8);
  font-size: var(--font-size-4);
  color: var(--color-accent);
}

.journal-card__headline {
  margin-top: var(--space-6);
  transition: color var(--duration-base) var(--ease-standard);
}

.journal-card:hover .journal-card__headline,
.journal-card:focus-visible .journal-card__headline {
  color: var(--color-text-muted);
}

@media (prefers-reduced-motion: reduce) {
  .journal-card__headline {
    transition: none;
  }
}

.journal-grid .journal-card__headline {
  font-size: var(--font-size-h3);
  line-height: var(--line-height-h3);
}

.journal-card__excerpt {
  margin-top: var(--space-4);
}

/* Truncate to a fixed number of lines rather than letting a long excerpt
   overrun a compact card. */
.journal-grid .journal-card__excerpt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}
