/* ---------------------------------------------------
   Dario Capalbi — Portfolio
   Swiss / brutalist inspired single-page layout
--------------------------------------------------- */

:root {
  --paper: #F5F4F1;
  --ink: #0A0A0A;
  --ink-soft: rgba(10, 10, 10, 0.6);
  --ink-mute: rgba(10, 10, 10, 0.4);
  --ink-ghost: rgba(10, 10, 10, 0.16);
  --line: rgba(10, 10, 10, 0.14);
  --accent: #FF4D23;
  --yellow: #FFF959;
  --dark: #0A0A0A;
  --dark-line: rgba(245, 244, 241, 0.16);

  --font: 'Switzer', 'Helvetica Neue', Arial, sans-serif;

  --gutter: clamp(20px, 5vw, 64px);
  --section-pad: clamp(72px, 12vw, 160px);

  --ease: cubic-bezier(.16, 1, .3, 1);
  --reveal-dist: 32px;
  --reveal-dur: 0.9s;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }

ul { list-style: none; margin: 0; padding: 0; }

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

/* -------------------- grain overlay -------------------- */

.grain {
  position: fixed;
  inset: 0;
  z-index: 999;
  pointer-events: none;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* -------------------- custom cursor -------------------- */
/* A small yellow ball that follows the pointer and grows over links —
   solid yellow at all times, no blend-mode color shifting. Position
   tracks the mouse directly (no lag); size change is what's animated.
   Desktop/mouse only — left inert (and invisible) on touch devices. */

.cursor-ball {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease;
}

.cursor-ball.is-active {
  opacity: 1;
}

.cursor-ball__dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--yellow);
  transition: width 0.8s cubic-bezier(0.21, 1, 0.4, 0.98),
    height 0.8s cubic-bezier(0.21, 1, 0.4, 0.98);
}

.cursor-ball.is-hovering .cursor-ball__dot {
  width: 64px;
  height: 64px;
}

html.has-custom-cursor a,
html.has-custom-cursor button {
  cursor: none;
  position: relative;
  z-index: 3;
}

@media (prefers-reduced-motion: reduce) {
  .cursor-ball { display: none; }
}

/* -------------------- nav -------------------- */

.nav {
  position: fixed;
  top: 0;
  left: 50%;
  right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: clamp(18px, 3vw, 28px) var(--gutter);
  color: #fff;
  mix-blend-mode: difference;
}

.nav a {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  position: relative;
  padding-bottom: 3px;
}

.nav a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.35s ease;
}

.nav a:hover::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* -------------------- hero -------------------- */

.hero {
  position: sticky;
  top: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  min-height: 100vh;
}

/* A sticky box taller than the viewport pins the instant you scroll and its
   lower half becomes unreachable - on phones, and on laptops short enough
   that the headline alone overflows, that lower half is the light panel
   holding the wordmark. (bottom: 0 is no cure: a sticky box taller than the
   scrollport simply never pins.) So the hero only pins while it fits;
   main.js sets this class whenever it doesn't, and the deck stacking
   resumes at Work either way. */
.hero--unpinned {
  position: static;
}

.hero__panel {
  position: relative;
  /* Vertical padding gives way on a short screen so the headline does not
     have to. */
  padding: min(var(--gutter), 6vh) var(--gutter);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 60vh;
}

/* Bottom-aligned like the light panel, so the headline and the wordmark
   across the split finish on the same line. */
.hero__panel--dark {
  justify-content: flex-end;
}

.hero__panel--dark {
  background: var(--dark);
  color: var(--paper);
}

.hero__panel--light {
  background: var(--paper);
  color: var(--ink);
}

.hero__tag {
  position: absolute;
  top: var(--gutter);
  left: var(--gutter);
  font-size: 12px;
  letter-spacing: 0.06em;
  color: rgba(245, 244, 241, 0.55);
}

.hero__headline {
  /* The hero has to fit the viewport to stay pinned, but that budget comes
     out of the margin and the leading, not the type: the headline is the
     thing, and it should fill the dark panel. The vh cap only bites on a
     genuinely short screen, where the four lines wrap to eight. */
  margin: clamp(20px, min(8vw, 3.5vh), 88px) 0 0;
  font-weight: 800;
  font-size: clamp(34px, min(6vw, 9vh), 76px);
  line-height: 1.0;
  letter-spacing: -0.01em;
}

.hero__headline span {
  display: block;
  opacity: 0;
  transform: translateY(100%);
  animation: rise 0.9s var(--ease) forwards;
}

.hero__accent {
  font-style: normal;
  color: var(--yellow);
}

.hero__headline span:nth-child(1) { animation-delay: 0.05s; }
.hero__headline span:nth-child(2) { animation-delay: 0.15s; }
.hero__headline span:nth-child(3) { animation-delay: 0.25s; }
.hero__headline span:nth-child(4) { animation-delay: 0.35s; }

@keyframes rise {
  to { opacity: 1; transform: translateY(0); }
}

/* Out of the flow and over to the right: the dark panel is bottom-aligned
   now, and in the flow this cue would take the baseline that "connect." has
   to share with the wordmark across the split. */
.hero__scroll {
  position: absolute;
  right: var(--gutter);
  bottom: min(var(--gutter), 6vh);
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  letter-spacing: 0.08em;
  color: rgba(245, 244, 241, 0.5);
}

.hero__scroll i {
  font-style: normal;
  animation: bob 1.6s ease-in-out infinite;
}

@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(5px); }
}

.hero__meta {
  position: absolute;
  top: var(--gutter);
  right: var(--gutter);
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--ink-mute);
  max-width: 180px;
  text-align: right;
}

.hero__wordmark {
  display: flex;
  justify-content: flex-start;
}

.wordmark__img {
  width: clamp(320px, 42vw, 720px);
  height: auto;
}

/* -------------------- sections -------------------- */

.section {
  position: relative;
  background: var(--paper);
  padding: var(--section-pad) var(--gutter);
  border-top: 1px solid var(--line);
}

/* Used to pin About in place, since removed - position: sticky here
   had effectively unbounded slack (nothing bounds how long it stays
   "stuck" once its own content clears, since it's not based on
   proximity to the next section) so About stayed glued in front of
   Contact well after Contact had started covering it, bleeding
   through. Scrolls normally now, same as Work's slots and Services. */
.section--pin {
  position: relative;
}

/* Scroll reveal — same fade + slide-up language as the hero,
   applied per element so each row/line animates in as it's scrolled to. */
.reveal {
  opacity: 0;
  transform: translateY(var(--reveal-dist));
  transition: opacity var(--reveal-dur) var(--ease), transform var(--reveal-dur) var(--ease);
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
}

/* Word-by-word scroll reveal for headings — each word is masked and slides
   up from below individually, staggered ~80ms apart. JS splits the text
   into .emerge-word-wrapped spans and toggles .is-visible on scroll-into-
   view; this file only defines the two visual states. */
.emerge-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.emerge-word > span {
  display: inline-block;
  opacity: 0;
  transform: translateY(100%);
  transition: transform 0.6s cubic-bezier(0.21, 1, 0.4, 0.98),
    opacity 0.6s cubic-bezier(0.21, 1, 0.4, 0.98);
}

.emerge.is-visible .emerge-word > span {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .emerge-word > span { opacity: 1; transform: none; transition: none; }
}

.section__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: clamp(40px, 6vw, 72px);
}

.kicker {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-mute);
}

.section__head h2 {
  margin: 0;
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 800;
  letter-spacing: -0.01em;
}

/* Full-width section intro: the heading spans edge to edge at a huge
   fluid size, pinned like the rest of the page's sections until the
   content after it slides up and covers it. Declared after the base
   .section__head h2 rule so it wins at equal specificity. */
.section__head--full {
  position: relative;
  align-items: center;
  justify-content: center;
  height: 40vh;
  margin: 0 calc(-1 * var(--gutter));
  padding: 0 4vw;
  background: var(--paper);
  overflow: hidden;
}

.section__head--full h2,
.section__head--wide h2 {
  font-size: 13vw;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 0.9;
  white-space: nowrap;
}

/* Same oversized edge-to-edge heading, without the sticky pin - for
   sections (like About) whose outer section is already the thing
   that pins as a whole. */
.section__head--wide {
  justify-content: center;
  margin: 0 calc(-1 * var(--gutter)) clamp(96px, 14vw, 180px);
  padding: 0 4vw;
}

/* Work's own heading pins and gets covered by the first slot, same as
   every other section - #work isn't itself the thing that pins (unlike
   About), so its heading needs the sticky treatment directly. */
#work .section__head--wide {
  position: sticky;
  top: 0;
  align-items: center;
  height: 40vh;
  margin-bottom: 0;
  overflow: hidden;
}

#about .section__head--wide {
  margin-bottom: clamp(56px, 8vw, 88px);
}

#about {
  padding-top: clamp(40px, 5vw, 56px);
  padding-bottom: clamp(80px, 12vw, 140px);
}

/* -------------------- work -------------------- */
/* Full-bleed stacked panels: each slot spans the entire viewport width,
   edge to edge, regardless of the section's own side padding. */

.work__empty {
  display: flex;
  flex-direction: column;
  width: 100vw;
  position: relative;
  left: 50%;
  right: 50%;
  margin-left: -50vw;
  margin-right: -50vw;
}

.work__slot {
  position: sticky;
  top: 0;
  width: 100%;
  height: 100vh;
  background: var(--paper);
  border-top: 1px solid var(--line);
  padding: var(--gutter);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(20px, 3vw, 32px);
  text-align: center;
}

.work__slot + .work__slot {
  border-top: none;
}

/* Placeholder color box standing in for the real project image - each
   slot pins (see position: sticky above) and the next slot's own paper
   background slides up over it, same cover effect as the rest of the
   page's sections. */
/* Matches the artwork's own 1846x852 proportions so nothing gets
   cropped. The third min() term caps the height at the previous 68vh
   by capping width instead, which keeps the ratio intact. */
.work__box {
  position: relative;
  width: min(96vw, 1320px, 147vh);
  aspect-ratio: 1846 / 852;
}

.work__box::after {
  content: "+";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 40px;
  font-weight: 300;
  color: rgba(10, 10, 10, 0.25);
}

.work__box--01 {
  background: url("../assets/work-01-flagged.webp") center / cover no-repeat;
}

.work__box--02 {
  background: url("../assets/work-02-fitmate.webp") center / cover no-repeat;
}

/* 2:1 rather than the 1846x852 the other two share, so it overrides
   the shared aspect-ratio to avoid being cropped. */
.work__box--03 {
  background: url("../assets/work-03-bacardi.webp") center / cover no-repeat;
  aspect-ratio: 2000 / 1001;
}

.work__box--04 {
  background: url("../assets/work-04-polaroid.webp") center / cover no-repeat;
  aspect-ratio: 2000 / 995;
}

/* No "+" placeholder over a slot that already has real artwork. */
.work__box--01::after,
.work__box--02::after,
.work__box--03::after,
.work__box--04::after { content: none; }

.work__title {
  position: relative;
  margin: 0;
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.work__status {
  font-size: 13px;
  letter-spacing: 0.03em;
  color: var(--ink-mute);
  transition: color 0.4s var(--ease);
}

/* Hover state, ahead of these slots becoming real links. */
.work__slot {
  cursor: pointer;
}

.work__box {
  transition: transform 0.7s var(--ease);
}

.work__slot:hover .work__box {
  transform: scale(1.02);
}

.work__title::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -0.18em;
  width: 100%;
  height: 2px;
  background: currentColor;
  transform: scaleX(0);
  transition: transform 0.45s var(--ease);
}

.work__slot:hover .work__title::after {
  transform: scaleX(1);
}

.work__slot:hover .work__status {
  color: var(--ink);
}

@media (prefers-reduced-motion: reduce) {
  .work__box,
  .work__title::after { transition: none; }
  .work__slot:hover .work__box { transform: none; }
}

.work__note {
  position: relative;
  background: var(--paper);
  margin: clamp(28px, 4vw, 40px) 0 0;
  font-size: 13px;
  color: var(--ink-mute);
}

/* -------------------- services -------------------- */
/* Pinned like the hero→work transition: the list stays fixed in the
   viewport for an extra stretch of scroll, and only the "current" service
   (by scroll progress) inks in black — the others stay grey, same as the
   reference site's sticky team-name list. */

.services__pin-wrap {
  position: relative;
  height: 240vh;
}

.services__pin-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.services__list {
  border-top: 1px solid var(--line);
}

.services__item {
  display: grid;
  grid-template-columns: 60px 1fr;
  align-items: baseline;
  gap: 4px 24px;
  padding: clamp(20px, 3vw, 32px) 0;
  border-bottom: 1px solid var(--line);
  cursor: default;
}

.services__index {
  font-size: 13px;
  color: var(--ink-mute);
}

.services__name {
  grid-column: 2;
  font-size: clamp(40px, 9vw, 112px);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--ink-ghost);
  transition: color 0.5s var(--ease);
}

/* Only the row that's "current" for the scroll position inks in solid
   black; the rest stay grey. Toggled continuously while the section is
   pinned (not a one-shot reveal — it reverts when it's no longer active). */
.services__item.is-active .services__name {
  color: var(--ink);
}

@media (prefers-reduced-motion: reduce) {
  .services__pin-wrap { height: auto; }
  .services__pin-stage { position: relative; height: auto; }
  .services__name { color: var(--ink) !important; }
}

.services__desc {
  grid-column: 2;
  margin: 6px 0 0;
  max-width: 46ch;
  font-size: 14px;
  color: var(--ink-mute);
}

/* -------------------- about -------------------- */

.about__grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  column-gap: clamp(32px, 6vw, 80px);
  row-gap: 0;
  align-items: start;
}

.about__statement {
  margin: 0 0 clamp(64px, 10vw, 120px);
  font-size: clamp(22px, 3.4vw, 36px);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.35;
}

/* Cut-out portrait on a solid panel - the image itself is a
   transparent PNG, so the panel is this element's own background. */
.about__portrait {
  margin: 0;
  justify-self: end;
  width: min(100%, 420px);
  aspect-ratio: 4 / 5;
  background: #deddd8;
  overflow: hidden;
}

.about__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.about__tools {
  margin-top: clamp(64px, 10vw, 120px);
  border-top: 1px solid var(--line);
  padding-top: clamp(20px, 3vw, 32px);
}

.about__tools-label {
  display: block;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-bottom: clamp(20px, 3vw, 32px);
}

.about__tools ul {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(20px, 4vw, 56px);
}

.about__tools li {
  font-size: clamp(18px, 2.4vw, 28px);
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* -------------------- contact -------------------- */

.section--contact {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
}

.contact__headline {
  width: calc(100% + 2 * var(--gutter));
  margin: 0 calc(-1 * var(--gutter));
  padding: 0 4vw;
  font-size: 13vw;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
  white-space: nowrap;
  text-align: center;
}

.contact__email {
  /* .reveal's transform (even at translateY(0), post-animation) makes
     this its own stacking context - without an explicit z-index here
     that beats the cursor ball's, the ball would paint over the whole
     link regardless of the link's own z-index, since that only
     matters for stacking *within* this context, not against siblings
     outside it. Same fix as .contact__socials below. */
  position: relative;
  z-index: 4;
  font-size: clamp(18px, 2.4vw, 24px);
  font-weight: 700;
  padding-bottom: 4px;
  border-bottom: 2px solid var(--ink);
  transition: color 0.3s ease, border-color 0.3s ease;
}

.contact__email:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.contact__socials {
  /* Same reasoning as .contact__email above: .reveal's transform makes
     this its own stacking context, so it needs its own z-index above
     the cursor ball's for its links to reliably paint on top of it. */
  position: relative;
  z-index: 4;
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}

.contact__socials a {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.03em;
  /* Constant --ink, no color change at all (including on hover) - this
     link sits under the cursor ball, which is bright yellow, and every
     color tried so far that shifts on hover (grey, accent orange) has
     turned out to have poor contrast against it. Solid near-black is
     the one color guaranteed to stay legible no matter what's behind
     it, so it just never changes. */
  color: var(--ink);
}

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

.footer {
  position: relative;
  background: var(--paper);
  display: flex;
  flex-wrap: wrap;
  gap: 12px 24px;
  align-items: center;
  justify-content: space-between;
  padding: 28px var(--gutter) 36px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--ink-mute);
}

.footer__name { font-weight: 700; color: var(--ink); }

.footer__top {
  font-weight: 600;
  color: var(--ink);
}

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

/* -------------------- responsive -------------------- */

@media (max-width: 860px) {
  /* Hero panels stack instead of splitting left/right, so the nav goes
     back to spanning the full width. */
  .nav { left: 0; }

  /* Dark panel with the headline on top, light panel with the wordmark
     under it. No vh floors here: fixed ones summed past the screen and
     forced the hero to overflow even when the content would have fitted.
     Let the panels take their natural height and the hero's own 100vh
     floor stretch them, so it stays pinned on phones that can hold it -
     and main.js unpins it on the short ones that cannot. */
  .hero {
    grid-template-columns: 1fr;
  }
  .hero__panel--dark { min-height: 0; }
  .hero__panel--light {
    min-height: 0;
  }

  /* Back in the flow: the panels are stacked here, so there is no wordmark
     across a split to keep a baseline with, and pinned bottom-right the cue
     landed on top of the headline's last line. */
  .hero__scroll {
    position: static;
    margin-top: 28px;
  }

  .about__grid {
    grid-template-columns: 1fr;
  }
  .about__portrait { justify-self: center; }

  .services__item {
    grid-template-columns: 1fr;
  }
  .services__index { grid-column: 1; }
  .services__name { grid-column: 1; }
  .services__desc { grid-column: 1; }
}

@media (max-width: 560px) {
  .nav a { font-size: 11px; }
  .footer { flex-direction: column; align-items: flex-start; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .hero__headline span { animation: none; opacity: 1; transform: none; }
  .hero__scroll i { animation: none; }
}
