/*
  adeolakod.com
  One stylesheet for every page. Light theme only for now.
  TODO: dark mode. All the colours come from the variables below, so it should only need a second :root block.
*/

@font-face {
  font-family: "Geist";
  /* Subset to Latin (plus Hungarian accents) to keep the file small. The full font is ~70 KB, this is ~19 KB:
     nothing on the site is lighter than 400, so the thin end of the weight axis is gone, and so are the
     stylistic sets I don't use.
     TODO: re-subset from the original if I ever want a light weight (under 400). */
  src: url("../fonts/Geist-Variable.woff2") format("woff2");
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --page: #FFFFFF;
  --bar: #F2F2F2;
  --panel: #F6F6F6;
  --line: #E3E3E3;
  --hover: #D9D9D9;
  --hair: 0.5px;   /* hairline borders: one device pixel on high-density screens */
  --ink: #000000;
  --text: #1F1F1F;
  --muted: #6B6B6B;
  --faint: #9A9A9A;
  --font: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --ease: cubic-bezier(.2, .7, .2, 1);
  color-scheme: light;
}

* { box-sizing: border-box; }
html, body { margin: 0; }
/* Footer sits at the bottom of the window even when a page is short */
body { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; }
body > main { flex: 1 0 auto; width: 100%; }
/* Always keep room for the scrollbar, so short pages (Research) don't sit a few pixels to the right of long ones. */
html { scrollbar-gutter: stable; }
@supports not (scrollbar-gutter: stable) { html { overflow-y: scroll; } }
body { background: var(--page); color: var(--text); font-family: var(--font); font-size: 16px; line-height: 1.6; -webkit-font-smoothing: antialiased; }
a { color: inherit; }
img { max-width: 100%; }
p { margin: 0 0 12px; }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.wrap { max-width: 1180px; margin: 0 auto; padding-inline: 24px; }
section { padding-block: 0 72px; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }


/* ---------- Top bar ---------- */

.bar { background: var(--bar); border-bottom: var(--hair) solid var(--line); position: sticky; top: env(safe-area-inset-top, 0px); z-index: 10; }
.bar-inner { display: flex; align-items: center; justify-content: space-between; gap: 20px; height: 46px; }
.menu { display: flex; align-items: center; gap: 4px; margin-right: -12px; }

.navlink { -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; position: relative; isolation: isolate; display: inline-flex; align-items: center; gap: 10px; height: 34px; padding-inline: 12px; text-decoration: none; color: var(--ink); font-weight: 400; font-size: 0.9rem; line-height: 1; white-space: nowrap; }
/* Only the grey box grows on hover. Scaling the link itself made the text go blurry mid-animation. */
.navlink::before { content: ""; position: absolute; inset: 0; z-index: -1; background: var(--hover); opacity: 0; transform: scale(0.96); transition: opacity .18s ease, transform .22s var(--ease); }
.navlink:hover::before { opacity: 1; transform: scale(1.07, 1.1); }
/* The current page stays highlighted: the menu link on inner pages, the name and photo on the home page. */
.navlink[aria-current="page"]::before { opacity: 1; transform: none; }
.navlink[aria-current="page"]:hover::before { transform: scale(1.07, 1.1); }

.brand { margin-left: -4px; padding-inline: 4px; }
.brand img { width: 28px; height: 28px; object-fit: cover; display: block; }


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

.actions { display: flex; flex-wrap: wrap; gap: 8px; }
/* Buttons in a row that should all be the width of the widest one */
.actions.equal { display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr; }
@media (max-width: 480px) { .actions.equal { grid-auto-flow: row; } }
/* No text selection on buttons: a long press on iOS was highlighting the label instead of just opening the link menu. */
.btn { display: inline-flex; align-items: center; justify-content: center; height: 44px; padding-inline: 20px; font-size: 0.98rem; font-weight: 500; text-decoration: none; white-space: nowrap; -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; }
.btn-dark { background: var(--ink); color: #FFFFFF; }
.btn-dark:hover { background: #262626; }
.btn-line { background: var(--page); color: var(--ink); box-shadow: inset 0 0 0 var(--hair) var(--line); }
.btn-line:hover { background: var(--panel); }
/* External-link icon. It sits inline after the label, sized to the capital height, so its
   bottom edge lands exactly on the text baseline. Only the arrow nudges on hover. */
.ext { display: inline-block; width: .72em; height: .72em; margin-left: .32em; vertical-align: baseline; overflow: visible; }
.ext .ea { transition: transform .2s var(--ease); }
a:hover .ext .ea { transform: translate(.8px, -.8px); }
.btn-sm { height: 36px; padding-inline: 14px; font-size: 0.9rem; min-width: 108px; }   /* same width side by side */


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

/* Sizes roughly follow elevenlabs.io: light 48px headline, 16px body. */
h1 { font-weight: 400; font-size: clamp(2.1rem, 4.2vw, 3rem); line-height: 1.08; letter-spacing: -0.025em; color: var(--ink); margin: 0 0 28px; text-wrap: balance; }
h2 { font-weight: 400; font-size: clamp(1.4rem, 2.4vw, 1.75rem); line-height: 1.2; letter-spacing: -0.02em; color: var(--ink); margin: 0 0 14px; text-wrap: balance; }
h3 { font-weight: 500; font-size: 1.05rem; line-height: 1.3; letter-spacing: -0.01em; color: var(--ink); margin: 0 0 8px; }
.label { font-size: 1rem; font-weight: 400; letter-spacing: 0; color: var(--text); margin: 0 0 14px; }


/* ---------- Page headers ---------- */

.hero { padding-block: 120px 48px; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 48px; align-items: start; }
.hero p { max-width: 36em; margin-bottom: 14px; }
.hero p a { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: currentColor; }

.page-head { padding-block: 64px 36px; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 40px; align-items: end; }
.page-head h1 { margin: 0; }
.page-head p { margin: 0; max-width: 36em; }
/* CV page: buttons on the same line as the title, against the right edge. They drop under the title only when they no longer fit. */
.page-head.end { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-end; gap: 20px 40px; }

.panel { background: var(--panel); border: var(--hair) solid var(--line); }


/* ---------- Home: areas of interest carousel ---------- */

/*
  Every tile is drawn at full size and the side ones are scaled down with a transform.
  Animating width/height meant resizing the WebGL canvas on every frame, which stuttered.
*/
.carousel { --big: 220px; --shrink: .667; --off: 320px; --row: 226px; --cap: 50px; --pt: 30px; position: relative; overflow: hidden; padding-block: var(--pt) 24px; background: var(--panel); border: var(--hair) solid var(--line); }
.stage { position: relative; height: calc(var(--row) + var(--cap)); }
.slide { position: absolute; top: 0; left: 50%; width: 300px; margin: 0; display: flex; flex-direction: column; align-items: center; text-align: center; transform: translateX(calc(-50% + var(--x, 0px))); transition: transform .6s var(--ease), opacity .4s ease; cursor: pointer; }
.slide.center { cursor: default; }
.slide.jump { transition: none; opacity: 0; }
.tile-area { height: var(--row); display: flex; align-items: center; justify-content: center; }
.frame { flex: none; width: var(--big); height: var(--big); opacity: .8; transform: scale(var(--shrink)); transition: transform .6s var(--ease), opacity .5s ease; will-change: transform; }
.slide.center .frame { opacity: 1; transform: none; }
.frame .orb { display: block; width: 100%; height: 100%; opacity: 0; transition: opacity .6s ease; }
.frame .orb.ready { opacity: 1; }

.slide figcaption { margin-top: 14px; width: 100%; }
.slide h3 { font-size: 0.95rem; font-weight: 400; color: var(--muted); margin: 0 0 6px; transition: color .4s ease; }
.slide.center h3 { color: var(--ink); }

/* Arrows centred on the caption line: padding + tile row + caption gap + half a line - half an arrow */
.arrows { position: absolute; left: 50%; top: calc(var(--pt) + var(--row) + 14px + 0.65em - 16px); font-size: 0.95rem; width: 330px; transform: translateX(-50%); display: flex; justify-content: space-between; pointer-events: none; }
.arrow { pointer-events: auto; width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; background: transparent; border: 0; color: var(--muted); cursor: pointer; transition: color .15s ease, background-color .15s ease; }
.arrow:hover { color: var(--ink); background: #E9E9E9; }
.arrow svg { width: 16px; height: 16px; }


/* ---------- Research ---------- */

/* Written as a list so new theses or papers are just another <li>. */
.list { list-style: none; margin: 0; padding: 0; }
.list > li { padding-block: 6px 0; }
.list > li + li { margin-top: 32px; }
.entry-title { font-weight: 500; color: var(--ink); margin: 0 0 4px; }
.entry-meta { color: var(--muted); font-size: 0.94rem; margin: 0 0 8px; }
.entry-desc { margin: 0 0 10px; max-width: 70ch; }
.entry-sub { font-size: 0.9rem; font-weight: 500; color: var(--muted); margin: 18px 0 6px; }
.entry-links { display: flex; gap: 16px; font-size: 0.9rem; margin: 0; }
/* Underlined text with the corner icon sitting on the text baseline */
.entry-links a { color: var(--ink); white-space: nowrap; text-underline-offset: 3px; text-decoration-color: currentColor; }


/* ---------- Projects ---------- */

.projects { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.project { padding: 32px; display: flex; flex-direction: column; min-width: 0; border-bottom: var(--hair) solid var(--line); }
.project:nth-child(odd) { border-right: var(--hair) solid var(--line); }
.project:nth-last-child(-n+2) { border-bottom: 0; }
/* Project titles are h2s for the outline, but sized like h3s */
.project h2 { font-weight: 500; font-size: 1.05rem; line-height: 1.3; letter-spacing: -0.01em; margin: 0 0 8px; }
.project p { color: var(--muted); }
.project .actions { margin-top: auto; padding-top: 8px; }
.project .stack-line { font-size: 0.88rem; margin-bottom: 14px; }

/* Isometric line drawings, inspired by the figures on linear.app */
.fig { height: 210px; margin: -8px -8px 22px; display: flex; align-items: center; justify-content: center; }
.iso { display: block; height: 170px; width: auto; max-width: 100%; overflow: visible; }
.iso path { vector-effect: non-scaling-stroke; stroke-width: 1; stroke-linejoin: round; stroke-linecap: round; }
.iso .s { fill: #F1F1F1; stroke: #A9A9A9; }        /* sides */
.iso .t { fill: #FFFFFF; stroke: #A9A9A9; }        /* top faces */
.iso .m { fill: none; stroke: #9B9B9B; }           /* marks drawn on a surface */
.iso .m.dot { fill: #9B9B9B; }
.iso .g { fill: none; stroke: #BDBDBD; stroke-dasharray: 1.5 3.5; }  /* dotted guides */
.iso .gl { fill: none; stroke: #E2E2E2; }          /* grid lines */
.iso .v { fill: #F3F3F3; stroke: none; }           /* visited cells */
.iso .route { fill: none; stroke: #6E6E6E; stroke-width: 1.4; stroke-dasharray: 600; stroke-dashoffset: 0; }
/* Each drawing plays a wave: each part marked "bob" rises and settles back, then the next one goes,
   so the motion travels through the drawing.
   kN is a part's place in the wave, w-* sets the gap between parts, tN how far each part rises. */
.iso .bob { --k: 0; --step: .2s; --ty: -8px; }
.iso .k1 { --k: 1; } .iso .k2 { --k: 2; } .iso .k3 { --k: 3; } .iso .k4 { --k: 4; }
.iso .k5 { --k: 5; } .iso .k6 { --k: 6; } .iso .k7 { --k: 7; } .iso .k8 { --k: 8; } .iso .k9 { --k: 9; }
.iso .w-layers { --step: .22s; }   /* thesis: bottom layer to top, like data moving up the pipeline */
.iso .w-nodes { --step: .3s; }     /* ROS: round the ring of nodes */
.iso .w-notes { --step: .11s; }    /* Not3s: back card to front */
.iso .t7 { --ty: -7px; } .iso .t12 { --ty: -12px; } .iso .t14 { --ty: -14px; }


/* ---------- CV ---------- */

.cv { display: grid; grid-template-columns: 200px minmax(0, 1fr); }
.cv > h2 { font-size: 1.15rem; font-weight: 500; letter-spacing: -0.01em; padding: 28px 24px; margin: 0; border-top: var(--hair) solid var(--line); }
.cv > div { padding: 28px 24px 28px 0; border-top: var(--hair) solid var(--line); min-width: 0; }
.cv > h2:first-of-type, .cv > h2:first-of-type + div { border-top: 0; }
.cv p:last-child { margin-bottom: 0; }
.cv strong { font-weight: 500; color: var(--ink); }
.item + .item { margin-top: 18px; }
.item-head { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.item-title { font-weight: 500; color: var(--ink); margin: 0; }
.item-when { color: var(--muted); font-variant-numeric: tabular-nums; margin: 0; white-space: nowrap; }
.item-sub { color: var(--muted); margin: 0 0 6px; }
.item ul { margin: 6px 0 0; padding-left: 18px; }
.item li { margin-bottom: 4px; }


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

footer { border-top: var(--hair) solid var(--line); }
.foot { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px 24px; padding-block: 14px; font-size: 0.85rem; line-height: 1.4; color: var(--muted); }
.foot nav { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.foot a { display: inline-flex; align-items: center; gap: 3px; color: var(--muted); text-decoration: none; transition: color .15s ease; }
.foot a:hover { color: var(--ink); }


/* ---------- Smaller screens ---------- */

@media (max-width: 860px) {
  .hero, .page-head { grid-template-columns: 1fr; gap: 20px; }
  .hero { padding-block: 76px 36px; }
  .page-head { padding-block: 44px 28px; }
  .projects { grid-template-columns: 1fr; }
  .project:nth-child(odd) { border-right: 0; }
  .project:nth-last-child(2) { border-bottom: var(--hair) solid var(--line); }
  .cv { grid-template-columns: 1fr; }
  .cv > h2 { padding: 24px 12px 0; border-top: var(--hair) solid var(--line); }
  .cv > div { border-top: 0; padding: 10px 12px 24px; }
}

@media (max-width: 640px) {
  .carousel { --big: 170px; --shrink: .61; --off: 195px; --row: 176px; --cap: 48px; --pt: 22px; }
  .slide { width: 220px; }
  .slide:not(.center) figcaption { opacity: 0; }
  .arrows { width: 300px; }
}

@media (max-width: 520px) {
  .wrap { padding-inline: 16px; }
  .menu { gap: 0; margin-right: -8px; }
  .navlink { padding-inline: 9px; font-size: 0.94rem; }
  .project { padding: 24px 20px; }
}


/* ---------- Reveal animations ---------- */

/*
  The little script in each page's <head> adds "js wait" to <html> before anything paints,
  so content never flashes up and then disappears. site.js removes "wait" once the font is ready,
  and the head script removes it after 3s anyway in case site.js fails to load.
*/
.js.wait main { opacity: 0; }

/* Top bar: on the first page of a visit the grey fades in from white and the links blur in.
   "bar-in" is set by the head script, so later pages show the bar straight away. */
.bar-in .bar { animation: barIn .65s var(--ease) both; }
@keyframes barIn { from { background-color: var(--page); border-bottom-color: transparent; } }
.bar-in .bar .navlink { opacity: 0; filter: blur(3px); animation: wordIn .6s var(--ease) forwards; animation-delay: 60ms; }
.bar-in .menu .navlink:nth-child(1) { animation-delay: 100ms; }
.bar-in .menu .navlink:nth-child(2) { animation-delay: 145ms; }
.bar-in .menu .navlink:nth-child(3) { animation-delay: 190ms; }
.bar-in.wait .bar, .bar-in.wait .bar .navlink { animation-play-state: paused; }

/* Headline words fade in one by one (only opacity and a light blur, so nothing shifts). Kept quick and subtle. */
.js h1 .w { display: inline-block; opacity: 0; filter: blur(4px); animation: wordIn .75s var(--ease) forwards; animation-delay: calc(var(--i) * 45ms + 50ms); will-change: opacity, filter; backface-visibility: hidden; }
.js.wait h1 .w { animation-play-state: paused; }
@keyframes wordIn { to { opacity: 1; filter: blur(0); } }

/* Everything else fades up as it scrolls into view. */
.js .blur-in { opacity: 0; filter: blur(3px); transform: translateY(6px); transition: opacity .7s var(--ease), filter .7s var(--ease), transform .7s var(--ease); transition-delay: var(--d, 0ms); }
.js .blur-in.is-in { opacity: 1; filter: blur(0); transform: none; }
.js .blur-in.no-tr { transition: none; }
.js .blur-in.quick { transition-duration: .45s; transition-delay: 0ms; }

@media (prefers-reduced-motion: no-preference) {
  /* The project drawings stay still until you hover a project card (or tab into it).
     Wave: in each 1.8s cycle a part spends the first third rising and settling, then waits for the rest. */
  .project:is(:hover, :focus-within) .iso .bob { animation: wave 1.8s ease-in-out calc(var(--k) * var(--step)) infinite both; }
  @keyframes wave { 0%, 34%, 100% { transform: none; } 17% { transform: translateY(var(--ty)); } }
  /* the dotted links between ROS nodes "carry messages" */
  .project:is(:hover, :focus-within) .iso .flow path { animation: march .8s linear infinite; }
  @keyframes march { to { stroke-dashoffset: -10; } }
  /* the pathfinding route draws itself, holds, then draws again */
  .project:is(:hover, :focus-within) .iso .route { animation: trace 3.2s cubic-bezier(.4, 0, .2, 1) infinite; }
  @keyframes trace { 0% { stroke-dashoffset: 600; } 55%, 100% { stroke-dashoffset: 0; } }
  /* site.js also pauses drawings that are off screen */
  .fig.paused .bob, .fig.paused .flow path, .fig.paused .route { animation-play-state: paused; }
}

@media (prefers-reduced-motion: reduce) {
  .navlink::before, .navlink:hover::before { transition: none; transform: none; }
  .slide, .frame, .slide h3 { transition: none; }
}
