/* Krisztián Hadi — v5. One stylesheet, light and dark, no framework. */

@font-face {
  font-family: "Menoe Grotesque";
  src: url("fonts/menoe-grotesque-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Menoe Grotesque";
  src: url("fonts/menoe-grotesque-italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

/* tokens: light by default, dark from the system or the switch */
:root {
  --bg: #f5f4f0;
  --fg: #2a2a28;
  --muted: #5f5e59;
  --rule: #d6d4cc;
  --mark: #ece6c8;
  --code-bg: #efeee8;
  --code-tag: #9a3a14;
  --code-attr: #4d6b1a;
  --code-string: #2b5d85;
  --page: 1040px;
  --side: 24px;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #1b1b1a;
    --fg: #e4e2dc;
    --muted: #a4a29b;
    --rule: #3b3a37;
    --mark: #4a4532;
    --code-bg: #222220;
    --code-tag: #ee9d76;
    --code-attr: #b6cb82;
    --code-string: #8dbfe0;
    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --bg: #1b1b1a;
  --fg: #e4e2dc;
  --muted: #a4a29b;
  --rule: #3b3a37;
  --mark: #4a4532;
  --code-bg: #222220;
  --code-tag: #ee9d76;
  --code-attr: #b6cb82;
  --code-string: #8dbfe0;
  color-scheme: dark;
}

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

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

[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 400 18px/30px "Menoe Grotesque", ui-sans-serif, system-ui, sans-serif;
  font-kerning: normal;
}

h1, h2, h3, p, ul, ol, dl, dd, figure, blockquote { margin: 0; padding: 0; }

ul, ol { list-style: none; }

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

/* no lonely last words: headings even out their lines, text avoids a one-word last line */
h1, h2, h3 { text-wrap: balance; }

p, li, dd, figcaption, blockquote { text-wrap: pretty; }

/* links: underlined, a dark block on hover */
a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

a:hover {
  background: var(--fg);
  color: var(--bg);
  text-decoration-color: transparent;
}

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

/* main only takes focus as a landing spot (skip link, back to top), it's not a control */
main:focus { outline: none; }

.skip {
  position: absolute;
  left: var(--side);
  top: -100px;
  padding: 8px 12px;
  background: var(--fg);
  color: var(--bg);
}

.skip:focus { top: 12px; }

.nowrap { white-space: nowrap; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* the page */
.wrap {
  max-width: calc(var(--page) + 2 * var(--side));
  margin-inline: auto;
  padding-inline: var(--side);
}

.top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-block: 24px;
}

.home { font-size: 18px; }

.theme {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-right: -10px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}


.theme:hover { background: var(--fg); color: var(--bg); }

/* back to top: floats in the bottom right corner once the page has scrolled a screen or so */
.to-top {
  position: fixed;
  right: max(16px, env(safe-area-inset-right, 0px));
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  z-index: 10;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 1px solid var(--fg);
  background: var(--bg);
  color: var(--fg);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(16px);
}

.to-top.shown { opacity: 1; visibility: visible; transform: none; }

.to-top:hover { background: var(--fg); color: var(--bg); }

@media (prefers-reduced-motion: no-preference) {
  .to-top { transition: opacity 250ms ease, transform 300ms cubic-bezier(0.2, 0.7, 0.2, 1), visibility 0s 300ms; }
  .to-top.shown { transition-delay: 0s; }
}

@media (min-width: 900px) {
  .to-top { right: 32px; bottom: calc(32px + env(safe-area-inset-bottom, 0px)); }
}

/* sections: the heading on top, the content under it */
.section {
  display: grid;
  gap: 32px;
  padding-block: 56px;
  border-top: 1px solid var(--rule);
}

.section > h2 {
  font-size: 28px;
  line-height: 36px;
  font-weight: 400;
  letter-spacing: -0.01em;
}

@media (min-width: 900px) {
  .section { padding-block: 72px; }
  .section > h2 { font-size: 32px; line-height: 44px; }
}

/* intro: a wide headline, then the text at a wide but readable measure, with room to breathe */
.intro { padding-block: 56px 80px; }

.intro h1 {
  max-width: 22em;
  font-size: 32px;
  line-height: 44px;
  font-weight: 400;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

.intro p { margin-top: 40px; max-width: 46em; text-wrap: balance; }

.intro p + p { margin-top: 20px; }

.intro .actions { margin-top: 48px; }

@media (min-width: 900px) {
  .intro { padding-block: 120px 128px; }
  .intro h1 { font-size: 44px; line-height: 56px; }
  .intro p { margin-top: 48px; }
  .intro p + p { margin-top: 24px; }
}

/* the rotating title: typed out, backspaced, typed again. The cursor blinks while it waits.
   The rotor always keeps its cursor box, only hidden, so the line holds its height even when empty */
.rotor { white-space: nowrap; }

.caret::after,
.rotor::after {
  content: "";
  display: inline-block;
  width: 0.08em;
  height: 0.9em;
  margin-left: 0.06em;
  vertical-align: -0.1em;
  background: currentColor;
}

.rotor:not(.caret)::after { visibility: hidden; }

@media (prefers-reduced-motion: no-preference) {
  .caret::after { animation: blink 1s steps(1) infinite; }
  .caret.typing::after { animation: none; }
}

@keyframes blink { 50% { opacity: 0; } }

/* the opening: only the headline types itself out, then the header drops in from the top
   and everything else rises from below, one after the other */
.entering .top { opacity: 0; transform: translateY(-100%); }

.entering .intro > :not(h1),
.entering main > :not(.intro),
.entering .foot { opacity: 0; transform: translateY(32px); }

.entering:not(.typing-in) .typed { visibility: hidden; }

/* the "Hey!" sits in the space above the headline, so it can leave without moving anything */
.intro h1 { position: relative; }

.hey { position: absolute; left: 0; bottom: 100%; transition: opacity 600ms ease; }

.hey.gone { opacity: 0; }

@media (prefers-reduced-motion: no-preference) {
  .top,
  .intro > :not(h1),
  main > :not(.intro),
  .foot { transition: opacity 600ms ease, transform 700ms cubic-bezier(0.2, 0.7, 0.2, 1); }
}

/* buttons: same family as the links, with a frame */
.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 32px;
}

.button {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 16px;
  border: 1px solid var(--fg);
  background: none;
  color: inherit;
  font: inherit;
  text-decoration: none;
  cursor: pointer;
}

.button:hover { background: var(--fg); color: var(--bg); }

/* css.gg 2.1.1 (MIT), the last release under that licence; GitHub and LinkedIn are the brand marks */
.icon {
  flex: none;
  width: 20px;
  height: 20px;
  fill: currentColor;
  fill-rule: evenodd;
}

.icon--brand { width: 16px; height: 16px; margin-inline: 2px; }

/* the switch shows where you'd go: the moon in light, the sun in dark */
.icon--sun { display: none; }

:root[data-theme="dark"] .icon--sun { display: block; }
:root[data-theme="dark"] .icon--moon { display: none; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .icon--sun { display: block; }
  :root:not([data-theme="light"]) .icon--moon { display: none; }
}

/* the copy confirmation: the note slides up from below and pushes the address out, then back */
.swap {
  display: grid;
  overflow: hidden;
}

.swap > span { grid-area: 1 / 1; text-align: left; }

.swap-note { transform: translateY(100%); }

[data-copied] .swap > span:first-child { transform: translateY(-100%); }

[data-copied] .swap-note { transform: none; }

@media (prefers-reduced-motion: no-preference) {
  .swap > span { transition: transform 260ms cubic-bezier(0.2, 0.7, 0.2, 1); }
}

/* images: 1px frame, 2px of air inside it */
.frame {
  border: 1px solid var(--rule);
  padding: 2px;
  background: var(--bg);
}

/* recommendations */
.quotes { display: grid; gap: 48px 32px; }

.quote { display: grid; align-content: start; gap: 20px; }

/* two columns; each quote spans two shared rows, words then signature, so the people in a row
   line up however long their quotes are */
@media (min-width: 700px) {
  .quotes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .quote { grid-row: span 2; grid-template-rows: subgrid; row-gap: 20px; }
}

.quote blockquote p { text-wrap: balance; }

.who {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr);
  column-gap: 16px;
  align-items: start;
  align-content: start;
  font-size: 16px;
  line-height: 24px;
  color: var(--muted);
}

.who img { width: 100%; filter: grayscale(1); }

.who .frame { grid-row: span 2; aspect-ratio: 1; }

.who a, .who .name { color: var(--fg); }

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

/* projects */
.projects { display: grid; }

.project {
  display: grid;
  grid-template-columns: 60px minmax(0, 1fr);
  column-gap: 20px;
  align-items: start;
  padding-block: 24px;
  border-top: 1px solid var(--rule);
}

.project:first-child { border-top: 0; padding-top: 0; }

/* the icon in a frame as tall as the two lines beside it, like the avatars */
.project-mark {
  display: grid;
  place-items: center;
  width: 60px;
  height: 60px;
  border: 1px solid var(--rule);
}

.project-mark .icon { width: 24px; height: 24px; }

.project .meta { font-size: 16px; line-height: 24px; color: var(--muted); }

.project h3 {
  margin-top: 4px;
  font-size: 22px;
  line-height: 32px;
  font-weight: 400;
}

/* the CV */
.cv { display: grid; gap: 32px; }

.cv > :not(.actions) { max-width: 44em; }

/* company and position a step above the body text */
.role h3,
.compact p:has(.what) { font-size: 20px; line-height: 30px; font-weight: 400; }

.cv .when { display: block; font-size: 16px; line-height: 24px; color: var(--muted); }

/* the market and size follow the years on one line; on a phone they get a line of their own,
   so the breaks land between the parts instead of in the middle of one */
.cv .when .about::before { content: " · "; white-space: pre; }

@media (max-width: 699px) {
  .cv .when .about { display: block; }
  .cv .when .about::before { content: none; }
}

.role ul { margin-top: 8px; display: grid; gap: 6px; }

.role li,
.list li {
  position: relative;
  padding-left: 20px;
}

.role li::before,
.list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 14px;
  width: 8px;
  height: 1px;
  background: var(--muted);
}

.compact { display: grid; gap: 16px; }

.compact p { color: var(--muted); }

.compact p:has(.what) { color: var(--fg); }

.compact .what { display: block; margin-top: 4px; font-size: 18px; color: var(--fg); }

.compact strong, .role strong { font-weight: 400; color: var(--fg); }

/* author's open questions stay visible until they're answered */
.todo {
  background: var(--mark);
  color: var(--fg);
  padding-inline: 4px;
}

/* the footer */
.foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 8px 24px;
  padding-block: 32px 48px;
  border-top: 1px solid var(--rule);
  font-size: 16px;
  line-height: 24px;
  color: var(--muted);
}

.foot { align-items: center; }

.foot nav { display: flex; flex-wrap: wrap; gap: 24px; }

.foot nav a { display: inline-flex; align-items: center; gap: 8px; }

/* case studies */
.case-head {
  padding-block: 48px 40px;
  max-width: 44em;
}

.case-head .meta { color: var(--muted); }

.case-head h1 {
  max-width: 20em;
  margin-top: 12px;
  font-size: 32px;
  line-height: 44px;
  font-weight: 400;
  letter-spacing: -0.01em;
}

.case-head .lede { margin-top: 24px; }

@media (min-width: 900px) {
  .case-head { padding-block: 72px 56px; }
  .case-head h1 { font-size: 40px; line-height: 52px; }
}

.facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 4px 24px;
  margin-top: 32px;
  font-size: 16px;
  line-height: 24px;
}

.facts dt { color: var(--muted); }

.prose { max-width: 44em; }

.prose p + p,
.prose p + .list,
.prose .list + p { margin-top: 16px; }

.prose h3 { margin-top: 24px; font-size: 18px; font-weight: 400; }

.list { display: grid; gap: 12px; }

.figure { margin-block: 48px; }

/* a section already ends in padding, so a figure right after it doesn't add its own gap on top */
.section + .figure { margin-top: 0; }

.figure + .figure { margin-top: 64px; }

.figure figcaption {
  margin-top: 12px;
  font-style: italic;
  max-width: 44em;
  font-size: 16px;
  line-height: 24px;
  color: var(--muted);
}

.figure--narrow { max-width: 420px; }

.phone p,
.clip p { margin-bottom: 8px; font-size: 16px; line-height: 24px; color: var(--muted); }

/* a phone and a wide screen side by side, as tall as each other: the columns are the two aspect ratios */
.pair {
  display: grid;
  grid-template-columns: 0.47fr 1.6fr;
  gap: 16px;
  align-items: end;
}

@media (min-width: 700px) { .pair { gap: 24px; } }

/* three phones in a row, as wide as the text. On a phone, a strip you swipe through instead,
   so each screen stays big enough to read */
.phones {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
  max-width: 44em;
}

/* screen recordings, three in a row across the page, a swipe strip on a phone too */
.clips {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
}

.clip video { display: block; width: 100%; height: auto; }

@media (max-width: 699px) {
  .phones,
  .clips {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 62%;
    gap: 16px;
    margin-inline: calc(-1 * var(--side));
    padding-inline: var(--side);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--side);
    scrollbar-width: none;
  }
  .clips { grid-auto-columns: 85%; }
  .phones > *,
  .clips > * { scroll-snap-align: start; }
}

/* themes: the screenshots stacked in one spot, the buttons fade between them. Each button gets an icon
   in that theme's own style in Juuku: a Lucide coffee cup (ISC), a Phosphor fill sparkle (MIT) and a pixel invader */
.theme-switch { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 16px; }

.icon--line { fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }

/* pixel art stays crisp only on its own 24px grid */
.icon--pixel { width: 24px; height: 24px; margin-block: -2px; shape-rendering: crispEdges; }

.theme-switch [aria-pressed="true"] { background: var(--fg); color: var(--bg); }

.themes { display: grid; }

.themes picture { grid-area: 1 / 1; transition: opacity 300ms ease, visibility 300ms; }

.themes .off { opacity: 0; visibility: hidden; }

/* on a phone the wide screenshots are too small to tell the typefaces apart, so the phone ones come in instead */
@media (max-width: 699px) {
  .themes { max-width: 62%; }
}

/* code in place of a screenshot, dressed like an editor: line numbers, a few colours.
   Scrolls sideways on a phone instead of wrapping */
.code pre { margin: 0; padding: 20px 24px 20px 0; overflow-x: auto; background: var(--code-bg); font: inherit; font-size: 15px; line-height: 24px; counter-reset: line; }
.code code { font: inherit; }
.code .line { display: block; width: max-content; min-width: 100%; }
/* the numbers are left out of copies and of what a screen reader says */
.code .line::before {
  counter-increment: line;
  content: counter(line) / "";
  display: inline-block;
  width: 3.5em;
  margin-right: 1em;
  padding-right: 0.75em;
  border-right: 1px solid var(--rule);
  text-align: right;
  color: var(--muted);
  user-select: none;
}
.code .c { color: var(--muted); font-style: italic; }
.code .p { color: var(--muted); }
.code .t { color: var(--code-tag); }
.code .a { color: var(--code-attr); }
.code .s { color: var(--code-string); }

.case .section:first-of-type { border-top: 0; }

.pull {
  margin-block: 32px;
  padding-left: 20px;
  border-left: 1px solid var(--rule);
}

/* Ilya's letter, typed out so it reads like the rest of the page; the button swaps in the scan.
   Without script.js there's no button, just the text */
.letter { margin-bottom: 32px; }

/* the swap under the letter: a small link with a document icon, not a full button */
.letter-swap {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 16px;
  line-height: 24px;
  cursor: pointer;
}

.letter-swap .icon { width: 18px; height: 18px; }

.letter-swap span { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.2em; }

.letter-swap:hover { background: var(--fg); color: var(--bg); }

.letter-swap:hover span { text-decoration-color: transparent; }

.sheet {
  display: grid;
  gap: 16px;
  padding: 32px var(--side);
  border: 1px solid var(--rule);
  font-size: 16px;
  line-height: 26px;
}

@media (min-width: 700px) { .sheet { padding: 48px 56px; } }

.sheet .to { text-align: right; }

.sheet h3 { margin-block: 8px; font-size: 16px; font-weight: 400; text-align: center; }

.sheet sup { line-height: 0; }

/* the signature from the scan, as a mask, so it takes the ink of the page in light and dark */
.signature {
  width: 140px;
  aspect-ratio: 210 / 108;
  background: currentColor;
  -webkit-mask: url("images/crytek-signature.png") center / contain no-repeat;
  mask: url("images/crytek-signature.png") center / contain no-repeat;
}

.letter .scan img { width: 100%; }

.pull footer { margin-top: 12px; font-size: 16px; line-height: 24px; color: var(--muted); }

.next {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 16px;
  padding-block: 40px;
  border-top: 1px solid var(--rule);
}

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

/* scrolling: each block fades and rises into place as it comes up from the bottom of the screen, tied to the
   scroll position, so scrolling back plays it in reverse. Pure CSS; browsers without scroll-driven
   animations (Firefox for now) just show everything as it is */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .section > h2,
    .quote > *,
    .project,
    .role .when,
    .role h3,
    .role li,
    .compact p,
    .cv .actions,
    .case-head .facts,
    .prose > p,
    .prose .list li,
    .prose .pull,
    .case .figure,
    .case > p,
    .next {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 0 entry 200px;
    }

    /* the right-hand column of a two-up grid trails a step behind the left */
    .quote:nth-child(even) > * { animation-range: entry 40px entry 240px; }
  }
}

/* a snapshot audit can catch a block half faded at the bottom edge and call it low contrast.
   Nobody reads there, so that one is a known false positive, kept on purpose */
@keyframes rise {
  from { opacity: 0; transform: translateY(48px); }
}

/* the Konami code: the page goes dark, then an old CRT switches on. A dot of light stretches into a line,
   the line opens into the screen, and the flash settles to dark glass with neon green text.
   Switching off runs it backwards: the picture collapses to a line, then to a dot, then it's gone */
.matrix {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: #050605;
  opacity: 0;
}

.matrix.on { opacity: 1; }

.tube {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  padding-inline: var(--side);
  background: #0b120c;
  box-shadow: inset 0 0 18vmin rgba(0, 0, 0, 0.85);
  color: #4dff6a;
  font-size: clamp(22px, 4vw, 36px);
  line-height: 1.4;
  text-shadow: 0 0 12px rgba(77, 255, 106, 0.55);
  visibility: hidden;
}

.tube > div { width: min(100%, var(--page)); margin-inline: auto; }

.tube.on { visibility: visible; }

.tube.off { visibility: hidden; }

.in-matrix .wrap { opacity: 0; }

@media (prefers-reduced-motion: no-preference) {
  .wrap { transition: opacity 900ms ease; }
  .matrix { transition: opacity 400ms ease; }
  .tube.on { animation: crt-on 750ms cubic-bezier(0.2, 0.7, 0.2, 1) both; }
  .tube.off { visibility: visible; animation: crt-off 700ms cubic-bezier(0.6, 0, 0.8, 0.4) both; }
}

@keyframes crt-on {
  0% { transform: scale(0.01, 0.004); background-color: #f4fff4; }
  30% { transform: scale(1, 0.004); background-color: #f4fff4; }
  60% { transform: scale(1, 1); background-color: #5f8a65; }
  100% { transform: none; background-color: #0b120c; }
}

@keyframes crt-off {
  0% { transform: none; }
  45% { transform: scale(1, 0.004); background-color: #f4fff4; color: transparent; text-shadow: none; }
  80% { transform: scale(0.01, 0.004); background-color: #ffffff; color: transparent; text-shadow: none; opacity: 1; }
  100% { transform: scale(0, 0); background-color: #ffffff; color: transparent; text-shadow: none; opacity: 0; }
}
