/* tobeyosborne.com, one page, quiet as possible. */

:root {
  --bg: #fbfaf8;
  --fg: #14181c;
  --muted: #6b7580;
  --rule: #e4e1db;
  --card: #f1eee8;
  --accent: #60dfff;
  /* Keyboard focus keeps its own colour. The accent is bright enough that a
     focus ring in it would be hard to see against the paper background. */
  --focus: #b8412f;
  /* Text sitting on top of a filled accent, in either scheme. */
  --on-accent: #10161b;

  /* Full page width, the left header rail, and the reading measure used for
     prose inside the wide right column. */
  --page: 75rem;
  --rail: 12rem;
  --gutter: 4rem;
  --measure: 36rem;
  --radius: 14px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14181c;
    --fg: #e8e6e1;
    --muted: #8e97a1;
    --rule: #262c33;
    --card: #1b2127;
    --accent: #60dfff;
    --focus: #e8735c;
  }
}

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

html {
  -webkit-text-size-adjust: 100%;
}

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
    Inter, Helvetica, Arial, sans-serif;
  font-size: 17px;
  line-height: 1.62;
  font-weight: 400;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img {
  max-width: 100%;
}

.page {
  max-width: var(--page);
  margin: 0 auto;
  padding: clamp(2.5rem, 7vw, 5rem) clamp(1.25rem, 5vw, 3rem) 4rem;
}

/* ---- Tab bar -------------------------------------------------------- */

/* Sticky, on the page background, with a hairline that only appears once
   there is content scrolled underneath it. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--bg);
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s ease;
}

.topbar.is-stuck {
  border-bottom-color: var(--rule);
}

.tabs {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0.9rem clamp(1.25rem, 5vw, 3rem);
  display: flex;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 0.25rem 1.5rem;
  font-size: 0.9375rem;
}

.tabs a {
  color: var(--muted);
  text-decoration: none;
  padding: 0.15rem 0;
  transition: color 0.15s ease;
}

.tabs a:hover,
.tabs a.is-current {
  color: var(--fg);
}

.tabs__out {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.tabs__out svg {
  transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
}

.tabs__out:hover {
  color: var(--accent);
}

.tabs__out:hover svg {
  transform: translate(1.5px, -1.5px);
}

/* Keep an anchored section clear of the sticky bar. */
[id] {
  scroll-margin-top: 4.5rem;
}

/* ---- Type ---------------------------------------------------------- */

h1 {
  font-size: clamp(1.5rem, 4vw, 2.125rem);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.1;
  margin: 0;
}

/* Section headers: small, quiet, pinned top left of each band. */
h2 {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 1.5rem;
}

h3 {
  font-size: 1.0625rem;
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.012em;
}

p {
  margin: 0 0 1.1rem;
}

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

.lede {
  color: var(--muted);
  margin: 0.5rem 0 0;
  font-size: 1.0625rem;
}

.prose {
  max-width: var(--measure);
}

/* Underline uses --muted, not --rule: --rule is tuned to disappear as a
   hairline and leaves links indistinguishable from body text in dark mode. */
a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--muted);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  transition: color 0.15s ease, text-decoration-color 0.15s ease;
}

a:hover {
  color: var(--accent);
  text-decoration-color: var(--accent);
}

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

strong {
  font-weight: 600;
}

/* ---- Hero ---------------------------------------------------------- */

.hero {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 1.25rem;
  row-gap: 1.75rem;
  align-items: center;
}

/* No forced ratio: the frame takes whatever shape the file has, so dropping a
   new portrait in never crops it. Keep the width and height attributes on the
   <img> matching the real file so the space is reserved before it loads. */
.hero__portrait {
  width: 104px;
  height: auto;
  flex: none;
  border-radius: var(--radius);
  background: var(--card);
}

.hero__intro {
  grid-column: 1 / -1;
}

/* One line closing the About copy, set smaller than it so it reads as an
   aside rather than a fourth paragraph. */
.hero__ask {
  margin: 1.6rem 0 0;
  font-size: 1rem;
  color: var(--muted);
}

/* ---- Stat row ------------------------------------------------------ */

/* Its own band, sitting between the About copy and the pitch. The band
   supplies the rule and the spacing, so the row itself carries neither.
   Held to the same measure as the paragraphs above so the three numbers line
   up with that block on both edges rather than spreading across the page. */
.stats {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: var(--measure);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
}

.stats strong {
  display: block;
  font-size: clamp(1.75rem, 5vw, 2.75rem);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.stats span {
  display: block;
  color: var(--muted);
  font-size: 0.875rem;
  margin-top: 0.3rem;
}

/* ---- Bands (a section with its header at top left) ------------------ */

.band {
  margin-top: clamp(3.25rem, 7vw, 5rem);
  padding-top: clamp(3.25rem, 7vw, 5rem);
  border-top: 1px solid var(--rule);
}

.band__lede {
  color: var(--muted);
  max-width: var(--measure);
  margin: 0 0 0.5rem;
}

.band__note {
  color: var(--muted);
  font-size: 0.9375rem;
  margin: 2.25rem 0 0;
}

/* ---- Selected work -------------------------------------------------- */

.work {
  list-style: none;
  margin: 2.5rem 0 0;
  padding: 0;
  display: grid;
  gap: clamp(2.75rem, 5vw, 4rem) 3rem;
}

/* Icon in a leading column, title and where stacked beside it, platforms
   pushed to the far right of the title line. */
.work__link {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  column-gap: 1.1rem;
  align-items: baseline;
  text-decoration: none;
  color: inherit;
}

.work__icon {
  grid-column: 1;
  grid-row: 1 / span 2;
  align-self: center;
  width: 76px;
  height: 76px;
  border-radius: 17px;
  background: var(--card);
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}

.work__link:hover .work__icon {
  transform: scale(1.06) rotate(-4deg);
}

.work__title {
  grid-column: 2;
  grid-row: 1;
  font-size: 1.375rem;
  letter-spacing: -0.02em;
  transition: color 0.15s ease;
}

.work__link:hover .work__title {
  color: var(--accent);
}

.work__tag {
  grid-column: 3;
  grid-row: 1;
  font-size: 0.9375rem;
  color: var(--muted);
  white-space: nowrap;
}

.work__where {
  grid-column: 2 / -1;
  grid-row: 2;
  font-size: 1rem;
  color: var(--muted);
  margin-top: 0.15rem;
}

.work__note {
  margin: 1.15rem 0 0;
  color: var(--muted);
  font-size: 1.0625rem;
  max-width: 34rem;
}

/* ---- Point lists (what I can do, how I work, what I care about) ----- */

.points {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
  display: grid;
  gap: 1.75rem 3rem;
}

.points strong {
  display: block;
  font-size: 1rem;
  letter-spacing: -0.01em;
}

.points span {
  display: block;
  color: var(--muted);
  font-size: 0.9375rem;
  margin-top: 0.15rem;
}

/* ---- Rotating reviews ----------------------------------------------- */

.quotes {
  margin-top: 2.25rem;
  max-width: var(--measure);
}

/* Every quote sits in the same grid cell, so the block is as tall as the
   longest one and nothing jumps as they rotate. */
.quotes__track {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
}

.quote {
  grid-area: 1 / 1;
}

/* Only hide them once the script is there to bring them back. */
.has-js .quote {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.5s ease;
}

.has-js .quote.is-active {
  opacity: 1;
  visibility: visible;
}

/* Without JS the quotes stack as a plain list rather than pile up. */
html:not(.has-js) .quotes__track {
  display: block;
}

html:not(.has-js) .quote + .quote {
  margin-top: 2rem;
}

.quote__text {
  margin: 0;
  font-size: clamp(1.125rem, 2.4vw, 1.4375rem);
  line-height: 1.45;
  letter-spacing: -0.015em;
}

.quote__text::before {
  content: "\201C";
}

.quote__text::after {
  content: "\201D";
}

.quote__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem 0.7rem;
  margin: 1rem 0 0;
  font-size: 0.9375rem;
  color: var(--muted);
}

.quote__stars {
  letter-spacing: 0.1em;
  font-size: 0.8125rem;
}

.quote__app::before {
  content: "\00B7";
  margin-right: 0.7rem;
}

.quotes__dots {
  display: flex;
  gap: 0.5rem;
  margin-top: 1.9rem;
}

.quotes__dot {
  width: 7px;
  height: 7px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--rule);
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;
}

.quotes__dot:hover {
  background: var(--muted);
}

.quotes__dot.is-active {
  background: var(--fg);
  transform: scale(1.35);
}

/* ---- Contact ------------------------------------------------------- */

/* The one filled element on the page, so it reads as the thing to click.
   Dark ink on the accent rather than accent-coloured text, which keeps it
   legible in both schemes. */
.contact__cta {
  margin: 1.75rem 0 0;
}

.cta {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.8rem 1.4rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
  transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1), filter 0.2s ease;
}

.cta:hover {
  color: var(--on-accent);
  transform: translateY(-1px);
  filter: brightness(1.07);
}

.cta svg {
  transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
}

.cta:hover svg {
  transform: translateX(2px);
}

.copy {
  font: inherit;
  font-size: 0.8125rem;
  letter-spacing: 0;
  color: var(--muted);
  background: none;
  border: 0;
  padding: 0 0 0 0.6rem;
  cursor: pointer;
  transition: color 0.15s ease;
}

.copy:hover,
.copy.is-copied {
  color: var(--accent);
}

.contact-links {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.75rem;
  font-size: 0.9375rem;
}

footer {
  margin-top: 4.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
  font-size: 0.8125rem;
  color: var(--muted);
}

/* ---- Medium and up: two columns of cards and points ----------------- */

@media (min-width: 48rem) {
  .points {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Cards pair up only once two of them are still worth looking at. Below this
   they run full width, which is the bigger image anyway. */
@media (min-width: 56rem) {
  .work {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ---- Desktop: header rail on the left ------------------------------- */

@media (min-width: 62rem) {
  body {
    font-size: 18px;
  }

  .hero {
    grid-template-columns: var(--rail) minmax(0, 1fr);
    column-gap: var(--gutter);
    row-gap: 2.25rem;
    align-items: start;
  }

  /* Spans both rows so the intro copy sits directly under the name rather
     than dropping to the bottom of a portrait-height row, and centres itself
     against the full run of copy instead of hanging from the top. */
  .hero__portrait {
    width: 100%;
    grid-column: 1;
    grid-row: 1 / span 2;
    align-self: center;
  }

  .hero__id {
    grid-column: 2;
    grid-row: 1;
  }

  .hero__intro {
    grid-column: 2;
    grid-row: 2;
  }

  /* Header sits in column one, everything else stacks in column two.
     The work grid opts out and spans the full page width. */
  .band {
    display: grid;
    grid-template-columns: var(--rail) minmax(0, 1fr);
    column-gap: var(--gutter);
  }

  .band > h2 {
    grid-column: 1;
    grid-row: 1;
    margin-bottom: 0;
    padding-top: 0.4rem;
  }

  .band > :not(h2) {
    grid-column: 2;
  }

  /* The card grid and its footnote break out of the text column and use the
     whole page, so the images get as much room as the layout allows. */
  .band--wide > .work {
    grid-column: 1 / -1;
    margin-top: 3rem;
  }

  .band--wide > .band__note {
    grid-column: 1 / -1;
  }

  .points {
    margin-top: 2.5rem;
  }

  /* No lede above the points list, so pull the list up to the header line. */
  .band > h2 + .points {
    margin-top: 0;
  }

  footer {
    margin-top: 5.5rem;
  }
}

/* ---- Very wide: keep card images from getting silly ------------------ */

@media (min-width: 90rem) {
  :root {
    --rail: 14rem;
    --gutter: 5rem;
  }
}

/* ---- Small screens ------------------------------------------------- */

@media (max-width: 30rem) {
  body {
    font-size: 16px;
  }

  .hero__portrait {
    width: 92px;
  }

  /* Platforms cannot share the title line at this width, so the whole meta
     block stacks beside the icon. */
  .work__link {
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 0.9rem;
  }

  .work__icon {
    width: 64px;
    height: 64px;
    border-radius: 14px;
    grid-row: 1 / span 3;
  }

  .work__title {
    font-size: 1.25rem;
  }

  .work__where {
    grid-column: 2;
    grid-row: 2;
  }

  .work__tag {
    grid-column: 2;
    grid-row: 3;
    white-space: normal;
  }

  .work__note {
    font-size: 1rem;
  }
}

/* ---- Reveal on scroll ---------------------------------------------- */

/* Scoped to .has-js so the page stays readable if the script never runs. */
.has-js [data-reveal] {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.55s ease, transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}

.has-js [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

.stats strong[data-count] {
  display: block;
  min-width: 2.6ch;
}

/* ---- Type "toast" --------------------------------------------------- */

.toast-egg {
  position: fixed;
  left: 50%;
  bottom: 0;
  margin-left: -48px;
  z-index: 50;
  pointer-events: none;
  will-change: transform;
  animation: toast-pop 2.4s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  filter: drop-shadow(0 14px 24px rgba(0, 0, 0, 0.28));
}

@keyframes toast-pop {
  0% { transform: translateY(130%) rotate(-14deg); opacity: 0; }
  16% { transform: translateY(-46%) rotate(7deg); opacity: 1; }
  30% { transform: translateY(-14%) rotate(-5deg); }
  44% { transform: translateY(-34%) rotate(4deg); }
  58% { transform: translateY(-20%) rotate(-2deg); }
  72% { transform: translateY(-27%) rotate(0deg); }
  85% { transform: translateY(-25%) rotate(0deg); opacity: 1; }
  100% { transform: translateY(130%) rotate(12deg); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
  }

  .has-js [data-reveal] {
    opacity: 1;
    transform: none;
  }

  .work__link:hover .work__icon {
    transform: none;
  }
}
