From b85fab79e40b0f5e6c7e137d994e203f425eab8f Mon Sep 17 00:00:00 2001 From: Sunny Patel Date: Thu, 5 Mar 2026 21:34:24 -0500 Subject: [PATCH 1/2] feat(share): added LinkedIn share badge with SVG certificate and social sharing - shareable SVG badge with score, name, and branding - SVG to PNG download via canvas API - LinkedIn share post and add-to-profile via URL schemes - copy-to-clipboard for pre-drafted LinkedIn post text --- .../components/scoring/ScoreDashboard.svelte | 62 +- src/lib/components/scoring/ShareBadge.svelte | 695 ++++++++++++++++++ 2 files changed, 740 insertions(+), 17 deletions(-) create mode 100644 src/lib/components/scoring/ShareBadge.svelte diff --git a/src/lib/components/scoring/ScoreDashboard.svelte b/src/lib/components/scoring/ScoreDashboard.svelte index 5407707..cdaa6a1 100644 --- a/src/lib/components/scoring/ScoreDashboard.svelte +++ b/src/lib/components/scoring/ScoreDashboard.svelte @@ -5,6 +5,7 @@ import KeywordAnalysis from './KeywordAnalysis.svelte'; import WeakestAreas from './WeakestAreas.svelte'; import ResumeStats from './ResumeStats.svelte'; + import ShareBadge from './ShareBadge.svelte'; import type { Suggestion, StructuredSuggestion } from '$engine/scorer/types'; // derived stats for the summary card header @@ -14,6 +15,7 @@ // toggle between grid cards and detailed breakdown view let activeView = $state<'cards' | 'detailed'>('cards'); + let showShareBadge = $state(false); // collapsible suggestion cards let expandedSuggestion = $state(null); @@ -281,21 +283,40 @@ - +
+ + +
@@ -423,6 +444,8 @@ {/if} + + From 737edbf70a7ad2d4b91b59a87d2dc53914829ee0 Mon Sep 17 00:00:00 2001 From: Sunny Patel Date: Thu, 5 Mar 2026 22:05:05 -0500 Subject: [PATCH 2/2] fix(share): improved LinkedIn badge UX and removed JSON export - redesigned badge SVG with gradient border frame, verification seal watermark, and tighter proportions (520x720) - LinkedIn share now pre-populates post text with all 6 ATS platforms and hashtags via feed intent URL - LinkedIn profile link (linkedin.com/in/sunny-patel-30b460204) included for attribution - download filename: "Name - ATS Screener Badge - Month Day Year.png" (cross-platform safe) - removed copy share text button and JSON export button from dashboard - removed unused copied state, copyShareText function, exportResults function --- .../components/scoring/ScoreDashboard.svelte | 34 --- src/lib/components/scoring/ShareBadge.svelte | 287 ++++++++---------- 2 files changed, 124 insertions(+), 197 deletions(-) diff --git a/src/lib/components/scoring/ScoreDashboard.svelte b/src/lib/components/scoring/ScoreDashboard.svelte index cdaa6a1..6504de5 100644 --- a/src/lib/components/scoring/ScoreDashboard.svelte +++ b/src/lib/components/scoring/ScoreDashboard.svelte @@ -51,25 +51,6 @@ return suggestions.slice(0, 5); }); - // exports results as a JSON file download - function exportResults() { - const data = { - exportedAt: new Date().toISOString(), - mode: scoresStore.mode, - averageScore: avgScore, - passingCount: passCount, - totalSystems: totalCount, - results: scoresStore.results - }; - const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `ats-scores-${new Date().toISOString().slice(0, 10)}.json`; - a.click(); - URL.revokeObjectURL(url); - } - // color based on average score function getAvgColor(score: number): string { if (score >= 80) return '#22c55e'; @@ -301,21 +282,6 @@ Share - diff --git a/src/lib/components/scoring/ShareBadge.svelte b/src/lib/components/scoring/ShareBadge.svelte index d0b9b32..db9a4a9 100644 --- a/src/lib/components/scoring/ShareBadge.svelte +++ b/src/lib/components/scoring/ShareBadge.svelte @@ -4,7 +4,6 @@ let { open = $bindable(false) }: { open: boolean } = $props(); - let copied = $state(false); let badgeSvgEl: SVGSVGElement | undefined = $state(); const avgScore = $derived(scoresStore.averageScore); @@ -40,12 +39,11 @@ const dashOffset = $derived(circumference - (avgScore / 100) * circumference); const shareText = $derived( - `Just scored ${avgScore}/100 on ATS Screener, a free open-source tool that simulates how real ATS platforms like Workday, Taleo, and Greenhouse parse your resume.\n\n${passCount}/${totalCount} systems passed. Check yours at ats-screener.vercel.app` + `Just scored ${avgScore}/100 on ATS Screener, a free open-source resume tool by linkedin.com/in/sunny-patel-30b460204 that simulates how real ATS platforms (Workday, Taleo, iCIMS, Greenhouse, Lever, and SuccessFactors) parse your resume.\n\n${passCount}/${totalCount} systems passed. Try it free at ats-screener.vercel.app\n\n#ATSScreener #Resume #JobSearch #OpenSource #CareerTips` ); function close() { open = false; - copied = false; } function handleBackdropClick(e: MouseEvent) { @@ -80,7 +78,9 @@ if (!blob) return; const a = document.createElement('a'); a.href = URL.createObjectURL(blob); - a.download = `ats-badge-${avgScore}-${new Date().toISOString().slice(0, 10)}.png`; + const d = new Date(); + const dateStr = d.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }).replace(',', ''); + a.download = `${displayName} - ATS Screener Badge - ${dateStr}.png`; a.click(); URL.revokeObjectURL(a.href); }, 'image/png'); @@ -91,7 +91,7 @@ } function shareToLinkedIn() { - const url = `https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent('https://ats-screener.vercel.app')}`; + const url = `https://www.linkedin.com/feed/?shareActive=true&text=${encodeURIComponent(shareText)}`; window.open(url, '_blank', 'noopener,noreferrer'); } @@ -103,25 +103,6 @@ window.open(url, '_blank', 'noopener,noreferrer'); } - async function copyShareText() { - try { - await navigator.clipboard.writeText(shareText); - copied = true; - setTimeout(() => (copied = false), 2000); - } catch { - // fallback - const ta = document.createElement('textarea'); - ta.value = shareText; - ta.style.position = 'fixed'; - ta.style.opacity = '0'; - document.body.appendChild(ta); - ta.select(); - document.execCommand('copy'); - document.body.removeChild(ta); - copied = true; - setTimeout(() => (copied = false), 2000); - } - } {#if open} @@ -144,29 +125,29 @@ - - - - + + + + - - - + + + + + + - - - @@ -174,42 +155,51 @@ + + + + + + + - + - - + + + + + - - {#each Array(20) as _, i} - - + + {#each Array(18) as _, i} + + {/each} - + - - - + + + - + - + ATS SCREENER - - + + + + + + + + {#each Array(12) as _, i} + {@const angle = (i * 30 * Math.PI) / 180} + + {/each} + VERIFIED + - - - + + + + @@ -254,7 +263,7 @@ r="58" fill="none" stroke={scoreColor} - stroke-width="8" + stroke-width="10" stroke-dasharray={circumference} stroke-dashoffset={dashOffset} stroke-linecap="round" @@ -267,7 +276,7 @@ x="0" y="-8" font-family="system-ui, -apple-system, sans-serif" - font-size="56" + font-size="52" font-weight="800" fill={scoreColor} text-anchor="middle" @@ -277,9 +286,9 @@ {scoreLabel.toUpperCase()} - - + + - + - - - + + CANDIDATE - - - - MODE - {modeLabel} + + + MODE + {modeLabel} - - - SCAN DATE - {scanDate} + + + SCAN DATE + {scanDate} + + + - + {#each scoresStore.results as result, i} {@const col = i % 3} {@const row = Math.floor(i / 3)} - {@const x = (col - 1) * 160} - {@const y = row * 36} + {@const x = (col - 1) * 148} + {@const y = row * 34} {result.system} - - + + + ats-screener.vercel.app Not an official ATS certification @@ -480,20 +459,6 @@ Add to LinkedIn Profile - @@ -656,10 +621,6 @@ border-color: rgba(255, 255, 255, 0.15); } - .copied-text { - color: #22c55e; - } - /* scrollbar styling for the dialog */ .badge-dialog::-webkit-scrollbar { width: 4px;