/* ==========================================================================
   THE NAVBAR - one definition, every page.

   This file is the single source of truth for the bar and the mobile drawer.
   index.html, vola.html and gpay.html all link it, so the nav cannot drift
   between the homepage and a case study.

   Theming: everything reads a --nav-* token declared right below. A page
   re-tints the bar by overriding those tokens in its own stylesheet - it must
   never restyle .nav, .cta or .mnav directly, or this stops being one navbar.

     vola.css  ->  --nav-accent:#FBB931
     gpay.css  ->  --nav-accent:#FDBE01
   ========================================================================== */

/* The bar carries its own two faces so a page that uses a completely different
   type system (VOLA runs on Bebas + DM Sans) still gets the real navbar. */
@font-face{font-family:'Satoshi';src:url('../fonts/satoshi-400.woff2') format('woff2');font-weight:400;font-display:swap}
@font-face{font-family:'Satoshi';src:url('../fonts/satoshi-500.woff2') format('woff2');font-weight:500;font-display:swap}
@font-face{font-family:'Satoshi';src:url('../fonts/satoshi-700.woff2') format('woff2');font-weight:700;font-display:swap}

:root{
  --nav-bg:#1c1c1c;
  --nav-ink:#fff;
  --nav-muted:#8b8b8f;
  --nav-faint:#5a5a5f;
  --nav-line:rgba(255,255,255,.12);
  /* THE BAR'S TINTS, TOKENISED 2026-08-03 so it can go light.
     These were four hardcoded rgba(255,255,255,...) - a fill and three edges -
     and every one of them is invisible on a light bar. The values here are the
     dark ones the bar has always had, so vola.html and gpay.html are unchanged
     and inherit them; index.html overrides them per theme. Anything that
     paints a tint on the bar belongs here, not in a rule. */
  --nav-fill:rgba(255,255,255,.05);      /* the CTA chip's fill */
  --nav-edge:rgba(255,255,255,.2);       /* its border */
  --nav-edge-hi:rgba(255,255,255,.45);   /* its border, hovered or pressed */
  --nav-hair:rgba(255,255,255,.06);      /* the bar's own bottom rule */
  --nav-press:rgba(255,255,255,.04);     /* drawer row, pressed */
  --nav-grid:rgba(255,255,255,.035);     /* the drawer's square grid */
  --nav-accent:#ff6a2f;
  --nav-serif:'Playfair Display',Georgia,serif;
  /* The bar's sans, tokenised for the same reason --nav-serif is: a page whose
     body runs a different face overrides this rather than restyling .nav. */
  --nav-sans:'Satoshi',system-ui,sans-serif;
  --nav-x:clamp(20px,6vw,120px);       /* matches every section's edge */
  --nav-h:72px;
  --nav-ease:cubic-bezier(.7,0,.2,1);
}

/* ==========================================================================
   BAR
   ========================================================================== */

.nav{
  position:fixed;inset:0 0 auto 0;z-index:60;   /* above the trail (z20) and the drawer (z50) */
  display:flex;align-items:center;justify-content:space-between;
  min-height:var(--nav-h);                      /* stable height -> the drawer sits flush under it */
  padding:16px var(--nav-x);                    /* brand aligns with section content */
  font-family:var(--nav-sans);
  font-weight:500;font-size:15px;
  background:var(--nav-bg);                     /* solid fill so trail images pass UNDER it */
  border-bottom:1px solid var(--nav-hair);
}
.brand{
  font-family:var(--nav-serif);font-weight:600;font-size:18px;letter-spacing:0;
  color:var(--nav-ink);text-decoration:none;cursor:pointer;
}
.nav-right{display:flex;align-items:center;gap:clamp(18px,2.4vw,34px)}
/* THE BACK ARROW - case studies only. Lives in the bar, left of the brand, so
   it is always reachable and can never sit on top of page content. Pages
   without a .nav-left (index, projects) are untouched. nav.js makes it go back
   in history when the visitor came from this site, else to projects.html. */
.nav-left{display:flex;align-items:center;gap:6px;min-width:0}
.nav-back{
  flex:none;display:inline-grid;place-items:center;
  width:36px;height:36px;margin-left:-6px;   /* hit area; the arrow sits flush with the edge */
  color:var(--nav-ink);opacity:.9;
  transition:opacity .25s ease;
  -webkit-tap-highlight-color:transparent;
}
.nav-back svg{width:24px;height:24px;transition:transform .25s var(--nav-ease)}
.nav-back:hover{opacity:1}
.nav-back:hover svg{transform:translateX(-3px)}
.nav-back:focus-visible{outline:2px solid var(--nav-accent);outline-offset:2px;border-radius:6px}
@media (hover:none){.nav-back{width:44px;height:44px;margin-left:-10px}.nav-back:active{opacity:.6}}

.nav-links{display:flex;align-items:center;gap:clamp(2px,.6vw,10px)}

/* Hit frame is bigger + wider than the text so nearby hovers/clicks register.
   Vertical padding is tuned to the CTA height so the bar height is unchanged. */
.nav-item{
  position:relative;display:inline-flex;align-items:center;
  padding:9px 16px;
  color:var(--nav-ink);text-decoration:none;font-weight:500;
  opacity:.9;cursor:pointer;transition:opacity .25s ease;
}
.nav-item:hover{opacity:1}
/* brackets anchored to the TEXT (not the padded frame) so they stay snug */
.nav-item .lbl{position:relative}
.nav-item .lbl::before,.nav-item .lbl::after{
  position:absolute;top:50%;color:var(--nav-muted);font-weight:500;opacity:0;
  transition:transform .28s var(--nav-ease),opacity .28s var(--nav-ease);pointer-events:none;
}
.nav-item .lbl::before{content:"[";right:100%;margin-right:6px;transform:translateY(-50%) translateX(5px)}
.nav-item .lbl::after{content:"]";left:100%;margin-left:6px;transform:translateY(-50%) translateX(-5px)}
.nav-item:hover .lbl::before,.nav-item.active .lbl::before,
.nav-item:hover .lbl::after,.nav-item.active .lbl::after{opacity:1;transform:translateY(-50%) translateX(0)}
.nav-item.active{opacity:1}

/* ==========================================================================
   GET-IN-TOUCH CTA - dark pill with two googly eyes
   ========================================================================== */

.cta{
  display:inline-flex;align-items:center;gap:11px;
  padding:8px 8px 8px 18px;border-radius:999px;
  background:var(--nav-fill);border:1px solid var(--nav-edge);
  color:var(--nav-ink);font-weight:500;font-size:14px;text-decoration:none;white-space:nowrap;
  transition:border-color .25s ease;
}
.cta:hover{border-color:var(--nav-edge-hi)}
.cta-eyes{display:inline-flex;gap:4px}
.eye{
  /* AN EYEBALL IS WHITE, SO ON A LIGHT BAR IT NEEDS AN EDGE. Unringed, the
     white disc vanishes into a near-white chip and all that is left is two
     pupils floating in the button. The ring is transparent by default, so the
     dark bar is exactly as it was; a light page gives --nav-eye-ring a value. */
  width:22px;height:22px;border-radius:50%;background:#fff;
  box-shadow:inset 0 0 0 1px var(--nav-eye-ring,transparent);
  position:relative;transform-origin:center;transition:transform .12s ease;
}
.cta-eyes.blink .eye{transform:scaleY(.12)}   /* the blink */
.pupil{
  position:absolute;top:50%;left:50%;width:8.5px;height:8.5px;border-radius:50%;
  background:#0a0a0a;transform:translate(-50%,-50%);transition:transform .07s linear;
}

/* ==========================================================================
   BURGER (mobile only)
   ========================================================================== */

.burger{
  display:none;                      /* the mobile query turns this on */
  position:relative;                 /* the two bars are absolutely centred inside */
  width:44px;height:44px;margin-right:-10px;   /* 44px hit area, optically flush with the edge */
  align-items:center;justify-content:center;
  background:none;border:0;padding:0;cursor:pointer;
}
.burger span{
  display:block;position:absolute;width:22px;height:1.5px;background:var(--nav-ink);border-radius:2px;
  transition:transform .38s var(--nav-ease);
}
.burger span:first-child{transform:translateY(-4px)}
.burger span:last-child{transform:translateY(4px)}
/* the two rules cross into an X - same easing as the intro strip */
.burger.open span:first-child{transform:translateY(0) rotate(45deg)}
.burger.open span:last-child{transform:translateY(0) rotate(-45deg)}

/* ==========================================================================
   MOBILE DRAWER
   Drops as a curtain from under the nav, echoing the intro's strip wipe.
   z50 keeps it under the nav (z60) so the bar stays put while it opens.
   ========================================================================== */

.mnav{
  position:fixed;inset:0;z-index:50;background:var(--nav-bg);
  padding:calc(var(--nav-h) + 26px) clamp(20px,6vw,48px) 30px;
  display:none;flex-direction:column;   /* the mobile query turns this on */
  clip-path:inset(0 0 100% 0);
  visibility:hidden;
  transition:clip-path .52s var(--nav-ease),visibility 0s linear .52s;
  overflow-y:auto;overscroll-behavior:contain;
}
.mnav.open{clip-path:inset(0 0 0 0);visibility:visible;transition:clip-path .52s var(--nav-ease),visibility 0s}
/* same faint grid as the hero, so the drawer reads as part of the same world */
.mnav::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  background-image:
    linear-gradient(var(--nav-grid) 1px,transparent 1px),
    linear-gradient(90deg,var(--nav-grid) 1px,transparent 1px);
  background-size:56px 56px;
  -webkit-mask:radial-gradient(ellipse 90% 60% at 50% 30%,#000 20%,transparent 85%);
          mask:radial-gradient(ellipse 90% 60% at 50% 30%,#000 20%,transparent 85%);
}
.mnav-links{position:relative;display:flex;flex-direction:column;border-top:1px solid var(--nav-line)}
.mnav-item{
  display:flex;align-items:baseline;gap:28px;   /* room for the [ bracket to sit in */
  padding:20px 2px;min-height:44px;border-bottom:1px solid var(--nav-line);
  color:var(--nav-ink);text-decoration:none;cursor:pointer;
  opacity:0;transform:translateY(16px);
  transition:opacity .5s ease,transform .5s var(--nav-ease),background-color .2s ease;
}
.mnav.open .mnav-item{opacity:1;transform:none}
.mnav.open .mnav-item:nth-child(1){transition-delay:.10s}
.mnav.open .mnav-item:nth-child(2){transition-delay:.17s}
.mnav.open .mnav-item:nth-child(3){transition-delay:.24s}
.mnav-item:active{background:var(--nav-press)}
.mnum{font-size:11px;letter-spacing:.18em;font-weight:600;color:var(--nav-faint);flex:none}
.mnav-item .mlbl{
  position:relative;font-family:var(--nav-serif);font-weight:600;
  font-size:clamp(28px,7.6vw,40px);line-height:1.1;letter-spacing:-.01em;
}
/* TOUCH PARITY: desktop slides these in on hover - with no hover they are
   simply always rendered, dimmed, and brighten to the accent when active. */
.mnav-item .mlbl::before,.mnav-item .mlbl::after{
  position:absolute;top:50%;transform:translateY(-50%);
  color:var(--nav-faint);font-weight:500;transition:color .25s ease;
}
.mnav-item .mlbl::before{content:"[";right:100%;margin-right:8px}
.mnav-item .mlbl::after{content:"]";left:100%;margin-left:8px}
.mnav-item.active .mlbl::before,.mnav-item.active .mlbl::after{color:var(--nav-accent)}

.mnav-foot{
  position:relative;margin-top:auto;padding-top:34px;
  opacity:0;transform:translateY(16px);
  transition:opacity .5s ease .30s,transform .5s var(--nav-ease) .30s;
}
.mnav.open .mnav-foot{opacity:1;transform:none}
/* the CTA keeps its googly eyes - it just gets the full width and bigger eyes */
.mnav-foot .cta{
  opacity:1;display:flex;justify-content:space-between;
  width:100%;padding:15px 15px 15px 22px;font-size:16px;
}
.mnav-foot .cta:active{border-color:var(--nav-edge-hi);transform:scale(.985)}
.mnav-foot .eye{width:26px;height:26px}
.mnav-foot .pupil{width:10px;height:10px}
.mnav-meta{
  display:flex;justify-content:space-between;gap:16px;margin-top:26px;
  font-size:12px;font-weight:500;color:var(--nav-muted);
}
.mnav-meta b{color:var(--nav-ink);font-weight:700}

/* Drawer open: the page behind must not scroll. */
body.menu-open{position:fixed;left:0;right:0;width:100%;overflow:hidden}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width:820px){
  .nav{padding:16px 20px}
  .nav-links,.nav .cta{display:none}
  .burger{display:flex}
  .mnav{display:flex}
  .brand{font-size:17px}
}

/* Coarse pointers - kill hover-only styling that sticks after a tap. */
@media (hover:none){
  .nav-item:active,.brand:active{opacity:.6}
  /* the brand is a link - give it a real hit area, not just its text box */
  .brand{display:inline-flex;align-items:center;min-height:44px}
}
/* Coarse pointer on a wide screen (tablet landscape): the desktop nav is
   showing, so its controls need touch-sized targets too. */
@media (hover:none) and (min-width:821px){
  .nav-item,.cta{min-height:44px}
  .nav{min-height:80px}
}

@media (prefers-reduced-motion:reduce){
  .mnav,.mnav-item,.mnav-foot,.burger span{transition-duration:.01ms}
}

/* ==========================================================================
   THE THEME DIAL

   Moved here from index.html's inline block on 2026-09-11, when the case
   studies gained the same control. It reads --nav-* like everything else in
   this file, so it re-tints with the bar and a page that wants a different
   dial changes a token rather than this rule.

   Pages without a #themeSwitch in their markup are untouched by all of it.

   IT STAYS IN THE BAR ON MOBILE, where nav.css hides .nav-links and .cta. A
   theme switch folded into a burger drawer is a theme switch nobody finds,
   and this is one tap, not a navigation choice.
   ========================================================================== */
.tsw{
  flex:none;display:inline-grid;place-items:center;
  width:44px;height:44px;                  /* real touch target */
  background:none;border:0;padding:0;cursor:pointer;
  color:var(--nav-accent);
  -webkit-tap-highlight-color:transparent;
}
.tsw-dial{
  position:relative;display:grid;place-items:center;
  width:34px;height:34px;border-radius:50%;
  border:1px solid var(--nav-line);
  transition:transform .62s var(--nav-ease),border-color .25s ease,background-color .25s ease;
}
.tsw:hover .tsw-dial{border-color:var(--nav-edge-hi);background:var(--nav-fill)}
.tsw:focus-visible .tsw-dial{outline:2px solid var(--nav-accent);outline-offset:3px}

/* THE SPIN IS ON THE DIAL, THE SWAP IS INSIDE IT. Half a turn carries the
   sparkle out and the crescent in, so the two read as two faces of one object
   being turned over rather than two pictures cross-fading in place. Each face
   then counter-rotates by the same 180deg, which is what keeps the crescent
   upright at the end of the turn instead of hanging upside down - drop that
   and the dark state looks like a rendering fault. */
html.dark .tsw-dial{transform:rotate(180deg)}
.tsw-dial svg{
  grid-area:1/1;width:17px;height:17px;fill:none;stroke:currentColor;
  transition:opacity .3s ease,transform .45s var(--nav-ease);
}
.tsw-dial .tsw-moon{fill:currentColor;stroke:none}
.tsw-dial .tsw-sun circle{fill:currentColor;stroke:none}
.tsw-moon{opacity:0;transform:scale(.4) rotate(-180deg)}
html.dark .tsw-sun{opacity:0;transform:scale(.4) rotate(180deg)}
html.dark .tsw-moon{opacity:1;transform:none}

/* THE WIPE. The incoming theme is revealed as a circle growing out of the
   dial, driven by the View Transitions API - so there is no snapshot to fake,
   no second copy of the page in the DOM, and nothing to clean up if it is
   interrupted. The browser holds the old frame; theme.js animates a clip-path
   on the new one.

   THE DEFAULT CROSS-FADE HAS TO GO. Left on, it runs underneath the circle and
   the whole page dips through a ghost of itself while the circle travels - two
   transitions fighting, and the one you did not ask for is the one you see.
   `animation:none` on both halves is what stops it. */
::view-transition-old(root),
::view-transition-new(root){animation:none;mix-blend-mode:normal}
::view-transition-old(root){z-index:1}
::view-transition-new(root){z-index:2}

@media (prefers-reduced-motion:reduce){
  .tsw-dial,.tsw-dial svg{transition:none}
}

/* ==========================================================================
   THE COPIED-EMAIL ANNOTATION

   IT LIVES IN THE NAVBAR FILE FOR ONE REASON: this is the only stylesheet all
   five pages load. index.html carries its own CSS inline and each case study
   has its own sheet, so a component that belongs to every page has exactly one
   home, and this is it. It costs the bar nothing - the rules below touch
   nothing the bar owns.

   It re-tints per page for free, because it reads the same --nav-* tokens the
   bar does: on VOLA it comes up court green on paper, on GPay near-black, and
   it follows the light/dark switch without a second rule.
   ========================================================================== */
.copied{
  position:fixed;left:0;top:0;
  /* Over the bar (60), the drawer (50) and GPay's fixed pill (45); under the
     skip link (200), which must stay reachable whatever else is on screen. */
  z-index:90;
  display:inline-flex;align-items:center;gap:9px;
  max-width:calc(100vw - 24px);
  padding:9px 14px;
  border:1px solid var(--nav-edge);
  border-radius:8px;
  background:var(--nav-bg);
  color:var(--nav-ink);
  font-family:var(--nav-sans,'Satoshi',system-ui,-apple-system,sans-serif);
  font-size:12.5px;font-weight:700;line-height:1.25;
  letter-spacing:.06em;text-transform:uppercase;
  box-shadow:0 10px 30px rgba(0,0,0,.28);
  /* NEVER display:none. contact.js measures this box while it is hidden in
     order to centre it on the button, and a display:none box has no width to
     centre. visibility keeps the layout and still hides it from the reader
     and from the accessibility tree. */
  opacity:0;visibility:hidden;
  transform:translateY(-4px);
  transition:opacity .18s ease,transform .18s ease,visibility .18s;
  pointer-events:none;          /* it is a message, not a target */
}
.copied::before{
  content:'';flex:none;
  width:6px;height:6px;border-radius:50%;
  background:var(--nav-accent);
}
.copied.is-on{opacity:1;visibility:visible;transform:none}

/* The fallback state, where the copy was refused and the annotation is showing
   the address itself. An email address set in tracked capitals is not an email
   address anyone can read back. */
.copied.is-raw{text-transform:none;letter-spacing:.01em;font-weight:600}

@media (prefers-reduced-motion:reduce){
  .copied{transition:opacity .01s linear,visibility .01s linear}
  .copied,.copied.is-on{transform:none}
}
