/* The homepage stylesheet. Lifted out of web/index.html on 2026-08-26 and NOT an
   optimisation for its own sake: inline, it pushed <head> to 28KB and put the <h1> at byte
   30,814, so an agent that reads only the first chunk of the document saw a head and no
   content at all. An agent-readiness audit reported the page as having no H1 for exactly
   that reason. Keep it external. The page already loads /theme-light.css and /demo/demo.css
   the same way, so this costs no extra round trip on the same connection. */
  @font-face{font-family:"Fraunces";src:url("/fonts/Fraunces.woff2") format("woff2");font-weight:100 900;font-style:normal;font-display:swap}
  @font-face{font-family:"JetBrains Mono";src:url("/fonts/JetBrainsMono.woff2") format("woff2");font-weight:100 900;font-style:normal;font-display:swap}
  :root{
    --bg:#0a0c10; --panel:#13181f; --panel-2:#171d26; --line:#222a35;
    --ink:#e8ecf2; --muted:#98a3b3; --mint:#39aaaa; --amber:#aa3939; --red:#ff6b6b;
    --radius:14px; --max:1080px;
    --ease:cubic-bezier(.16,1,.3,1);
    --serif:"Fraunces","Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
    --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
    --mono:"JetBrains Mono",ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  }
  .btn[aria-disabled="true"] {
    pointer-events: none;
    opacity: 0.6;
    cursor: not-allowed;
  }
  *{box-sizing:border-box}
  html{scroll-behavior:smooth}
  body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);line-height:1.6;-webkit-font-smoothing:antialiased}
  a{color:inherit}
  .wrap{max-width:var(--max);margin:0 auto;padding:0 24px}
  .mint{color:var(--mint)} .amber{color:var(--amber)} .muted{color:var(--muted)}
  .mono{font-family:var(--mono)}

  /* nav */
  nav{position:sticky;top:0;z-index:20;backdrop-filter:blur(10px);background:rgba(10,12,16,.7);border-bottom:1px solid var(--line)}
  nav .wrap{display:flex;align-items:center;justify-content:space-between;height:64px}
  .brand{display:flex;align-items:center;gap:10px;font-weight:700;letter-spacing:-.01em}
  .brand .dot{width:11px;height:11px;border-radius:3px;background:var(--mint);box-shadow:0 0 12px var(--mint)}
  .brand small{color:var(--muted);font-weight:500}
  .nav-links{display:flex;gap:26px;align-items:center}
  .nav-links a{color:var(--muted);text-decoration:none;font-size:15px}
  .nav-links a:hover{color:var(--ink)}
  @media(max-width:760px){.nav-links a:not(.btn){display:none}}
  /* THE CTA MUST NEVER BE THE THING THAT GETS SQUEEZED OUT, and at 390px it was: "Start free" sat
     half off the right edge while the brand wrapped onto two lines. `.brand` is a flex item and so
     defaults to `min-width:auto`, which refuses to shrink below its own content no matter what the
     container wants. Same trap that has now bitten this project four times.
     The parent-company attribution is the part that gives way, because it is the widest and the
     least load-bearing here: it is still in the footer (`.ft-by`) and still in the Organization
     JSON-LD, so nothing about the InferHaven relationship is lost by dropping it on a phone.
     Mirrored in the inline nav styles in `web/solo.html`, which carries its own copy of this block. */
  @media(max-width:560px){
    nav .wrap{gap:10px}
    .brand{min-width:0;flex:0 1 auto;white-space:nowrap;overflow:hidden}
    /* The mark is a flex item too, so `min-width:0` on the parent let it shrink to nothing and the
       logo vanished at 360px while the wordmark stayed. Only the TEXT may give way. */
    .brand svg{flex:0 0 auto}
    .brand small{display:none}
    .nav-links{flex:0 0 auto;gap:14px}
  }
  /* Below this the BETA pill is what stops fitting, and `overflow:hidden` on .brand was slicing it
     down the middle, which reads as a broken element rather than a tight one. It survives at 390
     (the common phone width), so the cut-off is here and not in the block above. */
  @media(max-width:380px){
    .brand .beta{display:none}
    /* Buy the last ~25px from the CTA's own padding and the page gutter rather than from the
       wordmark, which `overflow:hidden` was otherwise clipping to "CodeTra" at 320. */
    nav .wrap{padding:0 16px}
    .nav-links .btn{padding:9px 13px;font-size:14px}
  }
  .beta{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--mint);border:1px solid var(--mint);border-radius:999px;padding:1px 7px;vertical-align:middle}

  /* beta banner */
  .beta-bar{background:var(--panel-2);border-bottom:1px solid var(--line);font-size:13.5px;color:var(--muted)}
  .beta-bar .wrap{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:9px 24px}
  .beta-bar a{color:var(--mint)}
  .beta-x{background:none;border:none;color:var(--muted);cursor:pointer;font-size:14px;line-height:1;padding:4px;flex:0 0 auto}
  .beta-x:hover{color:var(--ink)}

  .btn{display:inline-block;border-radius:10px;padding:11px 18px;font-weight:600;text-decoration:none;font-size:15px;border:1px solid transparent;cursor:pointer;transition:transform .16s var(--ease),filter .16s,box-shadow .16s,border-color .16s,color .16s}
  .btn-primary{background:var(--mint);color:#04181a;border-color:var(--mint);font-weight:700;box-shadow:0 6px 16px -9px rgba(57,170,170,.8)}
  .btn-primary:hover{filter:brightness(1.06);transform:translateY(-1px);box-shadow:0 12px 26px -12px rgba(57,170,170,.9)}
  /* background is load-bearing on <button>: without it a ghost button falls back to the UA's
     light `buttonface` and renders near-white text on near-white. Anchors were always fine. */
  .btn-ghost{background:transparent;border-color:var(--line);color:var(--ink)}
  .btn-ghost:hover{border-color:var(--mint);transform:translateY(-1px)}

  /* hero */
  header.hero{position:relative;padding:84px 0 52px;border-bottom:1px solid;border-image:linear-gradient(90deg,transparent,rgba(57,170,170,.28),transparent) 1}
  header.hero::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;background:radial-gradient(ellipse 70% 55% at 50% 8%,rgba(57,170,170,.10),transparent 68%)}
  .rain{position:fixed;inset:0;width:100%;height:100%;z-index:-1;pointer-events:none;mix-blend-mode:screen}
  header.hero>.wrap{position:relative;z-index:1}
  .eyebrow{font-family:var(--mono);font-size:13px;color:var(--mint);letter-spacing:.08em;text-transform:uppercase}
  .eyebrow::before{content:"> ";opacity:.75}
  h1{font-family:var(--serif);font-weight:600;font-size:clamp(36px,6.2vw,64px);line-height:1.04;letter-spacing:-.02em;margin:16px 0 0}
  .sub{font-size:clamp(17px,2.2vw,20px);color:var(--muted);max-width:620px;margin:20px 0 0}
  .cta-row{display:flex;gap:14px;flex-wrap:wrap;margin-top:30px}
  .micro{margin-top:14px;font-size:14px;color:var(--muted)}
  /* The two front doors. This was one sentence of `.micro` under the CTAs, so it was the fourth
     thing on a page whose H1 had already told an individual it was not for them. Quieter than the
     CTA row on purpose: choosing an audience is not the primary action, trying the lesson is.

     BUILT FROM `.card`'S RECIPE, NOT FROM SCRATCH. The first version was, and it diverged on six
     counts at once: flat `--panel-2` instead of the gradient, a grey `--line` border instead of the
     teal-tinted one, a 10px radius instead of `--radius`, half the padding, a weaker hover, and no
     entry in theme-light.css so it stayed grey while every sibling card went white. It did not read
     as cramped so much as foreign, because nothing else on the page is made this way.

     THE ONE DELIBERATE DEPARTURE IS SCALE: 16px/18px of padding against `.card`'s 24 and `.tier`'s
     26, and smaller type. These sit directly under the primary CTAs and must stay lighter than them,
     so this is a smaller sibling on purpose and not an unfinished one. Do not "fix" the padding up
     to 24. The hover glow, by contrast, is `.card:hover`'s exact value: being on brand means using
     the house number rather than a lookalike that drifts from it.

     `min-width:0` on the flex item for the reason it is needed everywhere else in this project,
     which is that the default `min-width:auto` refuses to shrink below the content. */
  .doors{display:flex;gap:14px;flex-wrap:wrap;margin-top:26px;max-width:620px}
  .door{flex:1 1 240px;min-width:0;display:flex;flex-direction:column;gap:5px;padding:16px 18px;background:linear-gradient(180deg,rgba(23,29,38,.72),rgba(19,24,31,.55));border:1px solid rgba(57,170,170,.14);border-radius:var(--radius);text-decoration:none;color:var(--ink);transition:transform .18s var(--ease),border-color .18s,box-shadow .18s}
  .door:hover{transform:translateY(-2px);border-color:rgba(57,170,170,.34);box-shadow:0 16px 36px -22px rgba(57,170,170,.45)}
  .door:focus-visible{outline:2px solid var(--mint);outline-offset:3px}
  .door strong{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:15px;font-weight:600}
  .door span{font-size:13.5px;color:var(--muted);line-height:1.45}
  /* The affordance, in CSS rather than in the markup so the arrow never enters the copy and so the
     em-dash lint and the markdown twins never see it. */
  .door strong::after{content:"→";color:var(--mint);opacity:.5;transition:transform .18s var(--ease),opacity .18s}
  .door:hover strong::after{opacity:1;transform:translateX(3px)}

  /* demo video showcase */
  .demo{position:relative;max-width:940px;margin:48px auto 0;background:linear-gradient(180deg,var(--panel),var(--bg));border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;box-shadow:0 40px 90px -50px rgba(0,0,0,.85)}
  .demo-bar{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--line);background:var(--panel-2)}
  .demo-bar i{width:11px;height:11px;border-radius:50%;display:inline-block}
  .demo-bar .t{margin-left:10px;font-family:var(--mono);font-size:13px;color:var(--muted)}
  .demo-stage{position:relative;aspect-ratio:16/9;background:#05070a}
  .demo-teaser,.demo-full{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;border:0}
  .demo-full{display:none}
  .demo.playing .demo-teaser{display:none}
  .demo.playing .demo-full{display:block}
  .demo.playing .demo-play{display:none}
  .demo-play{position:absolute;inset:0;display:flex;align-items:flex-end;justify-content:flex-start;gap:14px;padding:18px 20px;border:0;cursor:pointer;text-align:left;font-family:var(--mono);color:var(--ink);background:linear-gradient(0deg,rgba(5,7,10,.66) 0%,rgba(5,7,10,.24) 26%,transparent 52%);transition:background .2s}
  .demo-play:hover{background:linear-gradient(0deg,rgba(5,7,10,.76) 0%,rgba(5,7,10,.34) 30%,transparent 58%)}
  .demo-play-icon{flex:0 0 auto;display:grid;place-items:center;width:54px;height:54px;border-radius:50%;background:var(--mint);color:#04181a;font-size:20px;padding-left:3px;box-shadow:0 10px 30px -8px rgba(57,170,170,.75);transition:transform .2s}
  .demo-play:hover .demo-play-icon{transform:scale(1.06)}
  .demo-play-meta{display:flex;flex-direction:column;justify-content:center;gap:2px;min-height:54px}
  .demo-play-label{font-size:14px;letter-spacing:.02em;text-shadow:0 1px 10px rgba(0,0,0,.75)}
  .demo-play-time{font-size:12px;color:var(--muted);text-shadow:0 1px 10px rgba(0,0,0,.75)}
  .demo-play:focus-visible{outline:2px solid var(--mint);outline-offset:3px}

  /* counter strip */
  .strip{padding:34px 0;border-bottom:1px solid;border-image:linear-gradient(90deg,transparent,rgba(57,170,170,.26),transparent) 1;text-align:center}
  .strip p{font-family:var(--serif);font-size:clamp(20px,3vw,28px);margin:0;line-height:1.35}

  section{padding:74px 0;border-bottom:1px solid;border-image:linear-gradient(90deg,transparent,rgba(57,170,170,.20),transparent) 1}
  h2{font-family:var(--serif);font-weight:600;font-size:clamp(27px,4vw,40px);letter-spacing:-.02em;margin:0 0 14px}
  .lead{color:var(--muted);max-width:640px;font-size:18px;margin:0 0 38px}

  .grid{display:grid;gap:20px}
  .g3{grid-template-columns:repeat(3,1fr)} .g4{grid-template-columns:repeat(4,1fr)} .g2{grid-template-columns:repeat(2,1fr)}
  @media(max-width:860px){.g3,.g4{grid-template-columns:1fr 1fr}}
  @media(max-width:620px){.g3,.g4,.g2{grid-template-columns:1fr}}

  .card{background:linear-gradient(180deg,rgba(23,29,38,.72),rgba(19,24,31,.55));border:1px solid rgba(57,170,170,.14);border-radius:var(--radius);padding:24px;transition:transform .18s var(--ease),border-color .18s,box-shadow .18s}
  .card:hover{transform:translateY(-2px);border-color:rgba(57,170,170,.34);box-shadow:0 16px 36px -22px rgba(57,170,170,.45)}
  .card h3{margin:0 0 8px;font-size:19px;font-family:var(--serif);font-weight:600}
  .card p{margin:0;color:var(--muted);font-size:15.5px}
  .num{font-family:var(--mono);color:var(--amber);font-size:14px}

  .steps{counter-reset:s}
  .step{display:flex;gap:18px;padding:18px 0;border-top:1px solid rgba(57,170,170,.12)}
  .step:first-child{border-top:none}
  .step .n{counter-increment:s;flex:0 0 38px;height:38px;width:38px;border-radius:10px;background:var(--panel-2);border:1px solid rgba(57,170,170,.28);display:grid;place-items:center;font-family:var(--mono);color:var(--mint)}
  .step .n::before{content:counter(s)}
  .step h3{margin:4px 0 4px;font-size:18px;font-family:var(--serif);font-weight:600}
  .step p{margin:0;color:var(--muted)}

  /* pricing */
  .price-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;align-items:stretch}
  @media(max-width:980px){.price-grid{grid-template-columns:1fr 1fr}}
  @media(max-width:560px){.price-grid{grid-template-columns:1fr}}
  .tier{background:linear-gradient(180deg,rgba(23,29,38,.72),rgba(19,24,31,.55));border:1px solid rgba(57,170,170,.14);border-radius:var(--radius);padding:26px;display:flex;flex-direction:column;transition:transform .18s var(--ease),border-color .18s,box-shadow .18s}
  .tier:hover{transform:translateY(-2px);border-color:rgba(57,170,170,.32);box-shadow:0 16px 36px -22px rgba(57,170,170,.45)}
  .tier.feat{border-color:var(--mint);box-shadow:0 0 0 1px var(--mint) inset,0 20px 50px -30px rgba(57,170,170,.6)}
  .tier.feat:hover{box-shadow:0 0 0 1px var(--mint) inset,0 24px 56px -28px rgba(57,170,170,.72)}
  .tier .tag{font-family:var(--mono);font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted)}
  .tier.feat .tag{color:var(--mint)}
  .tier .amt{font-family:var(--serif);font-size:38px;margin:8px 0 2px}
  .tier .amt small{font-size:15px;color:var(--muted);font-family:var(--sans)}
  .tier ul{list-style:none;padding:0;margin:18px 0 22px;flex:1}
  /* NOT display:flex. It was, and then every nested <strong> or <a> became its own flex item with
     a 9px gap torn through the middle of the sentence ("The open-source skill , forever"). The
     tick is absolutely positioned instead, so a list item is ordinary inline text again. */
  .tier li{padding:7px 0 7px 21px;color:var(--muted);font-size:15px;position:relative}
  .tier li::before{content:"✓";color:var(--mint);font-weight:700;position:absolute;left:0;top:7px}
  .tier .btn{text-align:center}

  details{border:1px solid rgba(57,170,170,.14);border-radius:12px;padding:0 18px;margin-bottom:12px;background:linear-gradient(180deg,rgba(23,29,38,.6),rgba(19,24,31,.45));transition:border-color .18s}
  details:hover,details[open]{border-color:rgba(57,170,170,.3)}
  summary{cursor:pointer;padding:16px 0;font-weight:600;list-style:none}
  summary::-webkit-details-marker{display:none}
  details[open] summary{border-bottom:1px solid var(--line)}
  details p{color:var(--muted);padding:14px 0}

  .final{text-align:center;padding:84px 0}
  .final h2{margin-bottom:10px}
  /* footer rules live in web/footer.css, one copy for the whole site */
  /* Badge strip: two bands, one visual language.
     Both bands break out of .wrap deliberately. --max is 1080px, so .wrap gives 1032px usable,
     and band 1 at a 56px bar height needs about 1085px of badge BEFORE a single gap (PH 259 +
     Fazier 265 + Uneed 209 + SourceForge 80 + the Tiny Startups card ~272, which is inline HTML
     and does NOT scale with the img height rule). Inside the container the badges cannot be
     enlarged at all, which is exactly why the strip read as bunched up.
     56/38 rather than 60/48: at 1440 the wider pair totalled 1329 against 1272 usable and pushed
     Tiny Startups onto a second line, which is the thing this change exists to remove.
     .badge-bleed is the shared shell. Its 1440px cap is not cosmetic: see the ticker note below,
     where it is what keeps the two-copy loop seamless. */
  .badge-bleed{width:100%;max-width:1440px;margin:0 auto;padding:0 clamp(20px,4vw,56px)}
  footer .badges{margin-top:26px;padding-top:26px;border-top:1px solid var(--line)}
  footer .badges-label{display:block;font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);text-align:center;margin-bottom:18px}
  /* One row now, not two. Badge vendors still ship two incompatible shapes: wide wordmark bars
     (Product Hunt, Fazier, Uneed) and tall blocks (SourceForge's hexagon, Tiny Startups' card).
     The old fix was a 34px row stacked on a 96px row, a 2.8x jump that read as two unrelated
     widgets. Raising the bars to 60 and easing the hexagon to 92 closes that to 1.5x, and centre
     alignment with real gaps makes the two tall ones read as focal points instead of mistakes.
     Nothing is crushed, because nothing is forced to a single height. */
  footer .badges-row{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:clamp(18px,2.6vw,38px)}
  footer .badges img{display:block;height:56px;width:auto;opacity:.88;transition:opacity .15s,transform .15s}
  /* SourceForge alone. Below ~48px the hexagon's wordmark stops being readable (68 was small, 52
     was mush). 92 keeps it legible and sits level with the Tiny Startups card, which is ~88px from
     its own inline padding and is left at its natural size for that reason. */
  footer .badges a.sf img{height:92px}
  /* Tiny Startups is inline HTML, not an <img>, so it ignores the height rules above entirely.
     Its inline styles were converted from px to em so this one font-size drives the whole card
     (padding, gap, radius, all three text sizes and the star). Without this it stayed ~272px wide
     at every breakpoint and was the single element pushing the row onto a second line. */
  footer .badges a.ts{font-size:16px}
  footer .badges a.ts svg{width:3.5em;height:3.5em}
  footer .badges a:hover img{opacity:1;transform:translateY(-1px)}
  /* Step all three down together before the row is forced to wrap. flex-wrap is still the net. */
  @media(max-width:1180px){footer .badges img{height:42px}footer .badges a.sf img{height:68px}footer .badges a.ts{font-size:11px}footer .badges-row{gap:clamp(14px,1.8vw,20px)}}
  @media(max-width:760px){footer .badges img{height:40px}footer .badges a.sf img{height:62px}footer .badges a.ts{font-size:10px}}
  /* shine sweep + glow, brand teal — applies to all 3 tier-1 badges uniformly (drop-shadow, not
     box-shadow, since box-shadow gets clipped by the sweep's overflow:hidden; drop-shadow isn't) */
  footer .badges a{position:relative;overflow:hidden}
  footer .badges a::after{content:"";position:absolute;inset:0;background:linear-gradient(115deg,transparent 40%,rgba(57,170,170,.5) 48%,rgba(214,239,239,.9) 50%,rgba(57,170,170,.5) 52%,transparent 60%);transform:translateX(-140%);transition:transform .7s ease;pointer-events:none}
  footer .badges a:hover::after{transform:translateX(140%)}
  footer .badges a:hover{filter:drop-shadow(0 0 8px rgba(57,170,170,.6))}

  /* tier-2 badges: unproven/new directories. JS-driven ticker (badges-ticker.js) so it can
     follow the mouse. 2 copies only (1 real + 1 aria-hidden clone), because the JS measures real
     pixel widths and needs no CSS keyframe safety margin.
     INVARIANT: the visible width must stay narrower than ONE badge set, or the two-copy loop
     shows a gap at the wrap. At 16 badges and a 32px height one set measures roughly 2240px
     (about 2000px of badge plus 15 gaps at 16px). That is why .badge-bleed caps at 1440px rather
     than running edge to edge on the viewport: a true 100vw strip would break the loop on any
     display wider than ~2240px, and 2560px monitors are ordinary. 1440 still reads as full width
     on every normal laptop and is a third wider than the 1080px .wrap it replaced.
     Recompute this if badges are added or the height changes. */
  .badges-tier2{margin-top:24px}
  /* The vertical padding is load-bearing, not spacing. overflow:hidden is what makes the marquee
     a viewport, and it clips BOTH axes: without room above and below, the hover transform
     (translateY(-1px) scale(1.06)) pushed a 32px badge about 2px past the top edge and sheared the
     rounded borders flat. Most visible on the badges that carry their own outline. Keep this
     comfortably larger than whatever the hover transform can add. */
  .badges-ticker{overflow:hidden;width:100%;padding:6px 0}
  .badges-ticker-track{display:flex;gap:16px;will-change:transform}
  .badges-ticker-set{display:flex;align-items:center;gap:16px;flex:0 0 auto}
  .badges-ticker a{display:inline-flex;align-items:center}
  /* Tier 2 is deliberately quieter than tier 1. The row above it is real results (a Product
     Hunt feature, #1 of the day on Fazier, third on Uneed); this row is directory listings.
     They were the same 34px at the same opacity, so fifteen listings visually outweighed
     three wins and diluted them by association. Same links, same badges, less shouting. */
  .badges-ticker img{display:block;height:32px;width:auto;opacity:.55;filter:grayscale(1);transition:opacity .15s,transform .15s,filter .15s}
  .badges-ticker a:hover img,
  .badges-ticker a:focus-visible img{opacity:1;filter:grayscale(0);transform:translateY(-1px) scale(1.06)}
  /* Wider fade than before (24 -> 64) so a strip this long dissolves at both ends instead of
     being visibly chopped. */
  .badges-ticker{mask-image:linear-gradient(90deg,transparent,#000 64px,#000 calc(100% - 64px),transparent);-webkit-mask-image:linear-gradient(90deg,transparent,#000 64px,#000 calc(100% - 64px),transparent)}
  @media (prefers-reduced-motion:reduce){
    .badges-ticker-set[aria-hidden="true"]{display:none}
    .badges-ticker{overflow-x:auto}
  }

  /* logo animation: #27 (journey) morphs into #34 (proof) */
  .hero-top{display:flex;align-items:center;gap:46px}
  .hero-copy{flex:1;min-width:0}
  .hero-mark{flex:0 0 auto;line-height:0}
  @media(max-width:820px){.hero-top{flex-direction:column;align-items:flex-start;gap:26px}.hero-mark svg{width:132px;height:132px}}
  .btn-accent{background:var(--amber);color:#fff;border-color:var(--amber);font-weight:700;box-shadow:0 6px 16px -9px rgba(170,57,57,.85)}
  .btn-accent:hover{filter:brightness(1.1);transform:translateY(-1px);box-shadow:0 12px 26px -12px rgba(170,57,57,.9)}
  a.ih{color:inherit;text-decoration:none;border-bottom:1px solid rgba(57,170,170,.45)}
  a.ih:hover{color:var(--mint);border-color:var(--mint)}
  .anim .engine{animation:eng 4s cubic-bezier(.4,0,.6,1) infinite}
  @keyframes eng{0%{cy:15.5px;r:3.2px;opacity:1}12%{cy:15.5px;r:3.2px;opacity:1}46%{cy:23.5px;r:4.6px;opacity:1}60%{cy:23.5px;r:4.6px;opacity:1}72%{cy:23.5px;r:4.6px;opacity:0}100%{cy:15.5px;r:3.2px;opacity:0}}
  .anim .stamp{opacity:0;transform:scale(0);transform-box:fill-box;transform-origin:center;animation:stmp 4s ease-in-out infinite}
  @keyframes stmp{0%,44%{opacity:0;transform:scale(0)}46%{opacity:1;transform:scale(0)}64%{opacity:1;transform:scale(1.15)}72%{transform:scale(1)}88%{opacity:1;transform:scale(1)}95%{opacity:0;transform:scale(.9)}100%{opacity:0;transform:scale(0)}}
  @media (prefers-reduced-motion:reduce){.anim .engine{animation:none;opacity:0}.anim .stamp{animation:none;opacity:1;transform:none}}

  /* product hunt: launch banner + hero badge retired after PH day; footer badge stays */
  footer .badges .ph{display:inline-flex}


