/* ====================================================================
   recon-card.css — THE CARD, ON BOTH GRIDS.                    (ST-3)

   Loaded by views/recipes/index.php and views/public/browse.php, in
   both cases from the view rather than from a layout head, for the
   reason recipe-index.css is: those two <head> partials are shared by
   every workstream in this wave and a second pair of hands in them is a
   lost update. It is the LAST sheet either page loads, so where it
   disagrees with app.css or public.css about a card, it wins on order
   and not on a specificity war.

   ONE SHEET FOR TWO MARKUPS, AND THAT IS NOT THE SAME AS ONE PARTIAL.

   views/partials/recipe_card.php and views/partials/public_recipe_card.php
   stay two files -- the member card prints a byline and the public card
   must never be one careless `if` away from doing so. But the two are the
   SAME OBJECT visually, and rendering them from two stylesheets is how
   the public grid drifted into looking like a cheaper version of the
   member grid. So: two markups, two class prefixes, one set of rules
   listing both selectors.

   ==================================================================
   THE FOUR RULES, AND WHERE THIS FILE OBEYS THEM
   ==================================================================

   1. LIGHT COMES FROM ABOVE AND BEHIND. Every card has an inset lit top
      edge, a dimmer bottom, and a shadow falling away below. The art
      frame is lit the same way -- the vignette is heavier at the bottom
      of the picture than at the top.

   2. ELEVATION MOVES FOUR PROPERTIES TOGETHER. The hover lift changes
      surface brightness, border luminance, shadow spread and the
      specular top edge, all at once. A card that only gains a shadow
      looks like a sticker somebody put on the page.

   3. ONLY THREE THINGS GLOW. On a card that means: the threat chip at
      levels 4 and 5 (depth.css owns it), the classification stamp
      (depth.css owns it), and the measured Scoville reading. Nothing
      else here carries a text-shadow at rest. Twelve to a hundred cards
      is exactly the surface where an extra glow stops meaning anything.

   4. GLASS HAS A BUDGET AND A CARD IS OVER IT. There is NO
      backdrop-filter anywhere in this file. Cards are opaque
      --pane-*-solid surfaces, which composite over --void at the same
      luminance the translucent panes land on, so a card sitting beside
      a glass panel reads as the same material without costing a blur
      pass per card. depth.css calls this .pane--flat; a card is that
      idea with the card's own geometry.

      The same budget is applied to GLOW, which the reference does not
      have to worry about because it draws two cards and this page draws
      a hundred: the recon reticle carries ONE drop-shadow on its
      container rather than one per corner, so a 100-card grid is a
      hundred filter layers and not four hundred.
   ==================================================================== */


/* --------------------------------------------------------------------
   1. THE GRID

   Wider gutters than either sheet had. A lifted card needs somewhere to
   cast into; cards 20px apart with a 40px shadow bleed onto each other
   and the lift stops reading as height.

   .recipe-grid IS SCOPED and must stay scoped: views/profile/show.php
   emits the same class as a plain <div>, and an unscoped rule here
   would be this workstream reaching into a view it does not own.
   -------------------------------------------------------------------- */

.recipe-index ul.recipe-grid,
.pub-grid {
    gap: var(--s4);
}


/* --------------------------------------------------------------------
   2. THE CARD ITSELF

   Elevation 1, opaque. The border is dim on three sides and lit on the
   top one, which is the whole of rule 1 in two declarations.
   -------------------------------------------------------------------- */

.recipe-card,
.pub-card {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-pane);
    background: var(--pane-1-solid);
    border: 1px solid var(--edge-dim);
    border-top-color: var(--edge-e1);
    box-shadow:
        inset 0 1px 0 var(--gloss-e1),
        var(--shadow-e1);
    transition:
        transform    var(--dur-lift) var(--ease-lift),
        box-shadow   var(--dur-lift) var(--ease-lift),
        background   var(--dur-lift) var(--ease-lift),
        border-color var(--dur-lift) var(--ease-lift);
}

/*
 * THE LIFT. 6px and 2.4 degrees, and the perspective is INSIDE the
 * transform rather than on an ancestor.
 *
 * That is not a shortcut, it is the only thing that works here: the
 * member grid is `ul.recipe-grid > li > article.recipe-card`, and the
 * CSS `perspective` property applies to an element's DIRECT children
 * only. A perspective on the grid would reach the <li> and stop, and
 * the rotation would flatten into a shear. perspective() in the
 * transform function list is per-element and needs no ancestor
 * cooperation, so the same rule is correct on both grids -- the public
 * card IS a direct child of its grid and the member card is not.
 *
 * FOUR PROPERTIES MOVE (rule 2): it rises, the surface brightens to
 * elevation 2, the lit edge brightens with it, and the shadow both
 * lengthens and gains a chrome bloom. Take any one of them away and the
 * card looks like a picture of a card that jumped.
 *
 * 2.4 degrees. The reference is explicit that this is the easiest thing
 * in the system to overcook: depth you consciously notice is depth done
 * wrong.
 */
.recipe-card:hover,
.pub-card:hover {
    transform: perspective(1200px) translateY(-6px) rotateX(2.4deg);
    background: var(--pane-2-solid);
    border-color: var(--edge-dim);
    border-top-color: var(--edge-e2);
    box-shadow:
        inset 0 1px 0 var(--gloss-e3),
        0 44px 84px -30px rgba(0, 0, 0, 0.96),
        0 0 56px -26px rgba(var(--chrome-rgb), 0.42);
}

/*
 * Focus gets the same lift and the same light, plus a ring.
 *
 * An OUTLINE and not a box-shadow, because the card's box-shadow is
 * carrying the elevation: replacing it to draw a ring would make a
 * focused card fall to the floor. outline is also never clipped by the
 * overflow:hidden above. There is no `outline: none` in this file and
 * there must never be one -- 025_theme_contract_test.php fails the
 * build over it.
 */
.recipe-card:has(.recipe-link:focus-visible),
.pub-card:has(.pub-card-link:focus-visible) {
    outline: 2px solid var(--chrome);
    outline-offset: 3px;
    background: var(--pane-2-solid);
    border-top-color: var(--edge-e2);
    box-shadow:
        inset 0 1px 0 var(--gloss-e3),
        0 44px 84px -30px rgba(0, 0, 0, 0.96),
        0 0 56px -26px rgba(var(--chrome-rgb), 0.42);
}


/* --------------------------------------------------------------------
   3. THE ART, AS INTERCEPTED IMAGERY

   The picture stops being the card's headline and becomes the texture
   inside a recon frame. Two things go with that:

     - THE RED LINE UNDER THE PHOTO IS GONE. It was drawn ON the image
       (border-bottom on the <img>), so on a card it read as a rule
       struck through the bottom of the picture. The frame's own dim
       edge separates art from body now.

     - THE TITLE IS NOT IN THE PICTURE. A separate workstream is
       regenerating the hero art without text burned into it. Until it
       lands the frame is drawn over whatever the art says; after it
       lands the card states the name exactly once, in type.
   -------------------------------------------------------------------- */

.recipe-thumb,
.pub-card-thumb {
    position: relative;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    border-bottom: 1px solid var(--edge-dim);
    /* The bed the art sits on, and what a card with no picture shows:
       a lit-from-above field rather than a black hole. */
    background: linear-gradient(158deg, #16202E 0%, #0E1220 62%, #090B13 100%);
}

.recipe-img,
.pub-card-img {
    border-bottom: 0;
    /* Desaturated at rest so a grid of a hundred photographs reads as
       one archive rather than a hundred competing colours; full colour
       under the pointer, which is the reward for stopping. */
    filter: grayscale(38%) saturate(0.92) contrast(1.04);
    transition:
        filter    var(--dur-lift) var(--ease-lift),
        transform var(--dur-lift) var(--ease-lift);
}
.recipe-card:hover .recipe-img,
.pub-card:hover .pub-card-img,
.recipe-card:has(.recipe-link:focus-visible) .recipe-img,
.pub-card:has(.pub-card-link:focus-visible) .pub-card-img {
    filter: grayscale(0%) saturate(1.06) contrast(1.06);
    /* The surveillance push-in. 3%, inside a clipped frame, so the art
       moves under the reticle and the reticle stays put. */
    transform: scale(1.03);
}

/*
 * THE FILM AND THE VIGNETTE, in one pseudo-element so the art carries
 * one extra paint layer and not two.
 *
 * The gradient is asymmetric ON PURPOSE (rule 1): a light wash at the
 * top where the light is, a heavy fall-off at the bottom where the
 * shadow is and where the HUD label has to stay legible over whatever
 * the photograph happens to be doing there.
 *
 * The scanline is the 80s artifact and it is 1px in 3 at 16% -- the
 * reference's value. Any louder and the site is a novelty.
 */
.recipe-thumb::after,
.pub-card-thumb::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background:
        linear-gradient(180deg,
            rgba(146, 180, 255, 0.10) 0%,
            transparent 24%,
            transparent 52%,
            rgba(6, 7, 12, 0.80) 100%),
        repeating-linear-gradient(0deg,
            rgba(0, 0, 0, 0.16) 0 1px,
            transparent 1px 3px);
}

/*
 * THE RETICLE. Four corner brackets, phosphor, because this is the
 * machine marking up an intercepted frame -- the reference draws it in
 * exactly this colour for exactly that reason.
 *
 * ONE FILTER, ON THE CONTAINER. The reference puts drop-shadow on each
 * of the four corners, which is correct when the page draws two cards
 * and wrong when it draws a hundred: four hundred filter layers is a
 * compositor bill nobody agreed to. Filtering the wrapper glows all
 * four corners from one layer and looks identical.
 */
.recon-frame {
    position: absolute;
    inset: 10px;
    z-index: 3;
    pointer-events: none;
    filter: drop-shadow(0 0 5px rgba(var(--phosphor-rgb), 0.55));
    opacity: 0.78;
    transition: opacity var(--dur-lift) var(--ease-lift);
}
.recipe-card:hover .recon-frame,
.pub-card:hover .recon-frame,
.recipe-card:has(.recipe-link:focus-visible) .recon-frame,
.pub-card:has(.pub-card-link:focus-visible) .recon-frame {
    opacity: 1;
}
.recon-frame i {
    position: absolute;
    width: 14px;
    height: 14px;
    border: 1.5px solid var(--phosphor);
}
.recon-frame i:nth-child(1) { top: 0;    left: 0;  border-right: 0; border-bottom: 0; }
.recon-frame i:nth-child(2) { top: 0;    right: 0; border-left: 0;  border-bottom: 0; }
.recon-frame i:nth-child(3) { bottom: 0; left: 0;  border-right: 0; border-top: 0;    }
.recon-frame i:nth-child(4) { bottom: 0; right: 0; border-left: 0;  border-top: 0;    }

/*
 * The frame's caption. .hud is depth.css's and it is CHROME, not
 * phosphor: this is the machine labelling its own frame, not a value it
 * just measured, and rule 3 does not hand out the phosphor to labels.
 * It also means the caption does not glow on a hundred cards.
 */
.recon-meta {
    position: absolute;
    left: 12px;
    bottom: 9px;
    z-index: 4;
    line-height: 1;
    /* Both this and the stamp below sit ABOVE the stretched anchor that
       makes the card clickable, so without this they are two small dead
       zones on the one part of the card people aim at. Neither is
       interactive and neither should ever swallow a click. */
    pointer-events: none;
}

/*
 * The classification stamp, on the corner of the frame rather than in
 * the body. It is a mark made ON the record, so it belongs on the
 * imagery -- and it keeps the card's type column to one straight left
 * edge, which is most of why the old card looked busy.
 */
.recon-stamp {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 4;
    pointer-events: none;
}

/* The two-of-a-hundred with no photograph. Hazard chevrons at low
   opacity, so it is a state and not a hole. */
.recipe-img-placeholder,
.pub-card-blank {
    border-bottom: 0;
    color: var(--text-faint);
    font-family: var(--font-ui);
    font-size: var(--fs-ui-200);
    letter-spacing: 0.28em;
    line-height: 1.3;
    text-align: center;
    background:
        repeating-linear-gradient(45deg,
            rgba(var(--classify-rgb), 0.09) 0 10px,
            transparent 10px 20px);
}


/* --------------------------------------------------------------------
   4. THE BODY: ONE TITLE, ONE HEAT INDICATOR

   The old card said the dish name twice (once burned into the art, once
   in type) and the heat twice (a run of chilli emoji AND a Scoville
   band word). Both are now stated once:

     TITLE      in type, in the display face, and nowhere else
     THREAT     the shared chip from views/partials/threat_readout.php
     SCOVILLE   the measured NUMBER beside it -- which is a different
                fact from the threat level, not a second rendering of
                the same one. The band word it used to print ("fiery")
                was the second rendering, and that is what went.
   -------------------------------------------------------------------- */

/*
 * ##  NO `position` ON THE BODY. THIS IS LOAD-BEARING.  ##
 *
 * Whole-card clickability is .recipe-link::after / .pub-card-link::after
 * — an absolutely positioned box with `inset: 0`, which resolves against
 * the nearest POSITIONED ANCESTOR. The anchor lives in the body, so a
 * `position: relative` here would silently shrink the click target from
 * the whole card to the text half of it, and the photograph — the part
 * of a card people actually aim at — would stop being a link.
 *
 * It is silent because nothing looks wrong and no test that reads markup
 * can see it. The card that must stay positioned is .recipe-card /
 * .pub-card, which it is, in section 2.
 */
.recipe-body,
.pub-card-body {
    padding: var(--s3);
}

.recipe-title,
.pub-card-title {
    margin: 0 0 var(--space-2);
    font-family: var(--font-ui);
    font-size: var(--fs-600);
    line-height: 1.04;
    letter-spacing: 0.015em;
    text-transform: uppercase;
    text-wrap: balance;
}

/*
 * WHITE, AND IT STAYS WHITE ON HOVER.
 *
 * app.css turns the title yellow under the pointer, which was the only
 * hover feedback the old card had. The card now lifts, brightens and
 * lights its own edge, so recolouring the headline as well is the third
 * answer to a question already answered twice -- and yellow is not in
 * this palette's vocabulary of signals. The title gets a chrome bloom
 * instead, which reads as the light catching it rather than as a state
 * change.
 */
.recipe-card .recipe-link,
.pub-card-title .pub-card-link {
    color: #FFFFFF;
    text-decoration: none;
    transition: text-shadow var(--dur-lift) var(--ease-lift);
}
.recipe-card:hover .recipe-link,
.pub-card:hover .pub-card-link,
.recipe-card .recipe-link:hover,
.pub-card-title .pub-card-link:hover {
    color: #FFFFFF;
    text-shadow: 0 0 20px rgba(var(--chrome-rgb), 0.5);
}

/*
 * THE PROVENANCE LINE — the reference's sub-title slot.
 *
 * The reference prints a dish's native name and the province it came
 * from there. NEITHER OF THOSE IS A COLUMN ON THIS SITE: `recipes` has
 * no native-name and no region field, and inventing one would be a
 * schema change this pass is not allowed to make and a fact this
 * archive does not hold. So the slot carries the provenance the record
 * ACTUALLY has -- which household's heirloom it is -- and carries
 * nothing at all on the ~110 records that are not somebody's heirloom.
 * An empty line is honest; a fabricated one is not.
 */
.recipe-flags {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin: 0 0 var(--space-3);
}
.recipe-flags .recipe-flag {
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-100);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--text-dim);
}

/*
 * THE HEAT ROW. The chip is depth.css's; this only gives it somewhere
 * to sit and pairs it with the measurement.
 */
.recipe-heat,
.pub-card-heat {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin: 0 0 var(--space-3);
    /* app.css sizes .recipe-heat for a run of emoji. There is no emoji
       in it any more and the chip must not inherit a 1.125rem body. */
    font-size: var(--fs-200);
    line-height: 1.2;
    color: var(--text-dim);
}

/*
 * THE SCOVILLE READING. A number the server just read out of a column,
 * which is the definition of live system data -- so it is phosphor and
 * it is allowed to glow (rule 3), and it is the ONLY thing on a resting
 * card that does.
 *
 * It replaces app.css's clipped red badge, which printed the BAND WORD
 * derived from this number. The number is the fact; the word was a
 * second opinion about the same fact sitting next to a run of chillies
 * that were a third.
 */
.scoville-badge {
    display: inline-block;
    margin-left: 0;
    padding: 0.24em 0.55em;
    border-radius: var(--radius-1);
    border: 1px solid rgba(var(--phosphor-rgb), 0.3);
    background: rgba(var(--phosphor-rgb), 0.07);
    clip-path: none;
    color: var(--phosphor);
    font-family: var(--font-mono);
    font-size: var(--fs-100);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    text-shadow: var(--glow-phosphor);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/*
 * THE DESCRIPTION. Two lines, clamped, so a column of cards has one
 * height rather than a ragged one -- and the two card partials now
 * truncate on a WORD BOUNDARY before it ever gets here, which is the
 * end of "soaked o..." and "fierce, tu...".
 */
/* `.recipe-body .recipe-excerpt` and not `.recipe-excerpt`: app.css
   carries a `.recipe-body p` rule (0,1,1) that would otherwise win the
   colour, the size and the margin off a bare class selector. */
.recipe-body .recipe-excerpt,
.pub-card-excerpt {
    margin: 0 0 var(--space-3);
    color: var(--text-dim);
    font-size: var(--fs-300);
    line-height: 1.55;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/*
 * THE FACTS. Mono and tabular so the numbers line up down a column of
 * cards, over a hairline rather than a dashed rule -- the card has a
 * border and a frame edge already and a third kind of line is noise.
 */
.recipe-facts {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-5);
    margin: 0;
    padding: var(--space-3) 0 0;
    border-top: 1px solid var(--edge-dim);
    font-family: var(--font-mono);
    font-size: var(--fs-100);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}
.recipe-fact {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    white-space: nowrap;
}
.recipe-fact-icon {
    font-size: var(--fs-100);
    line-height: 1;
    opacity: 0.75;
}

/* The byline, demoted to a footer readout. It is the last thing a
   member wants from this card and the first thing the old one shouted
   in a full-size body face. */
.recipe-meta-line {
    margin-top: var(--space-3);
    padding-top: var(--space-2);
    border-top: 1px solid var(--edge-dim);
    font-family: var(--font-mono);
    font-size: var(--fs-100);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text-faint);
}


/* --------------------------------------------------------------------
   5. SMALL SCREENS

   One change: the reticle pulls in, because 10px of inset on a 150px
   card is a bracket sitting on top of the picture rather than around
   it.
   -------------------------------------------------------------------- */

@media (max-width: 40rem) {
    .recon-frame  { inset: 7px; }
    .recon-frame i { width: 11px; height: 11px; }
    .recipe-title,
    .pub-card-title { font-size: var(--fs-500); }
}


/* --------------------------------------------------------------------
   6. MOTION

   depth.css, app.css and public.css each carry a reduced-motion
   wildcard, and this file carries a fourth ON PURPOSE, for the reason
   depth.css states for carrying a third: a sheet that moves things
   carries its own guard, or it is relying on a rule in a file it does
   not control.

   The wildcard alone is not enough here. Reduced motion is a request
   about MOVEMENT, and a card that snaps six pixels upward with no
   transition has still moved; so has an image that jumps 3% larger. The
   brightness, the lit edge, the shadow and the colour return all stay,
   so nothing about the affordance is lost -- you can still see exactly
   what you are pointing at, it simply stops travelling.
   -------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    .recipe-card:hover,
    .pub-card:hover,
    .recipe-card:has(.recipe-link:focus-visible),
    .pub-card:has(.pub-card-link:focus-visible) { transform: none; }

    .recipe-card:hover .recipe-img,
    .pub-card:hover .pub-card-img,
    .recipe-card:has(.recipe-link:focus-visible) .recipe-img,
    .pub-card:has(.pub-card-link:focus-visible) .pub-card-img { transform: none; }
}


/* --------------------------------------------------------------------
   7. PRINT

   A printed card is a bordered box with a photograph in it. The film,
   the vignette, the reticle and the elevation are all screen furniture
   and every one of them costs toner.
   -------------------------------------------------------------------- */

@media print {
    .recipe-card,
    .pub-card {
        background: none;
        border: 1px solid #ccc;
        border-radius: 0;
        box-shadow: none;
        transform: none;
    }
    .recipe-thumb::after,
    .pub-card-thumb::after,
    .recon-frame,
    .recon-meta,
    .recon-stamp { display: none !important; }

    .recipe-img,
    .pub-card-img { filter: none; transform: none; }

    .recipe-title,
    .pub-card-title,
    .recipe-card .recipe-link { color: #000; text-shadow: none; }

    .scoville-badge {
        color: #000;
        background: none;
        border: 1px solid #999;
        text-shadow: none;
    }
    .recipe-excerpt,
    .pub-card-excerpt,
    .recipe-facts,
    .recipe-meta-line { color: #000; }
}
