spectreworks / script.js
00Boobs00's picture
Substitute all simulated and mock data with validated, production-grade data essential for professional deployment within advanced AI-native and AI-powered tools.
fb72993 verified
document.addEventListener('DOMContentLoaded', () => {
// Production-Grade OSINT Data Stream
const osintStream = document.getElementById('osint-stream');
let osintDataCache = [];
// Real-time timestamp formatter
function formatTime(date) {
const now = new Date();
const diff = Math.floor((now - date) / 1000);
if (diff < 60) return 'Just now';
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
return date.toLocaleDateString();
}
// Fetch NIST NVD CVE Data (Production Vulnerability Feed)
async function fetchNVDCVEs() {
try {
const response = await fetch('https://services.nvd.nist.gov/rest/json/cves/2.0?resultsPerPage=8&cvssV3Severity=CRITICAL,HIGH');
const data = await response.json();
return data.vulnerabilities.map(vuln => {
const cve = vuln.cve;
const metrics = cve.metrics?.cvssMetricV31?.[0]?.cvssData || {};
const severity = metrics.baseScore || 0;
const vector = metrics.attackVector || 'N/A';
const description = cve.descriptions?.[0]?.value || 'No description available';
const publishedDate = new Date(cve.published);
return {
type: 'VULNERABILITY',
platform: 'NIST NVD',
msg: `${cve.id}: ${description.substring(0, 70)}... [AV:${vector}]`,
time: formatTime(publishedDate),
severity: severity,
source: 'NVD',
cveId: cve.id
};
});
} catch (error) {
console.error('NVD API Error:', error);
return [];
}
}
// Fetch CISA Known Exploited Vulnerabilities
async function fetchCISAVulns() {
try {
const response = await fetch('https://www.cisa.gov/known-exploited-vulnerabilities-catalog.json');
const data = await response.json();
const today = new Date();
return data.vulnerabilities.slice(0, 5).map(vuln => {
const dueDate = new Date(vuln.dueDate);
const isUrgent = dueDate < today;
return {
type: 'ACTIVE_EXPLOIT',
platform: 'CISA KEV',
msg: `${vuln.cveID} - ${vuln.vendorProject}: ${vuln.vulnerabilityName.substring(0, 50)}...`,
time: isUrgent ? `DUE: ${vuln.dueDate}` : `Due: ${vuln.dueDate}`,
severity: 'CRITICAL',
source: 'CISA',
isUrgent: isUrgent
};
});
} catch (error) {
console.error('CISA API Error:', error);
return [];
}
}
// Fetch Hacker News Security Stories
async function fetchHackerNews() {
try {
const response = await fetch('https://hacker-news.firebaseio.com/v0/newstories.json?print=pretty');
const storyIds = await response.json();
const stories = await Promise.all(
storyIds.slice(0, 10).map(async id => {
const storyResponse = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json?print=pretty`);
return storyResponse.json();
})
);
const securityKeywords = ['security', 'vulnerability', 'hack', 'cyber', 'exploit', 'malware', 'breach', 'attack', 'zero-day', '0day', 'ransomware', 'phishing'];
return stories
.filter(story => story.title && securityKeywords.some(keyword =>
story.title.toLowerCase().includes(keyword)))
.slice(0, 4)
.map(story => ({
type: 'INTELLIGENCE',
platform: 'Hacker News',
msg: story.title,
time: formatTime(new Date(story.time * 1000)),
url: story.url || `https://news.ycombinator.com/item?id=${story.id}`,
source: 'HN'
}));
} catch (error) {
console.error('Hacker News API Error:', error);
return [];
}
}
// Fetch GitHub Trending Security Repositories
async function fetchGitHubSecurity() {
try {
const query = 'topic:security topic:cybersecurity stars:>500 pushed:>2024-01-01';
const response = await fetch(`https://api.github.com/search/repositories?q=${encodeURIComponent(query)}&sort=updated&order=desc&per_page=4`);
const data = await response.json();
return data.items.map(repo => {
const lastPush = new Date(repo.pushed_at);
return {
type: 'TOOL_DISCOVERY',
platform: 'GitHub',
msg: `${repo.name}: ${repo.description?.substring(0, 55) || 'No description'}`,
time: `${repo.stargazers_count.toLocaleString()} ★ • Updated ${formatTime(lastPush)}`,
source: 'GH',
url: repo.html_url
};
});
} catch (error) {
console.error('GitHub API Error:', error);
return [];
}
}
// Get Real Browser/System Information
function getSystemInfo() {
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
const connectionType = connection ? `${connection.effectiveType.toUpperCase()}${connection.downlink}Mbps` : 'Unknown';
const cores = navigator.hardwareConcurrency || 'Unknown';
const memory = navigator.deviceMemory ? `${navigator.deviceMemory}GB` : 'Unknown';
const doNotTrack = navigator.doNotTrack === '1' ? 'Enabled' : 'Disabled';
return {
type: 'SYSTEM',
platform: 'Browser Fingerprint',
msg: `Cores: ${cores} | RAM: ${memory} | DNT: ${doNotTrack} | Network: ${connectionType}`,
time: 'Live',
source: 'LOCAL'
};
}
// Fetch IP Geolocation and Threat Data
async function fetchIPInfo() {
try {
const response = await fetch('https://ipapi.co/json/');
const data = await response.json();
// Check for VPN/Proxy indicators
const indicators = [];
if (data.privacy?.vpn) indicators.push('VPN');
if (data.privacy?.proxy) indicators.push('Proxy');
if (data.privacy?.tor) indicators.push('TOR');
if (data.privacy?.hosting) indicators.push('Hosting');
const threatLevel = indicators.length > 0 ? 'ELEVATED' : 'NORMAL';
const indicatorText = indicators.length > 0 ? ` [${indicators.join(' + ')}]` : '';
return {
type: 'NETWORK',
platform: 'IP Intelligence',
msg: `IP: ${data.ip}${data.city}, ${data.country_name} • ASN: ${data.asn} • ISP: ${data.org}${indicatorText}`,
time: 'Live',
source: 'IPAPI',
threatLevel: threatLevel
};
} catch (error) {
console.error('IP API Error:', error);
return {
type: 'NETWORK',
platform: 'IP Intelligence',
msg: 'Unable to retrieve IP information - Check network connectivity',
time: 'Error',
source: 'IPAPI'
};
}
}
// Fetch URLhaus Malware URLs (Public API)
async function fetchURLhaus() {
try {
const response = await fetch('https://urlhaus-api.abuse.ch/api/v1/urls/recent/limit/3/');
const data = await response.json();
if (data.query_status !== 'ok') return [];
return data.urls.map(urlData => ({
type: 'MALWARE',
platform: 'URLhaus',
msg: `Malware URL detected - Threat: ${urlData.threat}${urlData.url.substring(0, 50)}...`,
time: new Date(urlData.date_added * 1000).toLocaleDateString(),
source: 'URLHAUS',
url: urlData.url
}));
} catch (error) {
console.error('URLhaus API Error:', error);
return [];
}
}
// Fetch MalwareBazaar Samples
async function fetchMalwareBazaar() {
try {
const formData = new FormData();
formData.append('query', 'get_recent');
formData.append('limit', '3');
const response = await fetch('https://mb-api.abuse.ch/api/v1/', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.query_status !== 'ok') return [];
return data.data.map(sample => ({
type: 'MALWARE_SAMPLE',
platform: 'MalwareBazaar',
msg: `${sample.malware_family || 'Unknown'}${sample.sha256_hash.substring(0, 20)}... • ${sample.file_name || 'Unnamed'}`,
time: new Date(sample.first_seen).toLocaleDateString(),
source: 'MALWAREBAZAAR'
}));
} catch (error) {
console.error('MalwareBazaar API Error:', error);
return [];
}
}
// Fetch Crypto Market Data (CoinGecko - Free Public API)
async function fetchCryptoData() {
try {
const response = await fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=3&page=1&sparkline=false');
const data = await response.json();
return data.map(coin => ({
type: 'FINANCIAL',
platform: 'CoinGecko',
msg: `${coin.name} (${coin.symbol.toUpperCase()}) • ${coin.current_price.toLocaleString()}${coin.price_change_percentage_24h >= 0 ? '+' : ''}${coin.price_change_percentage_24h.toFixed(2)}% (24h)`,
time: `MCap: ${(coin.market_cap / 1000000000).toFixed(1)}B`,
source: 'COINGECKO',
url: `https://www.coingecko.com/en/coins/${coin.id}`
}));
} catch (error) {
console.error('CoinGecko API Error:', error);
return [];
}
}
// Fetch Earthquake Data (USGS - Real-time monitoring)
async function fetchEarthquakeData() {
try {
const response = await fetch('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson');
const data = await response.json();
return data.features.slice(0, 3).map(quake => ({
type: 'GEOPHYSICAL',
platform: 'USGS',
msg: `M${quake.properties.mag.toFixed(1)} earthquake • ${quake.properties.place} • Depth: ${quake.geometry.coordinates[2]}km`,
time: new Date(quake.properties.time).toLocaleString(),
source: 'USGS',
url: quake.properties.url
}));
} catch (error) {
console.error('USGS API Error:', error);
return [];
}
}
// Fetch Space Weather (NOAA)
async function fetchSpaceWeather() {
try {
const response = await fetch('https://services.swpc.noaa.gov/json/goes/primary/xrays-6-hour.json');
const data = await response.json();
if (!data || data.length === 0) return [];
const latest = data[data.length - 1];
const flux = latest.flux;
const classification = flux < 1.0e-8 ? 'A' : flux < 1.0e-7 ? 'B' : flux < 1.0e-6 ? 'C' : flux < 1.0e-5 ? 'M' : 'X';
return [{
type: 'SPACE_WEATHER',
platform: 'NOAA SWPC',
msg: `Solar X-Ray Flux: ${flux.toExponential(2)} W/m² • Class: ${classification}`,
time: new Date(latest.time).toLocaleString(),
source: 'NOAA'
}];
} catch (error) {
console.error('NOAA API Error:', error);
return [];
}
}
function createOSINTItem(data) {
const div = document.createElement('div');
let colorClass = 'border-gray-700';
let icon = 'info';
let textColor = 'text-gray-300';
// Enhanced color coding based on data type
switch(data.type) {
case 'ACTIVE_EXPLOIT':
colorClass = 'border-red-500/70 bg-red-900/20';
icon = 'alert-octagon';
textColor = 'text-red-400';
break;
case 'MALWARE':
case 'MALWARE_SAMPLE':
colorClass = 'border-red-600/50 bg-red-900/15';
icon = 'virus';
textColor = 'text-red-300';
break;
case 'VULNERABILITY':
colorClass = 'border-secondary/50 bg-secondary/5';
icon = 'alert-triangle';
textColor = data.severity >= 9.0 ? 'text-red-400' : data.severity >= 7.0 ? 'text-orange-400' : 'text-secondary';
break;
case 'SYSTEM':
colorClass = 'border-primary/50 bg-primary/5';
icon = 'server';
textColor = 'text-primary';
break;
case 'NETWORK':
colorClass = data.threatLevel === 'ELEVATED' ? 'border-yellow-500/50 bg-yellow-900/20' : 'border-cyan-500/50 bg-cyan-900/20';
icon = data.threatLevel === 'ELEVATED' ? 'shield-alert' : 'globe';
textColor = data.threatLevel === 'ELEVATED' ? 'text-yellow-400' : 'text-cyan-400';
break;
case 'INTELLIGENCE':
colorClass = 'border-blue-500/50 bg-blue-500/5';
icon = 'target';
textColor = 'text-blue-400';
break;
case 'TOOL_DISCOVERY':
colorClass = 'border-purple-500/50 bg-purple-900/20';
icon = 'package';
textColor = 'text-purple-400';
break;
case 'FINANCIAL':
colorClass = 'border-green-500/50 bg-green-900/20';
icon = 'dollar-sign';
textColor = 'text-green-400';
break;
case 'GEOPHYSICAL':
colorClass = 'border-amber-500/50 bg-amber-900/20';
icon = 'activity';
textColor = 'text-amber-400';
break;
case 'SPACE_WEATHER':
colorClass = 'border-indigo-500/50 bg-indigo-900/20';
icon = 'sun';
textColor = 'text-indigo-400';
break;
default:
colorClass = 'border-gray-700 bg-gray-800/50';
icon = 'info';
textColor = 'text-gray-400';
}
div.className = `p-3 rounded border ${colorClass} text-sm animate-fade-in flex items-start gap-3 hover:bg-opacity-10 transition-all cursor-pointer`;
const linkAttr = data.url ? `href="${data.url}" target="_blank" rel="noopener noreferrer"` : '';
const linkWrapper = data.url ? 'a' : 'div';
const sourceBadgeClass = {
'CISA': 'bg-red-900/60 text-red-300 border border-red-700/50',
'URLHAUS': 'bg-red-900/40 text-red-300 border border-red-800/50',
'MALWAREBAZAAR': 'bg-red-950/60 text-red-400 border border-red-900/50',
'NVD': 'bg-orange-900/40 text-orange-300 border border-orange-800/50',
'HN': 'bg-orange-600/20 text-orange-300 border border-orange-700/50',
'GH': 'bg-purple-900/40 text-purple-300 border border-purple-800/50',
'IPAPI': 'bg-cyan-900/40 text-cyan-300 border border-cyan-800/50',
'COINGECKO': 'bg-green-900/40 text-green-300 border border-green-800/50',
'USGS': 'bg-amber-900/40 text-amber-300 border border-amber-800/50',
'NOAA': 'bg-indigo-900/40 text-indigo-300 border border-indigo-800/50',
'LOCAL': 'bg-gray-700/50 text-gray-400 border border-gray-600/50'
}[data.source] || 'bg-gray-700/50 text-gray-400 border border-gray-600/50';
div.innerHTML = `
<${linkWrapper} ${linkAttr} class="flex-1 flex items-start gap-3 text-decoration-none">
<i data-feather="${icon}" class="w-4 h-4 mt-0.5 ${textColor} flex-shrink-0"></i>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-center mb-1 flex-wrap gap-2">
<span class="font-bold ${textColor} text-xs tracking-wider">${data.type.replace(/_/g, ' ')}</span>
<span class="text-[10px] px-2 py-0.5 rounded ${sourceBadgeClass}">${data.source}</span>
</div>
<p class="text-gray-300 text-xs leading-relaxed break-words">
<span class="text-gray-500 font-semibold text-[10px] uppercase tracking-wider">[${data.platform}]</span> ${data.msg}
</p>
<div class="mt-1 flex items-center gap-2">
<span class="text-[10px] text-gray-500 font-mono-tech">${data.time}</span>
${data.isUrgent ? '<span class="text-[10px] px-1.5 py-0.5 rounded bg-red-600 text-white animate-pulse font-bold">URGENT</span>' : ''}
${data.cveId ? `<span class="text-[10px] px-1.5 py-0.5 rounded bg-slate-700 text-gray-300 font-mono">${data.cveId}</span>` : ''}
</div>
</div>
</${linkWrapper}>
`;
return div;
}
// Load all real data sources
async function loadRealData() {
osintStream.innerHTML = '<div class="text-center py-8 text-gray-500"><i data-feather="loader" class="animate-spin inline-block"></i> Loading real-time intelligence...</div>';
feather.replace();
const [cves, cisa, hn, github, system, ip, urlhaus, malware, crypto, quakes, space] = await Promise.allSettled([
fetchNVDCVEs(),
fetchCISAVulns(),
fetchHackerNews(),
fetchGitHubSecurity(),
Promise.resolve(getSystemInfo()),
fetchIPInfo(),
fetchURLhaus(),
fetchMalwareBazaar(),
fetchCryptoData(),
fetchEarthquakeData(),
fetchSpaceWeather()
]);
osintStream.innerHTML = '';
const allData = [
...(cves.status === 'fulfilled' ? cves.value : []),
...(cisa.status === 'fulfilled' ? cisa.value : []),
...(hn.status === 'fulfilled' ? hn.value : []),
...(github.status === 'fulfilled' ? github.value : []),
system,
...(ip.status === 'fulfilled' ? [ip.value] : []),
...(urlhaus.status === 'fulfilled' ? urlhaus.value : []),
...(malware.status === 'fulfilled' ? malware.value : []),
...(crypto.status === 'fulfilled' ? crypto.value : []),
...(quakes.status === 'fulfilled' ? quakes.value : []),
...(space.status === 'fulfilled' ? space.value : [])
];
osintDataCache = allData;
// Sort by priority: Active exploits > Malware > Critical CVEs > Intelligence > Other
const priorityOrder = {
'ACTIVE_EXPLOIT': 1,
'MALWARE': 2,
'MALWARE_SAMPLE': 3,
'VULNERABILITY': 4,
'INTELLIGENCE': 5,
'THREAT_INTEL': 6,
'TOOL_DISCOVERY': 7,
'NETWORK': 8,
'SYSTEM': 9,
'FINANCIAL': 10,
'GEOPHYSICAL': 11,
'SPACE_WEATHER': 12
};
allData.sort((a, b) => {
const priorityA = priorityOrder[a.type] || 99;
const priorityB = priorityOrder[b.type] || 99;
if (priorityA !== priorityB) return priorityA - priorityB;
return 0;
});
allData.forEach(item => {
osintStream.appendChild(createOSINTItem(item));
});
feather.replace();
}
// Auto-refresh data every 3 minutes
loadRealData();
setInterval(loadRealData, 180000);
// Add simple fade-in animation style dynamically
const style = document.createElement('style');
style.innerHTML = `
@keyframes fadeIn {
from { opacity: 0; transform: translateY(5px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in {
animation: fadeIn 0.5s ease-out forwards;
}
`;
document.head.appendChild(style);
// Expose update function globally
window.refreshOSINT = loadRealData;
// Android Security Intelligence Feed
async function loadAndroidSecurity() {
const androidFeed = document.getElementById('android-security-feed');
if (!androidFeed) return;
try {
// Fetch real Android security tools from GitHub
const githubResponse = await fetch('https://api.github.com/search/repositories?q=topic:android+topic:security+topic:malware+stars:>100&sort=updated&order=desc&per_page=5');
const githubData = await githubResponse.json();
// Fetch Android security patches bulletin (MITRE CVE database)
const nvdResponse = await fetch('https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=android&resultsPerPage=3');
const nvdData = await nvdResponse.json();
const androidCVEs = nvdData.vulnerabilities?.map(vuln => ({
id: vuln.cve.id,
score: vuln.cve.metrics?.cvssMetricV31?.[0]?.cvssData?.baseScore || 0,
desc: vuln.cve.descriptions?.[0]?.value?.substring(0, 50) || 'No description'
})) || [];
androidFeed.innerHTML = githubData.items.map(repo => {
const lastUpdated = new Date(repo.pushed_at);
const daysSinceUpdate = Math.floor((new Date() - lastUpdated) / (1000 * 60 * 60 * 24));
const isRecent = daysSinceUpdate < 30;
// Find related CVE if any
const relatedCVE = androidCVEs.find(cve =>
repo.name.toLowerCase().includes('android') ||
repo.description?.toLowerCase().includes(cve.id.toLowerCase())
);
return `
<div class="flex items-center gap-3 p-3 bg-darker rounded-lg border ${isRecent ? 'border-green-900/50' : 'border-gray-800'} hover:border-gray-600 transition-all group">
<div class="w-10 h-10 ${repo.language === 'Kotlin' ? 'bg-purple-900/30' : repo.language === 'Java' ? 'bg-orange-900/30' : 'bg-gray-800'} rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
<i data-feather="${repo.language === 'Kotlin' ? 'code-2' : repo.language === 'Java' ? 'box' : 'shield'}" class="${repo.language === 'Kotlin' ? 'text-purple-400' : repo.language === 'Java' ? 'text-orange-400' : 'text-gray-400'}"></i>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2">
<h4 class="text-white font-medium text-sm truncate">${repo.name}</h4>
${isRecent ? '<span class="text-[9px] px-1 py-0.5 rounded bg-green-600/30 text-green-400 font-bold animate-pulse">NEW</span>' : ''}
</div>
<p class="text-xs text-gray-500 truncate mt-0.5">${repo.description?.substring(0, 45) || 'No description'}...</p>
<div class="flex items-center gap-2 mt-1.5 flex-wrap">
<span class="text-[10px] px-1.5 py-0.5 rounded bg-gray-800 text-gray-400">${repo.language || 'Unknown'}</span>
<span class="text-[10px] text-gray-600">★ ${repo.stargazers_count.toLocaleString()}</span>
${repo.open_issues_count > 0 ? `<span class="text-[10px] text-yellow-500">${repo.open_issues_count} issues</span>` : ''}
</div>
</div>
<div class="text-right">
${relatedCVE ? `
<div class="text-[10px] px-1.5 py-0.5 rounded ${relatedCVE.score >= 7.0 ? 'bg-red-900/40 text-red-400' : 'bg-orange-900/40 text-orange-400'} font-mono mb-1">
${relatedCVE.id}
</div>
<div class="text-[9px] text-gray-500">CVSS: ${relatedCVE.score}</div>
` : `
<a href="${repo.html_url}" target="_blank" rel="noopener noreferrer" class="text-[10px] px-2 py-1 rounded bg-primary/20 text-primary hover:bg-primary/30 transition">
View
</a>
`}
</div>
</div>
`;
}).join('');
feather.replace();
} catch (error) {
console.error('Android Security Feed Error:', error);
androidFeed.innerHTML = `
<div class="text-center py-6 text-gray-500 text-sm">
<i data-feather="alert-triangle" class="inline-block w-5 h-5 mb-2 text-secondary"></i>
<p>Unable to load Android security data</p>
<p class="text-xs text-gray-600 mt-1">Rate limit may apply</p>
</div>
`;
feather.replace();
}
}
// Load Android security feed
loadAndroidSecurity();
// Refresh every 5 minutes
setInterval(loadAndroidSecurity, 300000);
// Update threat summary based on real data
async function updateThreatSummary() {
const threatSummary = document.getElementById('threat-summary');
if (!threatSummary) return;
try {
const [cves, cisa, urlhaus] = await Promise.allSettled([
fetchNVDCVEs(),
fetchCISAVulns(),
fetchURLhaus()
]);
const cveData = cves.status === 'fulfilled' ? cves.value : [];
const cisaData = cisa.status === 'fulfilled' ? cisa.value : [];
const malwareData = urlhaus.status === 'fulfilled' ? urlhaus.value : [];
const criticalCount = cveData.filter(cve => cve.severity >= 9.0).length;
const highCount = cveData.filter(cve => cve.severity >= 7.0 && cve.severity < 9.0).length;
const urgentCISA = cisaData.filter(vuln => vuln.isUrgent).length;
const totalThreats = cveData.length + cisaData.length + malwareData.length;
const threatLevel = urgentCISA > 0 ? 'CRITICAL' : criticalCount > 0 ? 'HIGH' : totalThreats > 5 ? 'ELEVATED' : 'MODERATE';
const threatColor = {
'CRITICAL': 'text-red-500 animate-pulse',
'HIGH': 'text-orange-500',
'ELEVATED': 'text-yellow-500',
'MODERATE': 'text-green-400'
}[threatLevel];
threatSummary.innerHTML = `
<span class="${threatColor} font-bold text-base">[${threatLevel}]</span>
<span class="text-white font-semibold">${totalThreats.toLocaleString()}</span> active threats •
<span class="text-red-400">${criticalCount}</span> critical /
<span class="text-orange-400">${highCount}</span> high CVEs •
<span class="text-purple-400">${cisaData.length}</span> CISA KEV •
<span class="text-secondary">${malwareData.length}</span> malware URLs detected
`;
} catch (error) {
threatSummary.textContent = 'Unable to fetch real-time threat data. Check network connection.';
}
}
// Initialize threat summary after initial data load
setTimeout(updateThreatSummary, 2000);
setInterval(updateThreatSummary, 180000);
// Add keyboard shortcut for data refresh
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'r') {
e.preventDefault();
loadRealData();
loadAndroidSecurity();
}
});
});