/* ==========================================================================
   Bluepill — $BLUEPILL
   --------------------------------------------------------------------------
   Written for the pharmaceutical-label mockup: a photographic blue hero, then
   two white cards on a blue ground — the drug-facts label on the left, the
   live figures on the right — then an ecosystem footer.

   Carried over from the sibling sites because they survived three builds:
   tabular numerals on every figure, whole-number counts beside cented money,
   icon tiles on a shared stroke spec, and entrance animations that the page
   works perfectly without. What is NOT carried over is the square-cornered
   voxel language — this mockup is rounded, photographic and clinical, and
   the two fight.

   Light theme only, by design: the artwork is a bright product photograph.
   ========================================================================== */

/* ---------- Tokens ---------------------------------------------------- */

:root {
  /* Sampled from the mockup. --ink is the deep blue of the carton's side
     panel and every heading; --accent is the brighter blue of the 50mg badge,
     the figures and the icons. */
  --ink:          #0a3b8c;
  --ink-soft:     #5c7cb8;
  --ink-faint:    #8ba4cc;
  --accent:       #1668e3;
  --accent-soft:  #e4edfc;

  --card:         #ffffff;
  --card-row:     #edf3fc;
  --hairline:     #d7e3f5;

  /* The grounds under the photographs — what a visitor sees before the
     artwork loads and what shows at any ratio it does not fill. SAMPLED from
     the delivered files, not picked by eye: --hero-ground is the mean of the
     header's own top and bottom border bands, --page the mean of the pill
     field's top edge. A guessed value here reads as a seam. */
  --hero-ground:  #3a72d7;
  --page:         #2b5ebf;
  /* The scrim the footer lays over the pill field. LIGHT, not dark, for two
     reasons that agreed with each other: the mockup's lower band is white
     with dark blue type, and both ecosystem lockups are dark artwork on
     transparency — over a dark scrim they disappeared almost completely,
     which a first render caught. A scrim is still needed rather than no
     scrim at all, because the field is viewport-fixed: which part of the
     photograph lands behind the footer changes with the page's height, so
     without it legibility is a matter of luck. */
  --foot-scrim-a: rgba(255, 255, 255, .84);
  --foot-scrim-b: rgba(255, 255, 255, .94);

  --radius:       18px;
  --radius-sm:    12px;
  --wrap:         1180px;
  --gutter:       16px;

  --shadow: 0 1px 2px rgba(8, 40, 100, .10), 0 12px 32px rgba(8, 40, 100, .14);
}

/* ---------- Reset ----------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  /* position: relative is load-bearing, not tidiness: it makes <body> the
     containing block for .pillfield, which is what lets that absolutely
     positioned layer stretch to the whole document instead of one viewport.
     Remove it and the pill field stops at the fold. */
  position: relative;
  /* Colour only — the fallback under the photograph, and what shows before
     it loads. The picture itself lives on .pillfield below. */
  background: var(--page);
  color: var(--ink);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
  font-size: 16px;
  line-height: 1.45;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, p, dl, dd, ol, ul { margin: 0; }

img { max-width: 100%; display: block; }

a { color: inherit; text-decoration: none; }

/* Every figure on the page is set in tabular numerals, so a value that ticks
   up on refresh does not shift the column it sits in. */
.stat__value, .stat__sub, .dose, .label-card__strap b, .btn__label--ca {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ---------- The pill field -------------------------------------------- */
/* The supplied pill photograph, behind everything below the hero. The panels
   are white cards floating on it, as the mockup has them.

   It SCROLLS WITH THE PAGE. It was pinned to the viewport at first — the
   parallax that was asked for — and the owner did not like the effect of the
   background holding still while the content slid over it, so it is one
   picture that moves with everything else now.

   `absolute`, not `fixed`, is the whole of that change: with <body> as the
   containing block it stretches to the full document rather than the
   viewport, so it travels with the content. Keep body's `position: relative`
   below — without it this would size to the viewport at the document origin
   and leave the lower half of a long page on the flat --page blue.

   aria-hidden and empty: it is scenery, and a screen reader announcing it
   would be announcing nothing. */
.pillfield {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--page) center / cover no-repeat;
  background-image: image-set(url('../../images/bg.webp?v=19') type('image/webp'),
                              url('../../images/bg.jpg?v=19') type('image/jpeg'));
}

/* ---------- Top bar --------------------------------------------------- */
/* Sits OVER the hero rather than above it, so the page opens on the artwork.
   Absolute, not fixed: a fixed bar over a tall product photograph follows the
   reader down onto the white cards, where its translucent fill has nothing
   to be translucent against. */

.topbar {
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 3;
  padding-block: 18px;
}

/* A scrim under the chrome, and it is not decoration. The bar sits over the
   carton's WHITE face, so white buttons on white card rendered as three
   ghost outlines — the CA button, the one control nobody can guess, worst of
   all. A first render caught it. The gradient is strongest exactly where the
   type is and gone by the bottom of the bar, so the photograph is untouched
   everywhere it matters.
   pointer-events: none so it never eats a click meant for a button. */
.topbar::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 190%;
  background: linear-gradient(to bottom,
              rgba(6, 30, 84, .58) 0%,
              rgba(6, 30, 84, .30) 45%,
              rgba(6, 30, 84, 0) 100%);
  pointer-events: none;
  z-index: -1;
}

.topbar__rail {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: #fff;
}

.brand__mark {
  width: 21px; height: 21px;
  border-radius: 5px;
  background: #fff;
  flex: none;
}

.brand__word {
  font-size: 25px;
  font-weight: 700;
  letter-spacing: -.02em;
}

/* ---------- Action buttons -------------------------------------------- */

.actions {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 42px;
  padding-inline: 18px;
  border: 1.5px solid rgba(255, 255, 255, .55);
  border-radius: 999px;
  background: rgba(255, 255, 255, .14);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: #fff;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background .18s ease, border-color .18s ease, translate .18s ease;
}

.btn:hover { background: rgba(255, 255, 255, .26); border-color: #fff; translate: 0 -1px; }
.btn:active { translate: 0 0; }

.btn:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 2px;
}

.btn__icon { width: 18px; height: 18px; flex: none; }

/* One stroke spec across every outlined icon on the page, so the set reads as
   one hand. Filled glyphs (the X mark) opt out by setting their own fill. */
.btn__icon--stroke,
.stat__icon svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.btn--ca .btn__icon { opacity: .9; }

.btn__label--ca { letter-spacing: .01em; }

/* ---------- Copy toast ------------------------------------------------ */

.ca-wrap { position: relative; display: inline-flex; }

.toast {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 13px;
  border-radius: var(--radius-sm);
  background: var(--ink);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: var(--shadow);
  opacity: 0;
  translate: 0 -5px;
  pointer-events: none;
  transition: opacity .18s ease, translate .18s ease;
}

/* app.js reveals the toast; it is driven by whichever of these it sets, so
   all three spellings resolve to the same visible state. */
.toast.is-on,
.toast.show,
.toast[data-show='true'] { opacity: 1; translate: 0 0; }

.toast__check { width: 15px; height: 15px; flex: none; }

/* ---------- Hero ------------------------------------------------------ */

.hero {
  position: relative;
  background: var(--hero-ground);
}

.hero__frame {
  position: relative;
  width: 100%;
  /* EXACTLY the delivered artwork's ratio — 1536×1024 is 3:2 — so
     object-fit: cover crops nothing at any width. That is the point: the
     carton spans almost the full frame, and a horizontal crop takes its ends
     off, which is how you lose "50 mg" from one side and the Base pill from
     the other. If the artwork is ever replaced at a different shape, change
     this one declaration to match it rather than letting cover decide. */
  aspect-ratio: 3 / 2;
  /* …but capped, because 3:2 at 1440 wide is 960px of hero and the dashboard
     never appears. The cap crops VERTICALLY only: the artwork's top is
     out-of-focus pills and its bottom is reflective floor, while the carton
     sits between roughly 11% and 82% of the height, so ~9% off each end
     never touches it. Horizontal crop is the one that must not happen — it
     takes "50 mg" off one side and the Base pill off the other — and
     aspect-ratio plus centred cover is what prevents it. */
  max-height: 78vh;
  overflow: hidden;
  background: var(--hero-ground);
}

.hero__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Biased UP the frame, so what the max-height cap trims comes off the
     BOTTOM — which is reflective floor — rather than being split evenly and
     taking the top of the carton with it. Centred, the cap cropped to within
     a couple of percent of the box's top edge and the Base mark on its side
     panel ran into the top bar. */
  object-position: center 32%;
}

/* ---------- The two panels -------------------------------------------- */

.panels {
  display: grid;
  grid-template-columns: 1.12fr 1fr;
  gap: 22px;
  align-items: start;
  padding-block: 26px 34px;
}

.label-card,
.stats-card {
  background: var(--card);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 26px 28px 28px;
}

/* ---------- Left: the label ------------------------------------------- */

.label-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

.label-card__name {
  font-size: clamp(34px, 4.4vw, 46px);
  font-weight: 800;
  letter-spacing: -.035em;
  line-height: 1;
  color: var(--ink);
}

.label-card__name sup {
  font-size: .32em;
  font-weight: 700;
  top: -1.4em;
  margin-left: .06em;
}

.label-card__generic {
  margin-top: 8px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .1em;
  color: var(--ink-soft);
}

.dose {
  flex: none;
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 7px 15px;
  border-radius: 10px;
  background: var(--accent);
  color: #fff;
  font-size: 27px;
  font-weight: 800;
  letter-spacing: -.02em;
}

.dose small { font-size: .55em; font-weight: 700; }

.label-card__strap {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  margin-top: 22px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--hairline);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.label-card__strap b {
  color: var(--ink);
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -.01em;
  text-transform: none;
}

.label-card__strap b small {
  display: block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.label-card__rubric {
  margin-top: 22px;
  margin-bottom: 10px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink);
}

.label-card__rubric span {
  font-weight: 600;
  text-transform: none;
  letter-spacing: .01em;
  color: var(--ink-soft);
}

.ingredients { border-top: 1px solid var(--hairline); }

.ingredients__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 11px 13px;
  font-size: 15.5px;
}

/* Zebra striping rather than a rule per row: a drug-facts panel reads as a
   block of alternating bands, and rules at this density turn into a grid. */
.ingredients__row:nth-child(odd) { background: var(--card-row); }

.ingredients__row dt { color: var(--ink); }
.ingredients__row dd { margin: 0; font-weight: 700; color: var(--ink); text-align: right; }

.label-card__inactive {
  padding: 2px 0 0;
  font-size: 15.5px;
  color: var(--ink);
}

.label-card__note {
  margin-top: 20px;
  padding-top: 15px;
  border-top: 1px solid var(--hairline);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-soft);
}

/* ---------- Right: the figures ---------------------------------------- */

.stats-card__title {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink);
  padding-bottom: 13px;
  border-bottom: 1px solid var(--hairline);
}

.stat {
  display: grid;
  grid-template-columns: 44px 1fr;
  align-items: center;
  gap: 15px;
  padding: 15px 2px;
  border-bottom: 1px solid var(--hairline);
  transition: translate .18s ease;
}

.stat:last-of-type { border-bottom: 0; padding-bottom: 4px; }

.stat:hover { translate: 2px 0; }

.stat__icon {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  color: var(--accent);
}

.stat__icon svg { width: 27px; height: 27px; }

/* The distributed tile's capsule is the one glyph that is the TOKEN rather
   than a metric, and at 27px on a 1.7 stroke it read as a paperclip. A
   heavier stroke and a filled half give it the two-tone a real capsule has. */
.stat[data-metric="distributed"] .stat__icon svg { stroke-width: 1.9; }
.stat[data-metric="distributed"] .stat__icon .capsule__half { fill: currentColor; opacity: .22; }

.stat__label {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--ink-soft);
  line-height: 1.3;
}

.stat__value {
  display: flex;
  align-items: baseline;
  gap: 7px;
  margin-top: 2px;
  font-size: clamp(21px, 2.4vw, 25px);
  font-weight: 800;
  letter-spacing: -.025em;
  line-height: 1.15;
  color: var(--ink);
}

.stat__unit {
  font-size: .56em;
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--ink-soft);
}

.stat__sub {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--ink-faint);
  line-height: 1.3;
}

.stat__sub b { font-weight: 600; }

/* ---------- Loading dots ---------------------------------------------- */
/* A value blinks three dots until the first load resolves. An em dash means
   the opposite — every source answered and none had a number — so the two
   must never look alike. */

.dots { display: inline-flex; align-items: center; gap: 3px; }

.dots i {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: currentColor;
  opacity: .28;
  animation: blink 1.25s infinite ease-in-out;
}

.dots i:nth-child(2) { animation-delay: .18s; }
.dots i:nth-child(3) { animation-delay: .36s; }

@keyframes blink {
  0%, 75%, 100% { opacity: .28; }
  35%           { opacity: .9; }
}

/* ---------- Source legend --------------------------------------------- */

.legend {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 16px;
  padding-top: 13px;
  border-top: 1px solid var(--hairline);
  font-size: 12px;
  color: var(--ink-faint);
}

.legend__dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--ink-faint);
  flex: none;
  transition: background .2s ease;
}

/* Whichever state class app.js sets, the dot carries it. */
.legend.is-live   .legend__dot, .legend__dot.is-live   { background: #17a866; }
.legend.is-stale  .legend__dot, .legend__dot.is-stale  { background: #d99a1a; }
.legend.is-error  .legend__dot, .legend__dot.is-error  { background: #cf4040; }

/* ---------- Footer ---------------------------------------------------- */

.foot {
  position: relative;
  /* No picture of its own: the pill field is already behind it, fixed. This
     is only the scrim that keeps the type and the lockups legible over
     whichever part of the photograph happens to sit here. */
  background: linear-gradient(var(--foot-scrim-a), var(--foot-scrim-b));
  color: var(--ink);
  padding-block: 40px 34px;
}

.foot__rail {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
  text-align: center;
}

.eco {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(20px, 6vw, 72px);
}

.eco__half {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 11px;
  transition: translate .18s ease;
}

.eco__half:hover { translate: 0 -2px; }

.eco__kicker {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Stockify is smooth artwork, 800×261, and scales to anything. */
.eco__logo { height: 54px; width: auto; }

/* Stonks.Exchange is PIXEL ART — a pixel-font wordmark beside a pixel icon,
   112px tall as supplied. It has to land on an INTEGER division of that or
   the grid averages into mush: at the 34px it was first given (0.30×) the
   wordmark was a grey smear and the icon unreadable. 56px is exactly half,
   so every source pixel becomes one clean 2×2 average.

   Keep any future value on 112/n — 56, 37⅓ is not one, 28. Matching the
   other lockup's height is the wrong instinct here; matching the grid is
   what makes it legible, and the two read as a pair anyway because the
   wordmark is long and low where Stockify's is short and tall. */
.eco__logo--stonks { height: 56px; }

.eco__split {
  width: 1px;
  align-self: stretch;
  background: var(--hairline);
}

.foot__tagline {
  margin-top: 34px;
  font-size: clamp(13px, 1.6vw, 15px);
  font-weight: 700;
  letter-spacing: .22em;
  text-transform: uppercase;
}

.foot__fine {
  max-width: 760px;
  margin: 22px auto 0;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--ink-soft);
}

/* The non-affiliation line leads the fine print and is the one sentence in
   it that has to be read, so it takes the full ink rather than the muted. */
.foot__fine b { font-weight: 700; color: var(--ink); }

/* ---------- Entrance animations --------------------------------------- */
/* The markup is visible by default and the inline script opts in by adding
   .anim to <html>. Nothing here may hide anything unless .anim is present,
   or a thrown exception leaves a blank page.

   These animate `translate` and `opacity`, never `transform`: a finished
   animation with fill:both keeps ownership of the property it animated, so
   animating `transform` would outrank .stat:hover's own and kill every hover
   on the page after the reveal ran. */

.anim .rise,
.anim [data-rise] {
  opacity: 0;
  translate: 0 14px;
}

.anim .rise.is-in,
.anim [data-rise].is-in {
  animation: rise .5s cubic-bezier(.22, .68, .36, 1) both;
  animation-delay: var(--d, 0ms);
}

@keyframes rise {
  from { opacity: 0; translate: 0 14px; }
  to   { opacity: 1; translate: 0 0; }
}

/* ---------- Responsive ------------------------------------------------ */

@media (max-width: 900px) {
  .panels { grid-template-columns: 1fr; gap: 16px; }
}

@media (max-width: 640px) {
  .topbar { padding-block: 13px; }
  .topbar__rail { gap: 8px; }

  .brand__word { font-size: 21px; }
  .brand__mark { width: 18px; height: 18px; }

  /* The three actions stay on one line, shedding padding and then their
     labels rather than wrapping under the Base mark. */
  .btn { height: 37px; padding-inline: 12px; font-size: 13.5px; gap: 6px; }
  .btn__icon { width: 16px; height: 16px; }
  .actions { gap: 7px; }

  .label-card, .stats-card { padding: 21px 18px 22px; }

  .stat { grid-template-columns: 38px 1fr; gap: 12px; padding: 13px 0; }
  .stat__icon { width: 38px; height: 38px; }
  .stat__icon svg { width: 24px; height: 24px; }

  .eco { gap: 22px; }
  .eco__logo { height: 32px; }
  /* 28px = 112/4, the next integer division down. 27 was not one. */
  .eco__logo--stonks { height: 28px; }
  .foot__tagline { letter-spacing: .16em; }
}

@media (max-width: 400px) {
  /* Below this the CA label is the first thing to go: the icon still says
     "copy", and the address itself is one tap away in the clipboard. */
  .btn--ca .btn__label { display: none; }
  .btn--ca { padding-inline: 11px; }
}

/* ---------- Reduced motion -------------------------------------------- */
/* Declined twice over: the inline script returns early under this query, and
   this flattens anything that still got through. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .anim .rise, .anim [data-rise] { opacity: 1; translate: none; }
  .stat:hover, .btn:hover, .eco__half:hover { translate: none; }
}
