/* ═══════════════════════════════════════════════════════════════════════════
   project-cards.css · the card every listing page shows a project in
   ═══════════════════════════════════════════════════════════════════════════

   /portfolio/ and /portfolio/work/ are the same gallery filtered two ways, so a
   project has to look the same on both. This file was inlined in /portfolio/ and
   the work index carried an older, simpler set of its own, which is how one
   ended up square with themed grounds and the other 16 by 10 with none.

   Everything is scoped to #work-grid. The two pages are separate documents, so
   both may carry that id, and a page that wants these cards opts in by using it.
   The companion is /portfolio/assets/js/project-cards.js, which mounts the live
   embeds; the two are a pair and neither works alone.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Uniform work-grid tiles (Mizzle showcase, equal height + width) */
#work-grid .card-img-scale-wrapper {
    aspect-ratio: 1 / 1;
    overflow: hidden;
}

/* Every tile is bounded, in both themes. This was light only, on the argument
   that a dark embed already reads against a near black page and needs no help.
   Two things were wrong with that. A tile whose edge appears in one theme and
   vanishes in the other is not one card described two ways, it is two cards. And
   the rule keyed on html[data-bs-theme], which the theme script sets but the
   markup does not, so a page whose script had not run yet, or which carried the
   attribute on <body> instead, drew no edge at all. The border is the card's own
   boundary, not a contrast patch, so it does not ask what theme it is in.

   --bs-secondary-border-subtle is the token that already flips with the theme:
   a near white line on the light page, a dark grey one on the near black. */
#work-grid .card-img-scale-wrapper {
    border: 1px solid var(--bs-secondary-border-subtle);
}

#work-grid .card-img-scale-wrapper img,
#work-grid .card-img-scale-wrapper picture,
#work-grid .card-img-scale-wrapper video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* The playbook covers are drawn at 800 by 500 on their own dark ground. Cover
   would crop the title off the side of a square tile, so they sit contained on a
   backdrop of that same ground instead, which is the treatment the work index
   already gives them. */
#work-grid .pf-cover-wide .card-img-scale-wrapper {
    background: #0e0e10;
}

#work-grid .pf-cover-wide .card-img-scale-wrapper img {
    object-fit: contain;
}

#work-grid .col-12 .card-img-scale-wrapper {
    aspect-ratio: 1600 / 650;
}

#work-grid .card-body {
    display: flex;
    flex-direction: column;
    gap: .25rem;
}

#work-grid .col-md-6 .card-body h5 a {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* The description carries the stack and the live status, so it is content, not a
   caption. Secondary grey at 400 read as a footnote and failed AA on the sibling
   work index. Gray 700 plus 500 weight holds it well clear of the title, which is
   larger and heavier still. The token flips to 70 percent white in dark mode. */
/* Every tile here runs the live work rather than a recording of it. A tile
   declaring --pf-live-w and -h is laid out at that fixed logical size and scaled
   down, so it renders the layout the work was designed at instead of dropping to
   a narrow breakpoint inside a small frame. */
#work-grid .pf-live {
    position: relative;
    overflow: hidden;
    background: var(--pf-live-bg, #eef0f2);
}

/* A tile that follows the page theme declares both grounds. The frame
   inside it is painting the matching one, so the tile under it agrees at
   both ends instead of showing a light ground through a dark card while
   the app loads. Inline style sets the variables, this rule reads them,
   so nothing here is competing with the tile's own declaration. */
html[data-bs-theme="dark"] #work-grid .pf-live[data-pf-live-theme] {
    background: var(--pf-live-bg-dark, var(--pf-live-bg, #eef0f2));
}

#work-grid .pf-live__poster {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: opacity .45s ease;
}

/* The frame is the tile's real width, so each app lays itself out at that size and
   the card shows the design responding rather than a desktop screenshot shrunk down.
   Watchtower reports a content height equal to its viewport at both 583 and 360, so
   its square tile fits exactly. */
#work-grid .pf-live iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: var(--pf-live-fh, 100%);
    border: 0;
    display: block;
}

/* Only the featured tile departs from the gallery's square. */
#work-grid .col-12 .card-img-scale-wrapper.pf-live {
    aspect-ratio: var(--pf-live-w) / var(--pf-live-h);
}

/* At phone width that ratio is a 240px letterbox, too short for any app to show
   itself in. Square gives the narrow layout room to be read. */
@media (max-width: 767.98px) {
    #work-grid .col-12 .card-img-scale-wrapper.pf-live {
        aspect-ratio: 1 / 1;
    }

    /* Except the trio, which is not one app laid out to the tile but three
       whole devices laid out to each other. Its ratio is the shape those
       three make, so squaring it would add ground under them rather than
       give anything room. It keeps its own at every width; small is the
       honest result of three phones in a phone's width. */
    #work-grid .col-12 .card-img-scale-wrapper.pf-live[data-pf-live-trio] {
        aspect-ratio: var(--pf-live-w) / var(--pf-live-h);
    }
}

#work-grid .pf-live.is-loaded .pf-live__poster {
    opacity: 0;
}

/* The card's stretched link sits above the frame, so the embed is a live view and
   the whole tile still routes to the case study, where the interactive ones live. */
#work-grid .card-body small {
    color: var(--bs-gray-700);
    font-weight: 500;
}

/* The description used to be one line of positioning and was clamped to two
   so the tiles stayed on a grid. It now carries a sentence or two of what
   the project actually is, which is the reason to read the card at all,
   and still runs past two lines at this width, so clamping it would hide
   the half that does the work. The floor
   stays, to keep the shortest description from sitting tight under the
   title; the ceiling is gone. Cards on the same row still align, because
   a Bootstrap row stretches its columns to the tallest one. */
#work-grid .col-md-6 .card-body small {
    min-height: 2.7em;
}
