<style>
  /*--- Fonts ------------------------------------------------------------------
    Roman is the 147KB .otf from Shopify Files, and it has to stay that way for
    now. DO NOT point these at assets/HaasGrotDisp-55Roman.woff2 — that file is
    corrupt. It looks fine from every angle that is easy to check (46KB on disk,
    a wOF2 magic number, served 200 as font/woff2) and Chrome still refuses to
    decode it: FontFace.load() on that URL rejects with a NetworkError, which is
    the generic "could not be decoded" for font data. The failure is silent in a
    page, because --font-display lists the JP face after the Haas ones, so the
    whole store quietly renders in Zen Kaku Gothic and pulls a 2.3MB CJK .ttf to
    do it. assets/HaasGrotDisp-75Bold.woff2 should be assumed just as bad.

    A VALID woff2 is still worth ~100KB a page and is the right fix — it just
    needs a real conversion from the .otf (woff2_compress, or fonttools
    `pyftsubset --flavor=woff2`), verified with FontFace.load() before shipping.

    Zen Kaku is a Files .ttf for the same kind of reason, plus its own: a CJK
    face has no small version, so the fix there is subsetting, not a container
    swap.

    Every Haas alias below points at the SAME Roman file, weight 500 included.
    The store's rule is 55 Roman everywhere (no medium, no bold), but two ported
    stylesheets still name "…Display Pro - Medium" and the cart-drawer app still
    asks for weight 500 — so the Medium cut was being downloaded (150KB) to paint
    a handful of legacy elements in a weight this store does not use. Resolving
    those requests to Roman both drops the request and enforces the rule.
    If a page ever genuinely needs the Medium cut, add it back as its own file
    rather than reintroducing the alias.
  ---------------------------------------------------------------------------*/
  @font-face {
    font-family: "Neue Haas Grotesk Display Pro - 55 Roman";
    src: url({{ 'HaasGrotDisp-55Roman.otf' | file_url }}) format("opentype");
    font-display: swap;
  }
  @font-face {
    font-family: "Neue Haas Grotesk Display Pro - Medium";
    src: url({{ 'HaasGrotDisp-55Roman.otf' | file_url }}) format("opentype");
    font-display: swap;
  }
  /* single-family aliases so the ported system CSS ("Neue Haas Grotesk Display Pro",
     weight 400/500) resolves to the same file */
  @font-face {
    font-family: "Neue Haas Grotesk Display Pro";
    font-weight: 400;
    src: url({{ 'HaasGrotDisp-55Roman.otf' | file_url }}) format("opentype");
    font-display: swap;
  }
  @font-face {
    font-family: "Neue Haas Grotesk Display Pro";
    font-weight: 500;
    src: url({{ 'HaasGrotDisp-55Roman.otf' | file_url }}) format("opentype");
    font-display: swap;
  }
  @font-face {
    font-family: "Fragment Mono";
    font-weight: 400;
    src: url({{ 'fragment-mono.ttf' | asset_url }}) format("truetype");
    font-display: swap;
  }
  /* Yotpo's widget config asks for a family named "Neue Haas New" — alias it
     to the real Haas file so the review widgets match the system too */
  @font-face {
    font-family: "Neue Haas New";
    font-weight: 400;
    src: url({{ 'HaasGrotDisp-55Roman.otf' | file_url }}) format("opentype");
    font-display: swap;
  }
  @font-face {
    font-family: "Zen Kaku Gothic New - Regular";
    src: url({{ 'ZenKakuGothicNew-Regular.ttf' | file_url }}) format("truetype");
    font-display: swap;
  }
  @font-face {
    font-family: "Zen Kaku Gothic New - Medium";
    src: url({{ 'ZenKakuGothicNew-Medium.ttf' | file_url }}) format("truetype");
    font-display: swap;
  }
</style>

<link rel="stylesheet" href="{{ 'beyond-store.css' | asset_url }}" media="print" onload="this.media='all'">
<link rel="stylesheet" href="{{ 'beyond-copy.css' | asset_url }}" media="print" onload="this.media='all'">
{%- comment -%} chrome styles load EAGERLY: the pill/ticker must be right on
first paint (a late media-flip restyles the chrome and causes a transparent ->
violet transition flash on every load) {%- endcomment -%}
{{ 'bs-chrome.css' | asset_url | stylesheet_tag }}

<noscript>{{ 'beyond-store.css' | asset_url | stylesheet_tag }}</noscript>
<noscript>{{ 'beyond-copy.css' | asset_url | stylesheet_tag }}</noscript>
<script src="{{ 'jquery.js' | asset_url }}" defer="defer"></script>
<script src="{{ 'details-disclosure.js' | asset_url }}" defer="defer"></script>
<script src="{{ 'details-modal.js' | asset_url }}" defer="defer"></script>
<script src="{{ 'cart-notification.js' | asset_url }}" defer="defer"></script>

{%- comment -%} locale-aware root for store-internal links ('' on the default
locale, '/en-jp' etc. on markets) {%- endcomment -%}
{%- assign sroot = routes.root_url -%}
{%- if sroot == '/' -%}{%- assign sroot = '' -%}{%- endif -%}

{%- comment -%} Which nav link lights up. This theme only serves the store, so
every non-blog page belongs to Shop; blog index and articles light Blog. Was
hardcoded to Shop, which left it underlined on the blog. {%- endcomment -%}
{%- assign is_blog = false -%}
{%- if request.path contains '/blogs' -%}{%- assign is_blog = true -%}{%- endif -%}

{%- comment -%} ===== TICKER — ship-schedule marquee (metaobject-driven, same
data as before; restyled as a black mono strip by bs-chrome.css) ===== {%- endcomment -%}
{%- assign speed = shop.metaobjects.header.live.marquee_seconds_usa_desktop | append: "" -%}
{%- assign mspeed = shop.metaobjects.header.live.marquee_seconds_usa_mobile | append: "" -%}
{%- if cart.currency.iso_code != 'USD' or localization.market.handle == '2026intl' -%}
  {%- assign speed = shop.metaobjects.header.live.marquee_seconds_outside_desktop | append: "" -%}
  {%- assign mspeed = shop.metaobjects.header.live.marquee_seconds_outside_mobile | append: "" -%}
{%- endif -%}
<div class="bs-ticker">
  <div class="marqueev2" style="--stime: {{ speed }}s; --mstime: {{ mspeed }}s; --gap: 120px; --mgap: 24px">
  <div class="bs-ticker-track">
    {%- if localization.market.handle == '2026intl' -%}
      {% for num in (1..2) %}
        <div class="mv2-content mv2-scroll">
          {% for mqItem in shop.metaobjects.header.live.marquee_strings_2026_intl.value %}
              <span>{{ mqItem.content.value }}</span><span>•</span>
          {% endfor %}
        </div>
      {% endfor %}
    {%- elsif cart.currency.iso_code == 'USD' -%}
      {% for num in (1..2) %}
        <div class="mv2-content mv2-scroll">
          {% for mqItem in shop.metaobjects.header.live.marquee_strings_usa_only.value %}
              <span>{{ mqItem.content.value }}</span><span>•</span>
          {% endfor %}
        </div>
      {% endfor %}
    {%- else -%}
      {% for num in (1..2) %}
        <div class="mv2-content mv2-scroll">
          {% for mqItem in shop.metaobjects.header.live.marquee_strings_outside_usa.value %}
              <span>{{ mqItem.content.value }}</span><span>•</span>
          {% endfor %}
        </div>
      {% endfor %}
    {%- endif -%}
  </div>
  </div>
</div>

<script>
/* Marquee note-bar. On the product page it tracks the configurator: visible
   while any part of it is on screen, gone once you scroll past (and back when
   you return). Elsewhere: hide on scroll down, return on scroll up. */
(function () {
  var ticker = document.querySelector('.bs-ticker');
  if (!ticker) return;
  var lastY = 0, ticking = false;
  function update() {
    var hide;
    var anchor = document.getElementById('bsp');
    if (anchor) {
      hide = anchor.getBoundingClientRect().bottom <= 130;   /* past the configurator */
    } else {
      var y = window.scrollY;
      hide = y > lastY && y > 60;
      lastY = y;
    }
    ticker.classList.toggle('bs-ticker--hidden', hide);
  }
  function onScroll() {
    if (ticking) return;
    ticking = true;
    requestAnimationFrame(function () { update(); ticking = false; });
  }
  window.addEventListener('scroll', onScroll, { passive: true });
  window.addEventListener('resize', onScroll, { passive: true });

  /* ---- the tape: JS-driven marquee with a scroll pull. Slow cruise by
     default; scrolling tugs the tape faster, then it eases back to cruise.
     Replaces the legacy linear CSS loop (disabled in bs-chrome.css). ---- */
  var track = ticker.querySelector('.bs-ticker-track');
  var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (track && !reduce) {
    /* coverage guard: keep cloning copies until the tape spans 2x the bar,
       so the wrap point is never visible */
    var guard = 0;
    while (track.scrollWidth < ticker.offsetWidth * 2 && track.children.length && guard++ < 10) {
      track.appendChild(track.children[0].cloneNode(true));
    }
    var x = 0, lastT = null;
    var CRUISE = 32;          /* px/s — a calm read speed */
    var v = CRUISE, target = CRUISE;
    function loop(t) {
      if (lastT !== null) {
        var dt = Math.min((t - lastT) / 1000, 0.05);
        v += (target - v) * Math.min(dt * 5, 1);   /* ease toward cruise/stop */
        x -= v * dt;
        var first = track.children[0];
        if (first) {
          var gap = parseFloat(getComputedStyle(track).columnGap) || 48;
          var w = first.getBoundingClientRect().width + gap;
          if (w > 0 && -x >= w) x += w;
        }
        track.style.transform = 'translate3d(' + x + 'px, 0, 0)';
      }
      lastT = t;
      requestAnimationFrame(loop);
    }
    requestAnimationFrame(loop);
    /* pause to read: ease to a stop on hover, ease back on leave */
    ticker.addEventListener('mouseenter', function () { target = 0; });
    ticker.addEventListener('mouseleave', function () { target = CRUISE; });
  }
})();
</script>

{%- comment -%} ETA line inside the marquee strings targets #coded_eta {%- endcomment -%}
<script defer="defer">
  let waveTable = {
    w1: ['USD'],
    w2: ['EUR', 'GBP', 'CAD'],
    w3: ['AUD', 'NZD', 'JPY'],
  }
  let bsEtaProduct = {
    shipping: {
      w1: [{ "soldMinimum": 0, "etaText": "{{ shop.metaobjects.header.live.marquee_early }}" }],
      w2: [{ "soldMinimum": 0, "etaText": "{{ shop.metaobjects.header.live.marquee_late }}" }],
      w3: [{ "soldMinimum": 0, "etaText": "{{ shop.metaobjects.header.live.marquee_late }}" }],
    },
  }
  let {currency} = {{ cart | json }};
  let matchedWaves = Object.keys(waveTable).filter(key => waveTable[key].includes(currency));
  let waveKey = matchedWaves.length ? matchedWaves[0] : 'w3';
  let etas = bsEtaProduct.shipping[waveKey];
  let finalEta = etas.length ? etas[etas.length - 1].etaText : 'Pre-order now';
  let etaElement = document.querySelector('#coded_eta');
  if (etaElement) etaElement.innerHTML = finalEta;
</script>

{%- comment -%} ===== FLOATING PILL NAV — mirrors the marketing site's nav
(src/site/partials/nav.html); marketing links absolute, store links locale-aware ===== {%- endcomment -%}
<header class="header-nav-background"></header>
<header class="header-nav-frame font-detail">
  <nav class="header-nav">
    <a class="header-logo" href="https://bigscreenvr.com">
      <img alt="Bigscreen" src="{{ 'bs-wordmark.svg' | asset_url }}" width="134" height="30">
    </a>
    <div class="header-links">
      <span class="nav-item has-menu">
        <a class="header-link" href="https://bigscreenvr.com/">{{ shop.metaobjects.header_nav.live.nav_beyond2 | default: 'Beyond-2' }}<i class="nav-caret" aria-hidden="true"></i></a>
        <div class="nav-menu"><div class="nav-menu-panel">
          <a class="nav-menu-link" href="https://bigscreenvr.com/"><span class="nml-title">{{ shop.metaobjects.header_nav.live.nav_overview | default: 'Overview' }}</span><span class="nml-sub">{{ shop.metaobjects.header_nav.live.nav_overview_sub | default: 'The Beyond-2 story' }}</span></a>
          <div class="nav-sub">
            <a class="nav-menu-link nav-sub-trigger" href="https://bigscreenvr.com/mybeyond"><span class="nml-title">{{ shop.metaobjects.header_nav.live.nav_resources | default: 'Resources' }}</span><span class="nml-sub">{{ shop.metaobjects.header_nav.live.nav_resources_sub | default: 'Setup guides &amp; support' }}</span></a>
            <div class="nav-flyout"><div class="nav-menu-panel">
              <a class="nav-menu-link" href="https://bigscreenvr.com/mybeyond"><span class="nml-title">{{ shop.metaobjects.header_nav.live.nav_mybeyond | default: 'My Beyond' }}</span><span class="nml-sub">{{ shop.metaobjects.header_nav.live.nav_mybeyond_sub | default: 'Headset setup &amp; support' }}</span></a>
              <a class="nav-menu-link" href="https://bigscreenvr.com/myhalomount"><span class="nml-title">{{ shop.metaobjects.header_nav.live.nav_myhalo | default: 'My Halo Mount' }}</span><span class="nml-sub">{{ shop.metaobjects.header_nav.live.nav_myhalo_sub | default: 'Setup &amp; usage tips' }}</span></a>
              <a class="nav-menu-link" href="https://bigscreenvr.com/myaudiostrap"><span class="nml-title">{{ shop.metaobjects.header_nav.live.nav_myaudio | default: 'My Audio Strap' }}</span><span class="nml-sub">{{ shop.metaobjects.header_nav.live.nav_myaudio_sub | default: 'Setup &amp; usage tips' }}</span></a>
            </div></div>
          </div>
        </div></div>
      </span>
      <span class="nav-item has-menu">
        <a class="header-link" href="https://bigscreenvr.com/experiences">{{ shop.metaobjects.header.live.experiences | default: 'Experiences' }}<i class="nav-caret" aria-hidden="true"></i></a>
        <div class="nav-menu"><div class="nav-menu-panel">
          <a class="nav-menu-link" href="https://bigscreenvr.com/experiences#simulation"><span class="nml-title">{{ shop.metaobjects.header_nav.live.nav_sim | default: 'Beyond for Simulation' }}</span><span class="nml-sub">{{ shop.metaobjects.header_nav.live.nav_sim_sub | default: 'Sim racing, flight, training' }}</span></a>
          <a class="nav-menu-link" href="https://bigscreenvr.com/experiences#gaming"><span class="nml-title">{{ shop.metaobjects.header_nav.live.nav_gaming | default: 'Beyond for Gaming &amp; Media' }}</span><span class="nml-sub">{{ shop.metaobjects.header_nav.live.nav_gaming_sub | default: 'PC VR titles, movies, and media' }}</span></a>
          <a class="nav-menu-link" href="https://bigscreenvr.com/experiences#social"><span class="nml-title">{{ shop.metaobjects.header_nav.live.nav_social | default: 'Beyond for Social VR' }}</span><span class="nml-sub">{{ shop.metaobjects.header_nav.live.nav_social_sub | default: 'VRChat, meetups, presence' }}</span></a>
          <div class="nav-menu-group">{{ shop.metaobjects.header_nav.live.nav_business_group | default: 'For Businesses' }}</div>
          <a class="nav-menu-link" href="https://bigscreenvr.com/experiences/enterprise"><span class="nml-title">{{ shop.metaobjects.header_nav.live.nav_enterprise | default: 'Beyond for Enterprise' }}</span><span class="nml-sub">{{ shop.metaobjects.header_nav.live.nav_enterprise_sub | default: 'Robotics &middot; automotive &middot; defense' }}</span></a>
        </div></div>
      </span>
      <a class="header-link" href="https://bigscreenvr.com/about">{{ shop.metaobjects.header.live.about_us | default: 'About Us' }}</a>
      <span class="nav-item has-menu">
        <a class="header-link{% unless is_blog %} is-active{% endunless %}" href="{{ sroot }}/">{{ shop.metaobjects.header.live.shop | default: 'Shop' }}<i class="nav-caret" aria-hidden="true"></i></a>
        {%- comment -%} The 2e entry deep-links to its own variant so the
        configurator opens on eyetracking instead of the base model. Resolved by
        SKU (BS2E) — titles are translated per-locale so a title match broke on
        JA pages; SKUs never translate. If the variant is unpublished or the SKU
        changes this falls back to the plain product URL. {%- endcomment -%}
        {%- assign bs2_2e_id = '' -%}
        {%- for v in all_products['bigscreen-beyond-2'].variants -%}
          {%- if v.sku == 'BS2E' -%}{%- assign bs2_2e_id = v.id -%}{%- endif -%}
        {%- endfor -%}
        <div class="nav-menu"><div class="nav-menu-panel">
          {%- comment -%} "Beyond-2", dash and all — the wordmark the top-level
          nav item already uses. {%- endcomment -%}
          <a class="nav-menu-link" href="{{ sroot }}/products/bigscreen-beyond-2"><span class="nml-title">{{ shop.metaobjects.header_nav.live.shop_beyond2 | default: 'Beyond-2' }}</span><span class="nml-sub">{{ shop.metaobjects.header_nav.live.shop_beyond2_sub | default: 'The headset' }}</span></a>
          <a class="nav-menu-link" href="{{ sroot }}/products/bigscreen-beyond-2{% if bs2_2e_id != '' %}?variant={{ bs2_2e_id }}{% endif %}"><span class="nml-title">{{ shop.metaobjects.header_nav.live.shop_beyond2e | default: 'Beyond-2e' }}</span><span class="nml-sub">{{ shop.metaobjects.header_nav.live.shop_beyond2e_sub | default: 'With eyetracking' }}</span></a>
          <div class="nav-menu-group">{{ shop.metaobjects.header_nav.live.shop_accessories_group | default: 'Accessories' }}</div>
          <a class="nav-menu-link" href="{{ sroot }}/products/halo-mount"><span class="nml-title">{{ shop.metaobjects.header_nav.live.shop_halo | default: 'Halo Mount' }}</span></a>
          <a class="nav-menu-link" href="{{ sroot }}/products/audio-strap"><span class="nml-title">{{ shop.metaobjects.header_nav.live.shop_audio | default: 'Audio Strap' }}</span></a>
          <a class="nav-menu-link" href="{{ sroot }}/products/prescription-lens-inserts-for-beyond"><span class="nml-title">{{ shop.metaobjects.header_nav.live.shop_lens | default: 'Lens Inserts' }}</span></a>
          <a class="nav-menu-link" href="{{ sroot }}/products/custom-fit-cushion"><span class="nml-title">{{ shop.metaobjects.header_nav.live.shop_cushion_custom | default: 'Custom-fit Cushion' }}</span></a>
          <a class="nav-menu-link" href="{{ sroot }}/products/universal-face-cushion"><span class="nml-title">{{ shop.metaobjects.header_nav.live.shop_cushion_universal | default: 'Universal-fit Cushion' }}</span></a>
        </div></div>
      </span>
      {%- comment -%} Blog badge: posts published in the last 60 days ("the
      last 2 months"), counted live on every render. Nav twin: the marketing
      site bakes the same count at build time from the blog's Atom feed
      (bigscreenvr-site scripts/build-pages.mjs), since it is static and the
      feed has no CORS. blog.articles iterates newest-first; the limit just
      caps the walk. Zero recent posts renders no badge. {%- endcomment -%}
      {%- assign badge_cutoff = 'now' | date: '%s' | plus: 0 | minus: 5184000 -%}
      {%- assign recent_posts = 0 -%}
      {%- for article in blogs.beyond.articles limit: 15 -%}
        {%- assign badge_pub = article.published_at | date: '%s' | plus: 0 -%}
        {%- if badge_pub >= badge_cutoff -%}{%- assign recent_posts = recent_posts | plus: 1 -%}{%- endif -%}
      {%- endfor -%}
      <a class="header-link has-badge{% if is_blog %} is-active{% endif %}" href="{{ sroot }}/blogs/beyond">{{ shop.metaobjects.header.live.blog | default: 'Blog' }}{% if recent_posts > 0 %}<span class="nav-badge" title="{{ 'bigscreen.header.blog_badge' | t: count: recent_posts | escape }}">{{ recent_posts }}</span>{% endif %}</a>
    </div>
    <div class="nav-btn" role="button" tabindex="0" aria-label="{{ 'bigscreen.header.open_menu' | t | escape }}">
      <div class="nav-btn-inner icon-hamburger"></div>
      <div class="nav-btn-inner icon-navx"></div>
    </div>
  </nav>
</header>

<header class="header-btn-frame font-detail">
  <div>
    <a class="nav-account" id="nav-account" href="https://bigscreenvr.com/account/home" aria-label="{{ 'bigscreen.header.account' | t | escape }}" title="{{ 'bigscreen.header.account' | t | escape }}"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="8.2" r="3.4"/><path d="M5.5 19.2a6.5 6.5 0 0 1 13 0"/></svg></a>
    {%- comment -%} The cart IS the chrome's action, at every breakpoint. Nav
    twin: the marketing site keeps an "Order now" button here because it has no
    cart to send you to; on the store that button was sending a shopper who is
    already shopping back to the product page, past the cart they were building.
    The old Builder storefront had it right — one "Cart (n)" pill, always in the
    same place. Markup shared with sections/cart-icon-bubble.liquid; see the
    snippet. {%- endcomment -%}
    <a id="cart-icon-bubble" class="bs-cart-btn" href="#cart-drawer" role="button" data-bs-toggle="offcanvas" aria-label="{{ 'templates.cart.cart' | t }}">
      {%- render 'bs-cart-button' -%}
    </a>
  </div>
</header>

<script>
/* Account icon — revealed only when a Bigscreen session cookie is present
   (requires the auth cookies to be scoped to .bigscreenvr.com to be visible on
   the store subdomain; harmless when absent). Runs BEFORE the fit guard below:
   the icon is 50px of the actions frame, so the nav has to be measured with it
   already in place. */
(function () {
  var el = document.getElementById("nav-account");
  if (el && /(?:^|;\s*)x-(?:access|refresh)-token=/.test(document.cookie)) {
    el.classList.add("is-visible");
  }
})();
</script>

<script>
/* Nav fit guard — the actions (account / cart) sit in their own
   fixed frame stacked over the nav frame, so nothing in the pill reserves room
   for them: the centred link row just kept growing until it slid under the cart
   button. A breakpoint can't answer this, because what runs out is the room
   between the logo and the actions and that depends on how wide the labels are
   in the market being served. So: measure the row against the space that is
   symmetrically free, and hand the whole nav to the hamburger (html.nav-compact
   → bs-chrome.css) when it can't clear the actions with 16px to spare.

   The phone has the same problem one size down: there the cart pill is the only
   action and it has to clear the wordmark instead, so a long translated label
   (WINKELWAGEN) gets dropped to the icon alone (html.cart-compact). Same
   measurement, same reason a breakpoint can't answer it. */
(function () {
  var root = document.documentElement;
  var CLEAR = 16, queued = false;
  function fit() {
    queued = false;
    /* always measure the roomy state, so widening the window comes back */
    root.classList.remove("nav-compact");
    root.classList.remove("cart-compact");
    if (!window.matchMedia("(min-width: 992px)").matches) {
      var wordmark = document.querySelector(".header-nav .header-logo");
      var cart = document.getElementById("cart-icon-bubble");
      if (wordmark && cart &&
          cart.getBoundingClientRect().left - wordmark.getBoundingClientRect().right < CLEAR) {
        root.classList.add("cart-compact");
      }
      return;
    }
    /* queried per run, not captured: the customizer re-renders this section and
       a stale node measures 0, which would read as "nothing fits" */
    var nav = document.querySelector(".header-nav");
    var links = nav && nav.querySelector(".header-links");
    var logo = nav && nav.querySelector(".header-logo");
    var actions = document.querySelector(".header-btn-frame > div");
    if (!nav || !links || !logo || !actions) return;
    var box = nav.getBoundingClientRect();
    /* the row is centred on the pill, so its half-width is capped by whichever
       side is wider — logo on the left, actions on the right */
    var side = Math.max(logo.getBoundingClientRect().right - box.left,
                        box.right - actions.getBoundingClientRect().left);
    if (links.getBoundingClientRect().width > box.width - 2 * (side + CLEAR)) {
      root.classList.add("nav-compact");
    }
  }
  function queue() { if (!queued) { queued = true; requestAnimationFrame(fit); } }
  fit();
  window.addEventListener("resize", queue, { passive: true });
  /* mono metrics land late; the fallback face measures narrower than Fragment */
  if (document.fonts && document.fonts.ready) document.fonts.ready.then(fit);
})();
</script>

<nav id="mnav-overlay" aria-label="{{ 'bigscreen.header.mobile_menu' | t | escape }}" aria-hidden="true">
  <button id="mnav-close" type="button" aria-label="{{ 'bigscreen.header.close_menu' | t | escape }}">&times;</button>
  <!-- .mnav-body is filled at runtime with a CLONE of the desktop .header-links,
       so the mobile menu always mirrors the desktop nav 1:1. -->
  <div class="mnav-body"></div>
  {%- comment -%} Utility tier: the footer destinations a phone can otherwise
  only reach by scrolling the whole page. Labels reuse the FOOTER metaobject
  (same words, same translations — no new fields). The script moves this block
  to the end of .mnav-body so it scrolls with the menu. Nav twin: the marketing
  site's src/site/partials/nav.html carries the same block (relative links,
  hardcoded labels — the deliberate mirror differences). {%- endcomment -%}
  <div class="mnav-util">
    <div class="mnav-util-col">
      <p class="mnav-util-head">{{ shop.metaobjects.footer.live.learn_more | default: 'Learn more' }}</p>
      <a href="https://bigscreenvr.com/account/home">{{ shop.metaobjects.footer.live.account | default: 'Account' }}</a>
      <a target="_blank" rel="noreferrer" href="mailto:support@bigscreenvr.com">{{ shop.metaobjects.footer.live.contact | default: 'Contact' }}</a>
      <a target="_blank" rel="noreferrer" href="https://bigscreenvr.com/about#openings">{{ shop.metaobjects.footer.live.careers | default: 'Careers' }}</a>
      <a target="_blank" rel="noreferrer" href="https://bigscreenvr.com/affiliate">{{ shop.metaobjects.footer.live.affiliate | default: 'Affiliate' }}</a>
      <a target="_blank" rel="noreferrer" href="https://bigscreenvr.com/press">{{ shop.metaobjects.footer.live.press | default: 'Press & Branding' }}</a>
    </div>
    <div class="mnav-util-col">
      <p class="mnav-util-head">{{ shop.metaobjects.footer.live.follow_us | default: 'Follow us' }}</p>
      <a target="_blank" rel="noreferrer" href="https://discord.gg/bigscreenbeyond">Discord</a>
      <a target="_blank" rel="noreferrer" href="https://x.com/bigscreenvr">X</a>
      <a target="_blank" rel="noreferrer" href="https://youtube.com/bigscreenvr">YouTube</a>
      <a target="_blank" rel="noreferrer" href="https://reddit.com/r/bigscreen">Reddit</a>
      <a target="_blank" rel="noreferrer" href="https://www.facebook.com/bigscreenvr">Facebook</a>
    </div>
  </div>
  <a id="mnav-cta" href="{{ sroot }}/products/bigscreen-beyond-2">{{ shop.metaobjects.header_nav.live.order_now | default: 'Order now' }}</a>
</nav>
<script>
(function () {
  var overlay = document.getElementById("mnav-overlay");
  if (!overlay) return;
  document.body.appendChild(overlay);

  // Build the mobile menu as a clone of the desktop nav — one source of truth.
  var body = overlay.querySelector(".mnav-body");
  var links = document.querySelector(".header-nav .header-links");
  if (body && links && !body.firstChild) {
    var menu = links.cloneNode(true);
    menu.removeAttribute("class");
    menu.className = "mnav-menu";
    [].forEach.call(menu.querySelectorAll(".nav-item"), function (item) {
      var link = item.querySelector(".header-link");
      var caret = link && link.querySelector(".nav-caret");
      if (!caret) return;
      var btn = document.createElement("button");
      btn.type = "button";
      btn.className = "mnav-toggle";
      btn.setAttribute("aria-expanded", "false");
      btn.setAttribute("aria-label", "Show " + link.textContent.trim() + " menu");
      btn.appendChild(caret);
      link.after(btn);
      btn.addEventListener("click", function () {
        var open = item.classList.toggle("is-open");
        btn.setAttribute("aria-expanded", open ? "true" : "false");
      });
    });
    body.appendChild(menu);
  }
  // Utility tier scrolls WITH the menu (inside .mnav-body), so it can never
  // compress the pinned Order Now CTA on short screens. Authored outside the
  // body because the clone-build above keys on .mnav-body being empty.
  var util = overlay.querySelector(".mnav-util");
  if (util && body) body.appendChild(util);

  var closeBtn = document.getElementById("mnav-close");
  var burger = document.querySelector(".header-nav .nav-btn");
  function setOpen(o) {
    if (o && getComputedStyle(overlay).display === "none") return;
    overlay.classList.toggle("open", o);
    overlay.setAttribute("aria-hidden", o ? "false" : "true");
    if (burger) burger.setAttribute("aria-expanded", o ? "true" : "false");
    document.body.style.overflow = o ? "hidden" : "";
    if (o) closeBtn.focus();
  }
  if (burger) {
    burger.setAttribute("aria-expanded", "false");
    burger.addEventListener("click", function (e) {
      e.preventDefault(); e.stopPropagation();
      setOpen(!overlay.classList.contains("open"));
    }, true);
    burger.addEventListener("keydown", function (e) {
      if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setOpen(!overlay.classList.contains("open")); }
    });
  }
  closeBtn.addEventListener("click", function () { setOpen(false); });
  overlay.addEventListener("click", function (e) {
    if (e.target.closest("a")) setOpen(false);
  });
  document.addEventListener("keydown", function (e) {
    if (e.key === "Escape" || e.keyCode === 27) setOpen(false);
  });
})();
</script>

{% schema %}
{
  "name": "Header",
  "settings": []
}
{% endschema %}
