:root{
  /* Tinted blue rather than near-white, so the white cards read as objects
     sitting on the page instead of dissolving into it. */
  --bg:#EFF4FD;
  --surface:#FFFFFF;
  --ink:#16161D;
  --muted:#6B7085;
  /* Hairlines instead of the old 2px black outlines. At twelve cards a page the
     heavy borders and hard offset shadows stacked into a very loud grid. */
  --line:#E9E9F0;
  --line-strong:#DCDCE6;
  --blue:#4C6FFF;
  --blue-soft:#EEF2FF;
  --pink:#FF4D8D;
  --pink-soft:#FFE6F0;
  --r:12px;
  --r-sm:8px;
  --r-pill:999px;
  --shadow-sm:0 1px 2px rgba(22,22,45,.05),0 1px 3px rgba(22,22,45,.04);
  --shadow-md:0 4px 14px rgba(22,22,45,.07),0 1px 3px rgba(22,22,45,.05);
  --shadow-lg:0 16px 40px rgba(22,22,45,.14),0 3px 10px rgba(22,22,45,.07);
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{
  background:var(--bg);color:var(--ink);
  font-family:"DM Sans",system-ui,sans-serif;
  font-size:16px;line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
a{color:inherit}

/* ---------- top bar ---------- */
.topbar{
  position:sticky;top:0;z-index:50;
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:12px 24px;color:var(--ink);
  /* Translucent rather than a solid blue slab: the bar stays legible while the
     grid scrolls under it, and it stops competing with the cards for attention. */
  background:rgba(239,244,253,.82);backdrop-filter:blur(12px);
  border-bottom:1px solid var(--line);
}
.logo{
  display:flex;align-items:center;gap:10px;
  font-family:"Space Grotesk",sans-serif;font-weight:700;
  font-size:20px;letter-spacing:-.01em;text-decoration:none;
}
.logo-mark{
  /* The 512 source rather than the 32: this renders at 44 device pixels on a
     retina screen, and the small file's bars come out visibly soft. */
  width:22px;height:22px;border-radius:6px;display:block;
}
.topbar-right{display:flex;align-items:center;gap:10px}
#search{
  font:inherit;font-size:14.5px;padding:8px 14px;width:min(340px,45vw);color:var(--ink);
  background:var(--surface);border:1px solid var(--line-strong);
  border-radius:var(--r-pill);box-shadow:var(--shadow-sm);
  transition:border-color .15s,box-shadow .15s;
}
#search::placeholder{color:#A2A5B8}
#search:focus{
  outline:none;border-color:var(--blue);
  box-shadow:0 0 0 3px rgba(76,111,255,.14);
}
.gh{
  font-weight:600;font-size:14px;text-decoration:none;white-space:nowrap;color:var(--ink);
  padding:8px 14px;background:var(--surface);border:1px solid var(--line-strong);
  border-radius:var(--r-pill);box-shadow:var(--shadow-sm);
  transition:border-color .15s,box-shadow .15s;
}
.gh:hover{border-color:#C4C4D2;box-shadow:var(--shadow-md)}

/* ---------- intro ---------- */
main{max-width:1400px;margin:0 auto;padding:0 24px 64px}
.intro{
  /* No row gap: the headline's row and the one below it are spaced by the copy's
     and the form's own top margins, which is what keeps the two aligned. */
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:0 32px;
  align-items:start;padding:56px 0 40px;
}
.intro h1{
  font-family:"Space Grotesk",sans-serif;font-weight:700;
  font-size:clamp(34px,4.6vw,56px);line-height:1.14;letter-spacing:-.025em;
}
/* An underline band rather than a padded block, so the highlight can never
   overlap the line beneath it however tight the leading is. */
.intro h1 em{font-style:normal;background:linear-gradient(transparent 62%,#FF8DA1 62%)}
.intro p{max-width:60ch;margin-top:22px;font-size:17px;color:#3A3A52}
.intro .meta{font-size:15px;color:var(--muted)}
/* The form sits beside the sub-headline instead of under the stats line, where it
   read as an afterthought and pushed the grid below the fold. Spanning the whole
   grid, so the form's right edge lands on the same line the chips end at rather
   than stopping where the text column does. Wrapping rather than a media query:
   the two fall into one column exactly when they stop fitting, which depends on
   the copy's length as much as on the viewport.
   stretch rather than flex-start so the copy and the form end up the same height:
   the shorter of the two is what gets stretched, and each distributes the slack
   internally so its last line lands on the block's bottom edge. That is what puts
   the button's baseline on the stats line's, whichever side happens to be taller
   at a given width. */
.intro-lead{grid-column:1/-1;display:flex;flex-wrap:wrap;align-items:stretch;gap:4px 40px}
/* Grows into whatever the form leaves, which is what pins the form to the right
   edge. 400px is the narrowest the prose stays comfortable at 17px, and it sets
   where the row wraps. The paragraph inside stays at its own max-width, so the
   growth shows up as space before the form rather than as a longer line.
   A column with space-between so that when the form is the taller of the two, the
   extra height opens up between the sub-headline and the stats line and the stats
   line sinks to the bottom, instead of leaving dead space under it. The 22px top
   margins stay as the minimum; space-between only ever adds to them. */
.intro-copy{flex:1 1 400px;min-width:0;display:flex;flex-direction:column;justify-content:space-between}
/* Sets the field's width, and with it where the row falls under the copy. The
   field only needs to hold an address, so it stops well short of the copy rather
   than stretching to meet it. */
.intro-lead .waitlist{flex:0 1 300px}
/* Matches the sub-headline's own margin-top, so the field and the first line of
   copy start on the same baseline when they sit side by side.
   A column only so the row below can take the whole of the stretched height; the
   honeypot is positioned out of flow and is not a second item here. */
.waitlist{margin-top:22px;display:flex;flex-direction:column}
/* flex-end pins the row's right edge to the line the chips above end on. It only
   has an effect once the row wraps on a narrow screen; at full width the field
   grows to fill, so there is no free space to distribute.
   align-content is the vertical counterpart, and the reason the button can reach
   the bottom edge: the field, the note and the button each take a line of their
   own, and any height the copy has over them is shared out between those lines.
   The 10px row gap stays as the minimum, so a narrow screen — where the form is
   alone on its line and there is no slack — spaces them exactly as before. */
.waitlist-row{display:flex;flex:1;gap:10px;flex-wrap:wrap;align-content:space-between;align-items:center;justify-content:flex-end}
#waitlist-email{
  /* A full-width basis, so the field takes the whole first line and the button
     always falls below it. The row's flex-wrap does the break, which keeps the
     two on separate lines without a second container. */
  font:inherit;font-size:15px;padding:11px 16px;flex:1 1 100%;min-width:0;color:var(--ink);
  background:var(--surface);border:1px solid var(--line-strong);
  border-radius:var(--r-pill);box-shadow:var(--shadow-sm);
  transition:border-color .15s,box-shadow .15s;
}
#waitlist-email::placeholder{color:#A2A5B8}
#waitlist-email:focus{
  outline:none;border-color:var(--blue);
  box-shadow:0 0 0 3px rgba(76,111,255,.14);
}
/* Only after a failed submit, so the field is not red while it is still being
   typed into. */
.waitlist.invalid #waitlist-email{border-color:#D92D584D;box-shadow:0 0 0 3px rgba(217,45,88,.12)}
#waitlist-submit{
  font:inherit;font-weight:600;font-size:15px;cursor:pointer;white-space:nowrap;
  padding:11px 20px;color:#fff;background:var(--ink);
  border:1px solid var(--ink);border-radius:var(--r-pill);box-shadow:var(--shadow-sm);
  transition:transform .15s,box-shadow .15s,opacity .15s;
}
#waitlist-submit:hover{transform:translateY(-1px);box-shadow:var(--shadow-md)}
#waitlist-submit:active{transform:none;transition-duration:.06s}
#waitlist-submit:disabled{cursor:default;opacity:.55;transform:none;box-shadow:var(--shadow-sm)}
/* Moved out of view rather than display:none, which the better bots check for
   and skip. The field stays laid out and fillable, it is just nowhere a person
   can see it. */
.waitlist-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
/* Two classes deep on purpose. This is a <p> inside .intro, so the .intro p rule
   would otherwise win on specificity and set it in 17px prose — which is how it
   once ended up larger than the stats line beside it. Two more of that rule's
   declarations have to be overridden here: its 22px top margin, which would stack
   on top of the row's gap, and its 60ch measure cap, which at 13px is narrower
   than the block and would wrap the note early inside a box the wrong width.
   The top margin lands on -2px rather than 0 because the row's gap applies to
   both sides of the note and this side wants 2px less — it reads as a caption on
   the field above it, not as something sitting between two controls.
   A full-width basis keeps it on its own line between the field and the button.
   Without it the note is only as wide as its text, and the short states —
   "Adding you…" — would fit beside the button and sit on the same line. */
.waitlist .waitlist-note{flex:1 1 100%;max-width:none;margin:-2px 0 0;font-size:13px;color:var(--muted);text-align:right}
.waitlist.ok .waitlist-note{color:#0F766E;font-weight:500}
.waitlist.invalid .waitlist-note,.waitlist.error .waitlist-note{color:#C2185B}
.intro-deco{display:grid;grid-template-columns:repeat(3,30px);gap:10px;padding-top:14px}
.chip{width:30px;height:30px;border-radius:9px;box-shadow:var(--shadow-sm)}
.c1{background:var(--pink)}
/* Not --blue-soft: on the tinted blue page it is the same colour as the
   background and the chip disappears. */
.c2{background:#C3D4FF}
.c3{background:var(--pink-soft)}
.c4{background:var(--blue)}
.c5{background:#B388FF}
.c6{background:#5BD1F0}

/* ---------- filters ---------- */
/* One flat wrap of sixteen pills read as noise. The categories already carry a
   "group" field, so each group gets its own labelled row and the eye has
   somewhere to rest between them. */
.filters{display:flex;flex-direction:column;gap:14px;padding:4px 0 24px}
.filter-group{display:flex;align-items:flex-start;gap:16px}
.filter-label{
  /* Wide enough that the longest group name ("Cloud & DevOps") stays on one line. */
  flex:none;width:124px;padding-top:7px;
  font-size:11px;font-weight:600;letter-spacing:.08em;
  text-transform:uppercase;color:#9A9DB0;
}
.filter-row{display:flex;flex-wrap:wrap;gap:8px;flex:1;min-width:0}
/* Anchors rather than buttons: each category is a real page, so a crawler can
   reach the whole taxonomy and a filtered view is a shareable URL. The script
   still intercepts the click on the index and filters without navigating. */
.filters a{
  font:inherit;font-weight:500;font-size:13.5px;cursor:pointer;text-decoration:none;
  display:flex;align-items:center;gap:7px;
  padding:6px 13px;background:var(--surface);color:var(--ink);
  border:1px solid var(--line-strong);border-radius:var(--r-pill);
  transition:background .15s,border-color .15s,color .15s;
}
.filters a:hover{background:#F4F4F8;border-color:#C9C9D8}
.filters a[aria-current=page]{background:var(--ink);color:#fff;border-color:var(--ink)}
.filters .swatch{width:9px;height:9px;border-radius:50%;flex:none}
.filters .n{opacity:.5;font-size:12.5px;font-variant-numeric:tabular-nums}
.count{font-size:14px;color:var(--muted);padding-bottom:18px}

/* ---------- grid ---------- */
/* Fixed 4 columns so a page is always a tidy 4x3 block of 12. */
.grid{
  display:grid;gap:24px;
  grid-template-columns:repeat(4,minmax(0,1fr));
}
.card{
  position:relative;
  display:flex;flex-direction:column;cursor:pointer;overflow:hidden;
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r);box-shadow:var(--shadow-sm);
  transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease;
}
.card:hover{transform:translateY(-3px);box-shadow:var(--shadow-lg);border-color:var(--line-strong)}
/* Presses in under the cursor, so the click that opens the modal is acknowledged
   before the modal itself arrives. */
.card:active{transform:translateY(-1px) scale(.994);box-shadow:var(--shadow-md);transition-duration:.06s}
/* Covers the whole card so it is a real link on the category pages, which ship no
   script. On the index the click handler intercepts it and opens the modal
   instead — except on a modified click, which then opens the product page in a
   new tab, which is what a middle-click on a card should do anyway. */
.card-link{position:absolute;inset:0;z-index:1}
.card-foot a{position:relative;z-index:2}
.shot{
  position:relative;aspect-ratio:16/10;overflow:hidden;
  border-bottom:1px solid var(--line);background:var(--blue-soft);
}
.shot img{width:100%;height:100%;object-fit:cover;object-position:top center;display:block}
.badge{
  font-size:10.5px;font-weight:600;letter-spacing:.02em;
  padding:3px 9px;border-radius:var(--r-pill);
  /* Category names run long; shrink and ellipsise rather than push the row wide. */
  min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.versions{
  position:absolute;top:10px;right:10px;
  background:rgba(255,255,255,.9);backdrop-filter:blur(6px);color:var(--muted);
  font-size:11px;font-weight:600;padding:3px 9px;
  border-radius:var(--r-pill);box-shadow:var(--shadow-sm);
}
.body{padding:16px 18px 18px;display:flex;flex-direction:column;gap:9px;flex:1}
.name-row{display:flex;align-items:center;justify-content:space-between;gap:10px}
.name{
  font-family:"Space Grotesk",sans-serif;font-weight:600;font-size:13px;
  letter-spacing:.06em;text-transform:uppercase;color:var(--muted);flex:none;
  text-decoration:none;
}
.name:hover{color:var(--ink)}
.headline{
  font-family:"Space Grotesk",sans-serif;font-weight:700;
  font-size:19px;line-height:1.22;letter-spacing:-.015em;
}
.sub{font-size:14px;color:#5A5A74;line-height:1.5}
.card-foot{
  margin-top:auto;padding-top:12px;display:flex;justify-content:space-between;gap:10px;
  font-size:12.5px;color:var(--muted);border-top:1px solid var(--line);
}
.card-foot a{font-weight:600;text-decoration:none;color:var(--blue)}
.card-foot a:hover{text-decoration:underline}
.empty{padding:48px 0;font-size:17px;color:var(--muted)}

/* ---------- pager ---------- */
.pager{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:center;padding:36px 0 0}
.pager button{
  font:inherit;font-weight:600;font-size:14px;cursor:pointer;
  min-width:38px;padding:7px 12px;background:var(--surface);color:var(--ink);
  border:1px solid var(--line-strong);border-radius:var(--r-sm);
  transition:background .15s,border-color .15s;
}
.pager button:hover:not(:disabled){background:#F4F4F8;border-color:#C9C9D8}
.pager button[aria-current=page]{background:var(--ink);color:#fff;border-color:var(--ink)}
.pager button:disabled{opacity:.4;cursor:default}

/* ---------- modal ---------- */
.modal{
  position:fixed;inset:0;z-index:100;display:grid;place-items:center;
  padding:24px;background:rgba(22,22,45,.4);backdrop-filter:blur(4px);overflow:auto;
  opacity:0;transition:opacity .14s ease-in;
}
/* display:grid outranks the hidden attribute's own display:none. */
.modal[hidden]{display:none}
.modal.open{opacity:1;transition:opacity .2s ease-out}
.modal-card{
  position:relative;width:min(880px,100%);max-height:90vh;overflow:auto;
  background:var(--surface);border:1px solid var(--line);
  border-radius:16px;box-shadow:var(--shadow-lg);padding:32px;
  /* Resting state doubles as the exit state: short, ease-in, no overshoot, so
     dismissing feels immediate rather than like the entrance played backwards. */
  /* Mostly scale, barely any travel: the card should look like it pops toward you,
     not like it slides up from below. */
  transform:translateY(6px) scale(.92);opacity:0;
  transition:transform .14s ease-in,opacity .14s ease-in;
}
.modal.open .modal-card{
  transform:none;opacity:1;
  /* Back-out curve — shoots past full size and springs back. Short duration and a
     faster opacity ramp so the card is already visible while it is still expanding;
     fading in over the whole motion is what made it read as a gentle ease. */
  transition:transform .28s cubic-bezier(.34,1.56,.64,1),opacity .12s ease-out;
}
@media (prefers-reduced-motion:reduce){
  .modal,.modal.open,.modal-card,.modal.open .modal-card{transition:none}
}
.modal-close{
  position:absolute;top:16px;right:16px;cursor:pointer;font:inherit;font-weight:600;
  width:32px;height:32px;color:var(--muted);
  background:var(--surface);border:1px solid var(--line-strong);border-radius:50%;
  transition:background .15s,color .15s;
}
.modal-close:hover{background:#F4F4F8;color:var(--ink)}
.modal h2{font-family:"Space Grotesk",sans-serif;font-size:28px;letter-spacing:-.02em}
.modal .url{font-size:14px;color:var(--muted);word-break:break-all}
.version{margin-top:28px;padding-top:24px;border-top:1px solid var(--line)}
.version:first-of-type{border-top:none}
.version-date{
  display:inline-block;font-size:11.5px;font-weight:600;
  letter-spacing:.04em;padding:3px 10px;margin-bottom:12px;
  border-radius:var(--r-pill);
}
.version h3{font-family:"Space Grotesk",sans-serif;font-size:22px;line-height:1.24;margin-bottom:8px}
.version p{font-size:15px;color:#5A5A74;max-width:62ch}
.version img{
  width:100%;margin-top:16px;display:block;
  border:1px solid var(--line);border-radius:var(--r-sm);box-shadow:var(--shadow-md);
}
.current{font-size:12px;font-weight:600;color:var(--pink);margin-left:8px}

footer{
  border-top:1px solid var(--line);background:var(--surface);
  padding:26px 24px;font-size:14px;color:var(--muted);
}
footer p{max-width:1400px;margin:0 auto}

/* ---------- category & product pages ---------- */
.crumb{padding:20px 0 0;font-size:13.5px;color:var(--muted)}
.crumb a{color:var(--muted);text-decoration:none}
.crumb a:hover{color:var(--ink);text-decoration:underline}
.crumb span{opacity:.5;margin:0 2px}
.page-head{padding:14px 0 26px;max-width:70ch}
.page-head h1{
  font-family:"Space Grotesk",sans-serif;font-weight:700;
  font-size:clamp(30px,3.4vw,42px);line-height:1.14;letter-spacing:-.025em;margin-top:12px;
}
.page-head .lede{font-size:17px;color:#5A5A74;line-height:1.55;margin-top:12px}
.page-head .lede a{color:var(--blue);text-decoration:none;word-break:break-all}
.page-head .lede a:hover{text-decoration:underline}
.page-head .meta{font-size:13.5px;color:var(--muted);margin-top:10px}
.badge-lg{display:inline-block;font-size:12px;padding:5px 12px;border-radius:var(--r-pill);font-weight:600}
.detail{padding-bottom:12px}
/* The version list is the page's substance, so it gets a card rather than the
   bare rules used inside the modal. */
.detail .version{
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  box-shadow:var(--shadow-sm);padding:26px;margin-bottom:20px;
}
.detail .version h2{
  font-family:"Space Grotesk",sans-serif;font-weight:700;
  font-size:clamp(22px,2.4vw,30px);line-height:1.2;letter-spacing:-.02em;margin:12px 0 8px;
}
.detail .version p{font-size:16px;color:#5A5A74;line-height:1.55;max-width:62ch}
.detail .version img{
  width:100%;height:auto;margin-top:20px;display:block;
  border:1px solid var(--line);border-radius:var(--r-sm);box-shadow:var(--shadow-md);
}
.siblings{padding:10px 0 40px;border-top:1px solid var(--line)}
.siblings h2{font-family:"Space Grotesk",sans-serif;font-size:15px;margin:22px 0 14px;color:var(--muted)}
.siblings ul{list-style:none;display:flex;flex-wrap:wrap;gap:8px}
.siblings a{
  display:block;padding:6px 13px;font-size:13.5px;text-decoration:none;color:var(--ink);
  background:var(--surface);border:1px solid var(--line-strong);border-radius:var(--r-pill);
}
.siblings a:hover{background:#F4F4F8;border-color:#C9C9D8}

@media (max-width:1180px){.grid{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (max-width:900px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:720px){
  .intro{grid-template-columns:1fr}
  /* One column stacks in source order, which would drop the chips between the
     headline and the copy. They belong after both, as they did before the form
     pushed them up the markup. The extra padding stands in for the row gap the
     two-column layout does not need. */
  .intro-deco{grid-template-columns:repeat(6,26px);order:1;padding-top:34px}
  .topbar{flex-wrap:wrap}
  /* The label gutter costs too much of a narrow screen: stack instead. */
  .filter-group{flex-direction:column;gap:8px}
  .filter-label{width:auto;padding-top:0}
}
@media (max-width:620px){.grid{grid-template-columns:1fr}}
