/* Guilherme Solski Alves — a light, minimal page coloured as a dawn.

   One rule governs every colour on this page: hue encodes a position in
   time. The career rail travels the spectrum from student (indigo 245°,
   2015) to today (amber 35°, 2025), and the sections below inherit their
   stop from the same walk, so scrolling the page warms it the way scrubbing
   the rail does. Nothing is tinted for decoration alone.

   System fonts only: zero font bytes, zero external requests. */

:root {
  --paper: #ffffff;
  --ink: #1d1e42;
  --body-text: #40425f;
  --muted: #616380;      /* 5.83:1 on paper — smallest AA text colour */
  --soft: #83859c;       /* 3.62:1 — icons and rules only, never text */
  --line: #e6e7f0;
  --accent: #8f5700;
  --night: #05004c;
  --dawn-indigo: hsl(245 70% 58%);
  --dawn-rose: hsl(320 65% 55%);
  --dawn-amber: hsl(35 90% 55%);
  --dawn-line: linear-gradient(90deg, var(--dawn-indigo), var(--dawn-rose) 55%, var(--dawn-amber));
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-back: cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Registered so the timeline glides across the spectrum when the selected
   year changes instead of snapping. inherits: true is load-bearing — the
   nav arrows read the hue set on the section. */
@property --year-h {
  syntax: "<number>";
  inherits: true;
  initial-value: 245;
}

* {
  box-sizing: border-box;
}

html {
  height: 100%;
}

body {
  margin: 0;
  min-height: 100%;
  overflow-x: clip; /* the intro aurora bleeds past the page column */
  background: var(--paper);
  color: var(--body-text);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection {
  background: hsl(35 90% 60% / 0.3);
}

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

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

.page {
  max-width: 60rem;
  margin: 0 auto;
  padding: 0 clamp(1.25rem, 5vw, 3rem) 0;
  transition: transform 0.5s ease, opacity 0.5s ease;
}

.page--leaving {
  transform: translateX(-6%);
  opacity: 0;
}

/* Above the fold the entrance follows the clock, staggered by reading
   order. Below it, the scroll takes over (see the @supports block). */
.intro,
.timeline,
.focus,
.cards,
.footer {
  animation: rise 0.6s var(--ease-out) both;
}

.timeline { animation-delay: 0.08s; }
.focus { animation-delay: 0.14s; }
.cards { animation-delay: 0.2s; }
.footer { animation-delay: 0.26s; }

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
}

/* Each element reveals on its own view() timeline, so the cascade comes free
   from document order. Fenced by @supports — everywhere else the load-time
   rise above still runs, so nothing can be left invisible. The range
   completes early so elements straddling the fold at load are near-opaque. */
@supports (animation-timeline: view()) {
  .focus,
  .cards {
    animation: none;
  }

  .focus__item,
  .card {
    animation: rise 1s var(--ease-out) both;
    animation-timeline: view(); /* must follow the shorthand — it resets this */
    animation-range: entry 4% entry 46%;
  }

  .footer {
    animation: rise 1s var(--ease-out) both;
    animation-timeline: view();
    animation-range: entry 0% entry 60%;
  }
}

/* ---- section labels ---- */

.section-label {
  margin: 0;
  font: 600 0.75rem/1.4 var(--font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---- intro ---- */

.intro {
  position: relative;
  padding-top: clamp(3rem, 10vh, 5.5rem);
}

/* The aurora behind the intro: the night scene's colours bleeding into the
   morning page. Two layers drifting in counter-phase with mismatched
   periods, so the sky never reads as a loop. Hotspots stay off the text
   column; transform and opacity only, so both live on the compositor. */
.intro::before,
.intro::after {
  content: "";
  position: absolute;
  z-index: -1;
  top: -6rem;
  /* Full-bleed regardless of how wide the text column happens to be: an
     inset measured from the column leaves a visible seam at some viewport
     widths. body's overflow-x: clip absorbs the scrollbar's worth of vw. */
  left: 50%;
  width: 100vw;
  margin-left: -50vw;
  height: 26rem;
  pointer-events: none;
}

.intro::before {
  background:
    radial-gradient(38rem 20rem at 16% 28%, hsl(245 80% 60% / 0.16), transparent 65%),
    radial-gradient(30rem 18rem at 78% 10%, hsl(35 90% 58% / 0.2), transparent 65%),
    radial-gradient(24rem 16rem at 52% 62%, hsl(320 75% 60% / 0.12), transparent 70%);
  animation: aurora-drift 26s ease-in-out infinite alternate;
  will-change: transform;
}

.intro::after {
  background:
    radial-gradient(30rem 16rem at 34% 12%, hsl(320 75% 60% / 0.1), transparent 65%),
    radial-gradient(26rem 18rem at 88% 48%, hsl(245 80% 62% / 0.08), transparent 70%);
  animation: aurora-breathe 19s ease-in-out infinite alternate;
  will-change: transform, opacity;
}

@keyframes aurora-drift {
  from { transform: translate3d(-1.5%, -1%, 0) scale(1); }
  to { transform: translate3d(1.5%, 2%, 0) scale(1.06); }
}

@keyframes aurora-breathe {
  from { opacity: 0.65; transform: translate3d(1%, 1%, 0); }
  to { opacity: 1; transform: translate3d(-1%, -1.5%, 0); }
}

/* The dawn orbits the portrait. The ring is its own element rather than a
   conic border on the avatar, so the rotation is a plain transform on the
   compositor instead of an interpolated custom property. */
.avatar {
  position: relative;
  display: block;
  width: 84px;
  height: 84px;
  margin-bottom: 1.5rem;
}

.avatar__ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(
    var(--dawn-indigo),
    var(--dawn-rose),
    var(--dawn-amber),
    var(--dawn-indigo)
  );
  animation: ring-spin 16s linear infinite;
}

@keyframes ring-spin {
  to { transform: rotate(360deg); }
}

.avatar__img {
  position: absolute;
  inset: 4px;
  width: 76px;
  height: 76px;
  border-radius: 50%;
  border: 2px solid var(--paper);
  background: var(--paper);
  object-fit: cover;
}

.eyebrow {
  margin: 0 0 0.875rem;
  font: 500 0.8125rem/1.4 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

.intro__name {
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
  margin: 0 0 1.5rem;
  font-size: clamp(2.5rem, 8vw, 3.75rem);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.05;
  color: var(--ink);
}

/* The name wears the dawn itself: a slow pan clipped to the glyphs, deep
   stops so AA holds. First and last stops match, so the 300% pan loops
   seamlessly. Kept off the h1 itself — a parent's clipped background paints
   through every descendant, which would swallow the star link beside it. */
@supports (-webkit-background-clip: text) or (background-clip: text) {
  .intro__name > span {
    background-image: linear-gradient(100deg, #281e8f, #8f1e7e 30%, #8f601e 55%, #8f1e7e 78%, #281e8f);
    background-size: 300% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    animation: name-drift 22s linear infinite;
  }
}

@keyframes name-drift {
  to { background-position: -300% 0; }
}

/* The night scene is a real link beside the heading, not a heading that is
   secretly a button: crawlers follow it, the h1 stays an h1. */
/* inline-flex, not inline: as a flex item the link would otherwise be
   blockified into a line box as tall as the 60px heading, dropping the
   16px star onto the name's baseline. em keeps it tracking the clamp(). */
.intro__star {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  margin-top: 0.22em;
  color: var(--soft);
  transition: color 0.25s ease, transform 0.4s var(--ease-back);
}

.intro__star:hover,
.intro__star:focus-visible {
  color: var(--accent);
  transform: rotate(72deg) scale(1.15);
}

.intro__lead {
  margin: 0 0 0.75rem;
  max-width: 45rem;
  font-size: 1.1875rem;
  line-height: 1.6;
  color: var(--ink);
}

.intro__about {
  margin: 0;
  max-width: 45rem;
  font-size: 1.0625rem;
  text-wrap: pretty;
}

/* ---- timeline (the signature) ---- */

.timeline {
  margin-top: clamp(3rem, 8vh, 5rem);
  padding-top: 2.5rem;
  border-top: 1px solid var(--line);
}

.timeline:focus-visible {
  outline-offset: 8px;
}

.timeline__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  margin-bottom: 1.75rem;
}

.timeline__nav {
  display: flex;
  gap: 0.5rem;
}

/* The step buttons borrow the selected year's hue from the section, so the
   controls sit in the same moment as the panel they are moving through. */
.timeline__step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid hsl(var(--year-h) 50% 78%);
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
  transition: border-color 0.3s ease, color 0.2s ease, transform 0.2s ease;
}

.timeline__step:disabled {
  opacity: 0.35;
  cursor: default;
}

.timeline__step:hover:enabled { transform: scale(1.06); }
.timeline__step:active:enabled { transform: scale(0.92); }

/* On a narrow screen the rail is wider than the page and scrolls; the mask
   fades whatever is cut off rather than letting it end mid-dot. */
.timeline__rail {
  position: relative;
  overflow-x: auto;
  scrollbar-width: none;
}

.timeline__rail::-webkit-scrollbar {
  display: none;
}

/* max-content, not 100%: once the dots overflow a narrow screen the strip
   must grow with them, or the rail line stays viewport-wide and stops
   halfway along a career that keeps going. */
.timeline__strip {
  position: relative;
  width: max-content;
  min-width: 100%;
}

/* Half a column of inset at each end, computed from --dots, so the line
   starts and stops under the first and last dot however many there are. */
.timeline__line,
.timeline__fill {
  position: absolute;
  left: calc(50% / var(--dots));
  right: calc(50% / var(--dots));
  bottom: 5px;
  height: 2px;
  border-radius: 999px;
}

.timeline__line {
  background: var(--line);
}

/* The travelled part of the career, clipped rather than scaled — scaling
   would squash the gradient and move every hue off its year. */
.timeline__fill {
  background: var(--dawn-line);
  transition: clip-path 0.5s var(--ease-out);
}

.timeline__dates {
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
}

.timeline__dates > li {
  flex: 1 1 0;
  display: flex;
  justify-content: center;
  min-width: 2.75rem; /* below this the rail scrolls instead of crushing */
}

.timeline__date {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 0 4px;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--muted);
}

/* Both slots keep their line whether or not they are filled, so every year
   label and every dot sits on one baseline across the rail. */
.timeline__date-star {
  display: block;
  height: 11px;
  color: hsl(var(--year-h) 65% 40%);
}

.timeline__date-year {
  display: block;
  height: 14px;
  font: 400 0.75rem/14px var(--font-mono);
  letter-spacing: 0.02em;
  color: inherit;
}

.timeline__date--passed,
.timeline__date--active {
  color: hsl(var(--year-h) 65% 34%);
}

.timeline__date--active .timeline__date-year {
  font-weight: 700;
}

/* The border carries the contrast (3.5:1 at every hue), the fill carries
   the colour — so the brighter amber the design wants stays legible. */
.timeline__dot {
  display: block;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 2px solid var(--line);
  background: var(--paper);
  transition: transform 0.25s var(--ease-back), border-color 0.2s ease, background 0.2s ease;
}

.timeline__date--passed .timeline__dot {
  border-color: hsl(var(--year-h) 62% 44%);
}

.timeline__date--active .timeline__dot {
  border-color: hsl(var(--year-h) 62% 44%);
  background: hsl(var(--year-h) 72% 55%);
  transform: scale(1.25);
}

/* Only the active dot beats. Giving the animation its own class keeps the
   other fourteen off the compositor. */
.timeline__dot--beat {
  animation: dot-pulse 2.8s ease-out infinite;
}

@keyframes dot-pulse {
  0% { box-shadow: 0 0 0 0 hsl(var(--year-h) 72% 55% / 0.5); }
  70%,
  100% { box-shadow: 0 0 0 10px hsl(0 0% 0% / 0); }
}

/* One panel at a time. The unselected ones are taken out of flow rather
   than out of the document so the height stays the selected panel's. */
.timeline__events {
  position: relative;
  margin-top: 2.5rem;
  /* Holds the height of a four-line milestone so stepping between short and
     long ones does not shuffle the rest of the page under the reader. */
  min-height: 11rem;
}

.timeline__event {
  position: absolute;
  inset: 0;
  visibility: hidden;
  opacity: 0;
}

.timeline__event--selected {
  position: relative;
  visibility: visible;
  opacity: 1;
}

.timeline__event--enter-right {
  animation: slide-in-right 0.45s var(--ease-out) both;
}

.timeline__event--enter-left {
  animation: slide-in-left 0.45s var(--ease-out) both;
}

@keyframes slide-in-right {
  from { opacity: 0; transform: translateX(1.5rem); }
}

@keyframes slide-in-left {
  from { opacity: 0; transform: translateX(-1.5rem); }
}

.timeline__event-date {
  margin: 0 0 0.625rem;
  font: 600 0.8125rem/1.4 var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: hsl(var(--year-h) 65% 34%);
}

.timeline__event-body {
  margin: 0;
  max-width: 45rem;
  font-size: 1.0625rem;
  line-height: 1.7;
  color: var(--ink);
  text-wrap: pretty;
}

.timeline__event-quote {
  margin: 1.125rem 0 0;
  padding-left: 1rem;
  max-width: 45rem;
  border-left: 2px solid hsl(var(--year-h) 62% 62%);
  font-size: 0.9375rem;
  line-height: 1.65;
  font-style: italic;
  color: var(--body-text);
}

/* ---- timeline, prerendered ----
   The rail shows one milestone at a time, which is right for a reader and
   wrong for anything that only sees the HTML — fourteen of fifteen panels
   would ship aria-hidden. So the prerendered page is the whole career as an
   ordered list with machine-readable dates, and WASM swaps in the rail once
   it boots. This is also what a reader sees for the second before that. */

.timeline__list {
  display: grid;
  gap: 2rem;
  margin: 2rem 0 0;
  padding: 0;
  list-style: none;
}

.timeline__entry {
  padding-left: 1.25rem;
  border-left: 2px solid hsl(var(--year-h) 62% 62%);
}

/* ---- what I work on ---- */

.focus {
  margin-top: clamp(3.5rem, 9vh, 5.5rem);
  padding-top: 2.5rem;
  border-top: 1px solid var(--line);
}

.focus__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 2.5rem 3rem;
  margin-top: 1.75rem;
}

.focus__rule {
  display: block;
  width: 40px;
  height: 3px;
  margin-bottom: 0.875rem;
  border-radius: 999px;
  background: hsl(var(--focus-h) 72% 55%);
}

.focus__title {
  margin: 0 0 0.5rem;
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--ink);
}

.focus__body {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.65;
  text-wrap: pretty;
}

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

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 3rem;
  margin-top: clamp(3.5rem, 9vh, 5.5rem);
}

/* No wash, no shadow: each card is named by a 2px rule in its own stop of
   the dawn, and the reading order walks the spectrum. */
.card {
  padding-top: 1rem;
  border-top: 2px solid hsl(var(--card-h) 72% 60%);
}

.card__title {
  margin: 0 0 0.875rem;
  font: 600 0.75rem/1.4 var(--font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: hsl(var(--card-h) 45% 36%);
}

.card__note {
  margin: 0.875rem 0 0;
  font: 400 0.75rem/1.6 var(--font-mono);
  letter-spacing: 0.02em;
  color: var(--muted);
}

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

.links {
  display: flex;
  flex-direction: column;
  gap: 0.6875rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.links__item {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  font-size: 0.9375rem;
  line-height: 1.4;
  color: var(--ink);
  text-decoration: none;
  overflow-wrap: anywhere;
}

/* Not every line in a card is a destination — the location is a fact. */
.links__item--plain {
  color: var(--body-text);
}

.links__item .icon {
  flex-shrink: 0;
  color: hsl(var(--card-h) 45% 48%);
  transition: transform 0.25s var(--ease-back);
}

/* The underline draws itself rather than switching on. */
.links__item span {
  background: linear-gradient(currentColor, currentColor) no-repeat 0 100% / 0% 1px;
  transition: background-size 0.25s ease;
}

a.links__item:hover span,
a.links__item:focus-visible span {
  background-size: 100% 1px;
}

a.links__item:hover .icon {
  transform: translateX(2px) scale(1.1);
}

/* ---- stack ---- */

.stack {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.6;
}

.stack__label {
  font: 400 0.6875rem/1.6 var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.stack__items {
  margin: 2px 0 0.625rem;
  color: var(--ink);
}

.stack__group:last-child .stack__items {
  margin-bottom: 0;
}

/* ---- spoken languages ---- */

.tags {
  margin: 0;
  padding: 0;
  list-style: none;
}

.tags__item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.4375rem 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.9375rem;
  color: var(--ink);
}

.tags__item:last-child {
  border-bottom: 0;
}

.tags__level {
  font: 400 0.6875rem/1.6 var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

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

.footer {
  position: relative;
  margin-top: clamp(3.5rem, 9vh, 5.5rem);
  padding: 1.5rem 0 3.5rem;
  font: 400 0.8125rem/1.6 var(--font-mono);
  color: var(--muted);
}

/* The rail, completed: the same gradient the timeline fills in, closing the
   page. A shine crosses it every eight seconds — mostly rest, one glint. */
.footer__rule {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  border-radius: 999px;
  opacity: 0.9;
  background:
    linear-gradient(100deg, transparent 42%, hsl(0 0% 100% / 0.85) 50%, transparent 58%) no-repeat,
    var(--dawn-line);
  background-size: 250% 100%, 100% 100%;
  animation: shimmer-sweep 8s ease-in-out infinite;
}

@keyframes shimmer-sweep {
  0%,
  55% { background-position: 220% 0, 0 0; }
  100% { background-position: -120% 0, 0 0; }
}

.footer__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.footer p {
  margin: 0;
}

.footer a {
  color: inherit;
  text-underline-offset: 3px;
}

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

/* ---- relax (the night scene) ---- */

.relax {
  position: fixed;
  inset: 0;
  z-index: 10;
  background: var(--night);
  animation: night-fall 1s ease both;
}

@keyframes night-fall {
  from { opacity: 0; }
}

.relax__canvas {
  position: absolute;
  inset: 0;
}

.relax__back {
  position: fixed;
  top: 1.25rem;
  left: 1.25rem;
  z-index: 11;
  padding: 0.5rem 0.75rem;
  font: 500 0.875rem/1 var(--font-mono);
  color: rgba(255, 255, 255, 0.65);
  text-decoration: none;
  transition: color 0.2s ease;
}

.relax__back:hover {
  color: #ffffff;
}

.relax__back:focus-visible {
  outline-color: #ffffff;
}

/* ---- wasm loader ----
   go-app's default loader is a full-screen overlay, which would hide the
   prerendered page while the wasm downloads. Restyle it into a quiet corner
   pill: the CV is readable immediately, the pill signals progress until the
   app becomes interactive. */

.goapp-app-info {
  position: fixed;
  top: auto;
  left: auto;
  right: 1rem;
  bottom: 1rem;
  width: auto;
  height: auto;
  flex-direction: row;
  gap: 0.6rem;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background-color: var(--paper);
  color: var(--muted);
  font-family: var(--font-mono);
  box-shadow: 0 4px 16px rgba(29, 30, 66, 0.08);
}

@media (prefers-color-scheme: light) {
  .goapp-app-info {
    color: var(--muted);
    background-color: var(--paper);
  }
}

.goapp-app-info .goapp-logo {
  width: 18px;
  height: 18px;
  border-radius: 50%;
}

/* ---- narrow screens ---- */

@media (max-width: 40rem) {
  .timeline__rail {
    /* The rail overflows here; fade the cut edge instead of ending mid-dot.
       Only the left — the rail opens scrolled to today, and a fade on the
       right would dim the one year the reader came for. */
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 28px);
    mask-image: linear-gradient(90deg, transparent, #000 28px);
  }

  .timeline__events {
    min-height: 15rem;
  }

  .cards {
    gap: 2.25rem;
  }
}

/* ---- motion preferences ---- */

/* Everything stops but nothing disappears: still aurora, still ring, still
   gradient name, cards simply visible. This block stays last so it outranks
   the @supports reveals by source order. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }
}
