/* ali.roudaki.me
   Single page, no build step.

   Left-aligned editorial composition: the asymmetry is what carries it now that
   there is no portrait. Four motion gestures, each with one job — entrance,
   rule draw, ambient drift, link underline. */

:root {
  --bg:      #fafaf9;
  --fg:      #16161a;
  --muted:   #63636e;
  --hairline: rgba(22, 22, 26, 0.14);
  --focus:   #3b5bdb;
  /* The one accent. Same value in both themes so the bar matches the red inside the
     wordmark exactly — two near-but-not-equal reds read as a mistake. */
  --rule:    #a8253a;

  --glow-a: 59, 91, 219;    /* cool */
  --glow-b: 214, 92, 118;   /* warm */
  --glow-strength: 0.52;

  --ease: cubic-bezier(0.22, 0.8, 0.24, 1);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:      #0a0a0c;
    --fg:      #f4f4f5;
    --muted:   #9d9da8;
    --hairline: rgba(244, 244, 245, 0.18);
    --focus:   #8ea2ff;
    --glow-strength: 0.6;
  }
}

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

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

body {
  margin: 0;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(1.75rem, 7vw, 5rem);

  background: var(--bg);
  color: var(--fg);
  font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont,
               "Segoe UI Variable Display", "Segoe UI", Inter, system-ui, sans-serif;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;

  overflow: hidden;   /* the orbs drift beyond the viewport */
}

/* ---------- ambient wash ---------- */

.ambient {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Hold the wash off the centre so text always sits on flat colour. */
  mask-image: radial-gradient(125% 95% at 50% 50%, transparent 12%, #000 72%);
}

.orb {
  position: absolute;
  width: min(70vw, 720px);
  aspect-ratio: 1;
  border-radius: 50%;
  filter: blur(80px);
  opacity: var(--glow-strength);
  will-change: transform;
}

.orb-a {
  top: -20%;
  left: -14%;
  background: radial-gradient(circle at 50% 50%, rgba(var(--glow-a), 0.9), transparent 68%);
  animation: drift-a 22s var(--ease) infinite alternate;
}

.orb-b {
  right: -18%;
  bottom: -22%;
  background: radial-gradient(circle at 50% 50%, rgba(var(--glow-b), 0.85), transparent 68%);
  animation: drift-b 27s var(--ease) infinite alternate;
}

@keyframes drift-a {
  to { transform: translate3d(24vw, 16vh, 0) scale(1.28); }
}

@keyframes drift-b {
  to { transform: translate3d(-21vw, -14vh, 0) scale(1.22); }
}

/* ---------- composition ---------- */

.stage {
  position: relative;
  z-index: 1;
  width: min(100%, 38rem);
  text-align: left;
}

/* ---------- wordmark ---------- */

/* Self-hosted so the page makes no external requests. Latin subset, 10 kB. */
@font-face {
  font-family: "IBM Plex Mono";
  src: url("assets/plex-mono-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* A dark tile in both themes. It carries its own ground colour, so it does not
   invert with the page — that is deliberate, it reads as a mark rather than as text. */
.mark {
  --mark-size: 4.5rem;
  --mark-ink: #f2f1ee;
  --mark-ground: #0d0f11;

  width: var(--mark-size);
  height: var(--mark-size);
  margin-bottom: clamp(1.5rem, 3.5vw, 2rem);
  background: var(--mark-ground);
  border-radius: 1rem;

  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(var(--mark-size) * 0.039);

  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, monospace;
  user-select: none;

  box-shadow: 0 0 0 1px var(--hairline), 0 10px 30px -12px rgba(0, 0, 0, 0.5);
  transition: transform 0.6s var(--ease);
}

.mark:hover { transform: translateY(-2px); }

.mark__a {
  font-size: calc(var(--mark-size) * 0.453);
  font-weight: 500;
  line-height: 0.78;
  color: var(--mark-ink);
  width: calc(var(--mark-size) * 0.242);
  height: calc(var(--mark-size) * 0.418);
}

/* The red bar holding the dot and stem of an "i".
   It arrives LAST, on the same rhythm as everything else: the entrance stagger is
   `--i * 110ms + 140ms`, and this is step 6 — one beat after the links at step 5.
   It fades in once and stays. No blink: a permanently flashing element on a landing
   page pulls the eye off the name for as long as the tab is open. */
.mark__i {
  width: calc(var(--mark-size) * 0.102);
  height: calc(var(--mark-size) * 0.391);
  background: var(--rule);

  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: flex-end;
  gap: calc(var(--mark-size) * 0.0195);

  opacity: 0;
  animation: mark-i-in 0.45s var(--ease) calc(6 * 110ms + 140ms) forwards;
}

.mark__i span {
  width: calc(var(--mark-size) * 0.0273);
  background: var(--mark-ink);
}

.mark__i span:first-child { height: calc(var(--mark-size) * 0.0273); }
.mark__i span:last-child  { height: calc(var(--mark-size) * 0.3125); }

@keyframes mark-i-in { to { opacity: 1; } }

.name {
  margin: 0;
  font-size: clamp(2.75rem, 10vw, 5rem);
  font-weight: 600;
  letter-spacing: -0.048em;
  line-height: 1.0;
}

/* Accent bar that draws itself in — the one flourish on the page. */
.rule {
  display: block;
  width: 0;
  height: 3px;
  margin: clamp(1.6rem, 3.5vw, 2.25rem) 0;
  background: var(--rule);
  animation: draw 1.1s var(--ease) forwards;
  animation-delay: 490ms;
}

@keyframes draw {
  to { width: 4rem; }
}

.tagline {
  margin: 0;
  font-size: clamp(1.0625rem, 2.5vw, 1.3rem);
  line-height: 1.5;
  font-weight: 400;
  color: var(--muted);
}

/* Each line is its own element so they can arrive in sequence. */
.tagline .line {
  display: block;
  text-wrap: balance;
}

.tagline .line + .line {
  margin-top: 0.2em;
}

/* ---------- links ---------- */

.links {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1.25rem, 4vw, 2.25rem);
  margin-top: clamp(2.5rem, 6vw, 3.5rem);
  padding-top: clamp(1.75rem, 4vw, 2.25rem);
  border-top: 1px solid var(--hairline);
}

.links a {
  position: relative;
  color: var(--fg);
  text-decoration: none;
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  padding-block: 0.15em;
  transition: color 0.35s var(--ease);
}

/* Underline sweeps in from the left, out to the right. */
.links a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.45s var(--ease);
}

.links a:hover::after,
.links a:focus-visible::after {
  transform: scaleX(1);
  transform-origin: left;
}

.links a:hover { color: var(--focus); }

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

/* ---------- entrance ---------- */

.reveal {
  opacity: 0;
  transform: translateY(16px);
  animation: rise 0.95s var(--ease) forwards;
  animation-delay: calc(var(--i) * 110ms + 140ms);
}

@keyframes rise {
  to { opacity: 1; transform: none; }
}

/* ---------- motion off ---------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  .rule   { width: 4rem; }

  /* Explicit, because the blanket rule above collapses durations but not delays —
     without this the red bar would still sit invisible for 800ms first. */
  .mark__i { animation: none; opacity: 1; }
}
