/* ==========================================================================
   Tyler Berggren — tylerberggren.com
   Hand-written static site. No framework, no build step.

   Theme is ported from reinstall.work: same background, same neutral grays,
   same component set. Two deliberate departures:

   1. --accent is #000, not a hue. Emphasis is carried by weight, underline,
      and fill instead of color. See "Accent" below — a black accent breaks
      two rules that worked when the accent was red, and both are fixed at
      the point of use rather than by bending the token.
   2. Martian Mono is the only family, body copy included. Mono glyphs are
      ~1.5x the advance width of the sans it replaces, so measure, tracking,
      and leading are all retuned; the values are not the originals.
   ========================================================================== */

/* --- Fonts ---------------------------------------------------------------- */

/* Martian Mono, latin subset, variable weight 100-800 (23KB). Pulled from
   Google Fonts and self-hosted — no third-party request on page load.
   Refresh: curl the latin src URL out of
   https://fonts.googleapis.com/css2?family=Martian+Mono:wght@100..800 */
@font-face {
  font-family: 'Martian Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('/fonts/martian-mono-var.woff2') format('woff2-variations');
}

/* Phosphor Light, subset to the 7 glyphs this site uses:
   U+E03E arrow-down, U+E092 arrow-up-right, U+E2EE linkedin-logo,
   U+E398 paper-plane-tilt, U+E4BC x-logo, U+E576 github-logo,
   U+E59C reddit-logo.
   Rebuild: pyftsubset Phosphor-Light.woff2
            --unicodes=U+E03E,U+E092,U+E2EE,U+E398,U+E4BC,U+E576,U+E59C
            --flavor=woff2 --output-file=phosphor-light-subset.woff2 */
@font-face {
  font-family: 'Phosphor-Light';
  font-style: normal;
  font-weight: normal;
  font-display: block;
  /* ?v= must be bumped whenever the glyph set changes — /fonts/* is served
     immutable for a year, so a stale cache would drop icons silently. */
  src: url('/fonts/phosphor-light-subset.woff2?v=1') format('woff2');
}

/* Phosphor Fill, subset to the 5 social glyphs used in the About block:
   U+E2EE linkedin-logo, U+E398 paper-plane-tilt, U+E4BC x-logo,
   U+E576 github-logo, U+E59C reddit-logo.
   Rebuild: pyftsubset Phosphor-Fill.woff2
            --unicodes=U+E2EE,U+E398,U+E4BC,U+E576,U+E59C
            --flavor=woff2 --output-file=phosphor-fill-subset.woff2 */
@font-face {
  font-family: 'Phosphor-Fill';
  font-style: normal;
  font-weight: normal;
  font-display: block;
  src: url('/fonts/phosphor-fill-subset.woff2?v=1') format('woff2');
}

/* --- Tokens --------------------------------------------------------------- */

:root {
  /* Palette — bg, ink, and neutrals are reinstall.work's values unchanged. */
  --bg: #f2f0ed;
  --ink: #0a0a0a;
  --accent: #000000;
  --neutral: #8a8a8a;
  --dark: #1e1e1e;

  /* Derived surfaces */
  --bg-raised: #f8f7f5;
  --rule: color-mix(in srgb, var(--neutral) 32%, transparent);
  --rule-strong: color-mix(in srgb, var(--neutral) 55%, transparent);
  --muted: #6b6b6b;

  /* Type — one family. Weight and tracking do the work a second family
     would otherwise do. */
  --font: 'Martian Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* Layout. --measure is 60ch against the original's 80ch: Martian Mono is
     far wider per character, so 80ch overruns a comfortable line length by
     a wide margin. --measure-prose is tighter still for the long bio. */
  --measure: 60ch;
  --measure-prose: 54ch;
  --page: 1120px;
  --gutter: clamp(1.25rem, 5vw, 3rem);
  --header-h: 4.25rem;

  --radius: 6px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* Light only — there is no dark theme. Declaring it stops the browser
     from auto-darkening form controls and scrollbars. */
  color-scheme: light;
}

/* --- Reset / base --------------------------------------------------------- */

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

html {
  scroll-behavior: smooth;
  /* Anchor targets clear the sticky header */
  scroll-padding-top: calc(var(--header-h) + 1.5rem);
  -webkit-text-size-adjust: 100%;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  /* A notch smaller than the original 1rem-1.0625rem: wide mono glyphs read
     larger at the same nominal size. */
  font-size: clamp(0.9375rem, 0.91rem + 0.14vw, 1rem);
  font-weight: 400;
  /* Martian Mono ships generous default sidebearings. Negative tracking pulls
     words back into recognisable shapes instead of spaced-out characters. */
  letter-spacing: -0.045em;
  /* Mono needs more leading than sans at the same measure. */
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img,
svg,
video {
  display: block;
  max-width: 100%;
  height: auto;
}

a {
  color: inherit;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

h1,
h2,
h3,
h4 {
  margin: 0;
  font-weight: 700;
  line-height: 1.15;
  /* Tighter than body — large mono text shows its gaps more. */
  letter-spacing: -0.06em;
  text-wrap: balance;
}

p {
  margin: 0;
  text-wrap: pretty;
}

strong {
  font-weight: 700;
}

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

/* --- Layout primitives ---------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  padding-block: clamp(4rem, 9vw, 7.5rem);
  border-top: 1px solid var(--rule);
}

.section__head {
  margin-bottom: clamp(2rem, 4vw, 3.25rem);
}

/* Accent: with no hue to spend, the eyebrow leans on caps, tracking, and a
   short leading rule. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 0.9rem;
}

.eyebrow::before {
  content: '';
  width: 1.75rem;
  height: 1px;
  background: var(--accent);
  flex-shrink: 0;
}

.eyebrow--muted {
  color: var(--neutral);
}

.eyebrow--muted::before {
  background: var(--neutral);
}

h2 {
  font-size: clamp(1.5rem, 1.1rem + 1.7vw, 2.25rem);
}

h3 {
  font-size: clamp(1rem, 0.95rem + 0.3vw, 1.15rem);
}

.lede {
  max-width: var(--measure);
  font-size: clamp(1rem, 0.96rem + 0.28vw, 1.125rem);
  letter-spacing: -0.05em;
  color: var(--muted);
  margin-top: 1rem;
}

.prose {
  max-width: var(--measure);
  display: grid;
  gap: 1.15em;
}

/* Accent fix 1 of 2: the original tinted prose links with the accent. At #000
   that is the body-text color, so a tinted link would be invisible as a link.
   Signal moves to weight plus a gray underline that darkens on hover.
   `:not(.btn)` matters — a .btn inside .prose would otherwise pick this up. */
.prose a:not(.btn) {
  color: var(--ink);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: var(--rule-strong);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  transition: text-decoration-color 0.18s var(--ease);
}

.prose a:not(.btn):hover {
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
}

.prose em {
  font-style: italic;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--ink);
  color: var(--bg);
  padding: 0.75rem 1.25rem;
  font-size: 0.8125rem;
  text-decoration: none;
}

.skip-link:focus {
  left: 0.5rem;
  top: 0.5rem;
}

/* --- Header --------------------------------------------------------------- */

.header {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: saturate(1.6) blur(12px);
  -webkit-backdrop-filter: saturate(1.6) blur(12px);
  border-bottom: 1px solid var(--rule);
}

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  width: 100%;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
  flex-shrink: 0;
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: -0.05em;
  white-space: nowrap;
}

.brand img {
  width: 1.625rem;
  height: 1.625rem;
  border-radius: 50%;
  flex-shrink: 0;
}

/* --- Nav ------------------------------------------------------------------ */

.nav {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.2vw, 1.75rem);
}

.nav a:not(.btn) {
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: -0.03em;
  color: var(--muted);
  text-decoration: none;
  padding-block: 0.35rem;
  position: relative;
  transition: color 0.18s var(--ease);
  white-space: nowrap;
}

.nav a:not(.btn)::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1.5px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.24s var(--ease);
}

.nav a:not(.btn):hover {
  color: var(--ink);
}

.nav a:not(.btn)[aria-current='true'] {
  color: var(--ink);
}

.nav a:not(.btn)[aria-current='true']::after {
  transform: scaleX(1);
}

/* Shown at every width: compact in the desktop bar, full-width at the foot
   of the mobile panel (see the mobile block). */
.nav__cta {
  padding: 0.6rem 1rem;
  margin-left: 0.35rem;
}

/* Mobile nav */
.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  margin-right: -0.5rem;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}

/* 760px is enough here — two nav links plus the CTA, where reinstall.work
   had five and needed 900px. */
@media (min-width: 760px) {
  .nav-toggle {
    display: none;
  }
}

@media (max-width: 759px) {
  .nav {
    position: fixed;
    inset: var(--header-h) 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--bg);
    border-bottom: 1px solid var(--rule);
    padding: 0.5rem var(--gutter) 1.5rem;
    transform: translateY(-8px);
    opacity: 0;
    pointer-events: none;
    visibility: hidden;
    transition: opacity 0.2s var(--ease), transform 0.2s var(--ease),
      visibility 0.2s;
  }

  .nav[data-open='true'] {
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
    visibility: visible;
  }

  .nav a:not(.btn) {
    padding-block: 0.85rem;
    font-size: 0.875rem;
    border-bottom: 1px solid var(--rule);
  }

  /* The last text link sits directly above the CTA — its divider would read
     as a stray second rule between them. */
  .nav a:not(.btn):has(+ .btn) {
    border-bottom: none;
  }

  .nav__cta {
    justify-content: center;
    margin: 0.75rem 0 0;
    padding-block: 0.85rem;
  }

  .nav a:not(.btn)::after {
    display: none;
  }
}

/* --- Icons ---------------------------------------------------------------- */

/* Minimal stand-in for the Phosphor web stylesheet — we ship 7 glyphs, so
   there is no reason to carry the full ~87KB of icon classes. */
.ph-light,
.ph-fill {
  font-style: normal;
  font-weight: normal;
  font-variant: normal;
  text-transform: none;
  letter-spacing: normal;
  line-height: 1;
  display: inline-block;
  speak: never;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.ph-light {
  font-family: 'Phosphor-Light' !important;
}

.ph-fill {
  font-family: 'Phosphor-Fill' !important;
}

/* Codepoints are identical across Phosphor weights — only the family differs. */
.ph-arrow-down::before {
  content: '\e03e';
}

.ph-arrow-up-right::before {
  content: '\e092';
}

.ph-linkedin-logo::before {
  content: '\e2ee';
}

.ph-paper-plane-tilt::before {
  content: '\e398';
}

.ph-x-logo::before {
  content: '\e4bc';
}

.ph-github-logo::before {
  content: '\e576';
}

.ph-reddit-logo::before {
  content: '\e59c';
}

/* --- Buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  font-family: var(--font);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1;
  white-space: nowrap;
  padding: 0.9rem 1.4rem;
  border-radius: var(--radius);
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--bg);
  text-decoration: none;
  cursor: pointer;
  transition: background 0.18s var(--ease), border-color 0.18s var(--ease),
    color 0.18s var(--ease), transform 0.18s var(--ease);
}

/* Accent fix 2 of 2: the original darkened the accent on hover, which is a
   no-op at #000. A black button has to get *lighter* to register a hover,
   so it lifts to --dark. */
.btn:hover {
  background: var(--dark);
  border-color: var(--dark);
  transform: translateY(-1px);
}

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule-strong);
}

.btn--ghost:hover {
  background: transparent;
  border-color: var(--ink);
  color: var(--ink);
}

.btn--sm {
  padding: 0.6rem 1rem;
  font-size: 0.75rem;
}

.btn i {
  font-size: 1.35em;
  line-height: 1;
  flex-shrink: 0;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2rem;
}

.btn-row--close {
  margin-top: clamp(2.25rem, 4vw, 3.25rem);
}

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

.hero {
  padding-block: clamp(3.5rem, 8vw, 6.5rem) clamp(3rem, 7vw, 5.5rem);
  border-top: 0;
}

.hero h1 {
  font-size: clamp(1.75rem, 1.15rem + 2.5vw, 2.875rem);
  letter-spacing: -0.07em;
  max-width: 24ch;
}

.hero .lede {
  margin-top: clamp(1.25rem, 2.5vw, 1.75rem);
  max-width: 52ch;
}

.hero__media {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  border-radius: calc(var(--radius) * 2);
  overflow: hidden;
  border: 1px solid var(--rule);
  background: var(--dark);
  aspect-ratio: 16 / 9;
}

.hero__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --- About ---------------------------------------------------------------- */

.about {
  display: grid;
  gap: clamp(2rem, 5vw, 3.5rem);
}

@media (min-width: 900px) {
  .about {
    grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
    align-items: start;
  }
}

.about__portrait {
  width: clamp(7rem, 20vw, 12rem);
  border-radius: 50%;
}

/* The longest single block of body copy on the site — tighter measure than
   the rest of the page. */
.about .prose {
  max-width: var(--measure-prose);
}

.about__meta {
  margin-top: 1.25rem;
  display: grid;
  gap: 0.35rem;
  font-size: 0.75rem;
  letter-spacing: -0.03em;
  line-height: 1.5;
  color: var(--muted);
}

.about__meta strong {
  color: var(--ink);
  font-weight: 700;
}

.about__links {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.5rem;
  flex-wrap: wrap;
}

.about__links a {
  display: inline-flex;
  color: var(--accent);
  text-decoration: none;
  transition: opacity 0.18s var(--ease);
}

.about__links a:hover {
  opacity: 0.55;
}

.about__links i {
  font-size: 1.3rem;
  line-height: 1;
}

/* --- Work cards ----------------------------------------------------------- */

/* Three projects — one row on desktop, two up at tablet, stacked on phones. */
.cards {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;
}

@media (min-width: 620px) {
  .cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 960px) {
  .cards {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* The whole card is the link — each project has exactly one destination, so
   this beats a nested link and gives a much larger hit target. */
a.card {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  padding: clamp(1.1rem, 2.2vw, 1.5rem);
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: calc(var(--radius) * 1.5);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.2s var(--ease), transform 0.2s var(--ease);
}

a.card:hover {
  border-color: var(--rule-strong);
  transform: translateY(-2px);
}

/* The three project logos have different aspect ratios and are full-color in
   an otherwise black-and-white page. A fixed-height box with `contain` keeps
   them from being cropped and lines all three up on a common baseline. */
.card__thumb {
  height: 7rem;
  display: block;
  padding: 0.65rem;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}

/* The image box is sized explicitly and `contain` letterboxes the logo inside
   it. Sizing the image itself (rather than centring an auto-sized image with
   `max-height: 100%`) is deliberate: as a centred grid item the percentage
   resolved against the content-sized row, not this 7rem box, so tall logos
   overflowed and got clipped to an empty strip. */
.card__thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

a.card h3 {
  letter-spacing: -0.05em;
}

a.card p {
  font-size: 0.8125rem;
  letter-spacing: -0.035em;
  color: var(--muted);
  line-height: 1.65;
}

a.card p em {
  font-style: italic;
}

/* Pushed to the bottom row so the arrows line up across cards of unequal
   text length — the grid stretches them to a common height. */
.card__go {
  margin-top: auto;
  padding-top: 0.35rem;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}

.card__go i {
  font-size: 1.15em;
  transition: transform 0.2s var(--ease);
}

a.card:hover .card__go i {
  transform: translate(2px, -2px);
}

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

.band {
  background: var(--dark);
  color: var(--bg);
  border-top: 0;
}

.band .lede {
  color: color-mix(in srgb, var(--bg) 70%, transparent);
}

.band h2 {
  max-width: 22ch;
}

/* A #000 accent button on a #1e1e1e band is very nearly invisible, so inside
   the band the fill inverts: paper on dark rather than dark on paper. */
.band .btn {
  background: var(--bg);
  border-color: var(--bg);
  color: var(--ink);
}

.band .btn:hover {
  background: #fff;
  border-color: #fff;
}

.band .btn--ghost {
  background: transparent;
  color: var(--bg);
  border-color: color-mix(in srgb, var(--bg) 35%, transparent);
}

.band .btn--ghost:hover {
  background: transparent;
  border-color: var(--bg);
  color: var(--bg);
}

/* --- Footer --------------------------------------------------------------- */

.footer {
  background: var(--dark);
  color: color-mix(in srgb, var(--bg) 62%, transparent);
  border-top: 1px solid color-mix(in srgb, var(--bg) 14%, transparent);
  padding-block: 2.5rem;
  font-size: 0.75rem;
  letter-spacing: -0.03em;
}

.footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
}

.footer__brand {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  flex-wrap: wrap;
}

.footer__social {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
}

.footer__social a {
  display: inline-flex;
  /* Optical alignment — the glyphs sit slightly high against the baseline. */
  margin-top: 1px;
}

.footer__social i {
  font-size: 1.25rem;
  line-height: 1;
}

.footer a {
  color: inherit;
  text-decoration: none;
  transition: color 0.18s var(--ease);
}

.footer a:hover {
  color: var(--bg);
}

/* --- 404 ------------------------------------------------------------------ */

.notfound {
  min-height: calc(100vh - var(--header-h));
  display: grid;
  align-content: center;
  border-top: 0;
}

.notfound .eyebrow {
  justify-content: flex-start;
}

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

[data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}

[data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal] {
    opacity: 1;
    transform: none;
  }
}
