/* ============================================================================
   Projects — shared visual system
   ----------------------------------------------------------------------------
   Used by both projects.html (free samples) and premium-projects.html (paid).
   They were two separate inline <style> blocks that had drifted apart, which is
   how one page ended up on a teal->indigo->purple gradient matching nothing
   else on the site: not the #3874ff brand blue, not the #e5780b of the nav CTA.

   Class names are deliberately unchanged. Both catalogues build their cards as
   HTML strings in JavaScript, so renaming anything here means editing template
   literals in two more files to gain nothing visible.

   One system, two skins: shared structure, with .pp-* (premium) picking up an
   amber edge and .pj-* (samples) staying on brand blue, so a visitor can tell
   paid from free before reading a word.
   ========================================================================= */

:root {
  --pr-blue:   #3874ff;   /* --phoenix-primary: the site's own brand blue */
  --pr-blue-d: #1d4ed8;
  --pr-navy:   #0b1f4b;
  --pr-amber:  #e5780b;   /* --phoenix-warning: the nav "Buy Tokens" colour  */
  --pr-amber-l:#fbbf24;   /* lighter, for amber text sitting on the navy hero */

  --pr-card:  #fff;
  --pr-line:  var(--phoenix-border-color, #cbd0dd);
  --pr-muted: var(--phoenix-body-tertiary-color, #6b7280);
  --pr-ink:   var(--phoenix-emphasis-color, #141824);
}
[data-bs-theme="dark"] {
  --pr-card: #161b2b;
  --pr-line: #2b3247;
  --pr-muted: #98a2be;
}

/* ============================================================================
   Hero
   ----------------------------------------------------------------------------
   Roughly 40% shorter than the block it replaces. The old one reserved a lot of
   vertical space for a gradient, pushing the actual catalogue - the reason
   anyone opens the page - below the fold on a laptop.
   ========================================================================= */
.pp-hero, .pj-hero {
  position: relative; overflow: hidden; isolation: isolate;
  border-radius: 1.25rem; color: #fff;
  padding: 1.35rem 1.6rem 1.45rem;
  margin-bottom: 1.4rem;
  background: linear-gradient(115deg, var(--pr-navy) 0%, #17399b 46%, var(--pr-blue-d) 100%);
}

/* THE contrast fix, and the reason this rule is this specific.
   Phoenix ships `h6,...,h1 { color: var(--phoenix-heading-color) }`. An element
   colour on the hero does not beat it, because the h1 matches a rule of its own
   and inheritance never competes with a direct match. So the headline rendered
   near-black on the gradient at 2.82:1 - under the 3.0 minimum for large text,
   i.e. an actual accessibility failure rather than a matter of taste.
   Naming both the hero and the h1 wins on specificity without !important. */
.pp-hero h1, .pj-hero h1 {
  color: #fff;
  font-weight: 800; letter-spacing: -.03em; margin: 0 0 .35rem;
  font-size: clamp(1.6rem, 3vw, 2.05rem);
}
.pp-hero .lede, .pj-hero .lede, .pp-hero p, .pj-hero p {
  opacity: .92; margin: 0; max-width: 44rem; font-size: .9rem; line-height: 1.55;
}
/* The "Try" footnote is secondary to the pitch above it, so it is quieter and sits close. */
.pp-hero .lede + .lede { margin-top: .4rem !important; font-size: .82rem; opacity: .82; }

/* Texture. Two flat washes rather than the blurred colour blobs the old hero
   animated: a 52px blur on a 20rem element is a real per-frame cost on a page
   that is mostly a grid of cards, and it bought a shape nobody looks at. */
.pp-hero::before, .pj-hero::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(38rem 20rem at 88% -20%, rgba(56, 116, 255, .55), transparent 70%),
    radial-gradient(24rem 16rem at 12% 120%, rgba(229, 120, 11, .22), transparent 70%);
}
/* A faint grid, masked so it fades out before it reaches the text. */
.pp-hero::after, .pj-hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image: linear-gradient(rgba(255, 255, 255, .07) 1px, transparent 1px),
                    linear-gradient(90deg, rgba(255, 255, 255, .07) 1px, transparent 1px);
  background-size: 2.75rem 2.75rem;
  -webkit-mask-image: radial-gradient(60% 90% at 75% 30%, #000 25%, transparent 75%);
          mask-image: radial-gradient(60% 90% at 75% 30%, #000 25%, transparent 75%);
}

.pr-eyebrow, .pj-eyebrow {
  display: inline-flex; align-items: center; gap: .45rem;
  background: rgba(255, 255, 255, .14); border: 1px solid rgba(255, 255, 255, .26);
  border-radius: 2rem; padding: .2rem .68rem; margin-bottom: .5rem;
  font-size: .7rem; font-weight: 700; letter-spacing: .01em;
}
.pr-eyebrow .dot, .pj-eyebrow .dot {
  width: .45rem; height: .45rem; border-radius: 50%; background: #4ade80;
  box-shadow: 0 0 0 .2rem rgba(74, 222, 128, .26);
}
/* Amber marks what costs money, on both pages: the premium eyebrow and prices. */
.pp-hero .pr-eyebrow { background: rgba(229, 120, 11, .20); border-color: rgba(251, 191, 36, .45);
  color: var(--pr-amber-l); }

.pj-stats {
  display: grid; gap: .75rem; grid-template-columns: repeat(4, minmax(0, 1fr));
  margin: 1.4rem 0 0; padding-top: 1rem; border-top: 1px solid rgba(255, 255, 255, .18);
}
.pj-stat b { display: block; font-size: 1.45rem; font-weight: 800; line-height: 1.15;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.pj-stat span { font-size: .74rem; opacity: .85; font-weight: 600; }

/* ============================================================================
   Cards — one structure, two accents
   ========================================================================= */
.pp-grid, .pj-grid {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
}

.pp-card, .pj-card {
  position: relative; display: flex; flex-direction: column; height: 100%;
  overflow: hidden; background: var(--pr-card);
  border: 1px solid var(--pr-line); border-radius: .9rem;
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}
/* The two catalogues pad at different levels and merging them lost this once already:
   a premium card wraps its content in .pp-body/.pp-foot, which carry the padding, whereas a
   sample card puts the heading, blurb and chips directly inside the card. With no padding here
   that content sat flush against the border and underneath the 3px accent bar. Padding belongs
   on .pj-card specifically - putting it on the shared rule would double it on premium cards. */
.pj-card { padding: 1.1rem 1.15rem 1rem; gap: .5rem; }
.pp-card:hover, .pj-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 .7rem 1.7rem rgba(15, 23, 42, .13);
}
.pp-card:hover { border-color: var(--pr-amber); }
.pj-card:hover { border-color: var(--pr-blue); }

/* The edge accent. Paid is amber, free is blue - the one cue that separates the
   two catalogues, applied at the same place on both so they still feel related. */
.pp-card::before, .pj-card::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; z-index: 1;
  background: var(--pj-accent, var(--pr-blue));
}
.pp-card::before { background: var(--pr-amber); }

.pp-shot { aspect-ratio: 16/9; width: 100%; object-fit: cover; background: #0f172a; }
.pp-shot-empty {
  aspect-ratio: 16/9; display: flex; align-items: center; justify-content: center;
  font-size: 1.9rem; color: rgba(255, 255, 255, .38);
  background: linear-gradient(125deg, var(--pr-navy), #17399b);
}
.pp-body { display: flex; flex-direction: column; gap: .45rem; flex: 1; padding: 1rem 1.1rem; }

/* The premium catalogue builds its cards in JS and emits a plain <h5> and <p> rather than
   classed elements, so these target the tags. Styling .pp-title / .pp-blurb would have looked
   tidier and matched nothing at all. */
.pp-body > h5, .pj-name {
  font-weight: 800; font-size: 1rem; line-height: 1.3; color: var(--pr-ink);
}
.pp-body > p, .pj-blurb { font-size: .82rem; color: var(--pr-muted); line-height: 1.55; }

/* Stack chips */
.pp-tech { display: flex; flex-wrap: wrap; gap: .28rem; }
.pp-tech span, .pj-inc i {
  font-style: normal; font-size: .67rem; font-weight: 700;
  padding: .15rem .45rem; border-radius: .35rem;
  background: rgba(56, 116, 255, .09); color: var(--pr-blue-d);
}
[data-bs-theme="dark"] .pp-tech span,
[data-bs-theme="dark"] .pj-inc i { background: rgba(129, 140, 248, .16); color: #a5b4fc; }
.pj-inc { display: flex; flex-wrap: wrap; gap: .3rem; }
.pj-inc i.more { background: transparent; color: var(--pr-muted); cursor: pointer;
  border: 1px dashed var(--pr-line); }

/* Price: the one place amber is allowed to shout. */
.pp-price { font-size: 1.35rem; font-weight: 800; letter-spacing: -.02em; color: var(--pr-amber); }
.pp-was { text-decoration: line-through; opacity: .55; font-weight: 500; font-size: .92rem;
  color: var(--pr-muted); }

.pp-foot, .pj-foot {
  margin-top: auto; display: flex; align-items: center; gap: .6rem;
  padding: .8rem 1.1rem; border-top: 1px solid var(--pr-line);
  flex-wrap: wrap;
}
/* The action buttons move as one block. margin-left:auto keeps them right-aligned beside the
   price while they fit, and drops the whole group onto its own line when they do not - rather
   than stranding a lone Download button below the others. */
.pp-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; margin-left: auto;
}
/* Without this the squeezed buttons broke mid-label - "Test-" above "Run", which reads as two
   controls. Better to let the group wrap than to hyphenate a button. */
.pp-actions .btn { white-space: nowrap; }
/* On a narrow card the buttons own the row, so the auto margin would shove them off the right. */
@media (max-width: 420px) {
  .pp-actions { margin-left: 0; width: 100%; }
}
/* The line that says why a disabled Buy button is disabled, on a card footer and in the detail
   modal's footer. flex-basis:100% claims a row of its own rather than competing with the price and
   the .pp-actions group for the one line - a third auto-width item on that row is exactly what
   pushed the buttons past the card border before .pp-actions existed. margin overrides Bootstrap's
   .modal-footer > * rule, whose .25rem on each side would take a full-width child wider than the
   footer's content box and bring back the horizontal scrollbar. */
.pp-off-note {
  flex: 0 0 100%; margin: .15rem 0 0; min-width: 0;
  font-size: .78rem; line-height: 1.35; text-align: left; color: var(--pr-muted);
}
.pj-foot { padding: .75rem 0 0; border-top: 0; justify-content: space-between; }

.pj-dl {
  background: var(--pr-blue); color: #fff; border: 0; border-radius: .55rem;
  padding: .45rem 1rem; font-size: .8rem; font-weight: 700; text-decoration: none;
  display: inline-flex; align-items: center; gap: .45rem;
  transition: background .15s ease, transform .15s ease;
}
.pj-dl:hover { background: var(--pr-blue-d); color: #fff; transform: translateY(-1px); }

/* ============================================================================
   Toolbar, groups, states — samples page
   ========================================================================= */
.pj-wrap { max-width: 74rem; margin: 0 auto; }
.pj-tools { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin-bottom: 1.2rem; }
.pj-search { position: relative; flex: 1 1 15rem; min-width: 12rem; }
.pj-search input {
  width: 100%; border: 1px solid var(--pr-line); background: var(--pr-card); color: var(--pr-ink);
  border-radius: 2rem; padding: .48rem .9rem .48rem 2.2rem; font-size: .85rem; font-weight: 500;
  outline: none; transition: border-color .15s ease, box-shadow .15s ease;
}
.pj-search input:focus { border-color: var(--pr-blue); box-shadow: 0 0 0 .18rem rgba(56, 116, 255, .16); }
.pj-search .fa-magnifying-glass { position: absolute; left: .85rem; top: 50%; transform: translateY(-50%);
  font-size: .8rem; color: var(--pr-muted); pointer-events: none; }

.pj-filter { display: flex; gap: .4rem; flex-wrap: wrap; }
.pj-chip {
  border: 1px solid var(--pr-line); background: var(--pr-card); color: var(--pr-ink);
  border-radius: 2rem; padding: .34rem .85rem; font-size: .79rem; font-weight: 700;
  cursor: pointer; display: inline-flex; align-items: center; gap: .38rem;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.pj-chip:hover { border-color: var(--pr-blue); }
.pj-chip.active { background: var(--pr-blue); border-color: var(--pr-blue); color: #fff; }
.pj-chip.active .fa-solid, .pj-chip.active .fa-brands { color: #fff !important; }
.pj-count { font-size: .8rem; color: var(--pr-muted); font-weight: 600; white-space: nowrap; }

.pj-group { margin-bottom: 2.2rem; }
.pj-group h2 {
  font-size: 1rem; font-weight: 800; display: flex; align-items: center; gap: .6rem;
  margin-bottom: .9rem; padding-bottom: .55rem; border-bottom: 1px solid var(--pr-line);
}
.pj-group h2 .ico { width: 1.7rem; height: 1.7rem; border-radius: .5rem; flex: none;
  display: inline-flex; align-items: center; justify-content: center; font-size: .82rem; }
.pj-group h2 .n { font-size: .72rem; font-weight: 700; color: var(--pr-muted);
  background: var(--pr-line); border-radius: 1rem; padding: .1rem .5rem; margin-left: auto; }

.pj-course { font-size: .67rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
  color: var(--pj-accent, var(--pr-muted)); }
.pj-meta { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap;
  font-size: .72rem; color: var(--pr-muted); font-weight: 600; }
.pj-meta .sep { opacity: .45; }
.pj-fmt i { font-style: normal; font-size: .66rem; font-weight: 800; border: 1px solid var(--pr-line);
  border-radius: .3rem; padding: .08rem .4rem; color: var(--pr-muted); margin-right: .25rem; }
.pj-verified { font-size: .72rem; color: #16a34a; font-weight: 700; display: flex;
  align-items: flex-start; gap: .35rem; line-height: 1.45; }
[data-bs-theme="dark"] .pj-verified { color: #4ade80; }
.pj-gap { font-size: .72rem; color: #b45309; font-weight: 700; }
[data-bs-theme="dark"] .pj-gap { color: var(--pr-amber-l); }
.pj-soon { font-size: .74rem; font-weight: 700; color: var(--pr-muted);
  border: 1px dashed var(--pr-line); border-radius: .55rem; padding: .38rem .8rem; }
.pj-size { font-size: .74rem; color: var(--pr-muted); font-weight: 600; }
.pj-card.planned { opacity: .72; border-style: dashed; }
.pj-card.planned:hover { transform: none; box-shadow: none; }
.pj-empty { text-align: center; padding: 2.5rem 1rem; color: var(--pr-muted);
  border: 1px dashed var(--pr-line); border-radius: 1rem; }

.pj-card, .pp-card { animation: prIn .42s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes prIn { from { opacity: 0; transform: translateY(10px); } }

@media (max-width: 900px) { .pj-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) {
  .pp-hero, .pj-hero { padding: 1.5rem 1.15rem; }
  .pj-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem .75rem; }
  .pp-grid, .pj-grid { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .pp-card, .pj-card { animation: none !important; transition: none; }
  .pp-card:hover, .pj-card:hover { transform: none; }
  .pj-dl:hover { transform: none; }
}
