/* base.css - reset, type roles, page chrome. Components live in components.css. */

/* ---------- fonts ----------
   Self-hosted on purpose. Loading these from fonts.googleapis.com sent every visitor's
   IP address to a third party in the United States before they had agreed to anything,
   which is the one transfer this site used to make. Serving them from our own origin
   removes it, drops two preconnect round trips, and is why the site needs no cookie
   banner. Re-download with the recipe in tools/REFRESH.md if a face is ever updated. */

@font-face {
  font-family: 'Jersey 25';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/jersey25-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Jersey 25';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/jersey25-latin.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;
}
@font-face {
  font-family: 'Silkscreen';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/silkscreen-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Silkscreen';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/silkscreen-latin.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;
}
@font-face {
  font-family: 'VT323';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/vt323-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'VT323';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/vt323-latin.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;
}

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

/* Two pages that share a header have no business repainting it from scratch when you move
   between them. Progressive by construction: a browser without cross-document view
   transitions simply navigates, which is exactly what it does today. The two named
   elements are the only ones that genuinely persist across the boundary. */
@view-transition { navigation: auto; }

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

body {
  margin: 0;
  min-height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--body-mixed);
  font-size: var(--fs-body);
  line-height: 1.35;
  overflow-x: hidden;
}

body.is-locked { overflow: hidden; }

a { color: inherit; text-decoration: none; }
/* No image on this site is its own target: every one sits inside a button or a link, or
   is decorative. Taking them out of hit testing stops the browser's native image drag
   from hijacking a rail drag, and stops a swipe over a thumbnail from selecting it
   instead of scrolling. The button underneath still hovers, focuses and clicks. */
img {
  max-width: 100%;
  display: block;
  pointer-events: none;
  user-select: none;
}
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
input { font: inherit; }

/* Focus must be structurally different from hover, because hover here is only a
   border-colour change. The offset gap is what reads as focus. outline does not
   reflow, which a border-width change would. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
:focus:not(:focus-visible) { outline: none; }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 8px;
  z-index: 100;
  font-family: var(--ui);
  font-size: var(--fs-ui);
  padding: 12px 16px;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
}
.skip-link:focus { left: 14px; }

/* ---------- topbar ---------- */

.topbar {
  view-transition-name: chrome;
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  background: rgba(16, 16, 16, .85);
  backdrop-filter: blur(10px);
  border-bottom: 2px solid var(--stroke);
}

.brand {
  view-transition-name: brand;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius);
}

.brand__mark {
  width: 18px;
  height: 18px;
  border-radius: 3px;
  object-fit: cover;
  image-rendering: pixelated;
}

.brand__name {
  font-family: var(--ui);
  font-size: var(--fs-ui-lg);
  letter-spacing: .5px;
}

.topnav { display: flex; align-items: center; gap: 10px; }

/* Two destinations do not need to be hidden behind a control on a 1440px screen. Above
   900px they are simply there; below it the burger takes over, because at 390px two more
   labels beside the wordmark is the row that wraps. Which one is current resolves from
   <body data-page>, so the chrome markup stays byte-identical on all three pages. */
.topnav__link {
  display: none;
  padding: 10px 4px;
  border-bottom: 2px solid transparent;
  font-family: var(--ui);
  font-size: var(--fs-ui);
  letter-spacing: var(--track-ui);
  color: var(--muted);
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.topnav__link:hover { color: var(--text); border-bottom-color: var(--stroke2); }

[data-page="work"] .topnav__link[data-accent="work"],
[data-page="timeline"] .topnav__link[data-accent="timeline"] {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

@media (min-width: 900px) {
  .topnav__link { display: inline-block; }
  .topnav__burger { display: none; }
}

.topnav__burger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 48px;
  min-height: 48px;
  padding: 10px 12px;
  border-radius: var(--radius);
  background: var(--bg2);
  border: var(--ui-border);
}
.topnav__burger:hover,
.topnav__burger[aria-expanded="true"] { border-color: var(--accent); }

.topnav__burger span,
.topnav__burger span::before,
.topnav__burger span::after {
  display: block;
  width: 18px;
  height: 2px;
  background: currentColor;
  transition: transform var(--dur-fast) var(--ease-out), top var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.topnav__burger span { position: relative; }
.topnav__burger span::before,
.topnav__burger span::after { content: ""; position: absolute; left: 0; }
.topnav__burger span::before { top: -6px; }
.topnav__burger span::after { top: 6px; }

/* Open is a close control, so it looks like one: the middle bar goes, the outer two slide
   to the centre and cross. The control never changes size, so nothing around it moves. */
.topnav__burger[aria-expanded="true"] span { background-color: transparent; }
.topnav__burger[aria-expanded="true"] span::before { top: 0; transform: rotate(45deg); }
.topnav__burger[aria-expanded="true"] span::after { top: 0; transform: rotate(-45deg); }

/* Not display:none. A menu that is switched off cannot animate, and the exit is the half
   everyone forgets: this one grows out of the corner the burger sits in and shrinks back
   into it. visibility is what keeps a closed menu out of the tab order, and its transition
   is delayed on the way out so the menu only disappears once the movement has finished. */
.topnav__dropdown {
  visibility: hidden;
  opacity: 0;
  transform: translateY(-6px) scale(.94);
  transform-origin: top right;
  position: absolute;
  right: 14px;
  top: 62px;
  min-width: 180px;
  padding: 10px;
  border-radius: var(--radius2);
  background: rgba(22, 22, 22, .98);
  border: var(--ui-border);
  transition: opacity var(--dur-ui) var(--ease-out),
              transform var(--dur-ui) var(--ease-out),
              visibility 0s linear var(--dur-ui);
}
.topnav__dropdown[data-open="true"] {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition-delay: 0s;
}

.topnav__dropitem {
  display: block;
  padding: 12px;
  border-radius: var(--radius);
  font-family: var(--ui);
  font-size: var(--fs-ui);
  letter-spacing: .5px;
  background: rgba(255, 255, 255, .03);
  border: var(--ui-border);
}
.topnav__dropitem + .topnav__dropitem { margin-top: 8px; }
.topnav__dropitem:hover { border-color: var(--accent); }

/* ---------- hero + tab bar ---------- */
/* The references all open with a promise and a face, centred, then get out of the way.
   Only the topbar and the tab bar are shared between pages now: everything below is the
   page's own, which is what stopped the work page's whole hero from being
   duplicated onto the timeline. Which tab is active is still resolved from
   <body data-page> down at .tabs__link, never in the HTML. */

.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 40px 0 32px;
  text-align: center;
}
/* On a phone the hero was around 580px of introduction before the first thumbnail, which is
   most of a screen spent on a page the visitor has not yet been given a reason to trust. It
   is the work that earns the trust, so the work arrives sooner. */
@media (max-width: 640px) {
  .hero { gap: 12px; padding: 24px 0 20px; }
}

.hero--compact {
  align-items: flex-start;
  gap: 12px;
  padding: 24px 0 8px;
  text-align: left;
}

.hero__id {
  display: flex;
  align-items: center;
  gap: 14px;
}

.hero__avatar {
  flex: none;
  width: 72px;
  height: 72px;
  border-radius: var(--pill);
  border: var(--ui-border);
  background: var(--panel2);
  object-fit: cover;
}

.hero__name {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(34px, 4.4vw, 44px);
  line-height: .95;
  letter-spacing: 1px;
  color: var(--text);
}

/* One word of the headline carries the accent. The rest stays --text so the colour reads
   as emphasis rather than as a second heading. Same job as .cta__pop, different section,
   so it keeps its own name rather than sharing one across two components.

   It also takes the only pixel wipe in the hero: a block rule that builds under the word in
   four visible steps instead of sliding, because four steps is what makes it read as blocks
   rather than as a bar. It runs once, after the entrance has landed, and afterwards it is
   simply an underline. [data-wipe="done"] is the reduced-motion state: the finished rule,
   with nothing having moved to produce it. */
.hero__pop {
  position: relative;
  color: var(--accent);
}
.hero__pop::after {
  content: "";
  position: absolute;
  /* The inline box is wider than the ink: letter-spacing hangs off the right of the last
     letter, so the rule needs that em back or it reads as shifted left. */
  left: 0;
  right: .03em;
  bottom: -.05em;
  height: 5px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
}
.hero__pop[data-wipe="done"]::after { transform: scaleX(1); }
.hero__pop[data-wipe="run"]::after { animation: pop-wipe var(--dur-ui) steps(4, end) forwards; }

@keyframes pop-wipe {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

.hero__title {
  margin: 0;
  max-width: 20ch;
  font-family: var(--display);
  font-size: clamp(40px, 7vw, 72px);
  font-weight: 400;
  line-height: .95;
  letter-spacing: 1px;
  color: var(--text);
}


.hero__role {
  margin: 6px 0 0;
  font-family: var(--ui);
  font-size: var(--fs-ui);
  line-height: 1.8;
  letter-spacing: var(--track-ui);
  text-transform: uppercase;
  color: var(--muted);
}

/* What he cuts in. Quieter than the role line above it and far quieter than the headline
   between them, because it is the least important of the three and should read that way. It
   replaced five pills, which were five buttons that could not be pressed. */
.hero__craft {
  margin: 0;
  font-family: var(--ui);
  font-size: var(--fs-ui);
  line-height: 1.9;
  letter-spacing: var(--track-ui);
  color: var(--muted2);
}

/* The headline Rayan has not written yet. Loud on purpose: a placeholder that looks
   like finished copy is how a placeholder ships. */
.ph {
  display: inline-block;
  padding: 4px 14px;
  border: 2px dashed var(--accent);
  border-radius: var(--radius);
  color: var(--accent);
}

/* Announced, never shown. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- page ---------- */

.page {
  max-width: 1320px;
  margin: 0 auto;
  padding: 18px 18px 72px;
}

/* ---------- footer ---------- */

.site-footer {
  margin-top: 44px;
  padding: 24px 18px 36px;
  text-align: center;
  font-family: var(--ui);
  font-size: var(--fs-ui);
  line-height: 2;
  letter-spacing: var(--track-ui);
  color: var(--muted);
}
.site-footer a { color: var(--accent); }
.site-footer a:hover { text-decoration: underline; text-underline-offset: 3px; }
.site-footer__note { color: #6f6f6f; }
