Vim Style Navigation (with Settings + Enhanced)

Vim-style scrolling + hint(f/F), find(/ n/N), history(H/L), page ops(r t x*), misc(yy p o ?). UserScript-safe fallbacks.

2025-09-01 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         Vim Style Navigation (with Settings + Enhanced)
// @name:zh-CN   Vim风格导航(带设置+增强模块)
// @namespace    https://www.bianwenbo.com/
// @version      2.2.0
// @author       Wenbo Bian
// @license      MIT
// @description  Vim-style scrolling + hint(f/F), find(/ n/N), history(H/L), page ops(r t x*), misc(yy p o ?). UserScript-safe fallbacks.
// @description:zh-CN  在网页中提供 Vim 风格操作:滚动(hjkl / d u / gg G)、链接提示(f/F)、查找(/ n/N)、历史(H/L)、页面操作(r t x*)、杂项(yy p o ?)。已针对 UserScript 权限做安全降级。
// @match        http://*/*
// @match        https://*/*
// @noframes
// @run-at       document-idle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_setClipboard
// @grant        GM_openInTab
// ==/UserScript==

(function () {
  'use strict';

  /** =======================
   *  默认配置
   *  ======================= */
  const DEFAULTS = {
    baseStep: 100,
    extraPage: 300,
    ggIntervalMs: 600,
    useNativeSmooth: true,
    ignoreWithModifier: true,
    excludeSites: [
      'https://docs.google.com/',
      'https://mail.google.com/',
      /.*\.notion\.site\/.*/,
      'https://www.notion.so/',
      'https://www.figma.com/'
    ],
    hintChars: 'asdfghjklqwertyuiop',
    hintMaxLetters: 3,
    hintMinClickableSize: 10,
    hintZIndex: 2147483646,
    findBarHeight: 32,
    findBarOpacity: 0.96,
    helpWidth: 520,
    helpOpacity: 0.96,
  };

  /** =======================
   *  配置持久化
   *  ======================= */
  function loadConfig() {
    const raw = GM_getValue('vimnav_config_v2', null);
    if (!raw) return structuredClone(DEFAULTS);
    const cfg = JSON.parse(raw);
    cfg.excludeSites = (cfg.excludeSites || []).map(item => {
      if (item && item.__type === 'RegExp') return new RegExp(item.source, item.flags || '');
      return item;
    });
    return { ...structuredClone(DEFAULTS), ...cfg };
  }
  function saveConfig(cfg) {
    const st = structuredClone(cfg);
    st.excludeSites = st.excludeSites.map(p => p instanceof RegExp ? ({ __type:'RegExp', source:p.source, flags:p.flags||''}) : p);
    GM_setValue('vimnav_config_v2', JSON.stringify(st));
  }
  let CONFIG = loadConfig();

  /** =======================
   *  菜单(可视化设置)
   *  ======================= */
  registerMenu();
  function registerMenu() {
    GM_registerMenuCommand(`步长 baseStep(${CONFIG.baseStep})`, () => editInt('基础步长(>=1)', 'baseStep', 1));
    GM_registerMenuCommand(`d/u 额外翻页 extraPage(${CONFIG.extraPage})`, () => editInt('额外翻页量(>=0)', 'extraPage', 0));
    GM_registerMenuCommand(`gg 时间窗 ms(${CONFIG.ggIntervalMs})`, () => editInt('双击 g 时间窗(>=100ms)', 'ggIntervalMs', 100));
    GM_registerMenuCommand(`切换 原生平滑(${CONFIG.useNativeSmooth?'开':'关'})`, () => toggle('useNativeSmooth'));
    GM_registerMenuCommand(`切换 忽略Ctrl/Alt/Meta(${CONFIG.ignoreWithModifier?'开':'关'})`, () => toggle('ignoreWithModifier'));
    GM_registerMenuCommand(`排除站点 管理`, manageExcludeSites);
    GM_registerMenuCommand(`Hint 字符集(${CONFIG.hintChars})`, () => editString('输入 Hint 标签字符集(不重复字符)', 'hintChars'));
    GM_registerMenuCommand(`Hint 标签最大长度(${CONFIG.hintMaxLetters})`, () => editInt('Hint 标签最大长度(1-4)', 'hintMaxLetters', 1, 4));
    GM_registerMenuCommand(`恢复默认配置`, () => { if (confirm('确认恢复默认?')) { CONFIG = structuredClone(DEFAULTS); saveConfig(CONFIG); alert('OK'); } });
  }
  function editInt(msg, key, min=Number.MIN_SAFE_INTEGER, max=Number.MAX_SAFE_INTEGER) {
    const v = prompt(msg, String(CONFIG[key]));
    if (v===null) return;
    const n = parseInt(v,10);
    if (!Number.isFinite(n) || n<min || n>max) return alert('无效数值');
    CONFIG[key]=n; saveConfig(CONFIG); alert('已保存');
  }
  function editString(msg, key) {
    const v = prompt(msg, String(CONFIG[key]||''));
    if (v===null) return;
    CONFIG[key]=v; saveConfig(CONFIG); alert('已保存');
  }
  function toggle(key) { CONFIG[key]=!CONFIG[key]; saveConfig(CONFIG); alert(`${key} = ${CONFIG[key]}`); }
  function manageExcludeSites() {
    const list = CONFIG.excludeSites.map((p,i)=>`${i}. ${p instanceof RegExp?`/${p.source}/${p.flags||''}`:p}`).join('\n') || '(空)';
    const act = prompt('排除站点:\n'+list+'\n\n指令:add / del <idx> / clear / done', 'add');
    if (act===null) return;
    const [cmd,arg] = act.trim().split(/\s+/);
    if (cmd==='add') {
      const s = prompt('输入前缀或正则(如 /.*\\.notion\\.site\\// )');
      if (!s) return;
      const pat = parsePattern(s.trim());
      if (!pat) return alert('格式无效');
      CONFIG.excludeSites.push(pat); saveConfig(CONFIG); alert('已添加');
    } else if (cmd==='del') {
      const i = parseInt(arg,10);
      if (!Number.isFinite(i) || i<0 || i>=CONFIG.excludeSites.length) return alert('索引无效');
      CONFIG.excludeSites.splice(i,1); saveConfig(CONFIG); alert('已删除');
    } else if (cmd==='clear') {
      if (!confirm('确认清空?')) return; CONFIG.excludeSites=[]; saveConfig(CONFIG); alert('已清空');
    }
  }
  function parsePattern(s){
    if (s.startsWith('/') && s.lastIndexOf('/')>0){
      const last=s.lastIndexOf('/'); const pattern=s.slice(1,last); const flags=s.slice(last+1);
      try{return new RegExp(pattern,flags);}catch{ return null;}
    }
    return s; // 字符串前缀
  }

  /** =======================
   *  站点排除
   *  ======================= */
  if (isExcluded(location.href, CONFIG.excludeSites)) return;
  function isExcluded(url, patterns){
    try{ return patterns.some(p=> typeof p==='string'? url.startsWith(p) : p instanceof RegExp? p.test(url) : false ); }catch{ return false; }
  }

  /** =======================
   *  工具函数
   *  ======================= */
  const SA = CONFIG.baseStep, EXTRA = CONFIG.extraPage, GG_MS = CONFIG.ggIntervalMs;
  let lastGTime = 0;
  let hintMode = null;  // {active, nodes, labelMap, filter, newTab}
  let findState = { active:false, term:'', bar:null, input:null, countSpan:null };
  let helpOverlay = null;

  function scrollingEl() { return document.scrollingElement || document.documentElement || document.body; }
  function getScrollTop() { return scrollingEl().scrollTop; }
  function getScrollHeight(){ return scrollingEl().scrollHeight; }
  function isEditable(t){ if(!t) return false; const tag=t.tagName; if(tag==='INPUT'||tag==='TEXTAREA'||tag==='SELECT') return true; if(t.isContentEditable) return true; if (document.designMode && document.designMode.toLowerCase()==='on') return true; return false; }

  function smoothScrollTo(position){
    const el=scrollingEl();
    if (CONFIG.useNativeSmooth){
      try{ el.scrollTo({top:position,left:0,behavior:'smooth'}); return; }catch{}
    }
    if (!window.requestAnimationFrame){ window.requestAnimationFrame = cb=>setTimeout(cb,17); }
    let current=el.scrollTop;
    (function step(){
      const d=position-current; current=current+d/5;
      if (Math.abs(d)<1){ el.scrollTo(0,position); }
      else { el.scrollTo(0,current); requestAnimationFrame(step); }
    })();
  }
  function smoothScrollBy(deltaY){ smoothScrollTo(getScrollTop()+deltaY); }
  function toTop(){ smoothScrollTo(0); }
  function toBottom(){ smoothScrollTo(getScrollHeight()); }

  function openInNewTab(url){ try{ GM_openInTab ? GM_openInTab(url, {active:false}) : window.open(url,'_blank'); }catch{ window.open(url,'_blank'); } }
  function copyToClipboard(text){
    try{ if (typeof GM_setClipboard==='function'){ GM_setClipboard(text); return true; } }catch{}
    if (navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(text).catch(()=>{}); return true; }
    return false;
  }
  async function readFromClipboard(){
    if (navigator.clipboard && navigator.clipboard.readText){
      try{ const t=await navigator.clipboard.readText(); return t||''; }catch{}
    }
    const t=prompt('无法直接读取剪贴板,请粘贴要打开的 URL:','');
    return t||'';
  }
  function isLikelyUrl(s){
    try{ new URL(s); return true; }catch{}
    if (/^[a-z]+:\/\/\S+/i.test(s)) return true;
    if (/^[\w.-]+\.[a-z]{2,}([/:?#].*)?$/i.test(s)) return true;
    return false;
  }

  /** =======================
   *  1) 链接提示(f/F)
   *  ======================= */
  function enterHintMode(newTab){
    if (hintMode && hintMode.active) return;
    hintMode = { active:true, nodes:[], labelMap:new Map(), filter:'', newTab: !!newTab };
    const layer = document.createElement('div');
    layer.id='__vimnav_hint_layer__';
    Object.assign(layer.style, { position:'fixed', inset:'0', zIndex:String(CONFIG.hintZIndex), pointerEvents:'none' });
    document.documentElement.appendChild(layer);

    const clickable = collectClickable();
    const labels = assignLabels(clickable.length, CONFIG.hintChars, CONFIG.hintMaxLetters);
    clickable.forEach((node, i)=>{
      const rect = node.getBoundingClientRect();
      if (rect.width<CONFIG.hintMinClickableSize && rect.height<CONFIG.hintMinClickableSize) return;
      const tag = renderHintTag(labels[i], rect);
      layer.appendChild(tag);
      hintMode.nodes.push({ node, label: labels[i], tag });
      hintMode.labelMap.set(labels[i], node);
    });

    if (hintMode.nodes.length===0){ exitHintMode(); return; }
  }
  function exitHintMode(){
    const layer=document.getElementById('__vimnav_hint_layer__');
    if (layer && layer.parentNode) layer.parentNode.removeChild(layer);
    hintMode = null;
  }
  function collectClickable(){
    const sel = [
      'a[href]',
      'button',
      'summary',
      '[role="button"]',
      '[onclick]',
      'input[type="submit"]',
      'input[type="button"]',
      'input[type="image"]',
      'area[href]'
    ].join(',');
    const nodes = Array.from(document.querySelectorAll(sel)).filter(el=>{
      const r=el.getBoundingClientRect();
      const style = getComputedStyle(el);
      const visible = r.width>0 && r.height>0 && style.visibility!=='hidden' && style.opacity!=='0';
      return visible;
    });
    return Array.from(new Set(nodes));
  }
  function assignLabels(n, chars, maxLen){
    const res=[];
    const base = chars.length;
    let length = 1;
    let capacity = base;
    while (capacity<n && length<maxLen){ length++; capacity *= base; }
    for (let i=0;i<n;i++) res.push(encodeIndex(i, base, length, chars));
    return res;
  }
  function encodeIndex(idx, base, length, chars){
    let s = '';
    for (let i=0;i<length;i++){ s = chars[idx % base] + s; idx = Math.floor(idx / base); }
    return s;
  }
  function renderHintTag(label, rect){
    const el = document.createElement('div');
    el.textContent = label;
    Object.assign(el.style, {
      position:'fixed',
      left: (Math.max(0, rect.left)+window.scrollX)+'px',
      top:  (Math.max(0, rect.top) +window.scrollY)+'px',
      font:'12px/1 monospace',
      padding:'2px 4px',
      borderRadius:'4px',
      background:'rgba(0,0,0,0.85)',
      color:'#fff',
      boxShadow:'0 1px 3px rgba(0,0,0,.3)',
      pointerEvents:'none',
      userSelect:'none'
    });
    return el;
  }
  function handleHintInput(ch){
    if (!hintMode || !hintMode.active) return;
    if (!CONFIG.hintChars.includes(ch)) return;  // 仅接受 hint 字符
    hintMode.filter += ch;
    const candidates = hintMode.nodes.filter(x=> x.label.startsWith(hintMode.filter));
    hintMode.nodes.forEach(x=>{ x.tag.style.opacity = x.label.startsWith(hintMode.filter) ? '1' : '0.15'; });
    if (candidates.length===1 && candidates[0].label===hintMode.filter){
      const target = candidates[0].node;
      const href = target.getAttribute('href');
      const click = ()=> target.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, view:window}));
      if (hintMode.newTab){
        if (href) openInNewTab(new URL(href, location.href).href);
        else openInNewTab(location.href);
      } else {
        click();
      }
      exitHintMode();
    }
  }

  /** =======================
   *  2) 查找(/,n/N)
   *  ======================= */
  function toggleFindBar(show){
    if (show){
      if (findState.active) return;
      findState.active = true;
      const bar = document.createElement('div');
      Object.assign(bar.style, {
        position:'fixed', left:'50%', top:'0',
        transform:'translateX(-50%)',
        height:CONFIG.findBarHeight+'px', lineHeight:CONFIG.findBarHeight+'px',
        background:`rgba(20,20,20,${CONFIG.findBarOpacity})`,
        color:'#fff', padding:'0 8px', borderRadius:'0 0 8px 8px',
        zIndex:String(CONFIG.hintZIndex), display:'flex', gap:'8px', alignItems:'center',
        font:'13px/1 system-ui,Arial,Helvetica,sans-serif'
      });
      const label = document.createElement('span'); label.textContent='/';
      const input = document.createElement('input');
      Object.assign(input.style, { width:'320px', height:'22px', outline:'none', border:'none', borderRadius:'4px', padding:'0 6px' });
      input.placeholder='Type to search...  Enter=next  Shift+Enter=prev  Esc=close';
      const count = document.createElement('span'); count.textContent='…';
      bar.append(label, input, count);
      document.documentElement.appendChild(bar);
      findState.bar=bar; findState.input=input; findState.countSpan=count; findState.term='';
      input.addEventListener('keydown', e=>{
        if (e.key==='Enter'){ e.preventDefault(); performFind(input.value, !e.shiftKey); }
        else if (e.key==='Escape'){ e.preventDefault(); closeFindBar(); }
      });
      setTimeout(()=>input.focus(), 0);
    } else {
      closeFindBar();
    }
  }
  function closeFindBar(){
    if (!findState.active) return;
    findState.active=false;
    if (findState.bar && findState.bar.parentNode) findState.bar.parentNode.removeChild(findState.bar);
    findState={ active:false, term:'', bar:null, input:null, countSpan:null };
  }
  function performFind(term, forward=true){
    if (!term) return;
    findState.term = term;
    const found = window.find(term, false, !forward, true, false, false, false);
    if (findState.countSpan) findState.countSpan.textContent = found ? '√' : '0/0';
  }
  function findNext(){ if (!findState.term) return; window.find(findState.term, false, false, true, false, false, false); }
  function findPrev(){ if (!findState.term) return; window.find(findState.term, false, true,  true, false, false, false); }

  /** =======================
   *  6) 帮助(?)
   *  ======================= */
  function toggleHelp(show){
    if (show){
      if (helpOverlay) return;
      const box = document.createElement('div');
      Object.assign(box.style, {
        position:'fixed', left:'50%', top:'10%',
        transform:'translateX(-50%)',
        width: CONFIG.helpWidth+'px', maxWidth:'90vw',
        background:`rgba(20,20,20,${CONFIG.helpOpacity})`, color:'#fff',
        padding:'12px 16px', borderRadius:'12px', zIndex:String(CONFIG.hintZIndex),
        font:'13px/1.45 system-ui,Arial,Helvetica,sans-serif'
      });
      box.innerHTML = `
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
          <b>Vim Style Navigation</b>
          <span style="opacity:.7">Press <kbd>?</kbd> to close</span>
        </div>
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
          <div>
            <b>Scroll</b><br/>
            <code>j/k</code> ↓/↑ &nbsp; <code>h/l</code> ←/→<br/>
            <code>d/u</code> page ↓/↑ (base+extra)<br/>
            <code>gg</code> top &nbsp; <code>G</code> bottom
          </div>
          <div>
            <b>Hint</b><br/>
            <code>f</code> open link &nbsp; <code>F</code> open in new tab
          </div>
          <div>
            <b>Find</b><br/>
            <code>/</code> open find &nbsp; <code>n/N</code> next/prev
          </div>
          <div>
            <b>History</b><br/>
            <code>H</code> back &nbsp; <code>L</code> forward
          </div>
          <div>
            <b>Page</b><br/>
            <code>r</code> reload &nbsp; <code>t</code> new tab<br/>
            <code>x</code> close tab <i>(only if opened by script)</i><br/>
            <code>J/K</code> switch tab — <i>not available in UserScript</i>
          </div>
          <div>
            <b>Misc</b><br/>
            <code>yy</code> copy page URL<br/>
            <code>p</code> open URL from clipboard<br/>
            <code>o</code> open URL or search
          </div>
        </div>
      `;
      document.documentElement.appendChild(box);
      helpOverlay = box;
    } else {
      if (helpOverlay && helpOverlay.parentNode) helpOverlay.parentNode.removeChild(helpOverlay);
      helpOverlay = null;
    }
  }

  /** =======================
   *  键盘处理
   *  ======================= */
  window.addEventListener('keydown', onKeyDown, { capture:false });

  function onKeyDown(e){
    if (e.defaultPrevented) return;

    // Hint 模式优先
    if (hintMode && hintMode.active){
      if (e.key==='Escape'){ exitHintMode(); e.preventDefault(); return; }
      if (!e.ctrlKey && !e.altKey && !e.metaKey && e.key.length===1){
        handleHintInput(e.key.toLowerCase());
        e.preventDefault();
        return;
      }
      return;
    }

    // 查找条激活时,交给输入框
    if (findState.active && findState.input && document.activeElement === findState.input){
      return;
    }

    const inEditable = isEditable(e.target);

    // 若开启“忽略修饰键”,带修饰键则不处理
    if (CONFIG.ignoreWithModifier && (e.ctrlKey || e.altKey || e.metaKey)) return;

    if (inEditable) return;

    const k = e.key;

    // 基础滚动与跳转
    switch (k){
      case 'h': smoothScrollBy(-SA); e.preventDefault(); return;
      case 'j': smoothScrollBy( SA); e.preventDefault(); return;
      case 'k': smoothScrollBy(-SA); e.preventDefault(); return;
      case 'l': smoothScrollBy( SA); e.preventDefault(); return;
      case 'd': smoothScrollBy( SA + EXTRA); e.preventDefault(); return;
      case 'u': smoothScrollBy(-(SA + EXTRA)); e.preventDefault(); return;
      case 'g': { const now=Date.now(); if (now - lastGTime <= GG_MS){ toTop(); lastGTime=0; e.preventDefault(); } else { lastGTime=now; } return; }
      case 'G': toBottom(); e.preventDefault(); return;
    }

    // 增强模块
    switch (k){
      // Hint
      case 'f': enterHintMode(false); e.preventDefault(); return;
      case 'F': enterHintMode(true);  e.preventDefault(); return;

      // Find
      case '/': toggleFindBar(true); e.preventDefault(); return;
      case 'n': findNext(); e.preventDefault(); return;
      case 'N': findPrev(); e.preventDefault(); return;

      // Page ops
      case 'r': location.reload(); e.preventDefault(); return;
      case 't': openInNewTab('about:blank'); e.preventDefault(); return;
      case 'x': window.close(); e.preventDefault(); return; // 仅脚本打开的标签可关

      // History
      case 'H': history.back(); e.preventDefault(); return;
      case 'L': history.forward(); e.preventDefault(); return;

      // Help
      case '?': toggleHelp(!helpOverlay); e.preventDefault(); return;

      // URL/搜索
      case 'o': {
        const s = prompt('输入 URL 或搜索词:','');
        if (s==null || !s.trim()) return;
        const text = s.trim();
        if (isLikelyUrl(text)){
          location.href = text.match(/^https?:\/\//i) ? text : ('https://' + text);
        } else {
          location.href = 'https://www.google.com/search?q=' + encodeURIComponent(text);
        }
        e.preventDefault(); return;
      }
      case 'y': break; // 交给 yy 组合
      case 'p': (async () => {
        const t = await readFromClipboard();
        if (!t) return;
        const text = t.trim();
        if (!text) return;
        if (isLikelyUrl(text)){
          location.href = text.match(/^https?:\/\//i) ? text : ('https://' + text);
        } else {
          location.href = 'https://www.google.com/search?q=' + encodeURIComponent(text);
        }
      })(); e.preventDefault(); return;
    }

    // 组合键序列(yy)
    if (k==='y'){
      if (onKeyDown.__lastY && (Date.now()-onKeyDown.__lastY)<=500){
        onKeyDown.__lastY=0;
        const ok = copyToClipboard(location.href);
        if (!ok) alert(location.href);
        e.preventDefault();
        return;
      }
      onKeyDown.__lastY = Date.now();
    } else {
      onKeyDown.__lastY = 0;
    }

    lastGTime=0;
  }

  // 失焦/点击时的清理
  window.addEventListener('blur', ()=>{ lastGTime=0; onKeyDown.__lastY=0; });
  window.addEventListener('mousedown', ()=>{ if (hintMode && hintMode.active) exitHintMode(); }, {capture:true});
})();