Skip to content

Instantly share code, notes, and snippets.

@sametcn99
Last active June 7, 2026 21:56
Show Gist options
  • Select an option

  • Save sametcn99/66cf2c0da5c793d6f56763ece2b9027a to your computer and use it in GitHub Desktop.

Select an option

Save sametcn99/66cf2c0da5c793d6f56763ece2b9027a to your computer and use it in GitHub Desktop.
A userscript that automatically hides viewed job postings in LinkedIn Jobs.

LinkedIn Hide Viewed Jobs

Hide or highlight viewed job postings on LinkedIn Jobs with a privacy-first userscript built for Tampermonkey and Violentmonkey — or install as a standalone browser extension for Chrome and Firefox.

This project focuses on three things: stable LinkedIn SPA behavior, high-confidence multilingual viewed/applied detection, and safer scrolling with guard and cooldown protections.

Quick Links

Screenshot

Screenshot of the userscript badge Screenshot of the extension popup

Project Links

Features

  • Dynamic Control:
    • ON/OFF status toggle.
    • Guard (ON/OFF): Scroll protection to prevent LinkedIn rate-limits. This is especially important when hiding jobs, as rapid scrolling through hundreds of hidden items can trigger bot-detection filters.
  • Two-Layer Detection:
    • Hide Mode: Automatically vanishes viewed jobs.
    • Highlight Mode: Keeps jobs visible but adds separate low-opacity full-card color filters for viewed and applied cards.
  • Active Card Accent: On jobs and jobs/search views, the currently open job card gets its own configurable highlight filter color so the selected row stays easy to spot.
  • Draggable Handle: Reposition the badge anywhere on the screen.
  • Custom Keyword Highlights: Add your own keywords to highlight or hide job cards based on company names, job titles, or any text appearing on the card — useful for filtering listings from specific companies or matching role titles you are targeting. Custom keywords are matched across every job card on the page (including recommended/discovery cards), independently of whether a card was previously viewed or applied to. Keyword matches take priority: keyword > active > viewed/applied. Keywords are managed via chip input in the settings panel with duplicate detection.
  • Dynamic Settings Panel: Expandable menu to switch between Hide and Highlight modes, tune viewed, applied, active, and keyword card colors, adjust filter opacity, and open the GitHub repository.
  • Navigation Reload Toggle: Choose whether SPA path changes should trigger a full page reload or stay on soft refresh.
  • Live Counter: Track the total number of detected cards (N hidden/N marked) — which includes both viewed/applied detections and custom-keyword matches, counted once per card — with a +N keyword breakdown showing how many of those are keyword matches, all updated in real-time.
  • Persistence: Remembers your preferences for ON/OFF, Scroll Guard, Detection Mode, Navigation Reload, Viewed/Applied/Active/Keyword Colors, Filter Opacity, Custom Keywords, and Badge Position.
  • Robust Navigation: Full support for LinkedIn's SPA routing; automatically restarts scanning when you switch pages or collections.
  • Multilingual: Intelligent keyword detection across 15+ languages.

Supported Pages

  • https://www.linkedin.com/jobs/*
  • https://www.linkedin.com/jobs/search/*
  • https://www.linkedin.com/jobs/collections/*

Browser Compatibility

Userscript

  • Chrome + Violentmonkey/Tampermonkey
  • Edge + Violentmonkey/Tampermonkey
  • Firefox + Violentmonkey/Tampermonkey

Browser Extension

  • Chrome 88+ (Manifest V3)
  • Firefox 109+ (Manifest V3)

Installation

Installation of Userscript

  1. Install a userscript extension in your browser:
  2. Chrome/Edge: Tampermonkey or Violentmonkey
  3. Firefox: Tampermonkey or Violentmonkey
  4. Import linkedin-hide-viewed-jobs.user.js into the extension.
  5. Save and enable the script.
  6. Refresh a LinkedIn Jobs page.

Alternative:

Installation of Browser Extension

The browser extension provides the same functionality as the userscript in a standalone package — no Tampermonkey/Violentmonkey needed.

Chrome:

  1. Download linkedin-hide-viewed-jobs-chrome.zip from the latest release.
  2. Unzip the file to a permanent folder on your computer.
  3. Open chrome://extensions in Chrome.
  4. Enable Developer mode (toggle in the top-right corner).
  5. Click Load unpacked and select the unzipped folder.
  6. Navigate to any LinkedIn Jobs page.

Firefox:

  1. Download linkedin-hide-viewed-jobs-firefox.zip from the latest release.
  2. Unzip the file to a permanent folder on your computer.
  3. Open about:debugging in Firefox.
  4. Click This FirefoxLoad Temporary Add-on.
  5. Select the manifest.json file from the unzipped folder.
  6. Navigate to any LinkedIn Jobs page.

Note: Firefox temporary add-ons are removed when the browser closes. For persistent installation, the extension needs to be signed by Mozilla.

GitHub Pages

This repository includes a GitHub Pages setup that renders this README through Jekyll with a polished dark editorial layout, a branded hero section, and responsive documentation styling.

Included site assets:

  • manifest via icons/site.webmanifest
  • robots.txt
  • sitemap.xml
  • favicon and Apple touch icon links from icons/
  • Open Graph, Twitter, canonical, and JSON-LD metadata

Publish flow:

  1. Open repository settings on GitHub.
  2. Go to Pages.
  3. Set Build and deployment to Deploy from a branch.
  4. Select the master branch and /(root) folder.
  5. Save.

Published site URL:

  • https://sametcn99.github.io/linkedin-hide-viewed-jobs/

Supported Languages

The script supports detection for the following languages:

  • English (Viewed, Seen, Applied)
  • Turkish (Görüntülenen, Görüntülendi, Başvurulan, Başvurulanlar, Başvuruldu)
  • Spanish (Visto, Vistos, Aplicado, Postulado)
  • Portuguese (Visualizado, Visualizados, Candidatado, Candidatura)
  • French (Vu, Vue, Postulé, Postulée, Candidature)
  • German (Angesehen, Gesehen, Beworben)
  • Italian (Visualizzato, Visto, Candidata, Candidati, Candidatura)
  • Dutch (Bekeken, Solliciteerd)
  • Russian (Просмотрено, Откликнулся)
  • Polish (Wyświetlono, Aplikowano)
  • Swedish (Visad, Sedd, Sökt)
  • Chinese (已查看, 已申请, 已檢視, 已申請)
  • Japanese (閲覧済み, 応募済み)
  • Korean (조회됨, 지원함, 지원 완료)
  • Arabic (تمت المشاهدة, تم التقديم)
  • Hindi (देखा गया, आवेदन किया गया)

Usage

  1. Open a LinkedIn Jobs listing page.
  2. The script scans for viewed cards.
  3. When OFF, viewed jobs are not hidden; they are only counted.
  4. When ON, viewed jobs are hidden.
  5. In settings, Reload OFF is the default. SPA navigation stays on soft refresh unless you explicitly enable Reload ON.
  6. In Highlight mode, Viewed and Applied cards use different colors so you can distinguish them at a glance.
  7. On jobs and jobs/search pages, the currently selected card also gets its own full highlight filter color.
  8. In settings, use the native color pickers to adjust Viewed, Applied, Active, and Keyword card colors, and use the opacity slider to make the highlight filter lighter or stronger.
  9. In settings, type a keyword in the chip input and press Enter to add it. Matching job cards are highlighted or hidden depending on the detection mode. Click the × on any chip to remove a keyword.
  10. The settings panel includes a direct GitHub Repo shortcut for the project source and issue tracker.
  11. If rapid downward scrolling is detected while most cards are viewed/hidden, the guard can enter a random cooldown (5-15s) and slow scroll steps to reduce LinkedIn rate-limit risk.
  12. If guard is triggered again while a cooldown is already active, the new cooldown is added on top of the remaining time (stacked), instead of restarting as separate back-to-back cooldowns.
  13. During cooldown, pagination controls inside div.jobs-search-pagination are temporarily disabled (including collections/search pagination buttons).
  14. Drag the badge using the handle on the left to reposition it.

Detection Logic

The script performs detection in multiple layers:

  • Card selectors: li[data-occludable-job-id] and related LinkedIn list item selectors
  • Footer/marker-focused detection (VIEWED_MARKER_SELECTORS)
  • Text, aria-label, and title checks inside each card
  • Card-level fallback scan for missed cases

Text matching uses normalize('NFD') plus diacritic removal for more stable multilingual matching.

Customization

Source-of-truth customization lives under src/** and the userscript bundle is generated from that source.

Common knobs:

  • VIEWED_KEYWORDS: Add more viewed-language phrases
  • APPLIED_KEYWORDS: Add more applied-language phrases
  • JOB_CARD_SELECTORS: Card selection scope
  • VIEWED_MARKER_SELECTORS: Marker selection scope
  • STORAGE_KEY: Preference storage key
  • UI_POSITION_KEY: Badge position storage key
  • HIDDEN_CLASS: CSS class used for hiding
  • KEYWORD_HIGHLIGHT_COLOR: Highlight/overlay color for keyword-matched cards

Architecture

Userscript Mode

main.ts → LocalStorageService → App → Badge / DetectionService / RouterService / StyleManager
  • Entry: src/main.ts
  • Storage: window.localStorage via LocalStorageService
  • UI: In-page draggable badge

Extension Mode

content.ts → ChromeStorageService → App → Badge / DetectionService / RouterService / StyleManager
popup.ts ← chrome.storage.local → background.ts → chrome.storage.onChanged → content.ts
  • Entry (content): src/extension/content.ts — injected into LinkedIn pages
  • Entry (background): src/extension/background.ts — service worker relaying storage changes
  • Popup: src/popup/popup.html + popup.ts + popup.css — settings UI in browser toolbar
  • Storage: chrome.storage.local via ChromeStorageService
  • Sync: Popup changes → chrome.storage.local → background relays → content script calls app.refreshSettings()

Shared Code

Both modes share the same core business logic:

  • src/core/App.ts — orchestrator (accepts IStorageService via dependency injection)
  • src/services/DetectionService.ts — viewed/applied/keyword detection
  • src/services/KeywordMatcher.ts — multilingual keyword matching and custom keyword matching
  • src/services/RouterService.ts — SPA route change detection
  • src/ui/Badge.ts — in-page badge UI
  • src/ui/StyleManager.ts — CSS injection

The storage adapter pattern (IStorageService) means App works identically whether backed by localStorage or chrome.storage.local.

Build Commands

bun run build              # Build userscript only (.user.js)
bun run build:extension    # Build browser extension (dist/extension-chrome/ and dist/extension-firefox/)
bun run build:all          # Build both
bun run package:chrome     # Create Chrome zip
bun run package:firefox    # Create Firefox zip
bun run package:all        # Create both zips
bun run lint               # Check code quality
bun run check              # Lint + format
bun run release             # Create GitHub release (local)

Limitations

  • If LinkedIn changes its DOM structure, selectors may need updates.
  • New phrasing variants in some languages may require additions to VIEWED_KEYWORDS.

Privacy

  • The script runs fully on the client side.
  • It makes no external API calls.
  • It does not send data anywhere.

Contributing

Contributions are welcome.

  1. Fork the repository.
  2. Create a feature branch (feature/your-change) or fix branch (fix/your-change).
  3. Make source changes under src/** and update README.md if behavior changes.
  4. Run bun run check to verify lint and formatting.
  5. Run bun run build:all to verify both userscript and extension build.
  6. Test on LinkedIn Jobs pages to verify detection, badge UI, toggle behavior, and extension popup.
  7. Open a pull request with a clear summary, before/after notes, and screenshots when UI is affected.

Guidelines:

  • Keep changes focused and minimal.
  • Avoid unrelated refactors in the same pull request.
  • Preserve compatibility with Tampermonkey/Violentmonkey on Chrome, Edge, and Firefox.
  • Preserve compatibility with the standalone browser extension (Manifest V3).
  • If you add new language keywords, include only high-confidence terms to reduce false positives.
  • If you change storage keys or settings, ensure both LocalStorageService and ChromeStorageService are updated.

Releasing

Releases are created manually via GitHub Actions:

  1. Update version in package.json.
  2. Commit all changes.
  3. Go to Actions → Release → Run workflow.
  4. The workflow builds both the userscript and extension, packages ZIP files, generates release notes from conventional commits, and creates a GitHub Release with all artifacts attached.

For local testing:

bun run build:all
bun run package:all
# Then sideload from dist/extension-chrome/ or dist/extension-firefox/
// ==UserScript==
// @name LinkedIn Hide Viewed Jobs
// @name:tr LinkedIn Goruntulenen Ilanlari Gizle
// @name:es LinkedIn Ocultar Empleos Vistos
// @name:de LinkedIn Angesehene Jobs Ausblenden
// @name:fr LinkedIn Masquer Les Offres Consultees
// @name:pt LinkedIn Ocultar Vagas Visualizadas
// @name:it LinkedIn Nascondi Annunci Visualizzati
// @name:ru LinkedIn Скрыть Просмотренные Вакансии
// @name:ja LinkedIn 閲覧済み求人を非表示
// @name:ko LinkedIn 확인한 채용 공고 숨기기
// @name:zh-CN LinkedIn 隐藏已查看职位
// @name:ar لينكدإن إخفاء الوظائف التي تمت مشاهدتها
// @namespace https://github.com/sametcn99
// @version 1.1.8
// @author sametcn99
// @description Hides viewed job cards on LinkedIn Jobs pages, adds a compact draggable badge, and lets you reveal hidden items anytime.
// @description:tr LinkedIn is sayfalarinda goruntulenen ilan kartlarini gizler, suruklenebilir kompakt bir badge ekler ve gizlenenleri istedigin zaman geri gostermenizi saglar.
// @description:es Oculta tarjetas de empleo vistas en LinkedIn Jobs, agrega una insignia compacta y arrastrable, y te permite mostrar los elementos ocultos cuando quieras.
// @description:de Blendet angesehene Jobkarten auf LinkedIn Jobs aus, fuegt ein kompaktes verschiebbares Badge hinzu und laesst dich ausgeblendete Eintraege jederzeit wieder anzeigen.
// @description:fr Masque les fiches d'emploi consultees sur LinkedIn Jobs, ajoute un badge compact deplacable et vous permet de reafficher les elements masques a tout moment.
// @description:pt Oculta cartoes de vagas visualizadas no LinkedIn Jobs, adiciona um selo compacto arrastavel e permite revelar itens ocultos a qualquer momento.
// @description:it Nasconde le schede delle offerte gia visualizzate su LinkedIn Jobs, aggiunge un badge compatto trascinabile e consente di mostrare di nuovo gli elementi nascosti in qualsiasi momento.
// @description:ru Скрывает просмотренные карточки вакансий в LinkedIn Jobs, добавляет компактный перетаскиваемый бейдж и позволяет в любой момент снова показать скрытые элементы.
// @description:ja LinkedIn Jobsで閲覧済みの求人カードを非表示にし、コンパクトでドラッグ可能なバッジを追加して、非表示項目をいつでも再表示できます。
// @description:ko LinkedIn Jobs 페이지에서 확인한 채용 카드들을 숨기고, 작고 드래그 가능한 배지를 추가하며, 숨긴 항목을 언제든 다시 표시할 수 있습니다.
// @description:zh-CN 在 LinkedIn 职位页面隐藏已查看职位卡片,添加可拖动的紧凑徽章,并可随时重新显示已隐藏项目。
// @description:ar يخفي بطاقات الوظائف التي تمت مشاهدتها في صفحات وظائف لينكدإن، ويضيف شارة مدمجة قابلة للسحب، ويتيح لك إظهار العناصر المخفية في أي وقت.
// @license MIT
// @copyright 2026, sametcn99
// @icon https://www.linkedin.com/favicon.ico
// @icon64 https://www.linkedin.com/favicon.ico
// @homepage https://github.com/sametcn99/linkedin-hide-viewed-jobs
// @homepageURL https://github.com/sametcn99/linkedin-hide-viewed-jobs
// @website https://github.com/sametcn99/linkedin-hide-viewed-jobs
// @source https://github.com/sametcn99/linkedin-hide-viewed-jobs
// @supportURL https://github.com/sametcn99/linkedin-hide-viewed-jobs/issues
// @downloadURL https://raw.githubusercontent.com/sametcn99/linkedin-hide-viewed-jobs/main/linkedin-hide-viewed-jobs.user.js
// @updateURL https://raw.githubusercontent.com/sametcn99/linkedin-hide-viewed-jobs/main/linkedin-hide-viewed-jobs.user.js
// @match https://www.linkedin.com/*
// @tag linkedin
// @tag jobs
// @tag productivity
// @tag userscript
// @tag ui
// @tag filtering
// @tag linkedin-jobs
// @grant none
// @inject-into content
// @run-at document-idle
// @compatible chrome Violentmonkey/Tampermonkey
// @compatible edge Violentmonkey/Tampermonkey
// @compatible firefox Violentmonkey/Tampermonkey
// @noframes
// ==/UserScript==
var t,n,i,e,o,s,r,h,a,l,d,c,u,p,g,b,v
t=Object.freeze({POLL_INTERVAL_MS:2e3,ROUTE_CHECK_INTERVAL_MS:500,ROUTE_BURST_INTERVAL_MS:250,ROUTE_BURST_MAX_TICKS:12,LAZY_RENDER_TIMEOUT_MS:8e3,MUTATION_DEBOUNCE_MS:80,UI_Z_INDEX:99999,UI_EDGE_MARGIN:8,ENABLE_HIGHLIGHT:1,VIEWED_HIGHLIGHT_COLOR:"#2ecc71",APPLIED_HIGHLIGHT_COLOR:"#f59e0b",ACTIVE_HIGHLIGHT_COLOR:"#0a66c2",KEYWORD_HIGHLIGHT_COLOR:"#9b59b6",HIGHLIGHT_OPACITY:.1,HIGHLIGHT_OPACITY_MIN:.04,HIGHLIGHT_OPACITY_MAX:.28,HIGHLIGHT_OPACITY_STEP:.01,HIGHLIGHT_BORDER_RADIUS:"6px",SCROLL_GUARD_ENABLED_DEFAULT:1,SCROLL_GUARD_TRIGGER_DELTA_PX:900,SCROLL_GUARD_TRIGGER_WINDOW_MS:1200,SCROLL_GUARD_COOLDOWN_MIN_MS:5e3,SCROLL_GUARD_COOLDOWN_MAX_MS:15e3,SCROLL_GUARD_ALLOWED_STEP_PX:110,SCROLL_GUARD_ALLOWED_STEP_MIN_INTERVAL_MS:120,SCROLL_GUARD_MIN_VIEWED_DENSITY:.55,SCROLL_GUARD_DENSITY_WINDOW_MS:6e3}),n=Object.freeze({STORAGE_KEY:"lhvj-show-hidden",SCROLL_GUARD_STORAGE_KEY:"lhvj-scroll-guard-enabled",DETECTION_MODE_STORAGE_KEY:"lhvj-detection-mode",RELOAD_ON_NAVIGATION_STORAGE_KEY:"lhvj-reload-on-navigation",VIEWED_HIGHLIGHT_COLOR_STORAGE_KEY:"lhvj-viewed-highlight-color",APPLIED_HIGHLIGHT_COLOR_STORAGE_KEY:"lhvj-applied-highlight-color",ACTIVE_HIGHLIGHT_COLOR_STORAGE_KEY:"lhvj-active-highlight-color",KEYWORD_HIGHLIGHT_COLOR_STORAGE_KEY:"lhvj-keyword-highlight-color",CUSTOM_KEYWORDS_STORAGE_KEY:"lhvj-custom-keywords",HIGHLIGHT_OPACITY_STORAGE_KEY:"lhvj-highlight-opacity",UI_POSITION_KEY:"lhvj-ui-position",HIDDEN_CLASS:"lhvj-hidden-by-script",UI_ID:"lhvj-toggle-root",VIEWED_HIGHLIGHT_CLASS:"lhvj-viewed-highlight",APPLIED_HIGHLIGHT_CLASS:"lhvj-applied-highlight",ACTIVE_HIGHLIGHT_CLASS:"lhvj-active-highlight",KEYWORD_HIGHLIGHT_CLASS:"lhvj-keyword-highlight"}),i=Object.freeze(["Viewed","Seen","G\xf6r\xfcnt\xfclenen","G\xf6r\xfcnt\xfclendi","Visto","Vistos","Visualizado","Visualizados","Vu","Vue","Angesehen","Gesehen","Visualizzato","Visto","Bekeken","\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043e","Wy\u015bwietlono","Visad","Sedd","\u5df2\u67e5\u770b","\u5df2\u6aa2\u8996","\u95b2\u89a7\u6e08\u307f","\uc870\ud68c\ub428","\u062a\u0645\u062a \u0627\u0644\u0645\u0634\u0627\u0647\u062f\u0629","\u0926\u0947\u0916\u093e \u0917\u092f\u093e"]),e=Object.freeze(["Applied","Ba\u015fvurulan","Ba\u015fvurulanlar","Ba\u015fvuruldu","Aplicado","Postulado","Candidatado","Candidatura","Postul\xe9","Postul\xe9e","Candidature","Beworben","Candidata","Candidati","Candidatura","Solliciteerd","\u041e\u0442\u043a\u043b\u0438\u043a\u043d\u0443\u043b\u0441\u044f","Aplikowano","S\xf6kt","\u5df2\u7533\u8bf7","\u5df2\u7533\u8acb","\u5fdc\u52df\u6e08\u307f","\uc9c0\uc6d0\ud568","\uc9c0\uc6d0 \uc644\ub8cc","\u062a\u0645 \u0627\u0644\u062a\u0642\u062f\u064a\u0645","\u0906\u0935\u0947\u0926\u0928 \u0915\u093f\u092f\u093e \u0917\u092f\u093e"]),o=Object.freeze(["[data-occludable-job-id]","li[data-occludable-job-id]","li.jobs-search-results__list-item","li.scaffold-layout__list-item","li.discovery-templates-entity-item",'li[class*="discovery-templates-entity-item"]',"article.job-search-card","div.job-search-card","div.base-card","article.base-card","li.jobs-collections-module__list-item","div.jobs-collections-module__list-item","li.jobs-collection__list-item","div.jobs-collection__list-item",".jobs-collections-module__job-card",".jobs-collections-module__job-card-container"]),s=Object.freeze(["li.job-card-container__footer-job-state",'li[class*="footer-job-state"]',".job-card-container__footer-wrapper li",'[class*="job-card-footer"]','[class*="job-state"]',"[data-jobstate]",'[data-viewed="true"]',"span.job-card-list__footer"]),r=Object.freeze(['a[href*="/jobs/view/"]','a[href*="/jobs/collections/"]','a[href*="/jobs/search/"]','a[href*="currentJobId="]','a[href*="trk=public_jobs"]',"a.job-card-container__link",'a[data-control-name*="job"]','a[class*="job-card"]',"a.base-card__full-link","a.jobs-collection-card__link","a.jobs-collections-module__link"]),h=o.join(","),a=s.join(","),l=r.join(","),d=[h,'[data-job-id],.job-card-container,.job-card-list,.base-card,.job-search-card,li[class*="jobs-search"],li[class*="job-card"],div[class*="job-card"],article[class*="job"],article[class*="base-card"],.jobs-collections-module__job-card,.jobs-collections-module__job-card-container,li.jobs-collections-module__list-item,div.jobs-collections-module__list-item'].join(","),c=class{t
i
normalizedCustomKeywords
constructor(t=[]){this.t=i.map(t=>this.normalize(t)).filter(t=>t.length>0),this.i=e.map(t=>this.normalize(t)).filter(t=>t.length>0),this.normalizedCustomKeywords=t.map(t=>this.normalize(t)).filter(t=>t.length>0)}setCustomKeywords(t){this.normalizedCustomKeywords=t.map(t=>this.normalize(t)).filter(t=>t.length>0)}matchCustomKeywords(t){if(0===this.normalizedCustomKeywords.length)return 0
const n=this.normalize(t)
return n?this.containsAnySubstring(n,this.normalizedCustomKeywords):0}matchCustomKeywordsFromElement(t){const n=(t.textContent||"").trim(),i=t.getAttribute("aria-label")||"",e=t.getAttribute("title")||"",o=[]
return t.querySelectorAll("[alt]").forEach(t=>{const n=t.getAttribute("alt")
n&&o.push(n)}),this.matchCustomKeywords(n)||this.matchCustomKeywords(i)||this.matchCustomKeywords(e)||o.length>0&&o.some(t=>this.matchCustomKeywords(t))}normalize(t){return(t||"").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g,"")}getDetectedStateFromText(t){const n=this.normalize(t)
return n?this.containsAnyKeyword(n,this.i)?"applied":this.containsAnyKeyword(n,this.t)?"viewed":null:null}getDetectedStateFromElement(t){const n=(t.textContent||"").trim(),i=t.getAttribute("aria-label")||"",e=t.getAttribute("title")||""
return this.getDetectedStateFromText(n)||this.getDetectedStateFromText(i)||this.getDetectedStateFromText(e)}containsAnyKeyword(t,n){for(const i of n)if(this.containsKeywordExactly(t,i))return 1
return 0}containsAnySubstring(t,n){for(const i of n)if(t.includes(i))return 1
return 0}containsKeywordExactly(t,n){let i=0
for(;t.length>i;){const e=t.indexOf(n,i)
if(-1===e)return 0
if(this.hasBoundary(t,e,n.length))return 1
i=e+1}return 0}hasBoundary(t,n,i){const e=n+i,o=t.length>e?t[e]:""
return!this.isAsciiLetterOrNumber(n>0?t[n-1]:"")&&!this.isAsciiLetterOrNumber(o)}isAsciiLetterOrNumber(t){if(!t)return 0
const n=t.charCodeAt(0)
return n>=48&&57>=n||n>=65&&90>=n||n>=97&&122>=n}},u=class{matcher
constructor(t){this.matcher=t}getJobCards(){const t=new Set
return document.querySelectorAll(h).forEach(n=>{t.add(n)}),Array.from(t)}getKeywordCandidateCards(){const t=new Set
return document.querySelectorAll(h).forEach(n=>{t.add(n)}),this.getPotentialViewedAnchors().forEach(n=>{const i=this.getCardFromAnchor(n)
i&&t.add(i)}),Array.from(t)}getDetectedCardsFromMarkers(){const t=new Map
return document.querySelectorAll(a).forEach(n=>{if(!this.isElementVisible(n))return
const i=this.matcher.getDetectedStateFromElement(n)
if(!i)return
const e=this.getCardFromNode(n)
e&&this.setDetectedState(t,e,i)}),t}getDetectedJobState(t){const n=this.matcher.getDetectedStateFromText(t.className||"")
if(n)return n
const i=this.matcher.getDetectedStateFromElement(t)
if(i)return i
const e=t.querySelectorAll("ul li")
for(let o=0;e.length>o;o++){if(!this.isElementVisible(e[o]))continue
const t=this.matcher.getDetectedStateFromElement(e[o])
if(t)return t}return this.cardContainsDetectedStateInDescendants(t,"[aria-label], [title], span, small, div, p, time",100)||(t.matches('li.discovery-templates-entity-item, li[class*="discovery-templates-entity-item"]')?this.cardContainsDetectedStateInDescendants(t,"*",140):null)}refreshDetectedAnchors(t){let n=0
const i=new Map
return this.shouldUseAnchorDetection()?(this.getPotentialViewedAnchors().forEach(e=>{const o=this.getCardFromAnchor(e),s=o||e.closest("li, article, div")||e,r="1"===e.getAttribute("data-lhvj-hidden-anchor"),h=r&&t?"viewed":this.getDetectedAnchorState(e,s)
h&&(n++,o&&(this.setDetectedState(i,o,h),this.applyVisibility(o,t),this.applyDetectedHighlight(o,t?null:h))),(h||r)&&this.applyAnchorVisibility(e,!!h&&t)}),{detectedAnchorCount:n,detectedAnchorCards:i}):(this.restoreHiddenAnchors(),{detectedAnchorCount:n,detectedAnchorCards:i})}refreshDetectedCardsFallback(t){const n=new Map
return this.isJobsPage()?(document.querySelectorAll(a).forEach(i=>{if(!this.isElementVisible(i))return
const e=this.matcher.getDetectedStateFromElement(i)
if(!e)return
const o=this.getCardFromViewedMarker(i)
o&&(this.setDetectedState(n,o,e),this.applyVisibility(o,t),this.applyDetectedHighlight(o,t?null:e))}),n):n}applyVisibility(t,i){i?(t.classList.add(n.HIDDEN_CLASS),t.setAttribute("data-lhvj-hidden","1")):(t.classList.remove(n.HIDDEN_CLASS),t.removeAttribute("data-lhvj-hidden"))}applyDetectedHighlight(t,i){const{VIEWED_HIGHLIGHT_CLASS:e,APPLIED_HIGHLIGHT_CLASS:o}=n
if(t.classList.remove(e,o),t.removeAttribute("data-lhvj-viewed"),t.removeAttribute("data-lhvj-applied"),"viewed"===i)return t.classList.add(e),void t.setAttribute("data-lhvj-viewed","1")
"applied"===i&&(t.classList.add(o),t.setAttribute("data-lhvj-applied","1"))}getActiveCards(t){const n=new Set,i=this.getPageCurrentJobId()
if(!i)return n
for(const e of t)this.cardContainsMatchingCurrentJobId(e,i)&&n.add(e)
return n}applyActiveHighlight(t,i){if(i)return t.classList.add(n.ACTIVE_HIGHLIGHT_CLASS),void t.setAttribute("data-lhvj-active","1")
t.classList.remove(n.ACTIVE_HIGHLIGHT_CLASS),t.removeAttribute("data-lhvj-active")}applyKeywordHighlight(t,i){if(i)return t.classList.add(n.KEYWORD_HIGHLIGHT_CLASS),void t.setAttribute("data-lhvj-keyword","1")
t.classList.remove(n.KEYWORD_HIGHLIGHT_CLASS),t.removeAttribute("data-lhvj-keyword")}isJobsPage(){return this.isJobsPath(location.pathname)}isElementVisible(t){if(t.hasAttribute("hidden"))return 0
if("true"===t.getAttribute("aria-hidden"))return 0
const n=window.getComputedStyle(t)
if("none"===n.display||"hidden"===n.visibility)return 0
if(0===parseFloat(n.opacity))return 0
try{const n=t.getClientRects()
if(n&&0===n.length)return 0}catch{}return 1}getCardFromNode(t){return t.closest(h)??null}getCardFromAnchor(t){return t.closest(h)||t.closest(d)||(t.matches('a[href*="/jobs/view/"], a[href*="/jobs/collections/"], a[href*="currentJobId="]')?t:null)}getCardFromViewedMarker(t){return t.closest(d)??null}isJobsRootPath(t){return"/jobs"===t||"/jobs/"===t}isJobsSubPath(t){return t.startsWith("/jobs/")}isJobsPath(t){return this.isJobsRootPath(t)||this.isJobsSubPath(t)||t.includes("/jobs")}shouldUseAnchorDetection(){return this.isJobsPath(location.pathname)}restoreHiddenAnchors(){document.querySelectorAll('a[data-lhvj-hidden-anchor="1"]').forEach(t=>{this.applyAnchorVisibility(t,0)})}getPotentialViewedAnchors(){const t=new Set
return document.querySelectorAll("a[href]").forEach(n=>{const i=n.getAttribute("href")||"";(i.includes("/jobs/view/")||i.includes("/jobs/collections/")||i.includes("/jobs/collections/recommended")||i.includes("/jobs/search/")||i.includes("currentJobId=")||i.includes("trk=public_jobs"))&&t.add(n)}),document.querySelectorAll(l).forEach(n=>{t.add(n)}),document.querySelectorAll('a[data-lhvj-hidden-anchor="1"]').forEach(n=>{t.add(n)}),Array.from(t)}getDetectedAnchorState(t,n){if(!this.isElementVisible(t))return null
const i=this.matcher.getDetectedStateFromElement(t)
if(i)return i
const e=t.querySelectorAll("[aria-label], [title]")
for(let o=0;e.length>o;o++){if(!this.isElementVisible(e[o]))continue
const t=this.matcher.getDetectedStateFromElement(e[o])
if(t)return t}return n?this.getDetectedStateInScope(n):null}getDetectedStateInScope(t){return this.cardContainsDetectedStateInDescendants(t,a,24)||this.cardContainsDetectedStateInDescendants(t,"[aria-label], [title], span, small, p, time, li",80)}cardContainsDetectedStateInDescendants(t,n,i){const e=t.querySelectorAll(n),o=Math.min(e.length,i)
let s=0
for(let r=0;o>r;r++){if(!this.isElementVisible(e[r]))continue
const t=this.matcher.getDetectedStateFromElement(e[r])
if("applied"===t)return"applied"
"viewed"===t&&(s=1)}return s?"viewed":null}setDetectedState(t,n,i){const e=t.get(n)
"applied"!==e&&("applied"!==i&&e||t.set(n,i))}applyAnchorVisibility(t,i){i?(t.classList.add(n.HIDDEN_CLASS),t.setAttribute("data-lhvj-hidden-anchor","1")):(t.classList.remove(n.HIDDEN_CLASS),t.removeAttribute("data-lhvj-hidden-anchor"))}getPageCurrentJobId(){const t=new URLSearchParams(location.search).get("currentJobId")
return t&&/^\d+$/.test(t)?t:null}cardContainsMatchingCurrentJobId(t,n){const i=t.matches("a[href]")?[t]:Array.from(t.querySelectorAll("a[href]"))
for(let e=0;i.length>e;e++)if(this.hrefMatchesCurrentJobId(i[e].href,n))return 1
return 0}hrefMatchesCurrentJobId(t,n){if(!t)return 0
try{return new URL(t,location.origin).searchParams.get("currentJobId")===n}catch{return 0}}},p=class{o=location.href
h=location.pathname
l=null
u=null
p=null
v=new Map
onRefresh
onPathChange
constructor(t,n){this.onRefresh=t,this.onPathChange=n}startObserving(){this.observeRouteChanges(),this.observeDomChanges()}stopAll(){this.stopDomObserver(),this.clearRouteRefreshBurst(),this.v.forEach(t=>clearTimeout(t)),this.v.clear()}queueRefresh(t){if(this.v.has(t))return
const n=setTimeout(()=>{this.v.delete(t),this.onRefresh()},t)
this.v.set(t,n)}startRouteRefreshBurst(){let n=0
this.clearRouteRefreshBurst(),this.l=setInterval(()=>{n++,this.onRefresh(),t.ROUTE_BURST_MAX_TICKS>n||this.clearRouteRefreshBurst()},t.ROUTE_BURST_INTERVAL_MS)}restartDomObserver(){this.stopDomObserver(),this.observeDomChanges()}observeRouteChanges(){const t=()=>this.onLocationMaybeChanged()
this.wrapHistoryMethod("pushState",t),this.wrapHistoryMethod("replaceState",t),window.addEventListener("popstate",t),window.addEventListener("hashchange",t)}observeDomChanges(){this.stopDomObserver(),this.u=new MutationObserver(()=>{this.p||(this.p=setTimeout(()=>{this.p=null,this.onRefresh()},t.MUTATION_DEBOUNCE_MS))}),document.body&&this.u.observe(document.body,{childList:1,subtree:1,attributes:0})}stopDomObserver(){this.p&&(clearTimeout(this.p),this.p=null),this.u&&(this.u.disconnect(),this.u=null)}clearRouteRefreshBurst(){this.l&&(clearInterval(this.l),this.l=null)}onLocationMaybeChanged(){const t=location.href,n=location.pathname
if(t!==this.o){if(this.o=t,n!==this.h)return this.h=n,void this.onPathChange()
this.onRefresh(),this.queueRefresh(120),this.queueRefresh(420)}}wrapHistoryMethod(t,n){const i=history[t]
"function"==typeof i&&(history[t]=function(...t){const e=i.apply(this,t)
return n(),e})}},g=class{m=null
inject(t){const n=document.getElementById("lhvj-style")
if(n)return this.m=n,void(this.m.textContent=this.buildCSS(t))
const i=document.createElement("style")
i.id="lhvj-style",i.textContent=this.buildCSS(t),document.head.appendChild(i),this.m=i}updateHighlightStyles(t){this.m&&document.head.contains(this.m)?this.m.textContent=this.buildCSS(t):this.inject(t)}buildCSS(i){const{HIDDEN_CLASS:e,UI_ID:o,VIEWED_HIGHLIGHT_CLASS:s,APPLIED_HIGHLIGHT_CLASS:r,ACTIVE_HIGHLIGHT_CLASS:h,KEYWORD_HIGHLIGHT_CLASS:a}=n,{UI_Z_INDEX:l,HIGHLIGHT_BORDER_RADIUS:d}=t,c=this.withAlpha(i.colors.viewed,i.opacity),u=this.withAlpha(i.colors.applied,i.opacity),p=this.withAlpha(i.colors.active,i.opacity),g=this.withAlpha(i.colors.keyword,i.opacity)
return`\n .${e} {\n height: 1px !important;\n min-height: 1px !important;\n max-height: 1px !important;\n margin-top: 0 !important;\n margin-bottom: 0 !important;\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n overflow: hidden !important;\n opacity: 0 !important;\n }\n\n #${o} {\n --lhvj-bg: linear-gradient(150deg, rgba(34, 40, 46, 0.98), rgba(22, 27, 33, 0.98));\n --lhvj-border: rgba(255, 255, 255, 0.16);\n --lhvj-text: #e6edf3;\n --lhvj-muted: #9aa8b6;\n --lhvj-chip-bg: rgba(255, 255, 255, 0.08);\n --lhvj-chip-border: rgba(255, 255, 255, 0.16);\n --lhvj-focus: #82c8ff;\n position: fixed;\n top: 76px;\n right: 16px;\n z-index: ${l};\n font-family: "Segoe UI Variable", "Segoe UI", "SF Pro Text", "Helvetica Neue", Arial, sans-serif;\n background: var(--lhvj-bg);\n border-radius: 999px;\n border: 1px solid var(--lhvj-border);\n box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35), 0 2px 6px rgba(0, 0, 0, 0.28);\n display: inline-flex;\n flex-direction: column;\n align-items: stretch;\n min-height: 36px;\n max-width: 280px;\n width: fit-content;\n overflow: hidden;\n user-select: none;\n backdrop-filter: blur(6px);\n transition: box-shadow 0.16s ease, transform 0.16s ease, border-color 0.16s ease;\n }\n\n #${o}[data-settings-open="1"] {\n border-radius: 14px;\n }\n\n #${o}[data-enabled="0"] {\n width: fit-content;\n }\n\n #${o}:hover {\n border-color: rgba(160, 214, 255, 0.38);\n box-shadow: 0 14px 30px rgba(0, 0, 0, 0.42), 0 3px 8px rgba(0, 0, 0, 0.26);\n }\n\n #${o}:focus-within {\n border-color: rgba(130, 200, 255, 0.75);\n box-shadow: 0 0 0 2px rgba(130, 200, 255, 0.22), 0 10px 24px rgba(0, 0, 0, 0.35);\n }\n\n #${o}.lhvj-dragging {\n transform: scale(1.01);\n box-shadow: 0 16px 34px rgba(0, 0, 0, 0.45), 0 4px 10px rgba(0, 0, 0, 0.28);\n }\n\n #${o} .lhvj-header {\n display: inline-flex;\n align-items: stretch;\n width: 100%;\n }\n\n #${o} .lhvj-content {\n display: flex;\n flex: 1;\n min-width: 0;\n flex-direction: column;\n }\n\n #${o} .lhvj-drag-handle {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n align-self: stretch;\n cursor: grab;\n border-right: 1px solid rgba(255, 255, 255, 0.1);\n color: #5a6774;\n flex-shrink: 0;\n transition: color 0.14s ease, background 0.14s ease;\n }\n\n #${o} .lhvj-drag-handle:hover {\n color: #b9c6d3;\n background: rgba(255, 255, 255, 0.05);\n }\n\n #${o} .lhvj-drag-handle::before {\n content: "";\n display: block;\n width: 8px;\n height: 12px;\n background: radial-gradient(circle, currentColor 1.2px, transparent 1.2px);\n background-size: 4px 4px;\n }\n\n #${o} .lhvj-main {\n display: inline-flex;\n align-items: center;\n justify-content: flex-start;\n gap: 8px;\n padding: 4px 10px 4px 8px;\n min-height: 36px;\n cursor: default;\n }\n\n #${o} .lhvj-footer {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n gap: 10px;\n padding: 0 10px 8px 8px;\n }\n\n #${o} .lhvj-count {\n display: inline-flex;\n align-items: baseline;\n gap: 4px;\n white-space: nowrap;\n }\n\n #${o} .lhvj-count-num {\n font-size: 13px;\n font-weight: 700;\n letter-spacing: 0.1px;\n line-height: 1;\n color: var(--lhvj-text);\n }\n\n #${o} .lhvj-count-unit {\n font-size: 11px;\n font-weight: 500;\n line-height: 1;\n color: var(--lhvj-muted);\n }\n\n #${o} .lhvj-state {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 34px;\n padding: 3px 8px;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.36px;\n line-height: 1;\n text-align: center;\n color: #d2dde7;\n border: 1px solid rgba(255, 255, 255, 0.14);\n background: rgba(255, 255, 255, 0.07);\n cursor: pointer;\n transition: border-color 0.14s ease, background 0.14s ease, color 0.14s ease;\n }\n\n #${o} .lhvj-state:hover {\n border-color: rgba(255, 255, 255, 0.28);\n background: rgba(255, 255, 255, 0.13);\n }\n\n #${o} .lhvj-state:focus-visible {\n outline: 2px solid var(--lhvj-focus);\n outline-offset: 2px;\n }\n\n #${o}[data-enabled="1"] .lhvj-state {\n color: #b8e0ff;\n border-color: rgba(112, 181, 249, 0.46);\n background: rgba(112, 181, 249, 0.2);\n }\n\n #${o}[data-enabled="0"] .lhvj-state {\n color: #ffc4c4;\n border-color: rgba(240, 120, 120, 0.34);\n background: rgba(240, 120, 120, 0.18);\n }\n\n #${o} .lhvj-guard-btn {\n border: 1px solid var(--lhvj-chip-border);\n background: var(--lhvj-chip-bg);\n color: #d0dbe6;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.34px;\n line-height: 1;\n padding: 4px 8px;\n cursor: pointer;\n transition: border-color 0.14s ease, background 0.14s ease, color 0.14s ease;\n }\n\n #${o} .lhvj-guard-btn:hover {\n background: rgba(255, 255, 255, 0.14);\n border-color: rgba(255, 255, 255, 0.24);\n }\n\n #${o} .lhvj-guard-btn:focus-visible {\n outline: 2px solid var(--lhvj-focus);\n outline-offset: 2px;\n }\n\n #${o}[data-scroll-guard="1"] .lhvj-guard-btn {\n border-color: rgba(243, 186, 99, 0.55);\n color: #ffe2b3;\n background: rgba(227, 147, 34, 0.24);\n }\n\n #${o} .lhvj-cooldown {\n min-width: 0;\n max-width: 0;\n overflow: hidden;\n opacity: 0;\n color: #ffe3b5;\n border: 1px solid rgba(243, 176, 88, 0.5);\n background: rgba(222, 131, 16, 0.24);\n border-radius: 999px;\n padding: 0;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.2px;\n line-height: 1;\n white-space: nowrap;\n transition: opacity 0.14s ease, max-width 0.14s ease, padding 0.14s ease;\n }\n\n #${o}[data-cooldown="1"] .lhvj-cooldown {\n opacity: 1;\n max-width: 74px;\n padding: 4px 7px;\n }\n\n #${o} .lhvj-settings-btn {\n border: 1px solid var(--lhvj-chip-border);\n background: var(--lhvj-chip-bg);\n color: #d0dbe6;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.34px;\n line-height: 1;\n padding: 4px 8px;\n cursor: pointer;\n }\n\n #${o} .lhvj-settings-btn:hover {\n background: rgba(255, 255, 255, 0.14);\n }\n\n #${o} .lhvj-settings-btn:focus-visible {\n outline: 2px solid var(--lhvj-focus);\n outline-offset: 2px;\n }\n\n #${o}[data-settings-open="1"] .lhvj-settings-btn {\n border-color: rgba(130, 200, 255, 0.5);\n color: #bee6ff;\n }\n\n #${o} .lhvj-settings-panel {\n display: none;\n width: 100%;\n padding: 8px 10px 10px 38px;\n border-top: 1px solid rgba(255, 255, 255, 0.12);\n background: rgba(0, 0, 0, 0.16);\n box-sizing: border-box;\n }\n\n #${o}[data-settings-open="1"] .lhvj-settings-panel {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n }\n\n #${o} .lhvj-settings-label {\n font-size: 11px;\n font-weight: 600;\n color: #c5d1dc;\n }\n\n #${o} .lhvj-color-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 8px;\n width: 100%;\n }\n\n #${o} .lhvj-slider-row {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n }\n\n #${o} .lhvj-keyword-row {\n display: flex;\n flex-direction: column;\n gap: 6px;\n width: 100%;\n }\n\n #${o} .lhvj-keyword-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n min-height: 24px;\n max-height: 80px;\n overflow-y: auto;\n }\n\n #${o} .lhvj-chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 6px 2px 8px;\n border-radius: 999px;\n background: rgba(155, 89, 182, 0.28);\n border: 1px solid rgba(155, 89, 182, 0.5);\n color: #d9b8ff;\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.2px;\n }\n\n #${o} .lhvj-chip-remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n border-radius: 999px;\n border: none;\n background: rgba(155, 89, 182, 0.4);\n color: #d9b8ff;\n font-size: 12px;\n line-height: 1;\n cursor: pointer;\n padding: 0;\n }\n\n #${o} .lhvj-chip-remove:hover {\n background: rgba(155, 89, 182, 0.7);\n }\n\n #${o} .lhvj-chip-input-row {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n #${o} .lhvj-keyword-input {\n flex: 1;\n min-width: 0;\n height: 28px;\n padding: 0 8px;\n border-radius: 999px;\n border: 1px solid rgba(255, 255, 255, 0.18);\n background: rgba(255, 255, 255, 0.07);\n color: #e6edf3;\n font-size: 11px;\n outline: none;\n }\n\n #${o} .lhvj-keyword-input::placeholder {\n color: #9aa8b6;\n }\n\n #${o} .lhvj-keyword-input:focus {\n border-color: rgba(155, 89, 182, 0.6);\n background: rgba(155, 89, 182, 0.12);\n }\n\n #${o} .lhvj-keyword-duplicate-msg {\n font-size: 10px;\n font-weight: 600;\n color: #f59e0b;\n opacity: 0;\n transition: opacity 0.3s ease;\n white-space: nowrap;\n }\n\n #${o} .lhvj-keyword-count {\n font-size: 10px;\n color: #9aa8b6;\n font-weight: 500;\n }\n\n #${o} .lhvj-opacity-input {\n flex: 1;\n accent-color: #9fd8ff;\n cursor: pointer;\n }\n\n #${o} .lhvj-opacity-value {\n min-width: 40px;\n text-align: right;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.22px;\n color: #d9e4ee;\n }\n\n #${o} .lhvj-color-control {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n }\n\n #${o} .lhvj-color-caption {\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.28px;\n color: #dbe6ef;\n }\n\n #${o} .lhvj-color-actions {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n\n #${o} .lhvj-color-input {\n inline-size: 36px;\n block-size: 28px;\n padding: 0;\n border: 1px solid rgba(255, 255, 255, 0.22);\n border-radius: 9px;\n background: rgba(255, 255, 255, 0.08);\n cursor: pointer;\n }\n\n #${o} .lhvj-color-input::-webkit-color-swatch-wrapper {\n padding: 3px;\n }\n\n #${o} .lhvj-color-input::-webkit-color-swatch,\n #${o} .lhvj-color-input::-moz-color-swatch {\n border: none;\n border-radius: 6px;\n }\n\n #${o} .lhvj-reset-btn {\n border: 1px solid rgba(255, 255, 255, 0.16);\n background: rgba(255, 255, 255, 0.05);\n color: #cad6e2;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.28px;\n padding: 4px 8px;\n cursor: pointer;\n }\n\n #${o} .lhvj-reset-btn:hover,\n #${o} .lhvj-color-input:hover {\n background: rgba(255, 255, 255, 0.12);\n }\n\n #${o} .lhvj-reset-btn:focus-visible,\n #${o} .lhvj-color-input:focus-visible {\n outline: 2px solid var(--lhvj-focus);\n outline-offset: 2px;\n }\n\n #${o} .lhvj-mode-group {\n display: inline-flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n }\n\n #${o}[data-enabled="0"] .lhvj-guard-btn,\n #${o}[data-enabled="0"] .lhvj-cooldown,\n #${o}[data-enabled="0"] .lhvj-count,\n #${o}[data-enabled="0"] .lhvj-footer,\n #${o}[data-enabled="0"] .lhvj-settings-btn,\n #${o}[data-enabled="0"] .lhvj-settings-panel {\n display: none !important;\n }\n\n #${o} .lhvj-mode-btn,\n #${o} .lhvj-link-btn {\n border: 1px solid rgba(255, 255, 255, 0.2);\n background: rgba(255, 255, 255, 0.06);\n color: #d4dde6;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.28px;\n padding: 4px 8px;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n text-decoration: none;\n }\n\n #${o} .lhvj-mode-btn:hover,\n #${o} .lhvj-link-btn:hover {\n background: rgba(255, 255, 255, 0.12);\n }\n\n #${o} .lhvj-mode-btn[data-active="1"] {\n border-color: rgba(130, 200, 255, 0.56);\n color: #b8e0ff;\n background: rgba(112, 181, 249, 0.24);\n }\n\n #${o} .lhvj-mode-btn:focus-visible,\n #${o} .lhvj-link-btn:focus-visible {\n outline: 2px solid var(--lhvj-focus);\n outline-offset: 2px;\n }\n\n .${s} {\n box-shadow: inset 0 0 0 999px ${c} !important;\n border-radius: ${d} !important;\n background-color: ${c} !important;\n }\n\n .${s} .job-card-container,\n .${s}[class*="job-card"],\n .${s} > div {\n box-shadow: inset 0 0 0 999px ${c} !important;\n border-radius: ${d} !important;\n background-color: ${c} !important;\n }\n\n .${r} {\n box-shadow: inset 0 0 0 999px ${u} !important;\n border-radius: ${d} !important;\n background-color: ${u} !important;\n }\n\n .${r} .job-card-container,\n .${r}[class*="job-card"],\n .${r} > div {\n box-shadow: inset 0 0 0 999px ${u} !important;\n border-radius: ${d} !important;\n background-color: ${u} !important;\n }\n\n .${h} {\n box-shadow: inset 0 0 0 999px ${p} !important;\n border-radius: ${d} !important;\n background-color: ${p} !important;\n }\n\n .${h} .job-card-container,\n .${h}[class*="job-card"],\n .${h} > div {\n box-shadow: inset 0 0 0 999px ${p} !important;\n border-radius: ${d} !important;\n background-color: ${p} !important;\n }\n\n .${a} {\n box-shadow: inset 0 0 0 999px ${g} !important;\n border-radius: ${d} !important;\n background-color: ${g} !important;\n }\n\n .${a} .job-card-container,\n .${a}[class*="job-card"],\n .${a} > div {\n box-shadow: inset 0 0 0 999px ${g} !important;\n border-radius: ${d} !important;\n background-color: ${g} !important;\n }\n\n html.lhvj-pagination-cooldown div.jobs-search-pagination button,\n html.lhvj-pagination-cooldown div.jobs-search-pagination [role="button"] {\n pointer-events: none !important;\n opacity: 0.45 !important;\n cursor: not-allowed !important;\n }\n\n @media (max-width: 900px) {\n #${o} {\n top: 70px;\n right: 8px;\n }\n\n #${o} .lhvj-color-grid {\n grid-template-columns: minmax(0, 1fr);\n }\n }\n `}withAlpha(n,i){const e=/^#[0-9a-fA-F]{6}$/.test(n)?n:t.VIEWED_HIGHLIGHT_COLOR
return`rgba(${parseInt(e.slice(1,3),16)}, ${parseInt(e.slice(3,5),16)}, ${parseInt(e.slice(5,7),16)}, ${i})`}},b=class i{static REPOSITORY_URL="https://github.com/sametcn99/linkedin-hide-viewed-jobs"
storage
onToggle
onScrollGuardToggle
onDetectionModeChange
onReloadNavigationToggle
onHighlightColorChange
onHighlightColorReset
onHighlightOpacityChange
onHighlightOpacityReset
onCustomKeywordsChange
customKeywords=[]
state={root:null,countNum:null,countUnit:null,stateEl:null,guardBtn:null,cooldownEl:null,settingsBtn:null,settingsPanel:null,modeHideBtn:null,modeHighlightBtn:null,reloadNavBtn:null,viewedColorInput:null,appliedColorInput:null,activeColorInput:null,keywordColorInput:null,viewedColorResetBtn:null,appliedColorResetBtn:null,activeColorResetBtn:null,keywordColorResetBtn:null,opacityInput:null,opacityValue:null,opacityResetBtn:null,keywordChipContainer:null,keywordChipInput:null,keywordCountDisplay:null}
j=0
constructor(t,n,i,e,o,s,r,h,a,l){this.storage=t,this.onToggle=n,this.onScrollGuardToggle=i,this.onDetectionModeChange=e,this.onReloadNavigationToggle=o,this.onHighlightColorChange=s,this.onHighlightColorReset=r,this.onHighlightOpacityChange=h,this.onHighlightOpacityReset=a,this.onCustomKeywordsChange=l,this.customKeywords=this.storage.getCustomKeywords()}ensure(t,i,e,o,s){if(this.state.root&&document.body.contains(this.state.root))return this.state.root
let r=document.getElementById(n.UI_ID)
if(r)return this.cacheElements(r),r
r=this.buildDom(t,i,e,o,s),document.body.appendChild(r)
const h=this.storage.getSavedPosition()
return h&&this.applyPosition(r,h.left,h.top,0),this.cacheElements(r),r}updateCount(t,n,i,e,o,s,r=0,h=0){const a=this.state.root
a&&this.state.countNum&&this.state.countUnit&&this.state.stateEl&&this.state.guardBtn&&this.state.cooldownEl&&this.state.settingsBtn&&this.state.modeHideBtn&&this.state.modeHighlightBtn&&this.state.reloadNavBtn&&this.state.viewedColorInput&&this.state.appliedColorInput&&this.state.activeColorInput&&this.state.keywordColorInput&&this.state.opacityInput&&this.state.opacityValue&&(a.setAttribute("data-enabled",n?"1":"0"),a.setAttribute("data-scroll-guard",i?"1":"0"),a.setAttribute("data-cooldown",r>0?"1":"0"),a.setAttribute("data-detection-mode",e),a.setAttribute("data-reload-on-navigation",o?"1":"0"),n||"1"!==a.getAttribute("data-settings-open")||(a.setAttribute("data-settings-open","0"),this.state.settingsBtn.textContent="Open settings"),this.state.countNum.textContent=t+"",this.state.countUnit.textContent=n?"hide"===e?"hidden":"marked":"off",this.state.stateEl.textContent=n?"ON":"OFF",this.state.guardBtn.textContent=i?"GUARD ON":"GUARD OFF",this.state.cooldownEl.textContent=r>0?`CD ${r}s`:"",this.state.modeHideBtn.setAttribute("data-active","hide"===e?"1":"0"),this.state.modeHighlightBtn.setAttribute("data-active","highlight"===e?"1":"0"),this.state.viewedColorInput.value=s.colors.viewed,this.state.appliedColorInput.value=s.colors.applied,this.state.activeColorInput.value=s.colors.active,this.state.keywordColorInput.value=s.colors.keyword,this.state.opacityInput.value=s.opacity+"",this.state.opacityValue.textContent=Math.round(100*s.opacity)+"%",this.state.reloadNavBtn.textContent=o?"Reload ON":"Reload OFF",this.state.reloadNavBtn.setAttribute("data-active",o?"1":"0"),this.state.settingsBtn.textContent="1"===a.getAttribute("data-settings-open")?"Close settings":"Open settings",this.state.keywordCountDisplay&&(this.state.keywordCountDisplay.textContent=h>0?`+${h} keyword`:""),this.syncKeywordChips())}remove(){const t=document.getElementById(n.UI_ID)
t&&t.remove(),this.state.root=null,this.state.countNum=null,this.state.countUnit=null,this.state.stateEl=null,this.state.guardBtn=null,this.state.cooldownEl=null,this.state.settingsBtn=null,this.state.settingsPanel=null,this.state.modeHideBtn=null,this.state.modeHighlightBtn=null,this.state.reloadNavBtn=null,this.state.viewedColorInput=null,this.state.appliedColorInput=null,this.state.activeColorInput=null,this.state.keywordColorInput=null,this.state.viewedColorResetBtn=null,this.state.appliedColorResetBtn=null,this.state.activeColorResetBtn=null,this.state.keywordColorResetBtn=null,this.state.opacityInput=null,this.state.opacityValue=null,this.state.opacityResetBtn=null,this.state.keywordChipContainer=null,this.state.keywordChipInput=null,this.state.keywordCountDisplay=null}syncPositionWithinViewport(){const t=document.getElementById(n.UI_ID)
if(!t)return
const i=t.getBoundingClientRect()
this.applyPosition(t,i.left,i.top,1)}buildDom(e,o,s,r,h){const a=document.createElement("div")
a.id=n.UI_ID,a.setAttribute("data-settings-open","0"),a.setAttribute("data-enabled",e?"1":"0"),a.setAttribute("data-scroll-guard",o?"1":"0"),a.setAttribute("data-detection-mode",s),a.setAttribute("data-reload-on-navigation",r?"1":"0")
const l=document.createElement("span")
l.className="lhvj-drag-handle",l.title="Drag to reposition",l.setAttribute("aria-label","Drag badge")
const d=document.createElement("div")
d.className="lhvj-header"
const c=document.createElement("div")
c.className="lhvj-content"
const u=document.createElement("div")
u.className="lhvj-main"
const p=document.createElement("span")
p.className="lhvj-count"
const g=document.createElement("span")
g.className="lhvj-count-num",g.textContent="0"
const b=document.createElement("span")
b.className="lhvj-count-unit",b.textContent=e?"hide"===s?"hidden":"marked":"off",p.appendChild(g),p.appendChild(b)
const v=document.createElement("span")
v.className="lhvj-state",v.textContent=e?"ON":"OFF",v.setAttribute("role","button"),v.setAttribute("tabindex","0"),v.setAttribute("aria-label","Enable or disable script logic"),v.addEventListener("click",t=>{t.preventDefault(),this.onToggle("1"!==a.getAttribute("data-enabled"))}),v.addEventListener("keydown",t=>{"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),this.onToggle("1"!==a.getAttribute("data-enabled")))})
const m=document.createElement("button")
m.type="button",m.className="lhvj-guard-btn",m.textContent=o?"GUARD ON":"GUARD OFF",m.setAttribute("aria-label","Toggle scroll cooldown guard"),m.addEventListener("click",t=>{t.preventDefault()
const n="1"!==a.getAttribute("data-scroll-guard")
this.onScrollGuardToggle(n)})
const j=document.createElement("span")
j.className="lhvj-cooldown"
const w=document.createElement("button")
w.type="button",w.className="lhvj-settings-btn",w.textContent="Open settings",w.setAttribute("aria-label","Toggle settings"),w.addEventListener("click",t=>{t.preventDefault()
const n=!("1"===a.getAttribute("data-settings-open"))
a.setAttribute("data-settings-open",n?"1":"0"),w.textContent=n?"Close settings":"Open settings"})
const f=document.createElement("div")
f.className="lhvj-footer"
const x=document.createElement("div")
x.className="lhvj-settings-panel"
const _=document.createElement("span")
_.className="lhvj-settings-label",_.textContent="Detected jobs:"
const y=document.createElement("div")
y.className="lhvj-mode-group"
const C=document.createElement("button")
C.type="button",C.className="lhvj-mode-btn",C.textContent="Hide",C.setAttribute("data-active","hide"===s?"1":"0"),C.addEventListener("click",t=>{t.preventDefault(),this.onDetectionModeChange("hide")})
const I=document.createElement("button")
I.type="button",I.className="lhvj-mode-btn",I.textContent="Highlight",I.setAttribute("data-active","highlight"===s?"1":"0"),I.addEventListener("click",t=>{t.preventDefault(),this.onDetectionModeChange("highlight")}),y.appendChild(C),y.appendChild(I)
const S=document.createElement("span")
S.className="lhvj-settings-label",S.textContent="Navigation:"
const $=document.createElement("button")
$.type="button",$.className="lhvj-mode-btn lhvj-reload-nav-btn",$.textContent=r?"Reload ON":"Reload OFF",$.setAttribute("data-active",r?"1":"0"),$.setAttribute("aria-label","Toggle full page reload on navigation"),$.addEventListener("click",t=>{t.preventDefault()
const n="1"!==a.getAttribute("data-reload-on-navigation")
this.onReloadNavigationToggle(n)})
const O=document.createElement("span")
O.className="lhvj-settings-label",O.textContent="Custom keywords:"
const H=document.createElement("div")
H.className="lhvj-keyword-row"
const A=document.createElement("div")
A.className="lhvj-keyword-chips"
const k=document.createElement("div")
k.className="lhvj-chip-input-row"
const D=document.createElement("input")
D.type="text",D.className="lhvj-keyword-input",D.placeholder="Type keyword and press Enter",D.setAttribute("aria-label","Add custom keyword")
const R=document.createElement("span")
R.className="lhvj-keyword-duplicate-msg",R.textContent="Already added",R.style.display="none",D.addEventListener("keydown",t=>{if("Enter"===t.key){t.preventDefault()
const n=D.value.trim().toLowerCase()
if(!n)return
if(this.customKeywords.includes(n))return R.style.display="inline",R.style.opacity="1",void setTimeout(()=>{R.style.opacity="0",setTimeout(()=>{R.style.display="none"},300)},1500)
const i=[...this.customKeywords,n]
this.customKeywords=i,this.onCustomKeywordsChange(i),D.value="",this.renderKeywordChips(A,D)}}),k.appendChild(D),k.appendChild(R),H.appendChild(A),H.appendChild(k),x.appendChild(O),x.appendChild(H),x.appendChild(_),x.appendChild(y),x.appendChild(S),x.appendChild($)
const E=document.createElement("span")
E.className="lhvj-settings-label",E.textContent="Card colors:"
const L=document.createElement("div")
L.className="lhvj-color-grid"
const T=this.buildColorControl("Viewed",h.colors.viewed,"viewed"),G=this.buildColorControl("Applied",h.colors.applied,"applied"),M=this.buildColorControl("Active",h.colors.active,"active"),N=this.buildColorControl("Keyword",h.colors.keyword,"keyword")
L.appendChild(T),L.appendChild(G),L.appendChild(M),L.appendChild(N),x.appendChild(E),x.appendChild(L)
const P=document.createElement("span")
P.className="lhvj-settings-label",P.textContent="Filter opacity:"
const U=document.createElement("div")
U.className="lhvj-slider-row"
const K=document.createElement("input")
K.type="range",K.className="lhvj-opacity-input",K.min=t.HIGHLIGHT_OPACITY_MIN+"",K.max=t.HIGHLIGHT_OPACITY_MAX+"",K.step=t.HIGHLIGHT_OPACITY_STEP+"",K.value=h.opacity+"",K.setAttribute("aria-label","Highlight filter opacity"),K.addEventListener("input",()=>{this.onHighlightOpacityChange(+K.value)})
const V=document.createElement("span")
V.className="lhvj-opacity-value",V.textContent=Math.round(100*h.opacity)+"%"
const z=document.createElement("button")
z.type="button",z.className="lhvj-reset-btn lhvj-opacity-reset",z.textContent="Reset",z.setAttribute("aria-label","Reset highlight opacity"),z.addEventListener("click",t=>{t.preventDefault(),this.onHighlightOpacityReset()}),U.appendChild(K),U.appendChild(V),U.appendChild(z),x.appendChild(P),x.appendChild(U),this.renderKeywordChips(A,D)
const B=document.createElement("span")
B.className="lhvj-settings-label",B.textContent="Project:"
const F=document.createElement("a")
F.className="lhvj-link-btn",F.href=i.REPOSITORY_URL,F.target="_blank",F.rel="noopener noreferrer",F.textContent="GitHub Repo",F.setAttribute("aria-label","Open the GitHub repository"),x.appendChild(B),x.appendChild(F),u.appendChild(v),u.appendChild(m),u.appendChild(j),f.appendChild(w),f.appendChild(p)
const W=document.createElement("span")
return W.className="lhvj-keyword-count-display",W.textContent="",f.appendChild(W),c.appendChild(u),c.appendChild(f),d.appendChild(l),d.appendChild(c),a.appendChild(d),a.appendChild(x),this.makeDraggable(a,l),a}cacheElements(t){this.state.root=t,this.state.countNum=t.querySelector(".lhvj-count-num"),this.state.countUnit=t.querySelector(".lhvj-count-unit"),this.state.stateEl=t.querySelector(".lhvj-state"),this.state.guardBtn=t.querySelector(".lhvj-guard-btn"),this.state.cooldownEl=t.querySelector(".lhvj-cooldown"),this.state.settingsBtn=t.querySelector(".lhvj-settings-btn"),this.state.settingsPanel=t.querySelector(".lhvj-settings-panel")
const n=t.querySelectorAll(".lhvj-mode-btn")
this.state.modeHideBtn=n[0]||null,this.state.modeHighlightBtn=n[1]||null,this.state.reloadNavBtn=t.querySelector(".lhvj-reload-nav-btn"),this.state.viewedColorInput=t.querySelector(".lhvj-viewed-color-input"),this.state.appliedColorInput=t.querySelector(".lhvj-applied-color-input"),this.state.activeColorInput=t.querySelector(".lhvj-active-color-input"),this.state.keywordColorInput=t.querySelector(".lhvj-keyword-color-input"),this.state.viewedColorResetBtn=t.querySelector(".lhvj-viewed-color-reset"),this.state.appliedColorResetBtn=t.querySelector(".lhvj-applied-color-reset"),this.state.activeColorResetBtn=t.querySelector(".lhvj-active-color-reset"),this.state.keywordColorResetBtn=t.querySelector(".lhvj-keyword-color-reset"),this.state.opacityInput=t.querySelector(".lhvj-opacity-input"),this.state.opacityValue=t.querySelector(".lhvj-opacity-value"),this.state.opacityResetBtn=t.querySelector(".lhvj-opacity-reset"),this.state.keywordChipContainer=t.querySelector(".lhvj-keyword-chips"),this.state.keywordChipInput=t.querySelector(".lhvj-keyword-input"),this.state.keywordCountDisplay=t.querySelector(".lhvj-keyword-count-display")}buildColorControl(t,n,i){const e=document.createElement("div")
e.className="lhvj-color-control"
const o=document.createElement("span")
o.className="lhvj-color-caption",o.textContent=t
const s=document.createElement("div")
s.className="lhvj-color-actions"
const r=document.createElement("input")
r.type="color",r.className=`lhvj-color-input lhvj-${i}-color-input`,r.value=n,r.setAttribute("aria-label",t+" highlight color"),r.addEventListener("input",()=>{this.onHighlightColorChange(i,r.value)})
const h=document.createElement("button")
return h.type="button",h.className=`lhvj-reset-btn lhvj-${i}-color-reset`,h.textContent="Reset",h.setAttribute("aria-label",`Reset ${t.toLowerCase()} highlight color`),h.addEventListener("click",t=>{t.preventDefault(),this.onHighlightColorReset(i)}),s.appendChild(r),s.appendChild(h),e.appendChild(o),e.appendChild(s),e}clampPosition(n,i,e){const o=t.UI_EDGE_MARGIN,s=Math.max(o,window.innerWidth-e.offsetWidth-o),r=Math.max(o,window.innerHeight-e.offsetHeight-o)
return{left:Math.min(Math.max(n,o),s),top:Math.min(Math.max(i,o),r)}}applyPosition(t,n,i,e){const o=this.clampPosition(n,i,t)
t.style.left=o.left+"px",t.style.top=o.top+"px",t.style.right="auto",e&&this.storage.savePosition(o)}renderKeywordChips(t,n){t.innerHTML=""
for(const i of this.customKeywords){const e=document.createElement("span")
e.className="lhvj-chip",e.textContent=i
const o=document.createElement("button")
o.type="button",o.className="lhvj-chip-remove",o.textContent="\xd7",o.setAttribute("aria-label","Remove keyword "+i),o.addEventListener("click",()=>{const e=this.customKeywords.filter(t=>t!==i)
this.customKeywords=e,this.onCustomKeywordsChange(e),this.renderKeywordChips(t,n)}),e.appendChild(o),t.appendChild(e)}n&&(n.placeholder="Type keyword and press Enter")}syncKeywordChips(){if(!this.state.keywordChipContainer||!this.state.keywordChipInput)return
const t=Array.from(this.state.keywordChipContainer.querySelectorAll(".lhvj-chip")).map(t=>t.childNodes[0]?.textContent??"")
t.length===this.customKeywords.length&&t.every((t,n)=>t===this.customKeywords[n])||this.renderKeywordChips(this.state.keywordChipContainer,this.state.keywordChipInput)}makeDraggable(t,n){let i=null,e=0,o=0
n.addEventListener("pointerdown",s=>{i=s.pointerId
const r=t.getBoundingClientRect()
e=s.clientX-r.left,o=s.clientY-r.top,this.j=1,t.classList.add("lhvj-dragging"),n.setPointerCapture(i),s.preventDefault()}),n.addEventListener("pointermove",n=>{this.j&&i===n.pointerId&&(this.applyPosition(t,n.clientX-e,n.clientY-o,0),n.preventDefault())})
const s=e=>{if(!this.j||i!==e.pointerId)return
this.j=0,t.classList.remove("lhvj-dragging"),n.hasPointerCapture(i)&&n.releasePointerCapture(i)
const o=t.getBoundingClientRect()
this.applyPosition(t,o.left,o.top,1),i=null,e.preventDefault()}
n.addEventListener("pointerup",s),n.addEventListener("pointercancel",s)}},v=new class i{static PAGINATION_COOLDOWN_CLASS="lhvj-pagination-cooldown"
static COUNT_COOLDOWN_STEP=20
storage
matcher
detection
styleManager
badge
router
showBadge
_
C
I
S
O
$
customKeywords=[]
A=0
detectedCount=0
keywordCount=0
H=0
k=0
R=0
D=Date.now()
L=0
T=0
G=0
M=null
N=0
P=Date.now()
U=0
K=0
constructor(t,n){this.storage=t,this.showBadge=0!=n?.showBadge,this.customKeywords=this.storage.getCustomKeywords(),this.matcher=new c(this.customKeywords),this.detection=new u(this.matcher),this.styleManager=new g,this._=this.storage.getShowHidden(),this.C=this.storage.getScrollGuardEnabled(),this.I=this.storage.getDetectionMode(),this.S=this.storage.getReloadOnNavigation(),this.O=this.storage.getHighlightColors(),this.$=this.storage.getHighlightOpacity(),this.badge=this.showBadge?new b(this.storage,t=>{this._=t,this.storage.setShowHidden(t),t||(this.resetScrollCooldown(),this.resetCountBasedCooldownProgress()),this.scheduleRefresh()},t=>{this.C=t,this.storage.setScrollGuardEnabled(t),t||(this.resetScrollCooldown(),this.resetCountBasedCooldownProgress()),this.scheduleRefresh()},t=>{this.I=t,this.storage.setDetectionMode(t),this.scheduleRefresh()},t=>{this.S=t,this.storage.setReloadOnNavigation(t)},(t,n)=>{this.updateHighlightColor(t,n)},t=>{this.resetHighlightColor(t)},t=>{this.updateHighlightOpacity(t)},()=>{this.resetHighlightOpacity()},t=>{this.updateCustomKeywords(t)}):null,this.router=new p(()=>this.scheduleRefresh(),()=>this.hardRestartRuntimeForPathChange())}init(){this.styleManager.inject(this.getHighlightSettings()),this.startRuntime(),this.router.startObserving()}refreshSettings(){const t=this.I,n=this.C,i=this._
this._=this.storage.getShowHidden(),this.C=this.storage.getScrollGuardEnabled(),this.I=this.storage.getDetectionMode(),this.S=this.storage.getReloadOnNavigation(),this.O=this.storage.getHighlightColors(),this.$=this.storage.getHighlightOpacity(),this.customKeywords=this.storage.getCustomKeywords(),this.matcher.setCustomKeywords(this.customKeywords),this.styleManager.updateHighlightStyles(this.getHighlightSettings()),t!==this.I&&this.clearDetectedVisualState(),(i&&!this._||n&&!this.C)&&(this.resetScrollCooldown(),this.resetCountBasedCooldownProgress()),this.scheduleRefresh()}getStats(){return{A:this.detectedCount,isJobsPage:this.detection.isJobsPage(),cooldownSecondsLeft:this.getCooldownSecondsLeft()}}startRuntime(){this.k||(this.D=Date.now(),this.N=window.scrollY,this.P=Date.now(),this.router.restartDomObserver(),this.scheduleRefresh(),this.router.queueRefresh(120),this.router.queueRefresh(420),window.addEventListener("resize",this.onWindowResize),window.addEventListener("scroll",this.onWindowScroll,{passive:1,capture:1}),window.addEventListener("wheel",this.onWheel,{passive:0,capture:1}),window.addEventListener("mousedown",this.onMouseDown,{capture:1}),window.addEventListener("auxclick",this.onAuxClick,{capture:1}),window.addEventListener("keydown",this.onKeyDown,{passive:0,capture:1}),window.addEventListener("touchstart",this.onTouchStart,{passive:1}),window.addEventListener("touchmove",this.onTouchMove,{passive:0,capture:1}),window.addEventListener("touchend",this.onTouchEnd,{passive:1}),window.addEventListener("touchcancel",this.onTouchEnd,{passive:1}),this.k=1,this.detection.isJobsPage()&&this.router.startRouteRefreshBurst())}hardRestartRuntimeForPathChange(){if(!this._||!this.S)return this.D=Date.now(),this.router.restartDomObserver(),this.scheduleRefresh(),this.router.queueRefresh(120),void this.router.queueRefresh(420)
this.R||(this.R=1,window.location.reload())}scheduleRefresh(){this.H&&cancelAnimationFrame(this.H),this.H=requestAnimationFrame(()=>{this.H=0,this.refresh()})}refresh(){this.syncCooldownState(),this.syncPaginationCooldownClass()
const n=this.A
if(!this.detection.isJobsPage())return this.A=0,this.detectedCount=0,this.keywordCount=0,this.resetScrollCooldown(),this.resetCountBasedCooldownProgress(),void this.badge?.remove()
if(!this._)return this.A=0,this.detectedCount=0,this.keywordCount=0,this.resetScrollCooldown(),this.resetCountBasedCooldownProgress(),this.clearDetectedVisualState(),this.badge?.ensure(this._,this.C,this.I,this.S,this.getHighlightSettings()),void this.badge?.updateCount(0,this._,this.C,this.I,this.S,this.getHighlightSettings(),0,0)
this.isCountCooldownPage()||this.resetCountBasedCooldownProgress()
const i=this.detection.getJobCards()
0===i.length&&t.LAZY_RENDER_TIMEOUT_MS>Date.now()-this.D&&(this.router.queueRefresh(180),this.router.queueRefresh(600))
const e=this.detection.getDetectedCardsFromMarkers(),o=new Map(e)
for(const t of i){const n=this.detection.getDetectedJobState(t)
n&&this.setDetectedState(o,t,n)}const s="hide"===this.I,r=this.detection.refreshDetectedAnchors(s),h=this.detection.refreshDetectedCardsFallback(s),a=new Map(o)
this.mergeDetectedCardStates(a,r.detectedAnchorCards),this.mergeDetectedCardStates(a,h)
const l=new Set(i),d=this.detection.getActiveCards(l),c=new Set
if(this.customKeywords.length>0)for(const t of this.detection.getKeywordCandidateCards())this.matcher.matchCustomKeywordsFromElement(t)&&c.add(t)
for(const t of i){const n=a.get(t)??null,i=c.has(t)
this.detection.applyVisibility(t,(!!n||i)&&s),this.detection.applyDetectedHighlight(t,s?null:n),this.detection.applyActiveHighlight(t,d.has(t)),this.detection.applyKeywordHighlight(t,!s&&i)}a.forEach((t,n)=>{if(l.has(n))return
const i=c.has(n)
this.detection.applyVisibility(n,s),this.detection.applyDetectedHighlight(n,s?null:t),this.detection.applyActiveHighlight(n,d.has(n)),this.detection.applyKeywordHighlight(n,!s&&i)}),c.forEach(t=>{l.has(t)||a.has(t)||(this.detection.applyVisibility(t,s),this.detection.applyActiveHighlight(t,d.has(t)),this.detection.applyKeywordHighlight(t,!s))}),document.querySelectorAll('[data-lhvj-hidden="1"]').forEach(t=>{s&&(a.has(t)||c.has(t))||this.detection.applyVisibility(t,0)}),document.querySelectorAll('[data-lhvj-viewed="1"], [data-lhvj-applied="1"]').forEach(t=>{a.has(t)&&!s||this.detection.applyDetectedHighlight(t,null)}),document.querySelectorAll('[data-lhvj-active="1"]').forEach(t=>{d.has(t)||this.detection.applyActiveHighlight(t,0)}),document.querySelectorAll('[data-lhvj-keyword="1"]').forEach(t=>{!s&&c.has(t)||this.detection.applyKeywordHighlight(t,0)}),this.A=Math.max(a.size,r.detectedAnchorCount),this.keywordCount=c.size
const u=new Set(c)
a.forEach((t,n)=>u.add(n)),this.detectedCount=u.size+Math.max(0,r.detectedAnchorCount-a.size),this.maybeStartCountBasedCooldown(n),this.badge?.ensure(this._,this.C,this.I,this.S,this.getHighlightSettings()),this.badge?.updateCount(this.detectedCount,this._,this.C,this.I,this.S,this.getHighlightSettings(),this.getCooldownSecondsLeft(),this.keywordCount)}updateHighlightColor(t,n){"viewed"===t?this.storage.setViewedHighlightColor(n):"applied"===t?this.storage.setAppliedHighlightColor(n):"active"===t?this.storage.setActiveHighlightColor(n):this.storage.setKeywordHighlightColor(n),this.O=this.storage.getHighlightColors(),this.styleManager.updateHighlightStyles(this.getHighlightSettings()),this.scheduleRefresh()}resetHighlightColor(t){"viewed"===t?this.storage.resetViewedHighlightColor():"applied"===t?this.storage.resetAppliedHighlightColor():"active"===t?this.storage.resetActiveHighlightColor():this.storage.resetKeywordHighlightColor(),this.O=this.storage.getHighlightColors(),this.styleManager.updateHighlightStyles(this.getHighlightSettings()),this.scheduleRefresh()}updateCustomKeywords(t){this.storage.setCustomKeywords(t),this.customKeywords=this.storage.getCustomKeywords(),this.matcher.setCustomKeywords(this.customKeywords),this.scheduleRefresh(),setTimeout(()=>this.scheduleRefresh(),300),setTimeout(()=>this.scheduleRefresh(),1e3)}updateHighlightOpacity(t){this.storage.setHighlightOpacity(t),this.$=this.storage.getHighlightOpacity(),this.styleManager.updateHighlightStyles(this.getHighlightSettings()),this.scheduleRefresh()}resetHighlightOpacity(){this.storage.resetHighlightOpacity(),this.$=this.storage.getHighlightOpacity(),this.styleManager.updateHighlightStyles(this.getHighlightSettings()),this.scheduleRefresh()}getHighlightSettings(){return{colors:this.O,opacity:this.$}}mergeDetectedCardStates(t,n){n.forEach((n,i)=>{this.setDetectedState(t,i,n)})}setDetectedState(t,n,i){const e=t.get(n)
"applied"!==e&&("applied"!==i&&e||t.set(n,i))}onWindowResize=()=>{this.badge?.syncPositionWithinViewport()}
onWheel=t=>{t.deltaY>0&&this.handleScrollGuardInput(t.deltaY,()=>{t.preventDefault(),t.stopPropagation()})&&this.scheduleRefresh()}
onWindowScroll=()=>{const n=Date.now(),i=window.scrollY,e=i-this.N,o=Math.max(1,n-this.P)
if(0>=e)return this.N=i,void(this.P=n)
if(!this.shouldUseScrollGuard())return this.N=i,void(this.P=n)
if(this.syncCooldownState(),this.L&&!this.U){const i=Math.max(14,t.SCROLL_GUARD_ALLOWED_STEP_PX*o/t.SCROLL_GUARD_ALLOWED_STEP_MIN_INTERVAL_MS)
if(e>i){const t=this.N+i
return this.U=1,window.scrollTo({top:t,behavior:"auto"}),this.N=t,this.P=n,window.setTimeout(()=>{this.U=0},0),void this.scheduleRefresh()}}this.N=i,this.P=n}
onMouseDown=t=>{1===t.button&&this.shouldBlockMiddleMouseDuringCooldown()&&(t.preventDefault(),t.stopPropagation())}
onAuxClick=t=>{1===t.button&&this.shouldBlockMiddleMouseDuringCooldown()&&(t.preventDefault(),t.stopPropagation())}
onKeyDown=t=>{if(this.isEditableTarget(t.target))return
const n=t.key
let i=0
if("ArrowDown"===n)i=96
else if("PageDown"===n)i=Math.max(.85*window.innerHeight,280)
else if(" "===n){if(t.shiftKey)return
i=Math.max(.85*window.innerHeight,280)}i>0&&this.handleScrollGuardInput(i,()=>{t.preventDefault(),t.stopPropagation()})&&this.scheduleRefresh()}
onTouchStart=t=>{0!==t.touches.length&&(this.M=t.touches[0].clientY)}
onTouchMove=t=>{if(0===t.touches.length||null===this.M)return
const n=t.touches[0].clientY,i=this.M-n
this.M=n,i>0&&this.handleScrollGuardInput(i,()=>{t.preventDefault(),t.stopPropagation()})&&this.scheduleRefresh()}
onTouchEnd=()=>{this.M=null}
handleScrollGuardInput(t,n){return this.shouldUseScrollGuard()?(this.syncCooldownState(),this.L?(this.shouldLockScroll()&&n(),this.applyControlledScroll(t),1):0):0}shouldUseScrollGuard(){return this.C&&this._?this.detection.isJobsPage():0}shouldLockScroll(){return this.L&&this.isJobsHomepage()}shouldBlockMiddleMouseDuringCooldown(){return this.shouldLockScroll()}shouldUseCountBasedCooldown(){return this.C&&this._?this.isCountCooldownPage():0}maybeStartCountBasedCooldown(t){if(!this.shouldUseCountBasedCooldown())return
if(this.A>t&&(this.K+=this.A-t),i.COUNT_COOLDOWN_STEP>this.K)return
const n=Math.floor(this.K/i.COUNT_COOLDOWN_STEP)
this.K-=n*i.COUNT_COOLDOWN_STEP
for(let i=0;n>i;i++)this.startRandomCooldown()}resetCountBasedCooldownProgress(){this.K=0}isJobsHomepage(){const t=location.pathname
return"/jobs"===t||"/jobs/"===t}isCollectionsPage(){return location.pathname.startsWith("/jobs/collections")}isCountCooldownPage(){return this.isJobsHomepage()||this.isCollectionsPage()}startRandomCooldown(){const n=t.SCROLL_GUARD_COOLDOWN_MIN_MS,i=t.SCROLL_GUARD_COOLDOWN_MAX_MS,e=n+Math.floor(Math.random()*(i-n+1))
this.L?this.T+=e:(this.L=1,this.T=Date.now()+e,this.G=0,this.syncPaginationCooldownClass()),window.setTimeout(()=>{this.L&&(this.syncCooldownState(),this.scheduleRefresh())},Math.max(0,this.T-Date.now())+20)}applyControlledScroll(n){const i=Date.now()
if(t.SCROLL_GUARD_ALLOWED_STEP_MIN_INTERVAL_MS>i-this.G)return
const e=Math.min(Math.max(n,0),t.SCROLL_GUARD_ALLOWED_STEP_PX)
e>0&&(window.scrollBy({top:e,behavior:"auto"}),this.G=i)}syncCooldownState(){this.L&&(this.T>Date.now()||this.resetScrollCooldown())}resetScrollCooldown(){this.L=0,this.T=0,this.G=0,this.U=0,this.syncPaginationCooldownClass()}getCooldownSecondsLeft(){if(!this.L)return 0
const t=this.T-Date.now()
return t>0?Math.ceil(t/1e3):0}isEditableTarget(t){return t instanceof HTMLElement?t.isContentEditable?1:!!t.closest('input, textarea, select, [contenteditable="true"], [role="textbox"]'):0}clearDetectedVisualState(){const{HIDDEN_CLASS:t}=n
document.querySelectorAll('[data-lhvj-hidden="1"]').forEach(t=>{this.detection.applyVisibility(t,0)}),document.querySelectorAll('[data-lhvj-viewed="1"]').forEach(t=>{this.detection.applyDetectedHighlight(t,null)}),document.querySelectorAll('[data-lhvj-applied="1"]').forEach(t=>{this.detection.applyDetectedHighlight(t,null)}),document.querySelectorAll('[data-lhvj-active="1"]').forEach(t=>{this.detection.applyActiveHighlight(t,0)}),document.querySelectorAll('[data-lhvj-keyword="1"]').forEach(t=>{this.detection.applyKeywordHighlight(t,0)}),document.querySelectorAll('a[data-lhvj-hidden-anchor="1"]').forEach(n=>{n.classList.remove(t),n.removeAttribute("data-lhvj-hidden-anchor")})}syncPaginationCooldownClass(){const t=document.documentElement
if(!t)return
const n=this.L&&this.detection.isJobsPage()
t.classList.toggle(i.PAGINATION_COOLDOWN_CLASS,n)}}(new class{getItem(t){try{return window.localStorage.getItem(t)}catch{return null}}setItem(t,n){try{window.localStorage.setItem(t,n)}catch{}}getShowHidden(){return"1"===this.getItem(n.STORAGE_KEY)}setShowHidden(t){this.setItem(n.STORAGE_KEY,t?"1":"0")}getScrollGuardEnabled(){const i=this.getItem(n.SCROLL_GUARD_STORAGE_KEY)
return"0"===i?0:"1"===i?1:t.SCROLL_GUARD_ENABLED_DEFAULT}setScrollGuardEnabled(t){this.setItem(n.SCROLL_GUARD_STORAGE_KEY,t?"1":"0")}getDetectionMode(){return"highlight"===this.getItem(n.DETECTION_MODE_STORAGE_KEY)?"highlight":"hide"}setDetectionMode(t){this.setItem(n.DETECTION_MODE_STORAGE_KEY,t)}getReloadOnNavigation(){return"1"===this.getItem(n.RELOAD_ON_NAVIGATION_STORAGE_KEY)}setReloadOnNavigation(t){this.setItem(n.RELOAD_ON_NAVIGATION_STORAGE_KEY,t?"1":"0")}getHighlightColors(){return{viewed:this.getHighlightColor(n.VIEWED_HIGHLIGHT_COLOR_STORAGE_KEY,t.VIEWED_HIGHLIGHT_COLOR),applied:this.getHighlightColor(n.APPLIED_HIGHLIGHT_COLOR_STORAGE_KEY,t.APPLIED_HIGHLIGHT_COLOR),active:this.getHighlightColor(n.ACTIVE_HIGHLIGHT_COLOR_STORAGE_KEY,t.ACTIVE_HIGHLIGHT_COLOR),keyword:this.getHighlightColor(n.KEYWORD_HIGHLIGHT_COLOR_STORAGE_KEY,t.KEYWORD_HIGHLIGHT_COLOR)}}setViewedHighlightColor(i){this.setItem(n.VIEWED_HIGHLIGHT_COLOR_STORAGE_KEY,this.normalizeHighlightColor(i,t.VIEWED_HIGHLIGHT_COLOR))}setAppliedHighlightColor(i){this.setItem(n.APPLIED_HIGHLIGHT_COLOR_STORAGE_KEY,this.normalizeHighlightColor(i,t.APPLIED_HIGHLIGHT_COLOR))}setActiveHighlightColor(i){this.setItem(n.ACTIVE_HIGHLIGHT_COLOR_STORAGE_KEY,this.normalizeHighlightColor(i,t.ACTIVE_HIGHLIGHT_COLOR))}setKeywordHighlightColor(i){this.setItem(n.KEYWORD_HIGHLIGHT_COLOR_STORAGE_KEY,this.normalizeHighlightColor(i,t.KEYWORD_HIGHLIGHT_COLOR))}resetViewedHighlightColor(){this.setViewedHighlightColor(t.VIEWED_HIGHLIGHT_COLOR)}resetAppliedHighlightColor(){this.setAppliedHighlightColor(t.APPLIED_HIGHLIGHT_COLOR)}resetActiveHighlightColor(){this.setActiveHighlightColor(t.ACTIVE_HIGHLIGHT_COLOR)}resetKeywordHighlightColor(){this.setKeywordHighlightColor(t.KEYWORD_HIGHLIGHT_COLOR)}getCustomKeywords(){try{const t=this.getItem(n.CUSTOM_KEYWORDS_STORAGE_KEY)
if(!t)return[]
const i=JSON.parse(t)
return Array.isArray(i)?this.normalizeKeywords(i.filter(t=>"string"==typeof t)):[]}catch{return[]}}setCustomKeywords(t){const i=this.normalizeKeywords(t)
this.setItem(n.CUSTOM_KEYWORDS_STORAGE_KEY,JSON.stringify(i))}normalizeKeywords(t){return Array.from(new Set(t.map(t=>t.trim().toLowerCase()).filter(t=>t.length>0)))}getHighlightOpacity(){const i=this.getItem(n.HIGHLIGHT_OPACITY_STORAGE_KEY)
return this.normalizeHighlightOpacity(i,t.HIGHLIGHT_OPACITY)}setHighlightOpacity(i){const e=this.normalizeHighlightOpacity(i+"",t.HIGHLIGHT_OPACITY)
this.setItem(n.HIGHLIGHT_OPACITY_STORAGE_KEY,e.toFixed(2))}resetHighlightOpacity(){this.setHighlightOpacity(t.HIGHLIGHT_OPACITY)}getSavedPosition(){try{const t=this.getItem(n.UI_POSITION_KEY)
if(!t)return null
const i=JSON.parse(t)
return i&&"number"==typeof i.left&&"number"==typeof i.top&&Number.isFinite(i.left)&&Number.isFinite(i.top)?{left:i.left,top:i.top}:null}catch{return null}}savePosition(t){this.setItem(n.UI_POSITION_KEY,JSON.stringify(t))}getHighlightColor(t,n){const i=this.getItem(t)
return this.normalizeHighlightColor(i,n)}normalizeHighlightColor(t,n){return t&&/^#[0-9a-fA-F]{6}$/.test(t)?t.toLowerCase():n}normalizeHighlightOpacity(n,i){if(!n)return i
const e=+n
return Number.isFinite(e)?Math.min(t.HIGHLIGHT_OPACITY_MAX,Math.max(t.HIGHLIGHT_OPACITY_MIN,e)):i}}),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>v.init(),{once:1}):v.init()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment