/*
 * This is a manifest file that'll be compiled into application.css.
 *
 * With Propshaft, assets are served efficiently without preprocessing steps. You can still include
 * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
 * cascading order, meaning styles declared later in the document or manifest will override earlier ones,
 * depending on specificity.
 *
 * Consider organizing styles into separate files for maintainability.
 */

/* Narrowest layout we support.

   302px, not the 320px of the smallest phone still in use. A 320px window in a
   desktop browser lays out in about 302: the classic scrollbar takes the rest,
   and it is taken off the viewport, not off the device. A floor at 320 made
   every page 18px wider than the space it had, so the whole layout — cards
   included — sat off the right edge at the very width the floor was meant to
   protect. The floor has to be the width the content actually gets.

   Below it the page scrolls sideways instead of deforming further. It is what
   puts a floor under the browse grid: two columns of a 286px content box (302px
   less the 1rem the page gutters take) are ~137px cards, and nothing narrower
   can be reached by dragging a desktop window down. The navbar and slide-over
   panels stop being squeezed at the same point, so the whole page has one
   breaking point instead of several. */
body {
  min-width: 302px;
}

.fade-out {
  opacity: 0;
  transition: opacity 1s ease-out;
}

.slide-in {
  animation: slideIn 0.5s forwards;
}

@keyframes slideIn {
  from {
    transform: translateY(-100%);
  }
  to {
    transform: translateY(0);
  }
}

.slide-in-from-right {
  animation: slideInFromRight 0.5s forwards;
}

@keyframes slideInFromRight {
  from {
    transform: translateX(100%);
  }
  to {
    transform: translateX(0);
  }
}

.slide-out-to-right {
  animation: slideOutToRight 0.5s forwards;
}

@keyframes slideOutToRight {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(100%);
  }
}

/* The navbar's portfolio drawer, which comes in from the other edge. */
.slide-in-from-left {
  animation: slideInFromLeft 0.5s forwards;
}

@keyframes slideInFromLeft {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}

.slide-out-to-left {
  animation: slideOutToLeft 0.5s forwards;
}

@keyframes slideOutToLeft {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-100%);
  }
}

.loading-animation {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid rgba(0, 0, 0, 0.1);
  border-top: 4px solid #000;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}


/* Masonry browse grid.
   Column width and gutters are driven by the .grid-sizer / .gutter-sizer helper
   elements read by masonry_controller.js. Widths subtract the (n-1) gutters so
   items pack without overlap.

   Sizing follows the Pinterest rule: columns stretch to fill the row but are
   never narrower than --grid-min-column, so growing the viewport mostly adds a
   column rather than inflating the cards. masonry_controller.js sets
   --masonry-columns from the *container's* width (so narrow embeds like the
   collection form preview get sensible counts); the media queries below are
   only the pre-JS guess, keeping the first paint close to the final layout.
   The bottom margin is the vertical gutter — Masonry includes it in each item's
   measured height. */
:root {
  --grid-gutter: 12px;
  --grid-min-column: 200px;
}

#grid-container {
  position: relative;
  --masonry-columns: 2;
}

@media (min-width: 640px) {
  #grid-container { --masonry-columns: 3; }
}

@media (min-width: 1024px) {
  #grid-container { --masonry-columns: 4; }
}

@media (min-width: 1280px) {
  #grid-container { --masonry-columns: 5; }
}

#grid-container .grid-sizer,
#grid-container .masonry-item {
  width: calc(
    (100% - (var(--masonry-columns) - 1) * var(--grid-gutter)) /
    var(--masonry-columns)
  );
}

#grid-container .gutter-sizer {
  width: var(--grid-gutter);
}

#grid-container .masonry-item {
  margin-bottom: var(--grid-gutter);
}

.masonry-item img {
  width: 100%; /* Fill the column */
  height: auto; /* Keep natural aspect ratio (no cropping) */
  display: block;
}

/* Uniform (square) grid. Deliberately the same rule as the masonry grid above,
   expressed in CSS Grid: auto-fill packs as many --grid-min-column tracks as
   fit and 1fr stretches them, and the min() floor guarantees two columns on
   phones, where half the row is narrower than --grid-min-column. Switching
   display styles therefore doesn't change the grid density. */
.uniform-grid {
  display: grid;
  gap: var(--grid-gutter);
  grid-template-columns: repeat(
    auto-fill,
    minmax(
      min(calc((100% - var(--grid-gutter)) / 2), var(--grid-min-column)),
      1fr
    )
  );
}

/* Cover skeleton frame.
   A fixed aspect-ratio box (set inline from the cover's stored width/height) with
   a shimmer placeholder that sits BEHIND the media. The card gets its final height
   before the image loads, so nothing shakes when it lands. Visibility never
   depends on JS: an unloaded <img> is transparent, so the skeleton shows through;
   once loaded the opaque image (z-index 1) covers it. masonry_controller.js only
   adds .is-loaded to stop the shimmer. */
.cover-frame {
  position: relative;
  /* Own stacking context: keeps the image's z-index:1 scoped inside the frame so
     it can't paint over the card's absolutely-positioned title/hashtag overlays
     (which sit outside the frame with z-index:auto). Without this, a loaded cover
     rises above and hides those overlays. */
  z-index: 0;
  width: 100%;
  overflow: hidden;
  background-color: #ececec;
}

.cover-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(
    100deg,
    transparent 25%,
    rgba(255, 255, 255, 0.55) 50%,
    transparent 75%
  );
  transform: translateX(-100%);
  animation: cover-shimmer 1.5s ease-in-out infinite;
}

.cover-frame.is-loaded::after {
  display: none;
}

.cover-frame img,
.cover-frame video {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover; /* Frame aspect == media aspect, so this fills without cropping */
  display: block;
}

@keyframes cover-shimmer {
  100% {
    transform: translateX(100%);
  }
}

/* Child Login Background - Rainbow Gradient */
.child-login-rainbow-bg {
  /* Repeat the first colour (#ff6b6b) at the end so the gradient tiles
     seamlessly, then scroll exactly one tile so it wraps to an identical frame
     (no orange -> blue -> (snap) -> orange jump). Wider background-size = wider
     colour bands; keep the keyframe end in sync at 100*S/(S-100)% of one tile
     (600% -> 120%). */
  background: linear-gradient(to right, #ff6b6b, #feca57, #48dbfb, #ff9ff3, #54a0ff, #ff6b6b);
  background-size: 600% 100%;
  animation: rainbowShift 16s linear infinite;
}

@keyframes rainbowShift {
  from { background-position: 0% 50%; }
  to { background-position: 120% 50%; }
}

main {
  transition: background 0.5s ease-in-out;
}

/* Wordmark — the name with a bookmark hanging behind and below the P.

   Three layers, because the bookmark has to work over arbitrary page content:

     1. the name, in ink, in normal flow
     2. .wordmark-badge, a solid shape painted OVER it
     3. .wordmark-knockout, a second copy of the name in paper, clipped to the
        badge's column, painted over that

   Layer 2 hides the letters it covers and layer 3 redraws them knocked out, so
   the ink/paper switch happens exactly at the badge's edge. An earlier version
   did this with mix-blend-mode: difference, which is far less markup — but a
   blended badge inverts whatever it is drawn over, and this one now hangs off
   the navbar and across the content grid, where that would mean inverted
   photographs. A solid badge is the only version that survives leaving its bar.

   Everything is in em, so one font-size drives the whole mark. */
.wordmark {
  --wordmark-ink: #171717;    /* the name itself */
  --wordmark-paper: #ffffff;  /* the letters knocked out of the bookmark */
  /* The bookmark, deliberately a shade off the name rather than the same black.
     It sits behind the P as a solid mass, and matching the ink exactly made the
     mark read as one heavy block; lifting it slightly separates the shape from
     the lettering without reading as a different colour. */
  --wordmark-badge: #303030;

  /* The badge's column, measured from the P's own origin. Hand-tuned to the
     current face (Hurricane): its P advances 0.553em but inks out to 0.761em,
     the flourish overshooting its own box, so `reach` clears the ink rather
     than the glyph box and leaves the air either side of the letter even.
     `lead` is the overhang to the left of the P. */
  /* Tuned to the face the wordmark ships on. `reach` has to clear the P's
     INK, and the picker in System Admin can swap that glyph underneath these
     numbers. Measured P ink-right, in em after each face's size-adjust:
     hurricane 0.858, playball 0.831, galada 0.587, chewy 0.482, chicle 0.471 —
     all inside the 0.910 below. But allura 1.142, mr-dafoe 1.087 and damion
     0.988 overrun it, and the knockout is clipped to this same column, so on
     those three the letter changes colour mid-stroke where it crosses the
     badge's edge. Raising reach for them means widening the badge to match. */
  --wordmark-lead: 0.20em;
  --wordmark-reach: 0.910em;

  /* How far the bookmark hangs below the top of the badge, and how deep the
     notch bites into its tail. Length is what makes it overhang the bar. */
  --wordmark-top: -0.27em;
  --wordmark-length: 2.20em;
  --wordmark-notch: 0.45em;

  position: relative;
  /* inline-block, NOT inline-flex: a flex container turns the P span and the
     "rofolio" text node into two separate flex items, which stops the script
     from joining across them. The name has to stay a single run of text. */
  display: inline-block;
  color: var(--wordmark-ink);

  /* Room for the badge's leftward overhang, so the mark does not hang into the
     page gutter and knock the logo out of line with everything below it. There
     is deliberately no vertical padding: the bookmark is *meant* to overflow
     its bar and hang across the content underneath. */
  padding-left: var(--wordmark-lead);
}

/* In the navbars the bookmark is longer, overhanging the bar and falling across
   the content beneath it — which is why neither bar reserves height for it. */
.wordmark--hanging {
  /* 116px at the landing navbar's text-5xl, 72px at the app navbar's text-3xl,
     against the contained 2.20em above — so the modifier lengthens the badge
     again, which is what its name promises. Kept in em rather than a fixed
     calc() so the mark still scales from one font-size, which does mean a
     length judged against one navbar lands proportionally smaller at the
     other. */
  --wordmark-length: 2.41em;
  --wordmark-notch: 0.53em;
}

/* The bookmark itself. Sits above the name it covers; the knockout below
   restores the letters on top of it. */
.wordmark-badge {
  position: absolute;
  z-index: 1;
  left: 0;
  top: var(--wordmark-top);
  width: calc(var(--wordmark-lead) + var(--wordmark-reach));
  height: var(--wordmark-length);
  background: var(--wordmark-badge);
  border-radius: 0.06em 0.06em 0 0;
  clip-path: polygon(
    0 0,
    100% 0,
    100% 100%,
    50% calc(100% - var(--wordmark-notch)),
    0 100%
  );
  pointer-events: none;
}

/* The second copy of the name, in paper, clipped to the badge's column so only
   the part lying on the bookmark paints. Vertical bounds are deliberately loose
   — the clip only needs to cut horizontally, and the letters never reach 3em. */
.wordmark-knockout {
  position: absolute;
  z-index: 2;
  top: 0;
  left: var(--wordmark-lead);
  color: var(--wordmark-paper);
  clip-path: polygon(
    calc(var(--wordmark-lead) * -1) -1em,
    var(--wordmark-reach) -1em,
    var(--wordmark-reach) 3em,
    calc(var(--wordmark-lead) * -1) 3em
  );
  pointer-events: none;
  white-space: pre;
  /* It is a second copy of the same word. Without this, selecting the logo — or
     copying it — yields "ProfolioProfolio". aria-hidden keeps it away from
     screen readers; this keeps it away from the selection. */
  user-select: none;
}

.wordmark-mark {
  /* A hair of air after the P, so "rofolio" starts clear of the badge's edge.
     Small on purpose: this is the one property that pushes the following text,
     and much more than this stops the script joining, so the name reads as two
     pieces. Both copies carry it, or they would not line up. */
  margin-right: 0.14em;
}
