/* ==========================================================================
   Site chrome: header, nav, footer, back-to-top

   Present on all five pages. The nav and the footer share the copy-to-clipboard
   flag, so those rules sit together here.
   ========================================================================== */


/* ---- Header ---- */
/* The rule under the nav runs the full width of the screen, not just the
   1280px column. The header itself stays full width and the content is held
   to the column by auto side padding, so the border spans the viewport
   without a second wrapper element in every page. */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 22px max(var(--gutter), calc((100% - var(--content-width)) / 2 + var(--gutter)));
  border-bottom: 1px solid var(--border);
}
.logo {
  font-family: var(--font-serif);
  font-size: 28px;
  color: var(--text-body);
}
.logo-dot { display: none; }
.mobile-menu-btn {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  width: 24px;
  height: 24px;
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
}
.mobile-menu-btn span {
  display: block;
  width: 100%;
  height: 2px;
  background: var(--text);
}
/* In the header the flag would sit against the top of the viewport, so it
   drops below its trigger instead of rising above it. */
.site-header .copy-flag,
.site-header .nav-flag {
  top: calc(100% + 7px);
  bottom: auto;
  transform: translate(-50%, -3px);
}
.site-header .copy-email.is-copied .copy-flag,
.site-header .nav-soon.is-flagged .nav-flag {
  transform: translate(-50%, 0);
}

@media (max-width: 900px) {
  .site-header { padding: 20px 24px; position: relative; }
  .logo-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent);
    margin-right: 8px;
  }
  .logo { font-size: 22px; }
  .mobile-menu-btn { display: flex; }
}


/* ---- Nav, About, Contact ---- */
.site-nav {
  display: flex;
  gap: 32px;
  font-family: var(--font-mono);
  font-size: 14px;
}
.site-nav a { color: var(--text-body); }
.site-nav a.active { color: var(--accent); font-weight: 500; }
/* The four case studies, listed under Work. The desktop bar has room for
   three items and no more, so this list only ever appears in a dropdown:
   a hover/focus panel on desktop, an indented block in the mobile menu. */
.nav-work-links { display: none; }
/* The current case study, in either dropdown. Three class-level parts, so it
   outranks the muted colour each dropdown sets on its own links. */
.site-nav .nav-work-links a[aria-current="page"] {
  color: var(--accent);
  font-weight: 500;
}

@media (min-width: 901px) {
  /* The panel hangs off the nav, and Work is the nav's first child, so
     `left: 0` lines the panel up with the word it belongs to. */
  .site-nav { position: relative; }
  .site-nav > .nav-work-links {
    display: flex;
    flex-direction: column;
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 20;
    /* Transparent padding, not a margin: it bridges the gap between the
       link and the panel, so the pointer never crosses dead space. */
    padding: 16px 0 6px;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s ease, visibility 0s linear 0.15s;
  }
  .site-nav > .nav-work-links::before {
    content: "";
    display: block;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 4px;
    box-shadow: 0 12px 28px rgba(var(--bg-dark-rgb), 0.14);
    position: absolute;
    inset: 10px 0 0;
  }
  .site-nav > .nav-work-links a {
    position: relative;
    min-width: 148px;
    padding: 7px 16px;
    color: var(--text-muted);
    white-space: nowrap;
  }
  .site-nav > .nav-work-links a:hover { color: var(--text); }
  /* Open on the trigger, and stay open while the pointer or the keyboard is
     inside the panel itself. The panel is Work's next sibling, which is what
     lets this work without a wrapper element. */
  .site-nav > a:hover + .nav-work-links,
  .site-nav > a:focus-visible + .nav-work-links,
  .site-nav > .nav-work-links:hover,
  .site-nav > .nav-work-links:focus-within {
    opacity: 1;
    visibility: visible;
    transition-delay: 0s;
  }
}

@media (min-width: 901px) and (prefers-reduced-motion: reduce) {
  .site-nav > .nav-work-links { transition: none; }
}
/* One flag, two users: the copy button's "Copied" and the About link's
   "coming soon...". Both sit above their trigger and neither takes space. */
.copy-flag,
.nav-flag {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 6px);
  transform: translate(-50%, 3px);
  padding: 3px 7px;
  border-radius: 3px;
  background: var(--bg-dark);
  color: var(--on-dark);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  line-height: 1;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.copy-email.is-copied .copy-flag,
.nav-soon.is-flagged .nav-flag {
  opacity: 1;
  transform: translate(-50%, 0);
}
/* ---- Header: About and Contact ----
   About goes nowhere yet, so it answers with a flag instead of navigating.
   Contact swaps the word for the same email component the footer carries. */
/* A button, not a link: there is no About section to point at yet, so the nav
   should not advertise a target that a middle-click could open. It is reset to
   read as the sibling links do. */
.nav-soon {
  position: relative;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--text-body);
  cursor: pointer;
}
/* The swap. The label stays in normal flow, so the span is the width of the
   word "Contact" before any script runs and the header never jumps on load.
   The email is absolute, so it adds no width; the script sets an explicit
   width when it slides in and clears it on the way back. */
.nav-contact {
  position: relative;
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  vertical-align: bottom;
  transition: width 0.62s cubic-bezier(0.33, 1, 0.68, 1);
}
.nav-contact-label,
.nav-contact-email {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  transition: opacity 0.38s ease, transform 0.62s cubic-bezier(0.33, 1, 0.68, 1);
}
.nav-contact-email {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translate(100%, -50%);
  opacity: 0;
  pointer-events: none;
}
.nav-contact-email a {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 0.15s ease;
}
.nav-contact-email a:hover {
  border-bottom-color: var(--accent);
}
.nav-contact.is-open .nav-contact-label {
  opacity: 0;
  transform: translateX(-100%);
  pointer-events: none;
}
.nav-contact.is-open .nav-contact-email {
  opacity: 1;
  transform: translate(0, -50%);
  pointer-events: auto;
}
/* The nav copy button is smaller than the footer's, to sit on one mono line. */
.nav-contact-email .copy-email {
  width: 20px;
  height: 20px;
}
.nav-contact-email .copy-email svg {
  width: 13px;
  height: 13px;
}

@media (prefers-reduced-motion: reduce) {
  .copy-flag,
  .nav-flag { transition: none; }
}

@media (prefers-reduced-motion: reduce) {
  .nav-contact,
  .nav-contact-label,
  .nav-contact-email { transition: opacity 0.22s ease; }
  .nav-contact-email { transform: translate(0, -50%); }
  .nav-contact.is-open .nav-contact-label { transform: none; }
}

@media (max-width: 900px) {
  .site-nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    gap: 0;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    padding: 8px 24px 16px;
    z-index: 10;
  }
  /* The dropdown floats over the page, so it needs to read as a layer
       rather than as part of the header. Same tone as the carousel's. */
  .site-nav.open {
      display: flex;
      box-shadow: 0 12px 28px rgba(var(--bg-dark-rgb), 0.14);
    }
  .site-nav > .nav-work-links {
    display: flex;
    flex-direction: column;
    padding: 2px 0 10px 16px;
  }
  .site-nav > .nav-work-links a { padding: 8px 0; color: var(--text-muted); }
  /* Child selectors, not descendant: the Contact swap holds its own links and
       they must not pick up the dropdown row's padding and rule. */
  .site-nav > a,
    .site-nav > .nav-soon,
    .site-nav > .nav-contact { padding: 10px 0; border-top: 1px solid var(--border); }
  .site-nav > .nav-soon { display: block; width: 100%; text-align: left; }
  .site-nav > :first-child { border-top: none; }
  /* The swap fills the row here, so the slide runs the width of the dropdown. */
  .site-nav > .nav-contact { display: block; width: auto; }
  .site-nav > .nav-contact .nav-contact-email { top: auto; bottom: 10px; transform: translateX(100%); }
  .site-nav > .nav-contact.is-open .nav-contact-email { transform: translateX(0); }
}


/* ---- Breadcrumb ---- */
.crumb {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
  letter-spacing: 0.02em;
}
.crumb .sep { color: var(--accent); margin: 0 6px; }


/* ---- Footer and email copy button ---- */
/* The footer's eyebrow is the one the file sets at 12px (429:156695), against
   11px everywhere else. */
.site-footer .section-label { font-size: 12px; }
/* ---- Footer ---- */
.site-footer {
  border-top: 1px solid var(--border);
  padding: 40px var(--gutter) 50px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  max-width: var(--content-width);
  margin: 0 auto;
}
.footer-email {
  font-family: var(--font-serif);
  font-size: 20px;
  margin-top: 6px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.footer-email a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 0.15s ease;
}
.footer-email a:hover {
  border-bottom-color: var(--accent);
}
/* The copy button. It stays quiet until hover, so the address reads as the
   footer's one piece of content. The "Copied" flag lives inside the button and
   is the only feedback -- no toast, no layout shift, because it is absolutely
   positioned. */
.copy-email {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--idle);
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.copy-email:hover,
.copy-email:focus-visible {
  color: var(--accent);
  background: rgba(var(--accent-rgb), 0.08);
}
.copy-email svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.footer-links {
  display: flex;
  gap: 24px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text-muted);
  margin-top: 6px;
}

@media (max-width: 900px) {
  .site-footer { flex-direction: column; gap: 20px; }
}


/* ---- Back to top ---- */
/* ---- Back to top ----
   Fixed to the bottom-right, hidden until the page has scrolled past 200px.
   It keeps `visibility: hidden` while out of view so it never takes a tab stop
   or a hover from the content underneath it. */
.back-to-top {
  position: fixed;
  isolation: isolate;
  right: 24px;
  bottom: 24px;
  z-index: 900;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 62px;
  height: 62px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--bg);
  color: var(--accent);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s, background-color 0.15s ease;
}
.back-to-top.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.back-to-top:hover,
.back-to-top:focus-visible {
  background: rgba(var(--accent-rgb), 0.8);
  color:white;
  border-color: white;
}
/* The arrival ping. One ring expands out of the button edge and fades, twice.
   `z-index: -1` puts it behind the chevron and `isolation` on the button keeps
   it from sliding behind the page. `is-pinging` is added by the script and
   removed on animationend, which is what lets the ping replay.

   The ring is a real span, not a `::after`. Chrome 152 does not dispatch
   animation events for pseudo-elements, so an `::after` ring animates but never
   reports animationend, and the class stays on forever. Verified on this page. */
.back-to-top-ring {
  position: absolute;
  inset: -1px;
  z-index: -1;
  border: 1px solid var(--accent);
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
}
.back-to-top.is-pinging .back-to-top-ring {
  animation: back-to-top-ping 1.1s ease-out 2;
}
@keyframes back-to-top-ping {
  from { transform: scale(1); opacity: 0.45; }
  to   { transform: scale(1.6); opacity: 0; }
}
/* Under reduced motion the ring holds still and only fades. That is the
   accommodation, not silence: "reduce" asks for no travel across the screen,
   and a stationary fade still draws the eye. Suppressing the animation
   outright would also strand `is-pinging`, since animationend is what removes
   it, so the ping must keep running in some form. */
@keyframes back-to-top-ping-fade {
  from { opacity: 0.5; }
  to   { opacity: 0; }
}
/* The hover label. It sits to the left of the button and is decorative: the
   button already carries the same words as its aria-label, so the span is
   hidden from the accessibility tree to avoid announcing them twice. */
.back-to-top-label {
  position: absolute;
  right: calc(100% + 10px);
  top: 50%;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  color: var(--text-body);
  font-family: var(--font-sans);
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transform: translateY(-50%) translateX(4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.back-to-top:hover .back-to-top-label,
.back-to-top:focus-visible .back-to-top-label {
  opacity: 1;
  transform: translateY(-50%) translateX(0);
}
.back-to-top svg {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (max-width: 900px) {
  .back-to-top svg{width:12px;height:12px;}
  .back-to-top {width:42px;height:42px;}
}

@media (max-width: 640px) {
  .back-to-top { right: 16px; bottom: 16px; }
}

@media (max-width: 640px) {
  /* No hover on touch, and the label would crowd the edge. */
  .back-to-top-label { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .back-to-top { transition: opacity 0.2s ease, visibility 0.2s; transform: none; }
  .back-to-top.is-visible { transform: none; }
  .back-to-top.is-pinging .back-to-top-ring { animation-name: back-to-top-ping-fade; }
  .back-to-top-label { transition: opacity 0.15s ease; transform: translateY(-50%); }
  .back-to-top:hover .back-to-top-label,
  .back-to-top:focus-visible .back-to-top-label { transform: translateY(-50%); }
}
