/* =========================================================================
   PetriHub site stylesheet — the layer petrihub.com and petrihub.com/pricing
   both stand on: tokens, buttons, the header, the dish, section scaffolding,
   the close, and the floor.

   It exists because the close is deliberately the SAME block on both pages and
   the tokens have to be. Held inline in each page they would have to be edited
   twice and would drift on the first change that only got made once.

   Page-specific rules stay inline in the page that owns them: the feature
   switcher belongs to index.html, the comparison table to pricing.html, and
   neither is worth carrying to the other.
   ========================================================================= */


/* ---- tokens (mirrors the app's DESIGN.md identity) ---- */
:root {
  --paper: #f3f7f9;
  --paper-2: #e9f0f4;
  --card: #ffffff;
  --ink: #10202b;
  --ink-2: #3d5462;
  --ink-3: #587384;
  --accent: #0f7490;
  --accent-strong: #0a566b;
  --accent-soft: #ddf1f7;
  --border: #dae4ea;
  --border-strong: #bccdd8;
  /* radii: the app's own 8/12/16 scale (DESIGN.md rounded.sm/md/lg) */
  --r-sm: 8px;
  --r: 12px;
  --r-lg: 16px;
  /* depth is tonal + border-first (DESIGN.md): one barely-there shadow,
     never a wide bloom under an already-bordered box */
  --shadow-sm: 0 1px 2px rgba(16,32,43,.04);
  /* 1600, up from 1400: the switcher's shot is the page's main evidence and
     at 1400 it rendered ~1000px wide, which scaled a 1440-wide capture to
     71% and dissolved the app's 13-15px type. 1600 with a narrower list
     column puts the frame at ~1250 — 87% of capture size, legible. */
  --maxw: 1600px;
  --font: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
h1, h2, h3 { font-family: var(--font); font-weight: 600; letter-spacing: -0.028em; line-height: 1.08; margin: 0; }
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 clamp(24px, 4vw, 52px); position: relative; z-index: 1; }
.muted { color: var(--ink-2); }

/* ---- buttons ----
   One action per view, so there is no filled/outlined pair anywhere on the
   page. No lift on hover and no glow under the fill: the state change is a
   tonal step to --accent-strong, and the arrow slides. */
.btn {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--font); font-weight: 600; font-size: 15px;
  padding: 13px 22px; border-radius: var(--r-sm); cursor: pointer;
  border: 1px solid transparent; transition: background .18s ease;
  white-space: nowrap;
}
.btn-primary { background: var(--accent); color: #fff; }
.btn-primary:hover { background: var(--accent-strong); }
.btn .arrow { transition: transform .18s ease; }
.btn:hover .arrow { transform: translateX(3px); }
.btn:focus-visible, .quiet-link:focus-visible, .nav-center a:focus-visible, .brand:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-sm);
}
/* on the accent-strong floor the teal ring would vanish into the fill */
.floor .btn:focus-visible, .floor a:focus-visible { outline: 2px solid #fff; outline-offset: 3px; border-radius: var(--r-sm); }
/* quiet secondary demo link — deliberately not a button */
.quiet-link {
  display: inline-block; font-family: var(--font); font-weight: 600; font-size: 15.5px;
  color: var(--accent); border-bottom: 1.5px solid rgba(15,116,144,.28); padding-bottom: 1px;
  transition: border-color .15s ease;
}
.quiet-link:hover { border-color: var(--accent); }

/* ---- header: brand left, links spread through the middle, actions right ---- */
header {
  position: sticky; top: 0; z-index: 40;
  backdrop-filter: saturate(1.4) blur(8px);
  background: rgba(243,247,249,.82);
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease;
}
header.scrolled { border-color: var(--border); }
.nav { display: flex; align-items: center; height: 72px; gap: 20px; }
.brand { display: flex; align-items: center; gap: 10px; font-family: var(--font); font-weight: 700; font-size: 18px; letter-spacing: -0.01em; flex-shrink: 0; }
.brand .mark { width: 28px; height: 28px; flex-shrink: 0; }
.nav-center { display: flex; align-items: center; justify-content: center; gap: clamp(28px, 5vw, 72px); flex: 1; min-width: 0; }
.nav-center a.link { font-size: 15px; font-weight: 500; color: var(--ink-2); transition: color .15s ease; }
.nav-center a.link:hover { color: var(--accent); }
.nav-right { display: flex; align-items: center; gap: 16px; flex-shrink: 0; }
/* the header CTA carries both labels; the narrow one is revealed below */
.cta-short { display: none; }
/* Pricing is a destination, not a section anchor, so it is weighted past the
   nav-center links and given the accent. aria-current on the pricing page
   itself draws the underline rather than a second colour. */
.nav-pricing {
  font-size: 15px; font-weight: 600; color: var(--accent);
  padding-bottom: 2px; border-bottom: 1.5px solid transparent;
  transition: border-color .15s ease, color .15s ease;
}
.nav-pricing:hover { border-color: var(--accent); }
.nav-pricing[aria-current="page"] { color: var(--accent-strong); border-color: var(--accent-strong); }
.nav-pricing:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-sm); }

/* ---- language toggle ---- */
.lang {
  display: inline-flex; align-items: center; gap: 1px;
  padding: 3px; border: 1px solid var(--border); border-radius: 999px; background: #fff;
}
.lang-btn {
  font-family: var(--font); font-weight: 600; font-size: 12.5px; letter-spacing: .02em;
  color: var(--ink-3); background: transparent; border: none; border-radius: 999px;
  padding: 5px 11px; cursor: pointer; line-height: 1;
  transition: color .15s ease, background .15s ease;
}
.lang-btn:hover { color: var(--ink); }
.lang-btn.on { color: #fff; background: var(--accent); }
.lang-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- hero (copy centered on the agar) ---- */
.hero { padding: 150px 0 96px; position: relative; z-index: 1; }

/* ==================================================================
   THE DISH — a top-down petri dish whose rim arcs over the hero copy.

   .dish is a TRUE circle (square box + border-radius:50%) far wider than
   the viewport, so only its top cap is ever on screen and it crops harder
   as the viewport narrows. NOT an SVG with preserveAspectRatio="none":
   that stretches the circle into an ellipse and puts uneven weight on the
   rim stroke. The rim is two rings — a weighted border plus a concentric
   hairline lip (::before) — which must stay exactly parallel.

   Colonies are positioned in % OF THE DISH, not the viewport, so they sit
   inside the rim at every width: a disc is inside when it is at radius
   < 50% from the dish's centre. They live in the gutters either side of
   the copy, so under 1180px they are cropped out (responsive block below).
   ================================================================== */
.hero-dish {
  position: absolute; left: 0; right: 0; top: 0;
  /* grown with the page: at --maxw 1600 and a ~1250px frame the old
     460-700px plate read small against the block it is supposed to arc
     over, and the rim landed mid-headline instead of above it */
  height: clamp(500px, 66vh, 780px);
  z-index: 0; overflow: hidden; pointer-events: none;
  /* long dissolve into the paper: the agar has no hard bottom seam */
  mask-image: linear-gradient(to bottom, #000 0%, #000 54%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 54%, transparent 100%);
}
/* scroll-sink wrapper: ONE layer. The colonies are on the plate, so they
   must never parallax against the rim — the whole dish moves as one rigid
   object or nothing does. Script drives it; see data-par in the markup. */
.dish-par { will-change: transform; }
.dish {
  position: absolute; top: 24px; left: 50%; transform: translateX(-50%);
  width: clamp(760px, 165vw, 2800px); aspect-ratio: 1; border-radius: 50%;
  background: rgba(15,116,144,.045);          /* agar */
  border: 3px solid rgba(15,116,144,.22);     /* rim */
}
.dish::before {                               /* the lip, concentric */
  content: ""; position: absolute; inset: 13px;
  border-radius: 50%; border: 1.2px solid rgba(15,116,144,.17);
}
/* --colony/--colony-ring so the same disc works on the agar and on the
   accent panel in the floor */
.col {
  position: absolute; aspect-ratio: 1; border-radius: 50%;
  background: var(--colony, rgba(15,116,144,.26));
}
.col::after {
  content: ""; position: absolute; inset: -38%;
  border-radius: 50%; border: 1px solid var(--colony-ring, rgba(15,116,144,.16));
}
/* Transform only: `backwards` holds scale(.86), never opacity 0, so if this
   animation never runs the plate is still whole. Killed under reduced motion. */
.hero-dish .col {
  animation: colonyRead .9s cubic-bezier(.16,.84,.3,1) backwards;
  animation-delay: calc(var(--i, 0) * 55ms + 120ms);
}
@keyframes colonyRead { from { transform: scale(.86); } }

.hero-copy { text-align: center; }
.hero h1 {
  font-size: clamp(27px, 3.4vw, 48px); font-weight: 600; letter-spacing: -0.03em;
  text-wrap: balance; max-width: 900px; margin: 0 auto;
}
/* The Spanish headline is the same sentence at 97 characters against the
   English 71, so it needs its own step or it wraps to 3 lines on every
   desktop width (and 5 on a phone). Measured: this holds it to 2 lines from
   768px up. Widening the measure instead is not an option — past ~1000px the
   headline's top corners would collide with the dish rim arcing over it. */
html[lang="es"] .hero h1 { font-size: clamp(24px, 2.5vw, 36px); }
.hero .sub {
  font-size: clamp(16px, 1.6vw, 19px); color: var(--ink-2);
  margin: 22px auto 0; max-width: 640px; text-wrap: pretty;
}

/* ---- the screenshot figure: hairline rim, flat header row naming the panel ---- */
.frame {
  border-radius: var(--r); background: #fff; border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-sm); overflow: hidden;
}
.frame .bar {
  display: flex; align-items: center; padding: 9px 14px;
  background: var(--paper-2); border-bottom: 1px solid var(--border);
}
.frame .bar .u {
  /* --ink-2, not --ink-3: at 12.5px on --paper-2 the lighter grey measured
     4.34:1, under the 4.5 AA floor for body-size text */
  font-family: var(--font); font-size: 12.5px; font-weight: 600; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}


/* ---- widows ----
   A last line holding one word reads as a mistake on a page this sparse, and a
   measurement across ten widths in both languages found 55 of them. text-wrap
   lets the browser pick the breaks instead of taking the first that fits:
   `balance` evens out a short block and suits headings, `pretty` optimises the
   final lines and suits running text. Chrome 117+, Firefox 137+, Safari 17.5+;
   anything older ignores the property and wraps the way it did before, so this
   costs nothing where it is not understood.

   Applied to the text elements by name rather than to `p` wholesale, because
   the fake chat bubbles and spreadsheet cells in the alternative section are
   paragraphs too and are meant to break like the UI they imitate. */
   Headings get `balance` and running text gets `pretty`: a heading is short
   enough that evening every line out is the better trade, while a paragraph
   only needs its ending fixed. Neither is sufficient on its own, so site.js
   also glues the last two words of these blocks with a non-breaking space. */
h1, h2, h3 { text-wrap: balance; }
.sub, .lede, .cta-note, .mods-note, .costline, .close-alt,
.setup-note p, .mobile-sec p, .sw-mdesc, .sw-item .d, .xcap, .plan-note {
  text-wrap: pretty;
}

/* ---- section scaffolding ---- */
section { padding: 92px 0; position: relative; }

/* ---- section header ---- */
.sec-head { max-width: 820px; }
.sec-head h2 { font-size: clamp(26px, 3.2vw, 36px); letter-spacing: -0.03em; text-wrap: balance; }
/* balance, not pretty: these run two or three lines, which is the range where
   evening the lines out beats only fixing the last one */
.sec-head .sub { margin: 16px 0 0; font-size: 17px; line-height: 1.6; color: var(--ink-2); max-width: 640px; text-wrap: balance; }


/* staggered scroll-reveal for the section children (gated by reduced-motion below) */
.stagger .reveal { transition-delay: calc(var(--i, 0) * 80ms); }

/* ---- the floor ----
   CTA and footer share one accent-strong ending (landing-variants §6 Alt 1),
   so the page stops once. Deliberate hard colour break: a footer stepping
   onto its own darker floor. The dish that opens the page closes it as a
   watermark cropped by the top-right corner, quieter than the old island's
   (.16 vs .22) so the heavier exit stays calm. The paper margin above gives
   the floor its own presence instead of welding it to the mobile band. */
.floor {
  background: var(--accent-strong); color: #fff;
  margin-top: 96px; position: relative; overflow: hidden;
}
.floor .dish-wm {
  --colony: rgba(255,255,255,.16);
  --colony-ring: rgba(255,255,255,.12);
  position: absolute; right: -110px; top: -70px;
  width: 380px; aspect-ratio: 1; border-radius: 50%;
  border: 3px solid rgba(255,255,255,.16); pointer-events: none;
}
.floor .dish-wm::before {
  content: ""; position: absolute; inset: 14px;
  border-radius: 50%; border: 1.2px solid rgba(255,255,255,.12);
}
/* ---- the close ----
   NO MAILTO, deliberately: it assumes a configured desktop mail client, which
   a phone or webmail reader does not have, and a link that opens nothing is
   worse than a string you can take with you. Copy writes to the clipboard;
   the address stays selectable, so no-JS and clipboard-refused still work. */
.close-grid { padding-top: 76px; padding-bottom: 60px; text-align: center; }
.close-grid h2 { color: #fff; font-size: clamp(27px, 3.4vw, 40px); letter-spacing: -0.03em; text-wrap: balance; max-width: 760px; margin: 0 auto; }
.close-grid .lede { color: rgba(255,255,255,.86); font-size: 17px; line-height: 1.6; margin: 18px auto 0; max-width: 560px; }
/* One shell holds address and copy control, split by a hairline. As two
   separate shapes the button outranked the address it was there to serve. */
.mailbox {
  margin: 34px auto 0; display: inline-flex; align-items: stretch;
  max-width: 100%; overflow: hidden;
  border: 1px solid rgba(255,255,255,.30); border-radius: var(--r);
  background: rgba(255,255,255,.06);
  transition: border-color .18s ease;
}
.mailbox:hover { border-color: rgba(255,255,255,.46); }
.mail-addr {
  display: flex; align-items: center; min-height: 68px; padding: 0 24px;
  font-family: var(--font); font-weight: 600; color: #fff;
  font-size: clamp(21px, 3.1vw, 34px); letter-spacing: -0.025em; line-height: 1.15;
  /* selectable by hand: the copy button is the convenience, not the only door */
  user-select: all; -webkit-user-select: all;
}
.mail-copy {
  appearance: none; font-family: var(--font); font-weight: 600; font-size: 14px;
  display: inline-flex; align-items: center; gap: 8px; padding: 0 20px;
  border: 0; border-left: 1px solid rgba(255,255,255,.22);
  background: transparent; color: rgba(255,255,255,.86); cursor: pointer;
  transition: background .16s ease, color .16s ease;
}
.mail-copy:hover { background: rgba(255,255,255,.13); color: #fff; }
.mail-copy svg { width: 15px; height: 15px; flex-shrink: 0; }
.mail-copy svg path, .mail-copy svg rect { stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; fill: none; }
/* the confirmation replaces the label in place; reserving the row keeps the
   control from resizing under the cursor at the moment it is clicked */
.mail-copy .lbl { min-width: 4.6em; text-align: left; }
.mail-copy.done { background: #fff; color: var(--accent-strong); }
/* stacked on a phone, still one shell: the hairline moves from side to top */
@media (max-width: 560px) {
  .mailbox { flex-direction: column; }
  .mail-addr { min-height: 0; padding: 16px 18px; justify-content: center; font-size: 22px; }
  .mail-copy { border-left: 0; border-top: 1px solid rgba(255,255,255,.22); padding: 13px 18px; justify-content: center; }
  .mail-copy .lbl { min-width: 0; text-align: center; }
}
.close-alt { margin: 30px 0 0; font-size: 15px; color: rgba(255,255,255,.78); }
.close-alt a { color: #fff; border-bottom: 1.5px solid rgba(255,255,255,.4); padding-bottom: 1px; font-weight: 600; }
.close-alt a:hover { border-color: #fff; }

/* The CTA that used to live here is now the close above, so this is the
   link row only — one centred line rather than the old 1.2fr/.8fr split,
   which had nothing left in its wide column. */
.floor-grid { padding-bottom: 34px; }
.floor .btn-primary { background: #fff; color: var(--accent-strong); }
.floor .btn-primary:hover { background: var(--accent-soft); }
/* the nav links' permanent home; the header set collapses under 860px */
.floor-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px 32px; }
.floor-links a { font-size: 15px; font-weight: 500; color: rgba(255,255,255,.82); transition: color .15s ease; width: fit-content; }
.floor-links a:hover { color: #fff; }
/* hairline on the wrap, so it shares the content margins */
.floor-base { border-top: 1px solid rgba(255,255,255,.16); }
.floor-base .row { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; justify-content: space-between; padding: 26px 0 34px; }
.floor-base span { font-size: 14px; color: rgba(255,255,255,.78); }
/* The address again, quietly, for anyone who scrolled past the close or came
   back to the page looking for it. Selectable, still not a link. */
.floor-base .addr { color: #fff; font-weight: 600; user-select: all; -webkit-user-select: all; }

/* ---- reveal animation ----
   TRANSFORM ONLY, never opacity. Content is visible by default: the worst
   case (JS present, observer never fires, script throws) is a block that
   sits 14px low and fully readable, not an empty section. The .js gate
   means a no-JS render is untouched. */
.reveal { transition: transform .7s cubic-bezier(.2,.7,.2,1); }
.js .reveal { transform: translateY(14px); }
.reveal.in { transform: none; }
.hero .reveal { transition-delay: calc(var(--i, 0) * 90ms); }

/* the colonies live in the gutters either side of the hero copy; under
   1180px there are no gutters, so the plate crops to rim + agar rather
   than letting a disc drift behind the headline */
@media (max-width: 1179px) {
  .hero-dish .col { display: none; }
}

/* Below this the brand + links + language + action stop fitting on one row
   and the links wrap to two lines. Measured: EN breaks at 768, ES at 820
   (its action label is longer), so the links come out at 860 with headroom
   for both rather than at the old 720. */
@media (max-width: 860px) {
  .nav-center { display: none; }
  /* nav-center was the flex:1 element holding the two ends apart. With it
     hidden the whole bar packed to the left and left a dead gap on the right,
     which is most of why the phone header looked unbalanced. */
  .nav-right { margin-left: auto; }
}
/* the floor's two columns stack; the links become a wrapped row so the
   dark block stays short on phones (the named cost of the merged ending) */
@media (max-width: 899px) {
  .floor-links { gap: 12px 24px; }
  /* the "On mobile" section is not rendered at this width (see index.html), so
     the link to it would scroll to nothing. Matches the cross-page form too. */
  .floor-links a[href$="#mobile"] { display: none; }
}

@media (max-width: 720px) {
  .btn { font-size: 14px; padding: 11px 16px; }
  section { padding: 68px 0; }
  /* the rim still has to clear the headline when the dish crops in */
  .hero { padding: 104px 0 64px; }
  .floor { margin-top: 68px; }
  .close-grid { padding-top: 52px; padding-bottom: 40px; }
  .mailbox { margin-top: 26px; }
  .floor .dish-wm { right: -150px; width: 300px; }
  .floor-base .row { padding: 20px 0 28px; }
}
/* ---- the bar on a phone ----
   Four things have to fit in ~343px and previously all four fought: an
   orphaned 28px mark with no wordmark, a link, a bordered two-state pill, and
   a solid button — four different treatments in one short row, and no brand.

   The space comes from the language control. A pill showing EN *and* ES spends
   about 70px telling you the language you are already reading. On a phone it
   becomes one plain word naming the language you would switch TO, which is the
   only part that was ever actionable. That pays for the wordmark, so the bar
   opens with the brand again instead of a stray dot. */
@media (max-width: 560px) {
  /* INLINE ONLY. The shorthand resets all four sides, and .close-grid,
     .floor-grid and .floor-base are all .wrap with their own vertical
     padding — this rule sits later in the file, so `padding: 0 16px` silently
     zeroed the whole floor's rhythm and left the heading jammed against the
     colour break. Never use the shorthand on .wrap. */
  .wrap { padding-left: 16px; padding-right: 16px; }
  .nav { height: 62px; gap: 12px; }
  .brand { font-size: 16.5px; gap: 8px; }
  .brand .mark { width: 24px; height: 24px; }
  .nav-right { gap: 14px; }

  .lang { border: 0; background: none; padding: 0; }
  .lang-btn { font-size: 13px; padding: 6px 2px; color: var(--ink-3); }
  /* the language you are in is not a control; only the switch remains */
  .lang-btn.on { display: none; }
  .lang-btn:not(.on) { color: var(--accent); }

  .nav-pricing { font-size: 14px; }
  .cta-long { display: none; }
  .cta-short { display: inline; }
  .nav .btn-primary { padding: 9px 14px; font-size: 13px; }
}
/* Below this the wordmark is what stops fitting, and the mark alone still
   carries the brand at the top-left. Everything else survives. */
@media (max-width: 374px) {
  .brand .wordmark { display: none; }
}
@media (max-width: 400px) {
  .btn { padding: 10px 13px; }
}


/* ---- reduced motion (shared half) ----
   The page-specific half — the switcher's transitions and pin — lives in
   index.html beside the rules it turns off. */
@media (prefers-reduced-motion: reduce) {
  .reveal { transform: none; transition: none; }
  .stagger .reveal { transition-delay: 0s !important; }
  /* the plate is composed, not animated: skip the read-out entirely */
  .hero-dish .col { animation: none; }
  /* script also skips the scroll sink under reduced motion; this
     neutralizes any inline transform left behind by a live mq flip */
  .dish-par { transform: none !important; }
  html { scroll-behavior: auto; }
}
