/* ============================================================
   Tokens
   ============================================================ */

:root {
  --type-size: 22px;
  --type-leading: 32px;   /* fixed, not a ratio */

  --gap-logo:  6px;    /* logo to title */
  --gap-block: 20px;   /* Main to Links, and between link items */
  --pad-side:  32px;   /* mobile text column horizontal padding */
  --pad-top:   64px;   /* section top padding, mobile */
  --measure-mobile:  624px; /* text column cap, mobile: 560px content + 32px padding each side */
  --measure-desktop: clamp(440px, 45.56vw, 656px); /* text column cap, desktop: fluid — 656px (560px content + 96px padding) at exactly 1440 and above, floor 440px */
  --measure-desktop-min: 320px; /* text column floor, desktop: never shrinks narrower than this */
  --inset-desktop: clamp(48px, 6.7vw, 96px); /* Text Group left padding: 96px at 1440+, ~60px at 900, floor 48px */
  --image-pin-x: calc(440px + 0.5 * (var(--measure-desktop) - 440px)); /* Skype/Spotify only:
    fixed image x, still tracking the text column but at half rate — matches the 900px floor
    (440px) exactly, then moves at 50% of how far --measure-desktop travels toward 656px, so
    it reads as "somewhat" following rather than fully static. See .section--skype/spotify
    .section__image below */

  --intro-offset: 33px; /* intro content offset below true vertical centre */
}

/* ============================================================
   Fonts
   ============================================================ */

@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/jost-v20-latin-400.woff2') format('woff2');
}

/* ============================================================
   Reset
   ============================================================ */

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

html, body { margin: 0; padding: 0; }

html { background: #000; } /* matches intro so overscroll and mid-reflow edges never flash white */

body {
  font-family: 'Jost', sans-serif;
  font-weight: 400;
  font-size: var(--type-size);
  line-height: var(--type-leading);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, p, ul { margin: 0; }

h1, h2 {
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
}

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

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

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

/* ============================================================
   Colour
   ============================================================ */

.section { background: var(--bg); color: var(--text); }

.section--intro   { --bg: #000000; --text: #FFFFFF; --muted: rgba(255, 255, 255, .5); }
.section--apple   { --bg: #FFFFFF; --text: #000000; --muted: rgba(0, 0, 0, .5); }
.section--sisou   { --bg: #0013CE; --text: #FFFFFF; --muted: #6887FF; }
.section--spotify { --bg: #171717; --text: #FFFFFF; --muted: rgba(255, 255, 255, .5); }
.section--skype   { --bg: #0074F1; --text: #FFFFFF; --muted: #83DEFF; }
.section--nokia   { --bg: #51317C; --text: #FFFFFF; --muted: #BC8EFF; }
.section--footer  { --bg: #000000; --text: #FFFFFF; --muted: rgba(255, 255, 255, .5); }

.date, .source { color: var(--muted); }

/* ============================================================
   Type
   ============================================================ */

.title, .description { color: var(--text); }

.link a,
.description a {
  color: var(--text);
  text-decoration: underline;
}

.description,
.link a {
  text-wrap: pretty;
}

/* Keyboard focus ring. Uses --text rather than a fixed white: white would be
   invisible against Apple's white background, and --muted fails contrast on
   Skype, Sisou and Nokia. --text is whatever the section already uses for body
   copy, so it is the highest-contrast colour available on every background.
   :focus-visible rather than :focus, so it appears for keyboard users without
   showing a ring on mouse clicks. */
a:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 3px;
}

/* ============================================================
   Structure (shared)
   ============================================================ */

.head {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--gap-logo);
}

.main {
  width: 100%;
  display: flex;
  flex-direction: column;
}

.links {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--gap-block);
}

.link {
  display: flex;
  flex-direction: column;
}

.source {
  display: block;
  margin: 0;
}

/* Footer: name and the links row sit as tight consecutive lines, no gap
   between them — matching the plain-text content mockup in SPEC.md,
   which shows no blank line between "Joe Phillips" and the links row. */
.section--footer .section__text {
  gap: 0;
}

/* Footer: Email / LinkedIn / CV sit on one line instead of stacked, with
   separator dots generated in CSS rather than written into the markup.
   The dot lives on .link itself (not inside the anchor), so it doesn't
   pick up the link's own underline. */
.section--footer .links {
  flex-direction: row;
  flex-wrap: wrap;
  gap: 0;
}

.section--footer .link {
  display: inline-flex;
  flex-direction: row;
  align-items: baseline;
}

.section--footer .link:not(:first-child)::before {
  content: "·";
  color: var(--muted);
  margin-inline: 0.5em;
}

.logo { width: auto; display: block; }
.logo--apple   { height: 30px; }
.logo--sisou   { height: 22px; }
.logo--spotify { height: 28px; }
.logo--skype   { height: 30px; }
.logo--nokia   { height: 14px; }

/* ============================================================
   Layout — mobile (below 900px)
   ============================================================ */

.section {
  padding-top: var(--pad-top);
  display: flex;
  flex-direction: column;
}

.section__text {
  width: 100%;
  max-width: var(--measure-mobile);
  padding-inline: var(--pad-side);
  display: flex;
  flex-direction: column;
  gap: var(--gap-block);
}

.section__image img {
  width: 100%;
  height: auto;
  display: block;
}

/* Intro overrides the generic section rules: vertically centred,
   content-driven, offset below true centre rather than top-padded.
   Padding lives on the flex container itself (not a child) so the
   centring math holds: offset = (padding-top - padding-bottom) / 2. */
.section--intro {
  min-height: 80svh;
  justify-content: center;
  padding-top: calc(var(--intro-offset) * 2);
  padding-bottom: 0;
}

/* Footer: padding-driven height at every breakpoint, not the fixed 800px
   used by the role sections — top padding matches the section padding
   used everywhere else, roughly double that below the content. */
.section--footer {
  padding-top: var(--pad-top);
  padding-bottom: calc(var(--pad-top) * 2);
}

/* ============================================================
   Layout — desktop (900px and above)
   ============================================================ */

@media (min-width: 900px) {
  .section {
    height: 800px;
    display: flex;
    padding: 0;
    position: relative;   /* containing block for cover images' overflow clip */
    overflow: hidden;     /* clips cover images' right-side bleed at the viewport edge */
  }

  .section__inner {
    width: 100%;
    max-width: 1440px;
    margin: 0 auto;
    display: flex;
    justify-content: space-between;
    align-items: center;
    height: 100%;
    position: relative;   /* containing block for Skype/Spotify's pinned image (below) */
  }

  .section__text {
    flex: 1 1 auto;
    min-width: var(--measure-desktop-min);
    max-width: var(--measure-desktop);
    padding-left: var(--inset-desktop);
    padding-right: 0;
    padding-block: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--gap-block);
  }

  /* 656 (text) + 784 (image) = 1440, the design width: image column width was
     reduced from 864 to 784 to give the text measure the extra 100px (784/1440
     = 54.4%, replacing the old 60%), so the two columns sit flush at 1440 and
     shrink proportionally below it since text also has flex-shrink: 1. */
  .section__image {
    width: 784px;
    max-width: 54.4%;
  }

  /* Cover images are sized by height alone and positioned absolutely, left
     edge fixed to where the image column begins (.section__image--cover
     stays in normal flow at its usual 784px/54.4% width so the flex layout
     and text-column arithmetic are undisturbed — it's just the positioning
     context for the image, which breaks out of it). width: auto means each
     image's natural aspect ratio decides how far right it extends past that
     784px box; .section's overflow: hidden clips it at the viewport edge.
     No object-fit/object-position — the whole image height always maps to
     the 800px band, so there's no vertical crop, only horizontal bleed. */
  .section__image--cover {
    height: 100%;
    position: relative;
  }

  .section__image--cover img {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: auto;
    max-width: none;   /* overrides the global img{max-width:100%} reset, which would
                           otherwise clamp this to its absolutely-positioned containing
                           block (the 784px picture box) and defeat width: auto */
  }

  /* Skype and Spotify: unlike the other cover sections, the image only tracks the
     text column's growth at half rate (--image-pin-x) instead of matching it 1:1, so
     it moves "somewhat" rather than sliding fully right as the window widens or
     staying fully static. Text sits on top (z-index only — no backing panel: an
     opaque background here would show as a visible rectangle over the image's own
     photo/gradient content, since it can't match that pixel-for-pixel the way it
     coincidentally could against a flat section colour) and grows over the image's
     left edge as its own width increases toward its 656px cap. Apple, Nokia and
     Sisou keep the standard 1:1 tracking behaviour above; their compositions read
     fine with it, Skype and Spotify's didn't. */
  .section--skype .section__image,
  .section--spotify .section__image {
    position: absolute;
    left: var(--image-pin-x);
    top: 0;
  }

  .section--skype .section__text,
  .section--spotify .section__text {
    position: relative;
    z-index: 1;
  }

  /* Intro has no image, so it never needed the overflow: hidden the
     generic .section rule above sets for clipping cover images' bleed —
     left in place, it's a second potential clipping ancestor for the
     J-glyph fix below, on top of .title's own overflow: visible. */
  .section--intro {
    overflow: visible;
  }

  /* Intro has no image: centred column, offset below true centre,
     left aligned, reusing the Text Group inset for cross-section
     alignment with the role sections beneath it. */
  .section--intro .section__inner {
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    padding-top: calc(var(--intro-offset) * 2);
    padding-bottom: 0;
  }

  .section--intro .section__text {
    width: 100%;
    min-width: var(--measure-desktop-min);
    max-width: var(--measure-desktop);
    padding-left: var(--inset-desktop);
  }

  /* Footer: padding-driven height here too, not the fixed 800px above —
     same top-matches-sections/doubled-bottom padding as mobile. */
  .section--footer {
    height: auto;
    padding-top: var(--pad-top);
    padding-bottom: calc(var(--pad-top) * 2);
  }
}

/* ============================================================
   Entrance animations
   ============================================================ */

/* Clip wrapper for the Apple slide-up. The clip has to sit on an ancestor
   that is not itself transformed: overflow on .section--apple (which the
   desktop .section rule already sets, for cover-image bleed) cannot contain
   the section's own transform, because the clip region is transformed along
   with the element. This wrapper keeps the section's layout box while the
   section translates inside it, so the slide can never paint over Sisou.
   Height is deliberately not declared — the wrapper is a block box that
   takes its height from the section in flow (800px on desktop, content-
   driven on mobile), so it stays correct at both breakpoints without
   restating the value. Horizontally it spans the viewport exactly as the
   section does, so it clips the cover image's right-side bleed at the same
   edge and the bleed is unaffected. */
.section-clip {
  overflow: hidden;
}

/* Jost's capital J has a descender hook that extends left of the glyph's
   own origin — clipped on mobile (no overflow: hidden anywhere in that
   ancestor chain, so this is the animated element's own box, not an
   ancestor) and during the animation on desktop. overflow: visible is
   explicit here rather than relied on as the default, since .title is
   the element .section--intro's overflow: visible above is trying to
   protect but a transform-animated element can still clip its own
   content in some engines regardless of ancestors.
   padding-left gives the hook room; margin-left is NOT the full
   negative of the padding — it's 3px short, so the padding shift nets
   out to a deliberate 3px rightward nudge instead of a no-op, aligning
   the J's stem with the "P"/"O" starting the lines below it once the
   hook is no longer clipped and visible. Check this at both breakpoints
   if the padding value ever changes, since the nudge is the padding
   value minus 3, not a fixed number on its own. */
.section--intro .title {
  overflow: visible;
  padding-left: 8px;
  margin-left: -5px;   /* -8px would fully cancel the padding (net 0px shift);
                           -5px leaves a net +3px rightward nudge */
}

/* Both sequences share one prefers-reduced-motion: no-preference block, so
   anyone with reduced motion enabled sees the final state immediately, with
   no animation and no JS-driven fallback needed.

   Intro — name, role, paragraph, then the three links, each fading in and
   moving up 32px. The sequence starts 300ms after page load (title's own
   delay — raised from an earlier 150ms), then staggers 150ms apart per
   element after that. Six elements × 150ms stagger + 1350ms duration = the
   last one finishes at 2400ms. Scoped to .section--intro only — every other
   section's identical .title/.date/.description/.link classes are untouched.

   Apple — the whole section (background and content together) slides up from
   one full section-height below into its normal flow position, no fade,
   transform only. animation-fill-mode: both holds the pre-animation
   translateY(100%) state during the delay so nothing flashes into place
   early. A transform doesn't affect layout, so the translated section would
   paint over Sisou — .section-clip above is what contains it; see SPEC.md
   "Apple slide-up occlusion" for why overflow on the section itself cannot
   do this.

   Ease throughout is cubic-bezier(0.16, 1, 0.3, 1) — a soft decelerate curve
   with a long, flattening tail (an intervening pass used the plain ease-out
   keyword for a firmer landing instead; back on the cubic-bezier curve now). */
@media (prefers-reduced-motion: no-preference) {
  .section--intro .title,
  .section--intro .date,
  .section--intro .description,
  .section--intro .link {
    animation: intro-fade-up 1350ms cubic-bezier(0.16, 1, 0.3, 1) both;
  }

  .section--intro .title       { animation-delay: 300ms; }
  .section--intro .date        { animation-delay: 450ms; }
  .section--intro .description { animation-delay: 600ms; }
  .section--intro .link:nth-child(1) { animation-delay: 750ms; }
  .section--intro .link:nth-child(2) { animation-delay: 900ms; }
  .section--intro .link:nth-child(3) { animation-delay: 1050ms; }

  @keyframes intro-fade-up {
    from {
      opacity: 0;
      transform: translateY(32px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  .section--apple {
    animation: apple-slide-up 1400ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: 1350ms;
  }

  @keyframes apple-slide-up {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
  }
}
