/* ==========================================================================
   Greta Volungevičiūtė — shared foundation
   Loaded by every page before its own stylesheet. Holds the design tokens,
   the reset, and the header, so the wordmark and nav are identical
   everywhere and only ever need editing in one place.
   ========================================================================== */

:root {
  /* canvas — every page is authored at 1440 and scaled down to fit */
  --design-w: 1440px;

  /* palette */
  --bg: #0a0a0a;
  --bg-lift: #131313;
  --ink: #f4f3f1;
  --ink-dim: rgba(244, 243, 241, 0.55);
  --ink-faint: rgba(244, 243, 241, 0.28);
  --hairline: rgba(244, 243, 241, 0.12);

  /* type */
  --font: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-display: "Unbounded", var(--font);       /* wordmark, project titles */
  --font-nav: "Darker Grotesque", var(--font);    /* nav, captions, numbers */
}

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
}

body {
  font-family: var(--font);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a { color: inherit; text-decoration: none; }

img { display: block; max-width: 100%; }

/* --------------------------------------------------------------------------
   Header — sits at the top of the 1440 canvas on every page
   -------------------------------------------------------------------------- */

.header {
  position: absolute;
  z-index: 40;
  top: 0;
  left: 0;
  width: var(--design-w);
  height: 88px;
  padding: 0 48px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.brand {
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}

.nav {
  display: flex;
  gap: 34px;
}

.nav a {
  position: relative;
  font-family: var(--font-nav);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-dim);
  transition: color 0.35s ease;
}

/* hover strikes a hairline straight through the word. scaleY keeps it under a
   pixel so it reads as a rule, not an underline; the width trims the trailing
   letter-spacing so the line stops at the last glyph, not past it. */
.nav a::after {
  content: "";
  position: absolute;
  left: 0;
  /* Measured off the caps, not guessed off the box. Darker Grotesque at
     line-height: normal puts the baseline 1.0625em below the top (its ascent),
     and its capital height is 0.625em, so the middle of a capital is
     1.0625 − 0.3125 = 0.75em down. Half the line box — even nudged — sat 2.6px
     high of that, because the box carries a descender depth no capital uses. */
  top: 0.75em;
  width: calc(100% - 0.04em); /* trims the trailing letter-spacing */
  height: 1px;
  background: currentColor;
  transform: scaleX(0) scaleY(0.6);
  transform-origin: 0 50%;
  transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}

.nav a:hover { color: var(--ink); }
.nav a:hover::after { transform: scaleX(1) scaleY(0.6); }

/* the page you are already on reads as active. Full-strength ink only — the
   strike is reserved for hover, and leaving it on would read as struck out. */
.nav a.is-current { color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .nav a,
  .nav a::after { transition: none; }
}

/* --------------------------------------------------------------------------
   Footer — the same two lines on every page

   Lives here for the same reason the header does: one copy, four pages. The
   colours are tokens, so it comes out black on the white pages and white on
   the dark ones without a single page-specific rule. Only the space above it
   belongs to each page.
   -------------------------------------------------------------------------- */

.site-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  padding-top: 32px;
  border-top: 1px solid var(--hairline);
  font-family: var(--font-nav);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

.site-foot a { transition: color 0.35s ease; }
.site-foot a:hover { color: var(--ink); }

/* one line each on a phone — side by side the two would meet in the middle */
@media (max-width: 900px) {
  .site-foot {
    display: block;
    font-size: 13px;
  }

  .site-foot a {
    display: block;
    margin-top: 10px;
  }
}

/* --------------------------------------------------------------------------
   Menu — the word, and the panel it drops

   On a narrow window the three nav words hand over to a single word, and that
   word drops the list beneath it. The word is the control: it says Menu, and
   with the panel open it says Close, in the same place — a label that changes
   rather than an icon that has to be learned.

   Both the word and the panel are built by js/nav.js out of the .nav already
   in the header, so there is one list of links per page and it is still the
   one in the markup. The script appends them to <body> rather than to the
   header, and that placement is load-bearing: on index.html the header lives
   inside a transformed canvas, and a fixed element inside a transformed
   ancestor is positioned against that ancestor instead of the window. The two
   things that must sit against the real viewport are the two things that
   cannot be in the canvas — the same reason .pr-rail lives out here.

   js/nav.js also puts .has-burger on <html>, and every display switch below is
   scoped to it. With the script missing or broken the header keeps the inline
   nav it has always had, which is a working menu rather than no menu at all.

   The breakpoint is NOT set here. index.html collapses at 760, where its
   header stops being part of the canvas and starts drawing at true size;
   every other page collapses at 900, where its canvas is abandoned outright.
   Each page's own stylesheet flips the three display rules at its own width,
   because that is the width where its header changes shape.
   -------------------------------------------------------------------------- */

.menu__toggle,
.menu { display: none; }

/* One size for the whole menu. The word that opens it and the words inside it
   are the same thing said twice — MENU is the list's first line before the
   list exists — so they are set identically and the panel reads as the header
   growing rather than as a second, louder navigation. 16px of Darker Grotesque
   is quiet enough to sit beside a 12px wordmark without shouting at it, and
   the 44px rows below carry the touch target instead of the type. */
:root { --menu-size: 16px; }

/* The word sits where the last nav word sat: same right margin, and centred in
   a 44px box so its baseline lands on the wordmark's. 44 is also the touch
   target — the word is smaller than that, the box is not. */
.menu__toggle {
  position: fixed;
  z-index: 400;                        /* over the panel: it is also the close */
  top: var(--burger-t, 10px);
  right: var(--burger-r, 18px);
  align-items: center;
  height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font-family: var(--font-nav);
  font-size: var(--menu-size);
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  line-height: 1;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Both labels in the same grid cell, so the box is as wide as the longer one
   and neither word moves the other when they swap. Without that the button is
   anchored right and CLOSE would shove itself leftward mid-transition. */
.menu__swap { display: grid; }

.menu__swap > span {
  grid-area: 1 / 1;
  text-align: right;
  transition: opacity 0.3s ease;
}

.menu__swap .menu__shut { opacity: 0; }
.is-menu-open .menu__swap .menu__open { opacity: 0; }
.is-menu-open .menu__swap .menu__shut { opacity: 1; }

/* The panel drops rather than fades. clip-path is composited and costs nothing
   to animate, and unlike opacity it reveals from an edge — pulled down from
   the top, so the list arrives out of the word that opened it.

   visibility is switched with a zero-length transition delayed by the drop, so
   the panel is gone from the tab order the moment it finishes closing and
   present the moment it starts opening. */
.menu {
  position: fixed;
  inset: 0;
  z-index: 300;
  flex-direction: column;
  /* clears the header the panel is covering, so the first link lands one row
     below the word rather than on top of it */
  padding: calc(var(--burger-t, 10px) + 44px) var(--burger-r, 18px) 0;
  background: var(--bg);
  /* the links arrive from beyond the right margin — this is the edge they
     arrive from behind */
  overflow: hidden;
  visibility: hidden;
  clip-path: inset(0 0 100% 0);
  transition: clip-path 0.62s cubic-bezier(0.16, 1, 0.3, 1), visibility 0s linear 0.62s;
}

.is-menu-open .menu {
  visibility: visible;
  clip-path: inset(0 0 0 0);
  transition: clip-path 0.62s cubic-bezier(0.16, 1, 0.3, 1), visibility 0s;
}

/* The wordmark, at the header's coordinates rather than at the panel's.

   --burger-r is the same margin on both sides — the header's gutter and the
   word's inset are the same number on every page — and the 44px box is the
   toggle's own height, so centring the text inside it lands the wordmark on
   the exact baseline it sits on in the header. The panel drops and the name
   stays where it was, which is what makes it read as the header staying put
   rather than as another menu item.

   Absolute, so it takes no part in the list below it. */
.menu__brand {
  position: absolute;
  top: var(--burger-t, 10px);
  left: var(--burger-r, 18px);
  display: flex;
  align-items: center;
  height: 44px;
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ink);
}

.menu__list {
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: right;
}

/* The stagger. --i is the link's index, written into the markup by nav.js.

   0.11s a step, not the 0.055 it started at. At the shorter interval four
   links overlapped so heavily that the panel read as one block fading in —
   technically a cascade, visually a cross-fade. At 0.11 the last link starts
   after the first has landed, so what you see is four separate arrivals in
   order. Four items is the whole list, so the tail is 0.33s: long enough to
   read as a sequence, short enough that nobody is waiting for CONTACT. */
.menu__item { --d: calc(var(--i, 0) * 0.11s + 0.14s); }

/* Each row is 44px, which is the touch target and also the rhythm: the list
   reads as evenly spaced lines rather than as words with gaps between them.
   Dim by default and full ink for the page you are on — the same distinction
   .nav makes in the header, so the menu says where you are without a marker. */
.menu__link {
  display: block;
  padding: 14px 0;
  font-family: var(--font-nav);
  font-size: var(--menu-size);
  font-weight: 400;
  letter-spacing: 0.04em;
  line-height: 1;
  text-transform: uppercase;
  color: var(--ink-dim);
  opacity: 0;
  /* In from the right edge, not down from above.

     Down was the obvious direction and the wrong one: the first row sits 54px
     from the top, so a drop long enough to be seen starts it inside the header
     band and slides it straight through CLOSE. Sideways has no such neighbour,
     and on a list that is already right-aligned the words arrive along the
     edge they line up on — which is what makes four of them in a row read as a
     sequence rather than as a flicker.

     40px, and .menu clips, so they come from off the page rather than from a
     position on it. */
  transform: translateX(40px);
  transition: opacity 0.5s ease, transform 0.8s cubic-bezier(0.16, 1, 0.3, 1), color 0.3s ease;
}

.menu__item.is-current .menu__link { color: var(--ink); }

.is-menu-open .menu__link {
  opacity: 1;
  transform: none;
  transition-delay: var(--d, 0s);
}

/* the credit line, lifted off the footer the panel is covering */
.menu__foot {
  --d: 0.62s;                          /* last, after the links have landed */
  margin: 30px 0 0;
  font-family: var(--font-nav);
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: right;
  color: var(--ink-faint);
  opacity: 0;
  transform: translateX(40px);
  transition: opacity 0.5s ease, transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}

.is-menu-open .menu__foot {
  opacity: 1;
  transform: none;
  transition-delay: var(--d);
}

/* the page underneath must not scroll while the panel is over it */
html.is-menu-open,
html.is-menu-open body { overflow: hidden; }

/* Opening the panel moves focus to the first link, so a keyboard is going to
   land a ring on it every time. The UA's default is a blue box on a page that
   has no blue and no boxes; this is the same hairline the rest of the site is
   drawn with, held off the text so it reads as a mark rather than a border.
   :focus-visible, so a tap never draws it. */
.menu__toggle:focus-visible,
.menu__brand:focus-visible,
.menu__link:focus-visible {
  outline: 1px solid var(--ink);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  /* .is-menu-open .menu has to be named here, not just .menu. The open state
     carries its own transition shorthand at two classes of specificity, and a
     one-class override loses to it no matter how far down the file it sits —
     the panel would have gone on dropping for anyone who had asked it not to. */
  .menu,
  .is-menu-open .menu,
  .menu__swap > span,
  .menu__link,
  .is-menu-open .menu__link,
  .menu__foot,
  .is-menu-open .menu__foot { transition: none; }

  .menu__link,
  .menu__foot { transform: none; }
}

/* --------------------------------------------------------------------------
   Colourway flip

   One poster in four colourways, cut between on a loop. Hard cuts, not fades:
   the source animation switches, and a cross-fade would put two colours on
   screen at once and turn the line work to mud.

   object-fit is contain and the frame carries the posters' own ratio, so the
   sheet is never cropped. These sit outside the parallax for the same reason —
   the parallax works by making the picture taller than its frame.

   It lives here rather than in work.css because two pages run it now: the HND
   card on the work index and the HND case study itself, which has no other
   media. One definition, so the two cannot end up cycling at different speeds.
   -------------------------------------------------------------------------- */

.flip {
  position: absolute;
  inset: 0;
  display: block;
}

.flip__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  animation: flip-cycle 1.6s linear infinite;
}

.flip__frame:nth-child(1) { animation-delay: 0s; }
.flip__frame:nth-child(2) { animation-delay: 0.4s; }
.flip__frame:nth-child(3) { animation-delay: 0.8s; }
.flip__frame:nth-child(4) { animation-delay: 1.2s; }

@keyframes flip-cycle {
  0%, 24.99%  { opacity: 1; }
  25%, 100%   { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .flip__frame { animation: none; }
  .flip__frame:nth-child(1) { opacity: 1; }
}

/* --------------------------------------------------------------------------
   Boot screen

   A white card over everything with the wordmark typing itself out. It is
   plain markup rather than something the script builds, so it is on screen at
   the very first paint — a loading screen that waits for its own JavaScript
   would arrive after the thing it is meant to cover.

   js/boot.js takes it away and then removes it from the document. The
   animation below is the failsafe for the script never running at all: it is
   why removing the element matters, since an element still in the page at 6s
   would flash white again as the keyframes took over from the class.
   -------------------------------------------------------------------------- */

#boot {
  position: fixed;
  inset: 0;
  z-index: 999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #ffffff;
  transition: opacity 0.55s ease;
  animation: boot-failsafe 0.5s ease 6s forwards;
}

#boot.is-done {
  opacity: 0;
  pointer-events: none;
}

@keyframes boot-failsafe {
  to { opacity: 0; visibility: hidden; }
}

/* the wordmark, at the wordmark's own size and setting — this is the same
   declaration block as .brand, only black and centred */
.boot__line {
  position: relative;
  display: inline-block;
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #0d0d0d;
}

/* The finished name, held invisible, purely to give the box its full width
   from the start. Without it a centred box grows as it types and the whole
   name creeps leftward — the letters appear and the word slides at once. */
.boot__ghost { visibility: hidden; }

.boot__type {
  position: absolute;
  left: 0;
  top: 0;
}

.boot__caret {
  display: inline-block;
  width: 2px;
  height: 0.92em;
  margin-left: 4px;
  /* sits on the baseline rather than the middle of the line box, so it lines
     up with the caps it is following */
  vertical-align: -0.04em;
  background: #0d0d0d;
  animation: boot-blink 0.9s steps(1) infinite;
}

@keyframes boot-blink {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

@media (max-width: 900px) {
  /* The wordmark is 12px on phones and the loading screen follows it — but the
     size goes on the line, not on the typed word.

     .boot__ghost is the whole name held invisible, and it is the only thing
     giving the centred box its width. Shrinking .boot__word alone left a 12px
     name typing itself along the left edge of a box still measured for a 24px
     one: 196px of letters inside 391px of box, so the wordmark sat about 98px
     left of centre. Sizing the line moves the ghost and the typing together. */
  .boot__line { font-size: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  #boot { transition: none; }
  .boot__caret { animation: none; }
}
