:root {
  --color-bg: #FFFFFF;
  --color-text: #000000;
  --color-text-soft: #555555;   /* bio */
  --color-text-muted: #6B6B6B;  /* mono text on white */
  --color-callout-bg: #000000;
  --color-callout-text: #FFFFFF;
  --color-callout-muted: #B3B3B3; /* mono text on black */
  --font-heading: "Manrope", -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Fluid type ramp: interpolates 375px -> 1440px viewport, matching the
     live Squarespace site's computed sizes at both endpoints. */
  --fs-hero:     clamp(1.9087rem, 1.6102rem + 1.2737vw, 2.7565rem); /* 30.5 -> 44.1px */
  --fs-bio:      clamp(1.1779rem, 1.0784rem + 0.4246vw, 1.4605rem); /* 18.8 -> 23.4px */
  --fs-inquiry:  clamp(1.0561rem, 0.9898rem + 0.2830vw, 1.2445rem); /* 16.9 -> 19.9px */
  --fs-small: 0.8125rem;                                            /* 13px fixed */

  --gutter: 6vw; /* Squarespace mobile site gutter */
}

@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/manrope-500.woff2") format("woff2");
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-mono-400.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Google's Latin subset omits "→" (U+2192), used in the talks list. */
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-mono-400-arrow.woff2") format("woff2");
  unicode-range: U+2192;
}

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

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-heading);
  font-weight: 500;
  /* Matches the Squarespace site: grayscale antialiasing renders Manrope
     thinner on macOS than the default, especially white-on-black. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: opacity .15s ease;
}

a:hover,
a:focus-visible {
  opacity: .7;
}

/* Header */

header {
  padding: 26px var(--gutter);
}

.wordmark {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-inquiry);
  line-height: 1.4;
  text-decoration: none;
}

/* Hero */

.hero {
  padding: 71px var(--gutter) 56px; /* collage sits close under the headline */
}

.hero h1 {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-hero);
  line-height: 1.3;
  letter-spacing: 0.02em;
}

/* Image collage. Positions/crops replicate the live site's separate
   mobile and desktop grids; object-fit keeps them correct. The dancing
   buddy and the Vibes phone are transparent <video>s (HEVC for Safari,
   VP9 WebM elsewhere) and the horizon-spawn ring is an animated WebP;
   main.js plays them only when on screen and shows a still frame for
   reduced motion. The rest are plain <img>. */

.collage {
  position: relative;
  height: 188vw; /* 705px at 375 */
}

.collage img,
.collage video {
  position: absolute;
  object-fit: cover;
}

/* Mobile (Lauren, 2026-11-11): two loose groups, everything in vw so the
   arrangement scales as one picture. The Vibes phone and the buddy are the
   heroes, the buddy overlapping the phone's lower right as on desktop.
   Below them the Messenger logo sits over the chat bubbles, which are
   68vw wide so their text reads at about 12px on a 375px phone; the avatar
   stands in the empty corner under the bubbles with the HoloLens, the
   smallest piece, at its feet. Every box stays inside the viewport, so
   nothing can cause a sideways scroll. */
.collage-vibes            { left: 6vw;  top: 0;     width: 44vw; height: 93vw; }
.collage-buddy            { left: 26vw; top: 36vw;  width: 74vw; } /* square, see below */
.collage-messenger        { left: 7vw;  top: 107vw; width: 17vw; height: 17vw; }
.collage-chat             { left: 6vw;  top: 113vw; width: 68vw; height: 29.92vw; }
.collage-ring             { left: 54vw; top: 135vw; width: 40vw; height: 58vw; }
.collage-hololens         { left: 49vw; top: 172vw; width: 19vw; height: 12.8vw; }

/* Dancing buddy (replaced the Meta AI graphic): square slot, shown
   whole on its transparent background rather than cropped. */
.collage .collage-buddy {
  height: auto;
  aspect-ratio: 1;
  object-fit: contain;
}

/* Vibes phone (replaced the two-phone panel): the whole phone, never
   cropped. */
.collage .collage-vibes {
  object-fit: contain;
}

/* Show the whole Messenger logo, chat bubbles and HoloLens cutout rather
   than cropping their edges. */
.collage .collage-messenger,
.collage .collage-chat,
.collage .collage-hololens {
  object-fit: contain;
}

/* Bio */

.bio {
  padding: 110px var(--gutter) 85px; /* close under the collage, so the bio shows early in the scroll */
}

.bio p {
  margin: 0 0 max(26px, calc(39.6px - 0.945vw));
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-bio);
  line-height: 1.3664;
  letter-spacing: 0.02em;
  color: var(--color-text-soft);
}

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

/* Work — product name in the inquiry style, description in the
   on-white mono style. One column on mobile; see the desktop grid below. */

.work {
  padding: 0 var(--gutter) 110px; /* white space above the black callout */
}

.work-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  row-gap: 52px; /* two bio paragraph gaps */
}

/* Each company is its own group: a label, then a list of its products.
   The groups sit one row gap apart, so together they read as one grid. */
.work-group + .work-group {
  margin-top: 52px;
}

/* Company label: exactly the description's type. Up to 1024px it is a
   small heading over its products (18px of extra air above it, so 70px
   below the previous company, and 14.5px above its first product); in wider
   windows it moves into the left margin (see below the desktop grid).
   Only as wide as its text, so the hover arrow can step around it. */
.work-group {
  padding-top: 18px; /* padding, not a margin on the label: it must add to the gap between groups */
}

.work-co {
  width: fit-content;
  margin: 0 0 14.5px; /* label + gaps = 52px, one row of the grid */
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--color-text-muted);
}

.work-name {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-inquiry);
  line-height: 1.4;
}

/* Hover/focus without the springy version (touch, reduced motion, no
   JavaScript; see "Work links" under Motion for the other): an arrow fades
   in on the left and pushes the name right (after h1brd.com), and both
   turn grey. Each link gets its own arrow, so
   the "+" after Vibes is pushed along with it. The arrow is a CSS mask
   so it takes the link's color. */
.work-name a {
  position: relative;
  display: inline-block;
  text-decoration: none;
  transition: padding-left .3s ease, color .3s ease;
}

.work-name a::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 1em;
  height: 1em;
  transform: translateY(-50%);
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E") center / contain no-repeat;
  opacity: 0;
  transition: opacity .3s ease;
}

.work-name a:hover,
.work-name a:focus-visible {
  padding-left: 1.5em;
  color: var(--color-text-muted);
  opacity: 1; /* grey replaces the site-wide link fade */
}

.work-name a:hover::before,
.work-name a:focus-visible::before {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .work-name a,
  .work-name a::before {
    transition: none;
  }
}

.work-desc {
  margin: 4px 0 0;
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--fs-small);
  line-height: 1.5; /* tighter than the talks list (2.1) for multi-line descriptions */
  color: var(--color-text-muted);
}

/* Callout (black) — includes the talks list, matching the live site's
   layout. Manrope text is white; mono text is light grey. */

.callout {
  background: var(--color-callout-bg);
  color: var(--color-callout-text);
  padding: 42px var(--gutter) 119px;
  display: flex;
  flex-direction: column;
}

.callout-lede {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-hero);
  line-height: 1.3328;
}

.callout-inquiry {
  margin: 26px 0 0;
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-inquiry);
  line-height: 2.1;
}

.talks {
  margin: 162px 0 0;
}

/* Section label in the same style as the Work product names, in white. */
.talks-heading {
  margin: 0 0 8px;
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-inquiry);
  line-height: 1.4;
}

.talks-list {
  list-style: none;
  margin: 0;
  padding: 0;
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--fs-small);
  line-height: 2.1;
  color: var(--color-callout-muted);
}

/* Footer */

footer {
  padding: 100px var(--gutter) 110px;
  display: flex;
  justify-content: flex-end;
  gap: 1.5rem;
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--fs-small);
  color: var(--color-text-muted);
}

footer a {
  text-decoration: none;
}

footer a:hover,
footer a:focus-visible {
  text-decoration: underline;
  opacity: 1;
}

/* Desktop grid (Squarespace switches from its mobile grid at ~768px).
   Spacing uses vw so it scales proportionally like the fluid engine;
   values are exact at a 1440px viewport. */

@media (min-width: 768px) {
  :root {
    --gutter: 2vw;
    --text-measure: 43.2vw; /* sets the bio's right edge, 621px at 1440 */
  }

  .hero {
    /* Headline 100px below header at 1440. No bottom padding: the collage
       (below) is pulled up close under the headline. */
    padding: 6.94vw var(--gutter) 0 10vw;
  }

  .hero h1 {
    max-width: calc(var(--text-measure) * 1.3); /* 30% wider, 809px at 1440 */
  }

  .collage {
    height: 82.15vw; /* 1183px collage section at 1440 (vw, not aspect-ratio,
                        so scrollbar width doesn't shrink it) */
    /* The box has empty space above the phone (top 5.73%) and below the
       avatar's feet (lowest piece, ~89.3% down). The negative margins pull
       the headline and the bio into that space, so the visible gaps are
       set by the hero/bio padding alone. */
    margin-top: -1.5vw;
    margin-bottom: -8.1vw;
  }

  /* Keep the bio and Work text above the collage's (transparent) boxes. */
  .bio,
  .work {
    position: relative;
  }

  /* Desktop arrangement from Lauren's 2026-11-08 mockups (resting
     positions at the top of the page; the parallax below drifts them while
     scrolling). Each slot is sized to its asset's own aspect ratio, so
     contain/cover show it whole.
     At 1440: phone 706px tall, buddy 769px square (full-res 1300x1440 video). */
  .collage-vibes            { left: 15.49%; top: 5.73%;  width: 23.19%; height: 59.68%; }
  .collage-buddy            { left: 26.18%; top: 22.26%; width: 53.42%; height: 65.04%; }
  .collage-hololens         { left: 22.15%; top: 68.76%; width: 16.13%; height: 13.22%; }
  .collage-messenger        { left: 59.57%; top: 11.33%; width: 11.7%;  height: 14.25%; } /* 70% size, same bottom edge */
  .collage-chat             { left: 59.06%; top: 23.94%; width: 20.43%; height: 10.94%; }
  .collage-ring             { left: 51.39%; top: 57.14%; width: 32.13%; height: 39.12%; }

  .bio {
    /* Left edge on the wordmark line. The top leaves ~150px between the
       avatar's feet and the bio at 1440 (at the top of the page; the
       parallax planes shift it while scrolling), so the bio comes into view
       early. It is no longer matched to the Work list's 24vw bottom. */
    padding: 8.7vw var(--gutter) 6.6vw;
  }

  .bio p {
    /* Right edge stays on the hero line (10vw + text measure), ~55% across
       the Work grid. */
    max-width: calc(var(--text-measure) + 10vw - var(--gutter));
  }

  .work {
    padding: 0 var(--gutter) 10vw 10vw; /* 144px above the black callout at 1440 */
  }

  /* Column 1 starts on the hero's 10vw line; with a gutter-wide gap,
     column 2 starts at 50vw (the page's centre line) and ends at 89.1vw. */
  .work-list {
    grid-template-columns: 38vw 39.1vw;
    column-gap: var(--gutter);
  }

  .work-desc {
    max-width: 50%;
  }

  .callout {
    flex-direction: row;
    align-items: flex-start;
    padding: 7.64vw var(--gutter) 124.58vw; /* huge empty tail matches live site */
  }

  .callout-text {
    width: 36.19vw; /* 521px at 1440 (was 401px, +30%) */
    flex-shrink: 0;
  }

  .talks {
    margin-top: 7.92vw; /* 114px below the inquiry line at 1440 */
    /* Align with the hero and Work grid's 10vw line; the negative right margin keeps
       the list as wide as the column so items don't wrap. */
    margin-left: calc(10vw - var(--gutter));
    margin-right: calc(var(--gutter) - 10vw);
  }

  footer {
    padding: 7.85vw 4.3vw 11.1vw;
  }
}

/* Wide windows: the company label sits in the left margin, on the
   wordmark's line, level with its first product. The heading box has the
   product name's own font and line height, so the mono label inside it
   sits on the name's baseline. */
@media (min-width: 1024px) {
  .work-group {
    position: relative;
    padding-top: 0;
  }

  .work-co {
    position: absolute;
    top: 0;
    left: calc(var(--gutter) - 10vw);
    margin: 0;
    font-family: var(--font-heading);
    font-weight: 500;
    font-size: var(--fs-inquiry);
    line-height: 1.4;
    white-space: nowrap;
  }

  .work-co span {
    font-family: var(--font-mono);
    font-weight: 400;
    font-size: var(--fs-small);
  }
}

/* ---------------------------------------------------------------------
   Motion. Every effect is off when the visitor prefers reduced motion.
   --------------------------------------------------------------------- */

/* 1. Hero, driven by main.js. Every collage piece sits on a spring and on
   its own plane: it scrolls at its own speed, overshoots and settles, leans
   into the scroll, and (with a mouse) can be picked up and thrown; toss one
   off the screen and, after a beat, it bounces back into place. The
   HoloLens and the Messenger logo also float. The headline is the far
   background: it lags behind the page and fades out as the first pieces
   pass over it.

   The inline script in <head> adds .motion before first paint, so the
   pieces that main.js brings in start hidden. Without JavaScript, or for
   reduced motion, nothing is hidden and the hero is simply static. */
.motion .collage > * {
  opacity: 0;
  will-change: translate, rotate, scale;
}

@media (hover: hover) and (pointer: fine) {
  .motion .collage > * {
    cursor: grab;
    user-select: none;
    -webkit-user-drag: none;
  }

  .grabbing,
  .grabbing * {
    cursor: grabbing !important;
  }
}

/* A piece thrown off the right-hand side must not give the page a sideways
   scrollbar while it is away. */
html.motion {
  overflow-x: hidden;
}

/* Arrival: the pieces bounce in one by one (main.js); the text stays calm.
   The wordmark fades in, and the headline fades in with a small rise
   (main.js). main.js adds .ready once the fonts are in. */
.motion:not(.ready) .wordmark {
  opacity: 0;
}

.motion.ready .wordmark {
  animation: wordmark-in .9s ease backwards;
}

@keyframes wordmark-in {
  from { opacity: 0; }
}

.motion .hero h1 {
  opacity: 0;
  will-change: translate, opacity;
}

/* 1b. The black section's liquid top edge: an <svg> main.js adds inside
   the section, half above its top and half below. The edge rises as a
   dome, flattens as it comes up the window, and sloshes with the scroll
   speed. */
:root {
  --lip: 40px;
}

@media (min-width: 768px) {
  :root {
    --lip: 7vw;
  }
}

.callout {
  position: relative;
}

.callout-text {
  position: relative; /* above the edge */
  z-index: 1;
}

.lip {
  position: absolute;
  left: 0;
  top: calc(-1 * var(--lip));
  width: 100%;
  height: calc(2 * var(--lip));
  pointer-events: none;
}

.lip-bg  { fill: var(--color-bg); }
.lip-ink { fill: var(--color-callout-bg); }

/* 1c. Work links (mouse / trackpad and keyboard focus): under the cursor a
   name lifts, is pulled toward the cursor and leans with its speed, and a
   round arrow rides along the top of the name on a spring. main.js adds
   .tug and writes the custom properties; without it the plain hover in
   the Work section above stays. */
.tug .work-name a,
.tug .work-name a:hover,
.tug .work-name a:focus-visible {
  transition: none;
  padding-left: 0;
  color: var(--color-text); /* stays black instead of turning grey */
  opacity: 1;
  margin-right: calc(var(--lift, 0) * .45em); /* keeps the "+" after Vibes clear of the lifted name */
  translate: var(--tx, 0px) var(--ty, 0px);
  rotate: var(--rot, 0deg);
  scale: var(--sc, 1); /* lift; a click gives it a small squish */
}

.tug .work-name a::before {
  display: none; /* the round arrow replaces the inline one */
}

.tug-badge {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 900;
  width: 36px;
  height: 36px;
  margin: -18px 0 0 -18px;
  border-radius: 50%;
  background: var(--color-callout-bg);
  pointer-events: none;
  scale: 0;
  will-change: translate, scale, rotate;
}

.tug-badge::before {
  content: "";
  position: absolute;
  inset: 9px;
  background: var(--color-callout-text);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* 2. Text fades in on scroll. Each block of text starts transparent and a
   little low, and fades in and rises into place as the page scrolls it up
   from the bottom edge of the window. It is tied to the scroll position
   (not a timer), so it runs backwards when you scroll back up, like the
   headline's fade. Pure CSS (scroll-driven animations); see the fallback
   below for browsers without them.

   --in-from and --in-to are how far a block has travelled up from the
   bottom of the window when its fade starts and ends; --in-rise is how
   far below its final position it starts. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    :root {
      /* px, not vw, on mobile: the last lines of the page must be able to
         scroll far enough to finish fading in at every width. */
      --in-from: 30px;
      --in-to: 250px;
      --in-rise: 36px;
    }

    .bio p,
    .work-co,
    .work-list li,
    .callout-lede,
    .callout-inquiry,
    .talks-heading,
    .talks-list li {
      animation: text-fade-in linear both;
      animation-timeline: view();
      animation-range: cover var(--in-from) cover var(--in-to);
    }

    @media (min-width: 768px) {
      :root {
        --in-from: 4vw;  /* a 23vw-long fade, the same as the headline's */
        --in-to: 27vw;
        --in-rise: 3.5vw;
      }
    }
  }
}

@keyframes text-fade-in {
  from { opacity: 0; translate: 0 var(--in-rise, 0px); }
  to   { opacity: 1; translate: 0 0; }
}

/* Fallback for browsers without scroll-driven animations (Firefox today):
   the text fades up once, on a timer, the first time it comes into view.
   The hidden state only applies once the inline script in <head> has
   added .reveal-on, so without JS everything is simply visible.
   main.js adds .is-visible (and a small stagger delay). */
.reveal-on .reveal {
  opacity: 0;
  translate: 0 24px;
  transition:
    opacity .8s cubic-bezier(.2, .7, .2, 1),
    translate .8s cubic-bezier(.2, .7, .2, 1);
  transition-delay: var(--reveal-delay, 0ms);
}

.reveal-on .reveal.is-visible {
  opacity: 1;
  translate: 0 0;
}
