/* ui.css — the three interaction components, shared by every page.
 *
 *   1. Scroll text reveal      (reveal.js)      [data-reveal]
 *   2. Nav dropdowns + mobile  (site-nav.js)    [data-menu]
 *   3. Liquid contact popover  (site-nav.js)    [data-contact]
 *
 * Loaded after glass.css and before each page's own <style>, so a page can
 * still override anything here.
 *
 * All three are PROGRESSIVE ENHANCEMENTS. With JavaScript off: the reveal
 * text is simply visible, the nav links are ordinary links, and "Let's talk"
 * goes straight to WhatsApp. Nothing here is load-bearing for reading the
 * site or reaching a page.
 */

/* ══════════════════════════ 1 · text reveal ══════════════════════════
   Words and lines sit in an overflow-hidden mask and slide up out of it.
   The mask is what makes it read as a reveal rather than a fade — without
   it you get text sliding over whatever is above it.

   Nothing is hidden until JS has split the element and set .is-armed, so a
   failed script leaves readable text rather than an invisible heading. */
[data-reveal] .rv-line{display:block;overflow:hidden}
[data-reveal] .rv-i{display:inline-block;will-change:transform,opacity}
/* Holds a whole word together while its characters animate individually.
   Without it the browser breaks lines between characters. */
[data-reveal] .rv-w{display:inline-block;white-space:nowrap}

[data-reveal].is-armed .rv-i{
  opacity:0;
  transform:translateY(var(--rv-from,105%));
}
[data-reveal].is-armed.is-in .rv-i{
  opacity:1;
  transform:translateY(0);
  transition:transform var(--rv-dur,.8s) cubic-bezier(.22,1,.36,1) var(--d,0s),
             opacity   var(--rv-dur,.8s) cubic-bezier(.22,1,.36,1) var(--d,0s);
}

/* Scrubbed: progress is driven by scroll position, so there is no
   transition at all — every frame is a new resting state. */
[data-reveal][data-scroll="scrub"].is-armed .rv-i{transition:none}

/* Characters travel a shorter distance. At 105% a single letter looks like
   it is falling from another line rather than rising into its own. */
[data-reveal="chars"]{--rv-from:60%}

@media(prefers-reduced-motion:reduce){
  [data-reveal].is-armed .rv-i{opacity:1;transform:none;transition:none}
}

/* ══════════════════════ site footer ══════════════════════
   One markup block, identical on all three pages. Content model follows the
   reference the client gave (Company / Services / Legal columns, socials, a
   registration line) but every entry points at something that genuinely
   exists — see the note in the markup about what is deliberately absent. */
/* position/z-index are load-bearing on the homepage: #gl is a fixed canvas at
   z-index 1, so a static footer would be painted underneath it. */
.site-foot{position:relative;z-index:3;
           border-top:1px solid rgba(255,255,255,.10);background:#000;
           margin-top:clamp(3rem,8vw,6rem)}
.site-foot .fwrap{width:min(1320px,100%);margin:0 auto;
                  padding:clamp(2.75rem,6vw,4.5rem) clamp(20px,5vw,64px) 0}

.foot-top{display:grid;gap:clamp(2rem,4vw,3rem);
          grid-template-columns:minmax(0,1.5fr) repeat(3,minmax(0,1fr))}

.foot-brand img{height:44px;width:auto;display:block;margin-bottom:1.1rem}
.foot-brand p{color:rgba(255,255,255,.56);font-size:14.5px;line-height:1.6;max-width:34ch}
/* ── contrast floor in the footer ──────────────────────────────────
   Every muted value down here was set by eye against a bright monitor.
   Measured on #000 they composite to about #666 — 3.66:1 at 13–13.5px,
   under the 4.5:1 AA floor — and this is the only place on the site
   carrying the phone numbers, the email and the registration. .58 is
   about 5.9:1 and looks the same on a good screen. */
.foot-reg{margin-top:1.1rem;color:rgba(255,255,255,.58);font-size:12.5px;line-height:1.6}

.foot-col h3{font-size:12px;letter-spacing:.15em;text-transform:uppercase;
             color:rgba(255,255,255,.58);font-weight:600;margin-bottom:1rem}
.foot-col ul{list-style:none;display:grid;gap:.6rem}
.foot-col a{color:rgba(255,255,255,.68);text-decoration:none;font-size:14.5px;
            line-height:1.4;transition:color .16s ease}
.foot-col a:hover{color:#fff}
.foot-col address{font-style:normal;color:rgba(255,255,255,.56);font-size:14px;
                  line-height:1.6;margin-top:.9rem}

.foot-social{display:flex;gap:10px;margin-top:1.4rem}
.foot-social a{display:inline-flex;align-items:center;justify-content:center;
               width:38px;height:38px;border-radius:50%;
               border:1px solid rgba(255,255,255,.14);color:rgba(255,255,255,.72);
               transition:background .16s ease,color .16s ease,border-color .16s ease}
.foot-social a:hover{background:rgba(255,255,255,.09);color:#fff;
                     border-color:rgba(255,255,255,.26)}
.foot-social svg{width:17px;height:17px}

.foot-bottom{display:flex;flex-wrap:wrap;gap:.75rem 1.75rem;
             align-items:center;justify-content:space-between;
             margin-top:clamp(2rem,4vw,3rem);
             border-top:1px solid rgba(255,255,255,.08);
             padding:1.4rem 0 clamp(1.75rem,3vw,2.5rem)}
.foot-bottom p{color:rgba(255,255,255,.58);font-size:13px;line-height:1.5}
.foot-bottom nav{display:flex;flex-wrap:wrap;gap:.5rem 1.4rem}
.foot-bottom a{color:rgba(255,255,255,.58);text-decoration:none;font-size:13px;
               transition:color .16s ease}
.foot-bottom a:hover{color:rgba(255,255,255,.78)}

@media(max-width:900px){
  .foot-top{grid-template-columns:1fr 1fr}
  .foot-brand{grid-column:1/-1}
}
/* ── the footer is the biggest block on the site ────────────────────
   Measured on a 390px phone: 1447px, on all three pages. That is 1.7
   screens of footer, and 27% of the entire homepage — more than the hero
   and more than any section of real content.

   Stacking all three link columns is what did it. Company and What-we-do
   are short lists of short labels, so side by side they cost the height of
   the longer one instead of the sum of both. Contact keeps the full width:
   it holds the address, and it is the column a phone visitor actually came
   for. Nothing is removed. */
@media(max-width:560px){
  .foot-top{grid-template-columns:1fr 1fr;gap:1.9rem 1.5rem}
  .foot-brand{grid-column:1/-1}
  /* :last-child, not :nth-of-type — .foot-brand is a div too, so
     nth-of-type(3) picks "What we do" rather than Contact. */
  .foot-top > :last-child{grid-column:1/-1}
  .foot-brand p{max-width:none}
  .foot-bottom{flex-direction:column;align-items:flex-start;gap:0}
}

/* ── footer on phones ───────────────────────────────────────────────
   Measured at 360px: every link in these columns was a row 18px tall.
   That is a real target on a desktop pointer and a guess on a thumb, and
   the footer is the ONLY place a phone visitor can reach the phone
   numbers and the email — so these are load-bearing, not decoration.

   Each link becomes its own 44px row. The column gap drops to 0 to pay
   for it, so the footer barely grows: the space that was dead air between
   18px links is now inside the links themselves. */
@media(max-width:720px){
  .foot-col ul{gap:0}
  .foot-col li + li{border-top:1px solid rgba(255,255,255,.06)}
  .foot-col a{display:flex;align-items:center;min-height:44px}
  .foot-social{gap:12px;margin-top:1rem}
  .foot-social a{width:44px;height:44px}
  .foot-bottom nav{gap:0;width:100%}
  .foot-bottom nav a{display:flex;align-items:center;min-height:44px;width:100%}
  /* 12px uppercase with .15em tracking is the smallest thing on the page
     and it is a heading. */
  .foot-col h3{font-size:13px;margin-bottom:.35rem}
  .foot-reg{font-size:13px;margin-top:.85rem}
  .foot-bottom p,.foot-bottom a{font-size:13.5px}
  /* Paying for the 44px rows above by taking it out of the padding, so the
     footer gets more tappable without getting taller. */
  .site-foot .fwrap{padding-top:2.25rem}
  .foot-brand img{height:38px;margin-bottom:.85rem}
  /* The mark is a link; a 38px-tall row is not a target. */
  .foot-brand > a{display:inline-flex;align-items:center;min-height:44px;min-width:44px}
  .foot-col address{margin-top:.75rem}
  .foot-bottom{margin-top:1.5rem;padding:.5rem 0 1.5rem}
}

/* ══════════════════════ 2 · nav dropdowns ══════════════════════ */
.nav-glass{position:relative}

/* ── the logo ──────────────────────────────────────────────────────
   `hero-kit/assets/fenox-mark.png` is a processed copy of assets/logos/2.png,
   built by hand and worth not re-deriving:

     · the supplied file is a 500px square, rgb24, with a solid dark
       background and NO alpha, so dropped straight in it drew as a dark tile
       over the hero. `mix-blend-mode:screen` was tried first and failed —
       the background is a dark navy tint rather than pure black, and the
       pill's backdrop-filter makes its own stacking context to blend against.
       It is now keyed to real transparency (ffmpeg `lumakey`).
     · it was cropped to its content, because the original wastes ~22% of
       each edge on padding and the mark is small enough at nav size already.
     · brightness and saturation are lifted, because half the ribbon is dark
       navy and vanished against a dark pill.

   If a transparent PNG or an SVG ever arrives from the client, replace the
   file and delete this note. */
.nav-glass .brand{display:inline-flex;align-items:center;margin-right:12px;padding:0}
.nav-glass .brand img{width:auto;height:38px;display:block;transition:opacity .18s ease}
.nav-glass .brand:hover img{opacity:.8}
/* The mark stays 32px for balance against the 44px toggle, but the LINK
   gets a full-height target around it — it was 34x32. */
@media(max-width:720px){
  .nav-glass .brand img{height:32px}
  .nav-glass .brand{min-height:44px;min-width:44px;justify-content:center;
                    padding-right:4px;margin-right:2px}
}

/* The caret only appears once JS has attached a real panel to the link. */
.nav-ca{display:none;width:9px;height:9px;margin-left:5px;opacity:.55;
        transition:transform .28s var(--ease,cubic-bezier(.22,1,.36,1)),opacity .2s ease}
.has-menu .nav-ca{display:inline-block}
.has-menu{display:inline-flex;align-items:center}
.has-menu[aria-expanded="true"]{color:#fff}
.has-menu[aria-expanded="true"] .nav-ca{transform:rotate(180deg);opacity:1}

.nav-drop{
  position:absolute;top:calc(100% + 10px);left:0;z-index:60;
  min-width:200px;padding:8px;border-radius:18px;
  /* Near-opaque on purpose. At 86% the page showed straight through the menu
     and the labels fought whatever image happened to be behind them. A menu
     is a surface you read, not a window. */
  background:rgba(6,6,9,.975);border:1px solid rgba(255,255,255,.15);
  -webkit-backdrop-filter:blur(26px) saturate(150%);
  backdrop-filter:blur(26px) saturate(150%);
  box-shadow:0 22px 60px rgba(0,0,0,.55),inset 0 1px 0 rgba(255,255,255,.08);
  opacity:0;visibility:hidden;
  transform:translateY(-6px) scale(.97);
  transform-origin:var(--origin,20% 0);
  transition:opacity .22s ease,transform .3s var(--ease,cubic-bezier(.22,1,.36,1)),
             visibility 0s linear .3s;
}
.nav-drop.is-open{opacity:1;visibility:visible;transform:none;transition-delay:0s}
/* Invisible bridge across the gap between the pill and the panel, so moving
   the pointer down into the menu never leaves a hoverable surface. */
.nav-drop::before{content:"";position:absolute;left:0;right:0;top:-16px;height:16px}

.nav-drop a{display:block;padding:.55rem .7rem;border-radius:11px;
            text-decoration:none;color:rgba(255,255,255,.66);
            font-size:14px;line-height:1.35;transition:background .16s ease,color .16s ease}
.nav-drop a:hover,.nav-drop a:focus-visible{background:rgba(255,255,255,.07);color:#fff}
.nav-drop b{display:block;font-weight:600;font-size:14px;color:inherit}
.nav-drop small{display:block;margin-top:.15rem;font-size:12px;line-height:1.4;
                color:rgba(255,255,255,.42)}
.nav-drop a:hover small{color:rgba(255,255,255,.6)}

/* The rich variant: a column of links beside a preview that swaps on hover.
   Same component, wider panel — exactly the two shapes in the reference. */
/* Both tracks are DEFINITE widths on purpose. `minmax(0,236px)` collapses to
   its 0 minimum inside a shrink-to-fit, absolutely-positioned grid — the link
   column resolved to 0px, which then stretched the preview to 899px tall. A
   flexible track needs a container with a definite width to resolve against,
   and a floating panel does not have one. */
.nav-drop.is-mega{display:grid;grid-template-columns:236px 220px;
                  gap:10px;min-width:0;padding:10px}
.nav-prev{position:relative;overflow:hidden;border-radius:13px;background:#0d0d10;
          border:1px solid rgba(255,255,255,.08)}
.nav-prev img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
              opacity:0;transition:opacity .3s ease}
.nav-prev img.is-on{opacity:1}

@media(max-width:900px){ .nav-drop.is-mega{grid-template-columns:1fr} .nav-prev{display:none} }

/* ══════════════════════ 3 · mobile menu ══════════════════════
   Below 720px the pill hides its links. Before this there was no way at all
   to reach Services or Work from a phone — the nav was a logo and a CTA. */
/* 44px, not the 34px this used to be. On a phone this button IS the
   navigation — every other link in the pill is hidden — and it was the
   smallest target on the site. */
.nav-toggle{display:none;width:44px;height:44px;border-radius:50%;
            align-items:center;justify-content:center;
            background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.14);
            color:#fff;cursor:pointer;padding:0;margin-left:6px}
.nav-toggle svg{width:18px;height:18px}
.nav-toggle .ic-close{display:none}
.nav-toggle[aria-expanded="true"] .ic-open{display:none}
.nav-toggle[aria-expanded="true"] .ic-close{display:block}

/* 20px gutter to match the footer and the page text edge — the old
   clamp resolved to 23.4px here, matching nothing else on any page.
   overscroll-behavior stops a drag that reaches the end of the sheet from
   chaining into the page underneath. */
.m-sheet{position:fixed;inset:0;z-index:39;display:none;
         padding:92px 20px max(28px,env(safe-area-inset-bottom));
         background:rgba(4,4,6,.96);
         -webkit-backdrop-filter:blur(24px);backdrop-filter:blur(24px);
         overflow-y:auto;overscroll-behavior:contain}
.m-sheet.is-open{display:block}
/* The two actions sit at the bottom rather than trailing the list: collapsed,
   the sheet was leaving 330px of empty black under them, and expanded they
   were landing under the gesture bar. */
.m-sheet nav{display:flex;flex-direction:column;min-height:100%}
.m-sheet .m-cta{margin-top:auto}

/* The pill drops its logo on a phone, so the mark appears here instead.
   `align-self` is load-bearing: nav is a flex column, so the default
   `stretch` blows the image out to the full sheet width and `width:auto`
   cannot hold the ratio against it. */
.m-mark{height:34px;width:auto;align-self:flex-start;display:block;
        margin-bottom:1.75rem;opacity:.92}

/* ── three rows, two controls each ──────────────────────────────────
   The word is a link to the page. The caret is a separate button that
   opens that page's sections. Tapping the word must never be swallowed
   into "open the submenu" — a menu is a shortcut to a page, not a gate in
   front of it, which is the same rule the desktop dropdowns follow. */
.m-row{border-bottom:1px solid rgba(255,255,255,.09)}
.m-line{display:flex;align-items:center;gap:.75rem}
.m-link{flex:1 1 auto;display:block;padding:1.1rem 0;text-decoration:none;color:#fff;
        font-size:clamp(28px,8.5vw,36px);line-height:1.1;letter-spacing:-.038em;font-weight:600}
/* The page you are already on. Without this all three rows read identically
   and the current one is a link that reloads what you are looking at. */
.m-link[aria-current="page"]{color:rgba(255,255,255,.42)}
.m-link[aria-current="page"]::after{content:'';display:inline-block;
  width:6px;height:6px;margin-left:.55rem;border-radius:50%;vertical-align:.28em;
  background:linear-gradient(120deg,#7C3AED,#3B82F6)}
.m-more{flex:none;width:48px;height:48px;border-radius:50%;padding:0;cursor:pointer;
        display:grid;place-items:center;
        background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.15);
        color:rgba(255,255,255,.7);
        transition:background .18s ease,color .18s ease,border-color .18s ease}
.m-more svg{width:18px;height:18px;
            transition:transform .28s var(--ease,cubic-bezier(.22,1,.36,1))}
.m-more[aria-expanded="true"]{background:rgba(255,255,255,.13);color:#fff;
                              border-color:rgba(255,255,255,.28)}
.m-more[aria-expanded="true"] svg{transform:rotate(180deg)}

/* `[hidden]` is only `display:none` in the UA sheet, at effectively zero
   specificity — any `display` in an author rule silently defeats it. Without
   this line every section list renders open and the menu is the flat wall of
   fourteen links it was supposed to replace. */
.m-sub[hidden]{display:none}
.m-sub{display:grid;padding:0 0 .85rem}
.m-sub a{display:flex;align-items:center;min-height:44px;text-decoration:none;
         font-size:16px;font-weight:500;letter-spacing:-.01em;
         color:rgba(255,255,255,.66)}
.m-sub a:active{color:#fff}

.m-cta{margin-top:1.9rem;display:flex;flex-direction:column;gap:10px}
.m-cta .btn-solid,.m-cta .btn-glass{width:100%}
/* The svg() helper in site-nav.js emits a viewBox and no width/height, so an
   unsized icon inside a full-width flex button stretches to fill it — the
   WhatsApp mark rendered ~280px tall and pushed the button to 415px. Every
   other place that uses that helper sizes its icon; this one must too. */
.m-cta svg{width:18px;height:18px;flex:none}

@media(prefers-reduced-motion:reduce){ .m-more svg{transition:none} }

@media(max-width:720px){ .nav-toggle{display:inline-flex} }
@media(min-width:721px){ .m-sheet{display:none !important} }

/* ══════════ no nav bar on a phone — just the menu ══════════
   Client decision, 2026-08-09. The pill was still a bar: logo, sometimes a
   "Let's talk" button, and the toggle. On a 390px screen that is a strip of
   chrome across the top of every page for one control that is actually used.

   So below 720px the pill dissolves — no surface, no border, no shadow, no
   logo — and the toggle becomes the only thing left, a single floating glass
   button in the top-right. Everything the pill held is in the sheet: the
   mark, all three pages, and the WhatsApp/Call actions.

   `body > .topbar` rather than `.topbar` on purpose: all three pages define
   `.topbar{justify-content:center}` in their own inline <style>, which loads
   AFTER ui.css and would win at equal specificity. */
@media(max-width:720px){
  /* ── z-index 41 is not cosmetic: without it the menu traps the visitor ──
     `.m-sheet` is z-index 39. `.topbar` sets its own z-index in each page's
     inline <style> and they do not agree: index 20, services 30, work 40.
     So on the homepage and on services the sheet painted OVER the toggle —
     `elementFromPoint` at the button returned the sheet — and the close icon
     was unreachable by touch. `body{overflow:hidden}` is latched while the
     sheet is open and Escape does not exist on a phone, so the only ways out
     were to follow a link or leave the site. Verified by synthetic tap: the
     sheet stayed open.
     Set here rather than in three inline blocks so they cannot drift apart
     again; `body >` is needed to beat those inline rules at all. */
  body > .topbar{justify-content:flex-end;z-index:41}
  .topbar .nav-glass{background:none;border:0;box-shadow:none;padding:0;
                     -webkit-backdrop-filter:none;backdrop-filter:none}
  /* .glass::before paints a specular lip on the pill that is still there
     once the pill's own surface is gone — a hairline arc around nothing. */
  .topbar .nav-glass::before{display:none}
  .nav-glass .brand{display:none}
  /* On Services and Work the pill also carried the CTA. It moves into the
     sheet with everything else; it is still reachable in one tap, and it is
     in the footer and in the page body besides. */
  .nav-glass a.btn-solid{display:none}

  /* The toggle now IS the nav, so it carries the glass the pill used to. */
  .nav-toggle{width:48px;height:48px;margin-left:0;
              background:rgba(8,8,12,.72);border-color:rgba(255,255,255,.16);
              -webkit-backdrop-filter:blur(20px) saturate(150%);
              backdrop-filter:blur(20px) saturate(150%);
              box-shadow:0 8px 28px rgba(0,0,0,.4)}
  .nav-toggle svg{width:19px;height:19px}
}

/* ── the pill gets out of the way on phones ────────────────────────
   The nav is a floating pill centred at the top of the screen. On a wide
   screen it floats over margin; on a 390px phone it floats over the
   MIDDLE of the column, and it is near-opaque, so it sits on top of
   whatever you are reading — measured on the homepage covering the word
   "Media" in a card heading.

   Padding the content down would work and would cost a fixed 76px of
   every screen forever. Instead the pill leaves when you scroll down and
   comes back the moment you scroll up, which is the one gesture people
   already make when they want navigation. Reading area is full height;
   the nav is always one flick away.

   It never hides while the sheet is open, and never near the top of the
   page — see site-nav.js. */
@media(max-width:720px){
  .topbar{transition:transform .3s var(--ease,cubic-bezier(.22,1,.36,1)),opacity .22s ease}
  .topbar.nav-away{transform:translateY(-160%);opacity:0;pointer-events:none}
  /* Bigger sub-links in the sheet: these were ~41px rows. */
  .m-sheet .m-sub a{padding:.72rem 0}
}
@media(max-width:720px) and (prefers-reduced-motion:reduce){
  .topbar{transition:none}
}

/* ══════════════════════ 4 · liquid contact popover ══════════════════════
   "Let's talk" does not jump straight to WhatsApp any more — it opens
   WhatsApp / Call / Email, because those are the three routes the brief
   actually lists and only one of them was reachable before.

   The liquid part is an SVG filter on the WRAPPER: blur everything, then
   crush the alpha channel back to hard edges with a colour matrix. Shapes
   that overlap while blurred merge into one surface, so the panel appears
   to be pulled out of the button rather than drawn on top of it.

   The filter is why the panel is a SOLID fill and not glass — an SVG filter
   rasterises its subtree and backdrop-filter does not survive that. */
.talk-wrap{position:relative;display:inline-flex}
.talk-goo{position:absolute;top:0;left:0;right:0;z-index:-1;pointer-events:none;
          filter:url(#goo)}
.talk-blob{position:absolute;border-radius:999px;background:#fff;
           opacity:0;transform:scale(.2);
           transition:transform .42s cubic-bezier(.34,1.4,.5,1),opacity .2s ease}
.talk-wrap.is-open .talk-blob{opacity:1;transform:scale(1)}

.talk-menu{position:absolute;top:calc(100% + 12px);right:0;z-index:61;
           min-width:206px;padding:7px;border-radius:20px;background:#fff;
           box-shadow:0 24px 60px rgba(0,0,0,.5);
           opacity:0;visibility:hidden;transform:translateY(-10px) scale(.9);
           transform-origin:82% 0;
           transition:opacity .2s ease,transform .42s cubic-bezier(.34,1.4,.5,1),
                      visibility 0s linear .42s}
.talk-wrap.is-open .talk-menu{opacity:1;visibility:visible;transform:none;transition-delay:0s}
.talk-menu a{display:flex;align-items:center;gap:.65rem;
             padding:.6rem .7rem;border-radius:13px;text-decoration:none;
             color:#0A0A10;font-size:14.5px;font-weight:500;
             transition:background .15s ease}
.talk-menu a:hover,.talk-menu a:focus-visible{background:rgba(10,10,16,.07)}
.talk-menu svg{width:17px;height:17px;flex:none;opacity:.75}
.talk-menu small{display:block;font-size:12px;color:rgba(10,10,16,.5);font-weight:400}

/* The goo costs a full-layer rasterise every frame. Phones on the client's
   own do-not-want list ("slow-loading pages") do not need to pay for it, and
   the popover works identically without it. */
@media(max-width:720px),(prefers-reduced-motion:reduce){
  .talk-goo{display:none}
}
@media(prefers-reduced-motion:reduce){
  .talk-menu,.nav-drop{transition:opacity .01s linear,visibility 0s}
  .talk-menu,.nav-drop{transform:none}
}
