/* captable.fund
   Monochrome, paper-first. One accent-free palette; emphasis comes from
   weight, scale and hairlines rather than colour. */

:root {
  --paper:        #FBFAF8;
  --paper-raised: #FFFFFF;
  --paper-sunk:   #F4F2EE;
  --ink:          #15161A;
  --ink-2:        #4A4C54;
  --ink-3:        #86888F;
  --ink-4:        #B9B8B4;
  --rule:         #E2DFD8;
  --rule-strong:  #CFCBC2;
  --node:         #15161A;
  --node-line:    rgba(21, 22, 26, 0.20);

  /* the one colour on the site — `.fund`. --fund-strong is the deeper value
     used by the small proportion of particles that flare during the burst. */
  --fund:         #2754F5;
  --fund-strong:  #0B2FB0;
  --shadow:       0 1px 2px rgba(20, 20, 25, 0.04), 0 8px 24px rgba(20, 20, 25, 0.05);

  --font-sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
               "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, "JetBrains Mono",
               "Roboto Mono", Menlo, Consolas, monospace;

  /* Display type gets the wider measure; body copy sits just inside it, so
     the paragraphs never look like they are overrunning the sentence above
     them. Large type wraps coarsely — a single word is 80–150px — so the two
     ragged edges can never match exactly, and the body should be the tighter
     of the two. */
  --measure: 660px;
  --measure-body: 600px;
  --pad: clamp(1.25rem, 5vw, 4.5rem);
  --max: 1240px;

  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper:        #0D0E10;
    --paper-raised: #141518;
    --paper-sunk:   #101114;
    --ink:          #EDECE8;
    --ink-2:        #A2A29E;
    --ink-3:        #6C6D71;
    --ink-4:        #45464A;
    --rule:         #23252A;
    --rule-strong:  #33363C;
    --node:         #EDECE8;
    --node-line:    rgba(237, 236, 232, 0.20);
    /* the same hue lifted for the dark ground, where #2754F5 goes murky */
    --fund:         #6B87FF;
    --fund-strong:  #AFC0FF;
    --shadow:       0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);
  }
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* keeps anchored sections clear of the fixed nav */
  scroll-padding-top: 5rem;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  line-height: 1.6;
  letter-spacing: -0.006em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

a { color: inherit; }

::selection { background: var(--ink); color: var(--paper); }

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

.label {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* A label that needs to read as a heading rather than a caption. */
.label--strong { font-weight: 700; }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.75rem 1rem;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.75rem;
}
.skip:focus { left: 0; }

:where(a, button):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ---------------------------------------------------------------- nav --- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.15rem var(--pad);
  transition: background 0.3s ease, border-color 0.3s ease, backdrop-filter 0.3s ease;
  border-bottom: 1px solid transparent;
}

.nav[data-stuck='true'] {
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  border-bottom-color: var(--rule);
}

.nav__mark {
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  text-decoration: none;
  opacity: 0;
  transform: translateY(-2px);
  transition: opacity 0.35s ease, transform 0.35s ease;
  pointer-events: none;
}

.nav[data-stuck='true'] .nav__mark {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.nav__links {
  display: flex;
  gap: clamp(1rem, 3vw, 2rem);
  margin-left: auto;
}

.nav__link {
  position: relative;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  color: var(--ink-2);
  text-decoration: none;
  padding-block: 0.25rem;
  transition: color 0.2s ease;
}

.nav__link::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.nav__link:hover { color: var(--ink); }
.nav__link:hover::after { transform: scaleX(1); }

/* --------------------------------------------------------------- hero --- */

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto;
  overflow: clip;
}

.hero__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* the visual sits behind the wordmark and never eats a click meant for it */
  z-index: 0;
}

.hero__inner {
  position: relative;
  z-index: 1;
  grid-row: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: 8rem 3rem;
  pointer-events: none;
}

.hero__wordmark {
  position: relative;
  margin: 0;
  font-size: clamp(2.6rem, 9.5vw, 7rem);
  font-weight: 500;
  line-height: 0.94;
  letter-spacing: -0.045em;
  max-width: 14ch;
}

/* `.fund` is handed over to a canvas by wordmark.js. The text stays in the
   DOM — selectable, readable by screen readers, and fully visible if the
   script never runs — but goes transparent once the canvas takes over.
   --wm-ink survives that, so the canvas can still read its own colour. */
.dot { color: var(--fund); }

/* The dot between the two halves of the wordmark. It sits outside the .dot
   span, so wordmark.js leaves it alone — it stays still while `fund` moves. */
.wordmark__sep { color: var(--ink-3); }

/* The `l` of Captable is already a vertical bar, so it is segmented into four
   stacked holdings: the word carries a cap table without a single letterform
   being deformed — the stem keeps its width, its height and its rhythm. A
   hard-stop gradient clipped to the glyph, descending from full ink at the
   base to the faintest step at the top. Below roughly 34px the bands fuse and
   it simply reads as an `l`, which is the intended failure.

   The stops are not evenly spread over 0–100%: an inline element's background
   box runs from the font's ascent to its descent, and the `l` only inks the
   band from the baseline to its ascender — measured at 18%–77.6% of the box
   in this stack. Dividing the box instead of the glyph puts the bands in
   empty space, so these values are the four holdings remapped onto the ink.
   They are tied to the font's metrics: if the face is ever pinned to a
   webfont rather than the OS UI face, re-measure them. */
.wordmark__col {
  background-image: linear-gradient(
    to top,
    var(--ink) 0 38.3%,
    transparent 38.3% 39.8%,
    var(--ink-2) 39.8% 53.8%,
    transparent 53.8% 55.3%,
    var(--ink-3) 55.3% 66.3%,
    transparent 66.3% 67.8%,
    var(--ink-4) 67.8% 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero__wordmark .dot {
  --wm-ink: var(--fund);
  --wm-hot: var(--fund-strong);
}


.wm-live .hero__wordmark .dot { color: transparent; }

.wm-canvas {
  position: absolute;
  pointer-events: none;
  z-index: 1;
}

/* Reads as a single line at every width: the size is capped for large
   screens and falls back to a share of the viewport on small ones, rather
   than wrapping. */
.hero__tagline {
  margin: clamp(1.25rem, 2.5vw, 1.75rem) 0 0;
  font-size: min(1.1875rem, 3.7vw);
  line-height: 1.45;
  color: var(--ink-2);
  letter-spacing: -0.012em;
  white-space: nowrap;
}

.hero__foot {
  position: relative;
  z-index: 1;
  grid-row: 2;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 1.5rem 1.75rem;
  border-top: 1px solid var(--rule);
  pointer-events: none;
}

.hero__hint { pointer-events: none; }

/* On narrow screens the wordmark moves to the top third so the visual gets
   the lower half to itself instead of running underneath the type. */
@media (max-width: 860px) {
  .hero__inner {
    justify-content: flex-start;
    padding-block: 7.5rem 2rem;
  }
}

@media (max-width: 640px) {
  .hero__foot .hero__hint { display: none; }
}

/* -------------------------------------------------------------- intro --- */

.intro {
  padding-block: clamp(5rem, 12vw, 9rem);
}

/* No `text-wrap: balance` here on purpose: balancing evens the lines out but
   pulls them well short of the measure, which left the headline visibly
   narrower than the paragraphs beneath it. Filling the same measure as the
   body keeps both blocks on one edge whatever the copy says. */
.intro__lead {
  margin: 0;
  max-width: var(--measure);
  font-size: clamp(1.5rem, 3.4vw, 2.375rem);
  font-weight: 400;
  line-height: 1.22;
  letter-spacing: -0.03em;
  text-wrap: pretty;
}

.intro__body {
  max-width: var(--measure-body);
  margin-top: clamp(1.75rem, 4vw, 2.5rem);
}

.intro__body p {
  margin: 0 0 1.1em;
  color: var(--ink-2);
  text-wrap: pretty;
}

.intro__body p:last-child { margin-bottom: 0; }

/* ---------------------------------------------------------- portfolio --- */

.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem 2rem;
  padding-bottom: 1.5rem;
}

.section-head h2 {
  margin: 0;
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  font-weight: 500;
  letter-spacing: -0.03em;
}

.section-head p {
  margin: 0;
  color: var(--ink-3);
  font-size: 0.9375rem;
}

/* Hairlines come from each card's own 1px ring rather than a background
   showing through the gaps — so a short final row leaves clean paper
   instead of a stray filled cell. */
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  padding: 1px;
}

@media (max-width: 980px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .grid { grid-template-columns: 1fr; } }

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  min-height: 16.5rem;
  padding: clamp(1.25rem, 2.4vw, 1.75rem);
  background: var(--paper);
  box-shadow: 0 0 0 1px var(--rule);
  text-decoration: none;
  isolation: isolate;
  transition: background 0.35s ease;
}

.card::before {
  /* hairline that draws itself across the top on hover */
  content: '';
  position: absolute;
  inset: -1px -1px auto -1px;
  height: 2px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.45s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.card:hover,
.card:focus-visible { background: var(--paper-raised); }

.card:hover::before,
.card:focus-visible::before { transform: scaleX(1); }

.card__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.card__cat {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.card__year {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  color: var(--ink-3);
}

/* Fixed height with the overflow clipped: a logo file that carries padding
   around its mark is drawn oversized (--logo-h) so the mark itself reaches a
   consistent size, and the surrounding transparent margin is simply cut off. */
.card__logo {
  display: flex;
  align-items: center;
  height: 3.5rem;
  margin-top: auto;
  overflow: hidden;
}

.card__logo img {
  max-width: min(200px, 78%);
  max-height: var(--logo-h, 3.25rem);
  width: auto;
  height: auto;
  object-fit: contain;
  object-position: left center;
  flex: none;
  /* Pulls the image left by however much empty canvas sits to the left of
     its mark, so every logo in the grid starts on the same edge. The logo
     box clips what that pushes out. */
  margin-left: calc(-1 * var(--logo-x, 0px));
}

/* Logos are recoloured to the page's ink so fifteen sets of brand colours
   don't fight each other. How that is done depends on the artwork, set per
   company with `logoMode` in content.js:

     (default)   single-tone art on a transparent background — flatten it
     light-tile  art baked onto a light background
     dark-tile   art baked onto a dark background
     knockout    monochrome art with detail knocked out of it — never flatten,
                 or the knockout fills in
     keepColor   leave the artwork alone

   The tile modes lean on two filter chains and a blend mode. `--logo-crush`
   drives everything dark to black and leaves white alone; `--logo-lift` is
   the same curve inverted. Paired with multiply on a light page (white
   disappears) or screen on a dark one (black disappears), a baked-in
   background drops out and only the mark survives. */
.card__logo img {
  /* Both chains land the same way: mark near-black, background near-white,
     which multiply then drops onto a light page. Appending invert(1) flips
     that for the dark theme, where screen drops the black instead. The
     contrast is what actually separates a mid-luminance mark from its tile —
     a gentler curve leaves marks like FleetFox's orange stranded in grey.
     --logo-b moves where that split falls, for artwork with more than two
     tones — Qminder's icon is a light mark on a dark tile on white, and only
     a raised pivot puts the tile in ink and drops both the others out. */
  --logo-on-light: grayscale(1) brightness(var(--logo-b, 0.85)) contrast(6);
  --logo-on-dark: grayscale(1) invert(1) brightness(var(--logo-b, 1)) contrast(6);
  filter: brightness(0);
}

.card__logo img[data-logo='light-tile'] { filter: var(--logo-on-light); }
.card__logo img[data-logo='dark-tile'] { filter: var(--logo-on-dark); }
.card__logo img[data-logo='knockout'] { filter: none; }
.card__logo img[data-keep-color] { filter: none; }

/* The blend sits on the box, not on the image inside it. Both properties on
   one element is what mobile Safari drops: a filtered element is rendered
   into its own isolated group, and the blend against the card behind it is
   then skipped — the baked-in tile survives as a white square. Splitting them
   over two elements gives the compositor an unfiltered box to blend and a
   plain filtered image inside it, which every engine handles. The wrapper is
   transparent wherever the logo isn't, and multiply leaves a transparent
   source alone, so only the tile is affected. */
.card__logo[data-logo='light-tile'],
.card__logo[data-logo='dark-tile'] { mix-blend-mode: multiply; }

.card__logo[data-keep-color] { mix-blend-mode: normal; }

@media (prefers-color-scheme: dark) {
  .card__logo img { filter: brightness(0) invert(1); }
  .card__logo img[data-logo='light-tile'] { filter: var(--logo-on-light) invert(1); }
  .card__logo img[data-logo='dark-tile'] { filter: var(--logo-on-dark) invert(1); }
  .card__logo img[data-logo='knockout'] { filter: invert(1); }
  .card__logo img[data-keep-color] { filter: none; }

  .card__logo[data-logo='light-tile'],
  .card__logo[data-logo='dark-tile'] { mix-blend-mode: screen; }

  .card__logo[data-keep-color] { mix-blend-mode: normal; }
}

/* Stands in for a logo until one is supplied: the name set as a logotype. */
.card__wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: clamp(1.25rem, 1.5vw, 1.4375rem);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.032em;
  color: var(--ink);
  text-wrap: balance;
}

.card__wordmark .card__arrow { width: 0.62em; height: 0.62em; }

.card__body { display: flex; flex-direction: column; gap: 0.4rem; }

.card__name {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1.0625rem;
  font-weight: 500;
  letter-spacing: -0.02em;
}

.card__arrow {
  width: 0.7em;
  height: 0.7em;
  flex: none;
  opacity: 0;
  transform: translate(-3px, 3px);
  transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.card:hover .card__arrow,
.card:focus-visible .card__arrow { opacity: 0.55; transform: none; }

.card__desc {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--ink-2);
  text-wrap: pretty;
  /* The card's contents are anchored to its bottom, so a one-line
     description would otherwise push its logo lower than the two-line cards
     beside it. Reserving two lines keeps every logo in a row on one line. */
  min-height: 3em;
}

/* ------------------------------------------------------------- footer --- */

.footer {
  margin-top: clamp(5rem, 12vw, 9rem);
  border-top: 1px solid var(--rule);
  background: var(--paper-sunk);
}

.footer__inner {
  display: grid;
  gap: clamp(2.5rem, 6vw, 4rem);
  grid-template-columns: 1fr;
  padding-block: clamp(3.5rem, 9vw, 6rem) 2rem;
}

@media (min-width: 860px) {
  .footer__inner { grid-template-columns: 1.7fr 1fr; align-items: start; }
}

/* Fine print, deliberately: small, quiet, and set to a comfortable measure
   rather than run across the full column. */
/* Fine print, deliberately: small, quiet, and held to a measure that suits
   14px type — roughly 60 characters. It is NOT set to the intro's measure;
   at this size 660px would run to ~85 characters a line, well past
   comfortable. `balance` rather than `pretty` because the block is only four
   lines: browsers balance up to around six, and evening them out stops it
   ending on a stub. */
.footer__disclaimer {
  margin: 0.9rem 0 0;
  max-width: 56ch;
  font-size: 0.875rem;
  line-height: 1.65;
  color: var(--ink-3);
  text-wrap: balance;
}

.footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  margin-top: 0.75rem;
  padding: 0;
  list-style: none;
}

.footer__links a {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  color: var(--ink-2);
  text-decoration: none;
  transition: color 0.2s ease;
}

.footer__links a:hover { color: var(--ink); }

.footer__base {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  padding-block: 1.5rem;
  border-top: 1px solid var(--rule);
}

/* ------------------------------------------------------------ reveals --- */

[data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.7s cubic-bezier(0.2, 0.7, 0.2, 1),
              transform 0.7s cubic-bezier(0.2, 0.7, 0.2, 1);
  transition-delay: var(--reveal-delay, 0ms);
}

[data-reveal='in'] { opacity: 1; transform: none; }

/* No JS: nothing should ever stay invisible. */
.no-js [data-reveal] { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1 !important; transform: none !important; transition: none; }
  * { animation-duration: 0.001ms !important; }
}

/* Hero text stays selectable; everything else in the hero lets pointer
   events fall through to the canvas underneath. */
.hero__wordmark,
.hero__tagline,
.hero__foot .label { pointer-events: auto; }

.hero__canvas[data-hit='true'] { cursor: pointer; }

@media (max-width: 620px) {
  .card { min-height: 13.5rem; }
}
