PageSourceSearch

https://www.tuconnect.app/assets/js/navigation.5d42c985.js

js tuconnect.app collected 2026-10-08 06:57:12 UTC 5,752 bytes, 154 lines download raw bytes

1/* ==========================================================================
2   TuConnect · navigation.5d42c985.js
3   Header behaviour shared by every page. Handles mobile menu accessibility
4   (ARIA + Keyboard), active link verification across all product areas,
5   Light/Dark theme switching, and accessible marquee pause controls.
6   ========================================================================== */
7(function () {
8  'use strict';
9
10  /* -- Light / Dark Theme State Management ----------------------------- */
11  function getSystemThemePreference() {
12    return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
13  }
14
15  function getActiveTheme() {
16    var stored = localStorage.getItem('tuconnect-theme');
17    if (stored === 'dark' || stored === 'light') return stored;
18    return getSystemThemePreference();
19  }
20
21  function updateToggleLabels(theme) {
22    var buttons = document.querySelectorAll('.theme-toggle');
23    Array.prototype.forEach.call(buttons, function (btn) {
24      var nextTheme = theme === 'dark' ? 'light' : 'dark';
25      btn.setAttribute('aria-label', 'Switch to ' + nextTheme + ' theme');
26      btn.setAttribute('title', 'Switch to ' + nextTheme + ' theme');
27    });
28  }
29
30  function swapLogos(theme) {
31    var navLogos = document.querySelectorAll('.nav-logo .logo-img');
32    Array.prototype.forEach.call(navLogos, function (img) {
33      if (theme === 'dark') {
34        img.src = img.src.replace('logo.svg', 'logo-white.svg');
35      } else {
36        img.src = img.src.replace('logo-white.svg', 'logo.svg');
37      }
38    });
39  }
40
41  function setTheme(theme, save) {
42    document.documentElement.setAttribute('data-theme', theme);
43    if (save) {
44      localStorage.setItem('tuconnect-theme', theme);
45    }
46    swapLogos(theme);
47    updateToggleLabels(theme);
48  }
49
50  // Initialize theme button event listeners
51  var currentTheme = getActiveTheme();
52  setTheme(currentTheme, false);
53
54  var themeBtns = document.querySelectorAll('.theme-toggle');
55  Array.prototype.forEach.call(themeBtns, function (btn) {
56    btn.addEventListener('click', function () {
57      var active = document.documentElement.getAttribute('data-theme') || getActiveTheme();
58      var next = active === 'dark' ? 'light' : 'dark';
59      setTheme(next, true);
60    });
61  });
62
63  // Listen for OS theme preference changes when no explicit override exists
64  if (window.matchMedia) {
65    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function (e) {
66      if (!localStorage.getItem('tuconnect-theme')) {
67        setTheme(e.matches ? 'dark' : 'light', false);
68      }
69    });
70  }
71
72  /* -- Mobile menu ----------------------------------------------------- */
73  var toggle = document.getElementById('navToggle');
74  var panel = document.getElementById('mobilePanel');
75
76  if (toggle && panel) {
77    toggle.addEventListener('click', function () {
78      var open = toggle.getAttribute('aria-expanded') === 'true';
79      var nextState = !open;
80      toggle.setAttribute('aria-expanded', String(nextState));
81      panel.setAttribute('data-open', String(nextState));
82      if (nextState) {
83        document.body.classList.add('nav-open');
84      } else {
85        document.body.classList.remove('nav-open');
86      }
87    });
88
89    /* Escape closes and returns focus to the toggle. */
90    document.addEventListener('keydown', function (e) {
91      if (e.key === 'Escape' && toggle.getAttribute('aria-expanded') === 'true') {
92        toggle.setAttribute('aria-expanded', 'false');
93        panel.setAttribute('data-open', 'false');
94        document.body.classList.remove('nav-open');
95        toggle.focus();
96      }
97    });
98  }
99
100  /* -- Active Navigation State Management ------------------------------ */
101  var rawPath = window.location.pathname;
102  var cleanPath = rawPath.replace(/\/$/, '');
103  var segment = cleanPath.substring(cleanPath.lastIndexOf('/') + 1) || 'index.html';
104  
105  var currentFile = segment;
106  if (currentFile !== '' && currentFile !== 'index.html' && !currentFile.endsWith('.html')) {
107    currentFile += '.html';
108  }
109
110  var activeHref = '';
111  if (currentFile === '' || currentFile === 'index.html') {
112    activeHref = 'index.html';
113  } else if ([
114    'for-job-seekers.html',
115    'ai-resume-builder.html',
116    'ai-cover-letter.html',
117    'job-tracker.html',
118    'ai-career-copilot.html',
119    'localized-career-guidance.html',
120    'interview-preparation.html'
121  ].indexOf(currentFile) !== -1) {
122    activeHref = 'for-job-seekers.html';
123  } else if (currentFile === 'for-employers.html') {
124    activeHref = 'for-employers.html';
125  } else if (currentFile === 'resources.html') {
126    activeHref = 'resources.html';
127  }
128
129  if (activeHref) {
130    var allNavLinks = document.querySelectorAll('.nav-links .nav-link, .mobile-panel .nav-link');
131    Array.prototype.forEach.call(allNavLinks, function (link) {
132      var href = link.getAttribute('href');
133      if (href === activeHref) {
134        link.classList.add('is-active');
135        if (link.closest && link.closest('.nav-links')) {
136          link.setAttribute('aria-current', 'page');
137        }
138      }
139    });
140  }
141
142  /* -- Marquee pause (WCAG 2.2.2: any motion over 5s needs a control) --- */
143  Array.prototype.forEach.call(document.querySelectorAll('.marquee'), function (m) {
144    var btn = m.querySelector('.marquee__pause');
145    if (!btn) return;
146    btn.addEventListener('click', function () {
147      var paused = m.getAttribute('data-paused') === 'true';
148      m.setAttribute('data-paused', String(!paused));
149      btn.setAttribute('aria-pressed', String(!paused));
150      var lbl = btn.querySelector('.visually-hidden');
151      if (lbl) lbl.textContent = paused ? 'Pause scrolling logos' : 'Re
151sume scrolling logos';
152    });
153  });
154})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.