/* ==========================================================================
   Product card — Phase 3A
   One definition, both surfaces. The home grid and the archive grid used to
   draw the same plant two different ways: a full-width "للسلة" button here, a
   quiet ＋ mark there; a one-line ellipsised name here, a wrapping one there;
   grayscale when sold here, desaturated there. This file is now the only
   place the card is described, and index.html's grid renders the same
   pwBuildCard() markup the catalog renders.

   Loaded by index.html, which means catalog.html and every /plants/ page
   inherit it through the build scripts. catalog.css keeps what is genuinely
   catalogue-only: the masthead, the rail, the strip, the list view and the
   skeletons.
   ========================================================================== */

/* ---------- grid rhythm ----------
   Both grids are #grid; only the class differs. They carried different row
   gaps (home 28px on a phone, archive 40px) so the same plants breathed
   differently depending on the page. One rhythm now. Column counts are left
   alone — the home grid's four-up on a wide desktop is its own editorial
   choice, not an inconsistency.
   #grid.<class> is ID+class, which is what it takes to outrank identity-v2's
   own #grid rules. */
#grid.grid, #grid.cat-grid { gap: 54px 18px; }

@media (max-width: 1040px) {
  #grid.grid, #grid.cat-grid { gap: 44px 14px; }
}
@media (max-width: 760px) {
  /* B6: 12px let two cards nearly touch while their rows floated 40px apart —
     a 3.3:1 ratio that read as columns crushed together. 16 gives the specimens
     room to be separate objects without costing a meaningful amount of width. */
  #grid.grid, #grid.cat-grid { gap: 40px 16px; }
}

/* card entrance — archive language, once, subtle */
.cat-card {
  position: relative;
  animation: cat-enter 500ms cubic-bezier(.22, .72, .18, 1) both;
  animation-delay: var(--cd, 0ms);
}
@keyframes cat-enter {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

.cat-link {
  display: block;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}
.cat-link:focus-visible { outline: 2px solid var(--archive-clay); outline-offset: 3px; }

.cat-card .card-media { display: block; }

/* Sold out reads the same on both grids now. The home grid used
   `grayscale(1)` at 45% opacity, which drained the specimen of the colour
   that identifies it; the archive's lighter desaturation says "not available"
   without pretending the plant is a different plant. */
.cat-card.is-sold .card-img {
  opacity: 0.6;
  filter: saturate(0.4);
}

/* ---------- caption = mounted herbarium label ----------
   Phase 3B. The caption used to stack five blocks with gaps of 5, 0, 7 and 5px
   — no scale, and the name sat directly on its binomial with nothing between
   them. Everything is now a multiple of 4: 4 inside a group, 8 between groups,
   12 to the rule above. */
.cat-caption {
  --cap-step: 4px;
  display: block;
  padding: calc(var(--cap-step) * 3) 0 0;  /* 12 */
  border-top: 1px solid var(--archive-line-soft);
  margin-top: calc(var(--cap-step) * 3);   /* 12 */
}

/* ---------- B5: one metadata row ----------
   `SP-001 · شائعة .......... 80 د.ك` on a single line, without touching the
   card builder. The accession code and the tier/price row are siblings, so a
   grid places them on the same track: code in the first column, the existing
   .cat-meta row in the second, still pushing the price to the far end.

   .cat-meta is left intact as a real flex box — dissolving it with
   display:contents was the first attempt and it was wrong twice over: the
   caption is a column, so flex-wrap wraps into columns rather than rows, and
   the list view styles .cat-meta as a box whose max-width would have vanished.

   Scoped to the grid views; the list view keeps the stacked caption it has. */
#grid:not(.is-list) .cat-caption {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "name  name"
    "latin latin"
    "code  meta"
    "avail avail"
    "chip  chip";
  align-items: baseline;
  column-gap: calc(var(--cap-step) / 2);  /* 2 — the code sits against its row */
  row-gap: calc(var(--cap-step) * 2);     /* 8 */
}
#grid:not(.is-list) .cat-caption > .name-ar      { grid-area: name; }
#grid:not(.is-list) .cat-caption > .name-en      { grid-area: latin; }
#grid:not(.is-list) .cat-caption > .cat-sp       { grid-area: code; }
#grid:not(.is-list) .cat-caption > .cat-meta     { grid-area: meta; }
#grid:not(.is-list) .cat-caption > .cat-avail    { grid-area: avail; }
#grid:not(.is-list) .cat-caption > .fulfill-chip { grid-area: chip; }

/* A name and its binomial are one thought, so they sit at 4 from each other
   against the 8 that separates the groups. */
#grid:not(.is-list) .cat-caption > .name-en { margin-top: calc(var(--cap-step) * -1); }

/* The grid owns the rhythm now, so the row's own 7px top margin — one of the
   four loose values this phase replaced — has to stand down. */
#grid:not(.is-list) .cat-meta { margin-top: 0; }

/* The separator belongs to the pairing, not to either label. */
#grid:not(.is-list) .cat-tier::before {
  content: '·';
  margin-inline-end: calc(var(--cap-step) / 2);
  opacity: 0.55;
}

/* ---------- keeping the price on the line ----------
   The accession code takes the first column, so the tier and the price share
   what is left — about 118px on a 164px card. `غير شائعة` plus `80 د.ك` wanted
   roughly 122, and the row wrapped on all thirteen cards.

   The few pixels come back from the gaps, and the two sides are given
   different orders of priority: the price never shrinks and never breaks, and
   the tier is allowed to wrap inside itself first. So the line yields in the
   order it should — spacing, then the tier, and only then the price. */
#grid:not(.is-list) .cat-meta { column-gap: var(--cap-step); }

/* The code is six mono glyphs holding 38px of a 142px caption, and a tenth of
   an em of that is tracking. Tracking is what makes it read as a specimen
   stamp, so it keeps some — but at 0.1em it was buying letterform at the
   price's expense on exactly the widths where the line is tightest. */
#grid:not(.is-list) .cat-sp { letter-spacing: 0.04em; }

/* Same trade on the tier: 0.05em of tracking across nine glyphs of
   "غير شائعة" is three more pixels the price could be using. The label still
   reads as a mono stamp at 0.02em. */
#grid:not(.is-list) .cat-tier { letter-spacing: 0.02em; }

/* Availability used to be the caption's last row, and it was what kept the ＋
   clear of the price. With it gone for in-stock cards, the mark landed on top
   of the number on every tablet and desktop card. The row now ends short of
   the mark's column instead — which costs no height at all, and gives the
   wide layouts the same `price ＋` pairing the phone layout already had. */
#grid:not(.is-list) .cat-meta {
  padding-inline-end: 48px;
  /* With the rarity gone the price is alone in this row, and space-between
     parks a lone item at the start — which would slide the number back beside
     the accession code. It belongs at the far end, opposite the code, with the
     add mark past it. */
  justify-content: flex-end;
}

/* The price is the one thing on this line that never gives way. Everything
   else may shrink or break around it.

   nowrap was tried here and rejected: it did keep the price inline on every
   card, but it bought that by clipping "شائعة" and "اقتناء مميز" on the sale
   cards, where the two-number price block leaves the tier about 2px. Losing
   the end of a word is worse than moving a number. */
#grid:not(.is-list) .cat-price { flex-shrink: 0; }

/* The accession code is decoration — the quietest thing on the card, and the
   only text still allowed under the 11pt floor because nobody needs to read it.

   Phase 3D: it was painted in ink-soft while the rarity beside it sat in the
   muted neutral, so the 9px label carried more colour weight than the 11.2px
   one next to it — the hierarchy was upside down. It now takes the muted tone
   too, and with the rarity gone it is the whole eyebrow: an accession number
   on a specimen sheet, which is exactly as loud as that should be. */
.cat-sp {
  display: block;
  color: var(--archive-muted, #748079);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.56rem;
  letter-spacing: 0.1em;
  opacity: 0.85;
}

/* Two lines, always — clamped so a long name cannot push its neighbour out of
   line, and reserved so a short one still occupies the same height. Before
   this the home grid truncated to one line with an ellipsis and the archive
   wrapped freely, so a row of cards ended up ragged by up to 54px. */
.cat-caption .name-ar {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(2 * 1.25em);
  color: var(--archive-ink);
  font-family: var(--archive-display-ar);
  font-size: 1.2rem;
  font-weight: 600;
  line-height: 1.25;
}
html[dir='ltr'] .cat-caption .name-ar { font-family: var(--archive-display-en); font-size: 1.3rem; }
.cat-card:hover .name-ar { color: var(--archive-clay-deep); }

/* B4, level two: the botanical identity. It reads as the name's binomial —
   italic is what a herbarium label uses for a species, and it separates this
   line from the availability line that used to share its exact size and
   weight. Secondary to the Arabic name, but unmistakably identity, not status. */
.cat-caption .name-en {
  display: block;
  min-height: 1em;
  color: var(--archive-ink-soft);
  font-family: var(--archive-display-en);
  font-size: 0.76rem;
  font-style: italic;
  line-height: 1.35;
}

/* Tier on one side, price on the other. It wraps rather than clips: at 14px the
   worst case the catalogue can produce — the longest tier ("اقتناء مميز")
   beside a sale pair ("85 د.ك 75 د.ك") — no longer fits on one line inside a
   166px card on a 375pt phone. Wrapping drops the price to its own line only
   in that case; the rows stay level because grid rows stretch. */
.cat-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 2px 10px;
  margin-top: 7px;
}

/* 0.7rem = 11.2px, just clear of the 11pt floor. B3: an explicit 1.25 rather
   than the 1.7 these lines inherited from body copy — 1.7 gave a 16px line a
   27px box and inflated the caption for nothing. */
/* Phase 3D — rarity is the seal, and only the seal.
   It was being said twice: a stamped mark on the plate and the same fact
   spelled out in the caption underneath. Two visual languages for one piece of
   information is not hierarchy, it is repetition.

   Hidden rather than removed, and visually-hidden rather than display:none, so
   the words survive in the accessibility tree. That matters here: the seal is a
   <span> carrying its meaning in an aria-label, and aria-label on a plain span
   with no role is not reliably announced — this line is what actually
   guarantees a screen reader still hears "غير شائعة". */
.cat-tier {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* One price treatment for both grids. The home card used to set it at 700
   weight and the archive at 400 — the surface where people actually compare
   plants was showing the number more faintly than the one where they don't.
   14px at 600 carries on a specimen label without turning into a price sticker;
   the clay keeps it part of the archive rather than shouting over the name. */
.cat-price {
  color: var(--archive-clay-deep);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.25;
  white-space: nowrap;
}

/* B4, the quietest level: status, not identity.
   It keeps 11.2px — that floor was fought for in A4 and dropping below it to
   look calmer would undo it. Quiet comes from colour and letterform instead:
   the muted neutral rather than the ink the Latin name uses, and none of the
   tracking that made it read as a label competing with the tier.
   Sold out is the only state that earns colour, because it is the only one
   that changes what you can do. */
.cat-avail {
  display: block;
  color: var(--archive-muted, #748079);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.7rem;
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: 0.02em;
}
.cat-avail.out { color: var(--archive-clay-deep); }

/* "متوفر" said, on every single card that had a ＋ on it, exactly what the ＋
   already said. Availability now speaks only when it has news: sold out. The
   element is left in the markup and only hidden, so nothing about the card
   builder or the data changes, and a screen reader still reaches it.

   Low stock cannot be expressed here yet — the builder emits `.out` for sold
   out and nothing else, so there is no hook in the DOM to hang it on. Adding
   one is a one-line change to pwBuildCard, which is frozen. */
.cat-avail:not(.out) {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Two more things in this caption are read rather than admired, and both sat
   under the floor: how the plant is collected, and what it used to cost. The
   struck-through price is set here in rem rather than inherited as an em of
   .cat-price, so shrinking the price on a phone can never drag it back under. */
.cat-caption .fulfill-chip { font-size: 0.7rem; }
.cat-price .price-original { font-size: 0.7rem; }

/* ---------- quick add ----------
   44x44 of real button, with the 36px archive mark drawn by ::before. The
   hit area used to be 36px of button plus a 4px pseudo-element bleed, which
   arrives at 44 but only as long as nothing ever restyles that pseudo. The
   target is now the element itself; the mark is decoration painted inside it,
   and it looks exactly as it did. */
.cat-add {
  position: absolute;
  inset-inline-end: -4px;
  bottom: 16px;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  color: var(--archive-ink);
  background: none;
  border: 0;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  /* isolate + z-index:-1 below: an absolutely positioned ::before paints in the
     positioned-descendant layer, i.e. ON TOP of the button's own text, which
     hid the ＋ behind the mark. Pushing it back one layer needs a stacking
     context on the button, or the -1 would drop it behind the card instead. */
  isolation: isolate;
  transition: color 200ms ease, scale 130ms cubic-bezier(.22, .9, .24, 1);
}

.cat-add::before {
  content: '';
  position: absolute;
  inset: 4px;
  z-index: -1;
  background: var(--archive-paper);
  /* Phase 3D: this was drawn with --archive-line, the same hairline as the
     caption rule and the plate frame — so the one thing on the card you can
     press was rendered at exactly the weight of the things you cannot, and it
     read closer to a disabled field than to an action. A darker stroke is all
     it takes; the shape, the size and the flatness stay. */
  border: 1px solid rgba(20, 33, 27, 0.38);
  transition: background 200ms ease, border-color 200ms ease;
}

/* Press inverts the whole mark, not just its scale. On a touch screen there is
   no hover to preview the action, so the commit has to be unmistakable in the
   instant the finger lands — the same reason the ✓ that follows is a fill. */
.cat-add:hover, .cat-add.added, .cat-add:active { color: var(--archive-paper); }
.cat-add:hover::before,
.cat-add.added::before,
.cat-add:active::before {
  background: var(--archive-ink);
  border-color: var(--archive-ink);
}
.cat-add:active { scale: 0.94; }
.cat-add:focus-visible { outline: 2px solid var(--archive-clay); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .cat-card { animation: none; }
  .cat-add, .cat-add::before { transition: none; }
  .cat-add:active { scale: 1; opacity: 0.72; }
}

/* ---------- responsive ---------- */

@media (max-width: 1040px) {
  .cat-caption .name-ar { font-size: 1.1rem; }
}

@media (max-width: 760px) {
  .cat-caption .name-ar { font-size: 1.05rem; }
  html[dir='ltr'] .cat-caption .name-ar { font-size: 1.1rem; }
  /* Still clearly secondary against a 16.8px Arabic name, but no longer under
     the 11pt floor — it is the botanical identity, not decoration. */
  .cat-caption .name-en { font-size: 0.7rem; }
  /* The price does NOT shrink here. It used to drop to 0.8rem on a phone,
     which is the surface where people actually compare plants — the number
     holds 14px on every width. Checked for overflow on the narrowest card
     the grid produces (166px on a 375pt phone), including a sale row where
     the struck-through original sits beside it. */

  /* ---------- phones: the code and its tier become the eyebrow ----------
     An earlier pass put the code beside availability to free the price's line.
     It worked on width but paired the wrong two things: the accession code and
     the rarity are fixed properties of the specimen, availability is a state
     that changes when the last one sells. They should not share a line.

     So the code and the tier move together to the top, above the name, and the
     price takes a row of its own:

         SP-014 · شائعة
         مونستيرا
         Monstera
         17 د.ك                    ＋
         متوفر

     This also ends the width problem outright rather than negotiating with it.
     Nothing competes with the price for its line any more, so it cannot be
     pushed off one — the widest eyebrow (اقتناء مميز) needs 128px and the
     widest price block 99px, both inside a 142px caption on a 375pt phone.

     .cat-meta dissolves so the tier and the price can sit in different rows.
     display:contents is right here and was wrong before, because the caption
     is a grid now rather than a flex column — and it stays scoped away from the
     list view, which still needs .cat-meta to be a real box. */
  #grid:not(.is-list) .cat-meta { display: contents; }

  #grid:not(.is-list) .cat-caption {
    grid-template-areas:
      "code  tier"
      "name  name"
      "latin latin"
      "price price"
      "avail avail"
      "chip  chip";
  }
  /* Descendant, not child: display:contents changes how these are laid out,
     not whose children they are. */
  #grid:not(.is-list) .cat-tier  { grid-area: tier; justify-self: start; }
  #grid:not(.is-list) .cat-price { grid-area: price; justify-self: start; }

  /* The eyebrow is one unit, so its two halves sit tight; the name below it
     gets the full 8. */
  #grid:not(.is-list) .cat-caption { row-gap: calc(var(--cap-step) * 2); }
  #grid:not(.is-list) .cat-caption > .cat-sp { align-self: baseline; }

  /* The ＋ shares the price's line rather than floating below the last row of
     text, which is where the eye expects the acquisition mark to be. */
  .cat-add { bottom: 12px; }
}
